以前のエントリで「C#からC++のDLLを呼ぶ方法」を書いた。今回はその逆で「C++からC#のDLL関数を呼ぶ方法」を考える。
これが必要になるのは次のようなシチュエーション。C++で書かれたアプリがあり、今回そのアプリに機能を追加したい。もちろんC++で地道にコーディングすれば機能は追加できるけど工数は多くなる。C#と.NETを使えば工数が少なくなるのは明らかなので、今更C++でコーディングするのはだるい。サクサクっとC#でコーディングして、それをC++から呼べると楽だよね?
方法としては、C++からC#のDLLを直接呼ぶことはできないので、C#で作成したCOMをC++から呼ぶことになる。以下にテストコードを示す。
テストDLL (C#) のソース
DLLを作成するために、Visual C#で「クラスライブラリ」としてプロジェクトを作成する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Windows.Forms; namespace TestDll { // publish COM by generating dual interface. [ClassInterface(ClassInterfaceType.AutoDual)] public class Class1 { [StructLayout(LayoutKind.Sequential)] public struct Data { [MarshalAs(UnmanagedType.I4)] public int number1; [MarshalAs(UnmanagedType.I4)] public int number2; [MarshalAs(UnmanagedType.I4)] public int number3; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string message1; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string message2; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string message3; } [MethodImpl(MethodImplOptions.Synchronized)] public int func1(int intPtr) { System.IntPtr ptr = new System.IntPtr(intPtr); Data data = (Data)Marshal.PtrToStructure(ptr, typeof(Data)); MessageBox.Show("number1 = " + data.number1); MessageBox.Show("message1 = " + data.message1); data.number3 = data.number1 + data.number2; data.message3 = data.message1 + data.message2; Marshal.StructureToPtr(data, ptr, true); return 0; } } } |
プロジェクトの[プロパティ]-[アプリケーション]-[アセンブリ情報]設定で、[アセンブリをCOM参照可能にする]をチェックしておく。
そして、COMをビルドしてRegasm.exeを使ってシステムに登録する。
1 |
C:\Windows\Microsoft.NET\Framework\v4.0.30319\> Regasm.exe /codebase TestDll.dll |
Regasm.exe (アセンブリ登録ツール)
http://msdn.microsoft.com/ja-jp/library/tzat5yw6(v=vs.110).aspx
これでC#(COM)側の準備は完了。
テストアプリ (C++) のソース
C++からC#で書かれたCOMの関数を呼び出す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
typedef struct _DATA { int number1; int number2; int number3; char message1[32]; char message2[32]; char message3[64]; } DATA; void func() { ::CoInitialize(NULL); CLSID clsid; BSTR bstrDLL = _com_util::ConvertStringToBSTR("TestDll.Class1"); HRESULT hResult = ::CLSIDFromProgID(bstrDLL, &clsid); if (!SUCCEEDED(hResult)) { return; } IUnknown* pUnk = NULL; hResult = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pUnk); if (!SUCCEEDED(hResult)) { return; } IDispatch* pDisp = NULL; hResult = pUnk->QueryInterface(IID_IDispatch, (void**)&pDisp); if (!SUCCEEDED(hResult)) { return; } DISPID dispid = 0; LPOLESTR fun = L"func1"; hResult = pDisp->GetIDsOfNames(IID_NULL, &fun, 1, LOCALE_SYSTEM_DEFAULT, &dispid); if (!SUCCEEDED(hResult)) { return; } Data data; data.number1 = 123; data.number2 = 456; data.number3 = 0; strcpy(data.message1, "TEST+"); strcpy(data.message2, "ABC"); strcpy(data.message3, ""); DISPPARAMS params = {0}; params.cArgs = 1; params.cNamedArgs = 0; params.rgdispidNamedArgs = NULL; VARIANTARG* pVarg = new VARIANTARG[params.cArgs]; ::ZeroMemory(pVarg, sizeof(VARIANTARG) * params.cArgs); pVarg[0].vt = VT_INT; pVarg[0].intVal = (int)&data; params.rgvarg = pVarg; hResult = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶ms, NULL, NULL, NULL); TRACE("number3 = %d\n", data.number3); TRACE("message3 = %s\n", data.message3); ::CoUninitialize(); ::SysFreeString(bstrDLL); delete [] pVarg; } |
参考サイト
マネージドDLLとの接続、ActiveX(COM)による接続、アンマネージドDLLとの接続
http://satoshi3.sakura.ne.jp/memo/connect_dll.htm
C++からC#のdllを参照する際、引数内に構造体があった場合の処理 – C・C++ – 教えて!goo
http://oshiete.goo.ne.jp/qa/5493528.html?from=recommend