DLL関数の呼び出し

C++の関数を呼び出す方法

class Program
{
    // Win32 MessageBox関数の宣言
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

    static void Main(string[] args)
    {
        MessageBox(new IntPtr(0), "text", "caption", 0);
    }
}
使用例 - DllImportAttribute クラス (System.Runtime.InteropServices) | MSDN

このようにアンマネージドの関数を呼び出すと、コード分析で「P/Invoke を NativeMethods クラスに移動します」としてCA1060で警告されます。これに対処するには関数宣言をNativeMethodsクラス内に移動して、

internal static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
}

とします。このときクラス名をNativeMethodsとしないと、この警告は抑制できません。またメソッドが外部からアクセス可能だと「P/Invoke 'NativeMethods.***' の外部から可視できないように、アクセシビリティを変更します。」としてCA1401で警告されるため、internalとします。

C#から呼び出せるC++の関数の作成と、それを呼び出す方法

C++側での関数の定義

関数名で呼び出せるようにextern "C"を、外部から参照できるように__declspec(dllexport)を指定して宣言します。またC#側の呼び出し規約と一致するように__stdcallも指定します。

extern "C" __declspec(dllexport) int __stdcall Add(int a, int b);

定義では__stdcalだけ指定します。

int __stdcall Add(int a, int b)
{
    return a + b;
}

これらの指定がなくC#から関数を見つけられないと、C#からの呼び出し時に「DLL '***.dll' の '***' というエントリ ポイントが見つかりません。」としてEntryPointNotFoundExceptionが投げられます。

C#側からの呼び出し

DllImport属性を使用して宣言します。

class Program
{
    [DllImport("sample.dll")]
    public static extern int Add(int a, int b);

    static void Main(string[] args)
    {
        int result = Add(2, 3);
    }
}

C++の関数呼び出し時にMDAPInvokeStackImbalanceが検出されるときには、呼び出し規約が一致するようにC++の関数に__stdcallを指定するか、DllImportの引数でCallingConvention.Cdeclを指定します。.net - Why do I get "PInvokeStackImbalance was detected" for this simple example? - Stack Overflow

C#から利用できるC++のDLLを作成する方法 | Windows Mobile アプリケーション開発

C++のクラスの利用

対象のクラスの生成やそのメンバを呼び出す関数をC++側で定義しておき、C#からはその関数を介して対象のクラスを利用できるようにします。using a class defined in a c++ dll in c# code - Stack Overflow

たとえば対象のクラスが、次のように定義されているとします。

// 宣言
class __declspec(dllexport) MyClass
{
public:
    MyClass();
    ~MyClass();
    int Func(int);
};

// 定義
MyClass::MyClass(){}
MyClass::~MyClass(){}

int MyClass::Func(int a)
{
    return a * 2;
}

C#側からはこのクラスを生成できないため、それを仲介する関数を定義します。

// 宣言
extern "C"
{
    extern __declspec(dllexport) MyClass* Create();
    extern __declspec(dllexport) void Dispose(MyClass* obj);
    extern __declspec(dllexport) int Func(MyClass* obj, int a);
}

// 定義
MyClass* Create()
{
    return new MyClass();
}

void Dispose(MyClass* obj)
{
    if (obj != NULL)
    {
        delete obj;
        obj = NULL;
    }
}

int Func(MyClass* obj, int a)
{
    int result = 0;
    if (obj != NULL)
    {
        result = obj->Func(a);
    }
    return result;
}

そしてC#側からは、その関数を介してC#のクラスのメンバを呼び出します。

[DllImport("sample.dll")]
static public extern IntPtr Create();

[DllImport("sample.dll")]
static public extern void Dispose(IntPtr obj);

[DllImport("sample.dll")]
static public extern int Func(IntPtr obj, int a);


static void Main(string[] args)
{
    IntPtr myClass = Create();
    int result = Func(myClass, 2);
    Dispose(myClass);
}

COM

COMとして実装されたクラスならば、ラッパーを介せず呼び出せます。How do I DllExport a C++ Class for use in a C# Application - Stack Overflow

C++からの例外

C++から例外が投げられると、C#側ではその型にかかわらず「外部コンポーネントが例外をスローしました。」としてSEHExceptionが発生します。そのときErrorCodeプロパティから得られるHRESULTはつねに0x80004005であり、E_FAILの意味です。

Microsoft Learnから検索