端くれプログラマの備忘録 C# [C#] C++からC#のDLLを呼ぶ方法

[C#] C++からC#のDLLを呼ぶ方法

以前のエントリで「C#からC++のDLLを呼ぶ方法」を書いた。今回はその逆で「C++からC#のDLL関数を呼ぶ方法」を考える。

これが必要になるのは次のようなシチュエーション。C++で書かれたアプリがあり、今回そのアプリに機能を追加したい。もちろんC++で地道にコーディングすれば機能は追加できるけど工数は多くなる。C#と.NETを使えば工数が少なくなるのは明らかなので、今更C++でコーディングするのはだるい。サクサクっとC#でコーディングして、それをC++から呼べると楽だよね?

方法としては、C++からC#のDLLを直接呼ぶことはできないので、C#で作成したCOMをC++から呼ぶことになる。以下にテストコードを示す。

テストDLL (C#) のソース

DLLを作成するために、Visual C#で「クラスライブラリ」としてプロジェクトを作成する。

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参照可能にする]をチェックしておく。

VS2010-properties

そして、COMをビルドしてRegasm.exeを使ってシステムに登録する。

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の関数を呼び出す。

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, &params, 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