C#からC++のDLLを呼び出す

DLLは複数のプログラムから共通で利用できる部分を分離させたライブラリファイルです。

DLLには、 .Net Framework向けの物だったりCOMコンポーネントだったりといくつかの種類がありますが、ここではC++で作られたような昔ながらのDLLをC#から呼び出す方法についてまとめます。

プラットフォームターゲットに注意

x86(32ビット)で作成されたDLLは、x64(64ビット)のアプリケーションからは使用できません。

同様にx64(64ビット)で作成されたDLLは、x86(32ビット)のアプリケーションからは使用できません。

プラットフォームターゲットは合わせる必要があります。

 

ところで、C#には「Any CPU」というものがあります。

Any CPU」でビルドされたアプリケーションは、64ビットOSでは64ビットで動作し、32ビットOSでは32ビットで動作するというものですが、DLLはプラットフォームが固定となるので「Any CPU」は使わない方がようでしょう。

 

※プロジェクトのプロパティを見ると「Any CPU」の時には「32ビットを優先(P)」というチェックが現れます。

※これにチェックが入っていると64ビットOSでも32ビットで動作する事になるので混乱しないように注意しましょう。

[DllImport]で関数を定義する

DLLで提供される関数を、DllImport属性を使ってC#のソース内に定義していきます。

使用するDLLファイルはEXEファイルと同じフォルダにコピーしておきます。

C++で作られたDLLの関数定義が以下のような場合、

extern "C" {
  __declspec(dllexport) int __stdcall DLLTest_A(int a, double b);
}

 

C#で使用するには以下のように定義します。

public class Test
{
    [DllImport("DLLTest.dll")]
    private extern static int DLLTest_A(int a, double b);
}

DllImport属性に指定している "DLLTest.dll" の部分はDLLのファイル名です。

呼び出し規約に注意

呼び出し規約というのは、引数をスタックにプッシュする順序だったり呼び出し元と呼び出し先のどちらが最後にスタックから引数を削除するかというような、関数を呼び出す時の決まり事です。

 

詳細は https://msdn.microsoft.com/ja-jp/library/984x0h58.aspx を参照

 

 

呼び出す側のアプリと呼び出される側のDLLとで、呼び出し規約が一致していないといけません

C#では呼び出し規約の規定値は "Winapi" となっています。"Winapi" はOSによって規約が変化し Windows では "StdCall" 、Windows ce.NET では "Cdecl" となります。

 

 

上記の例では、C++側のDLLの定義が「__stdcall」とされています。

C#側には規約の指定が省略されていますが Windows で利用される場合規定値が「StdCall」な為正しく利用する事ができます。

 

C#側で規約を明示する場合は以下のようになります。

public class Test
{
    [DllImport("DLLTest.dll", CallingConvention = CallingConvention.StdCall)]
    private extern static int DLLTest_A(int a, double b);
}

文字列のマーシャリング

C#からDLLを利用する時注意が必要なのは文字列の扱いです。

C#では文字列をstring型で扱いますが、C++では通常 char型の配列(もしくはwchar_t型の配列)が使われます。この為DLLへ文字列を渡す場合、パラメータは char型ポインタとなっています。

 

但し、C#ではマーシャリングという機能が働く為 char型ポインタを string型へ置き換えて定義する事が出来るようになっています。

文字列を渡すだけの場合

C#側から値を与えるだけの場合は string型を使用します。

C++で作られたDLLの関数定義が以下のような場合、

extern "C" {
    __declspec(dllexport) int __stdcall DLLTest_B(const char* pText);
}

 

C#で使用するには以下のようにstring型に置き換えて定義します。

public class Test
{
    [DllImport("DLLTest.dll")]
    private extern static int DLLTest_B(string text);
}

文字列を受け取る場合

DLLから文字列を受け取る場合は StringBuilderクラスを使用します。

C++で作られたDLLの関数定義が以下のような場合、

extern "C" {
    __declspec(dllexport) int __stdcall DLLTest_C(char* pText, int textLen);
}

 

C#で使用するには以下のようにStringBuilderクラスに置き換えて定義します。

public class Test
{
    [DllImport("DLLTest.dll")]
    private extern static int DLLTest_C(StringBuilder text, int textLen);


    private void Test()
    {
        StringBuilder text = new StringBuilder(256);
        int           ret  = DLLTest_C(text, text.Capacity);
    }
}

使う時は9行目のようにStringBuilderのメモリを確保する文字数を指定しておきます。

文字コードは自動変換してくれる

C#は文字列をUnicodeで処理します。

DLLはchar型の時はASCIIコード(Shift-JIS)、wchar_t型の場合はUnicodeです。

 

上記の例では何も意識せずstring型を使っていますが、マーシャリング時に文字コードを自動的に変換してくれています。