月別アーカイブ: 2012年8月

C/C++ で作った DLL を C# で使う

Wednesday, August 8, 2012

この記事の目的は「アンマネージド DLL に親しむ」です。

  1. C/C++ で DLL を作る
  2. C# で DLL 関数を呼び出す
  3. 文字列を扱う

C/C++ で DLL を作る

C で書かれた次の資産 arithmetic.c を,C# のプログラムから使いたいとします。

// arithmetic.c

int add(int a, int b)
{
    return a + b;
}

これを EXE ではなく DLL としてコンパイルすれば,C# のプログラムから DLL を読み込むことで,add 関数を呼び出すことができます。ただし,正しく DLL 化するには少々の細工が必要です。実際に,正しく細工を施した次のコードを DLL としてコンパイルしてみてください。

// arithmetic.c

__declspec(dllexport) int __stdcall add(int a, int b);

__declspec(dllexport) int __stdcall add(int a, int b)
{
    return a + b;
}

※ VC++ で DLL としてコンパイルするには,[表示] - [(プロジェクト名) のプロパティ] から,[構成プロパティ] - [全般] - [構成の種類] を [ダイナミックライブラリ (.dll)] に設定してください。

※ BCC で DLL としてコンパイルするには,-WD オプションを指定してください。

C++ としてコンパイルする場合には,extern "C" を付加する必要があります。関数のオーバーロードの関係で,関数名がめちゃくちゃに変えられてしまうのを防ぐためです。

// arithmetic.cpp

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

extern "C" __declspec(dllexport) int __stdcall add(int a, int b)
{
    return a + b;
}

__declspec(dllexport) は,関数のエクスポートに必要なキーワードです。関数のエクスポートとは,関数を他のプログラムから利用できるように公開することです。

__stdcall は呼び出し規約といい,関数の呼び出し方を決定するために必要なキーワードです。よく使われる呼び出し規約には __stdcall 以外に __cdecl などがあります。__stdcall を修飾する関数はプロトタイプ宣言が必要とのことです。

__stdcall (MSDN)
http://msdn.microsoft.com/ja-jp/library/zxk0tw93.aspx

C でも C++ でも通用する書き方として,次のようなマクロ定義をしたコードをお勧めします。ここで __cplusplus は,C++ モードでコンパイラが自動的に定義するマクロ定数です。

// arithmetic.c または arithmetic.cpp

#ifdef __cplusplus
#define DLLEXPORT extern "C" __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllexport)
#endif

DLLEXPORT int __stdcall add(int a, int b);

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

C# で DLL 関数を呼び出す

さて,一方の DLL を呼び出す側ですが,C# のコードは次のようにします。

// Program.cs

using System;
using System.Runtime.InteropServices;

class Program
{
    [DllImport("arithmetic.dll")]
    static extern int add(int a, int b);

    static void Main()
    {
        Console.WriteLine("2 + 3 = {0}", add(2, 3));
        Console.WriteLine("4 + 5 = {0}", add(4, 5));
    }
}

arithmetic.dll 内にある add() を関数を読み込むために,次の 2 行からなる宣言を行っています。

    [DllImport("arithmetic.dll")]
    static extern int add(int a, int b);

DllImport 属性 (System.Runtime.InteropServices 名前空間) は,関数が指定したアンマネージド DLL から読み込まれることを示します。続く修飾子 static extern は決まり文句です。戻り値,関数名,引数リストは,型が C と C# に共通である限り,元の関数プロトタイプをそのまま写せば OK です。

さて,このプログラムをビルドして,生成された実行ファイルと同じディレクトリに DLL ファイルを置けば準備完了です。プログラムをコマンドプロンプトで走らせてみてください。

cdllsample.png

文字列を扱う

文字列を受け渡しする次の mystrcpy() 関数を DLL 化します。

// strutils.c

#include <string.h>

#ifdef __cplusplus
#define DLLEXPORT extern "C" __declspec(dllexport)
#else
#define DLLEXPORT __declspec(dllexport)
#endif

DLLEXPORT void __stdcall mystrcpy(char *dest, const char *src);

DLLEXPORT void __stdcall mystrcpy(char *dest, const char *src)
{
    strcpy(dest, src);
}

これを C# から呼び出すためには,次のようにします。

// Program.cs

using System;
using System.Runtime.InteropServices;
using System.Text;

class Program
{
    [DllImport("strutils.dll")]
    static extern void mystrcpy(StringBuilder dest, string src);

    static void Main()
    {
       StringBuilder dest= new StringBuilder(1024);
       string src = "this is a test.";
       mystrcpy(dest, src);
       Console.WriteLine(dest.ToString());
    }
}

cdllsample2.png

char *System.Text.StringBuilderconst char *string に対応付けられます。StringBuilder を使用する際は,new StringBuilder(1024) のように,十分な領域を確保する必要があります (*1)。

C の型 C# の型
const char * string
char * System.Text.StringBuilder

関数宣言の頭に unsafe を指定すれば,ポインタをポインタのまま残すことも可能です。

その他の型の対応については,次のサイトが参考になります。

.NET TIPS Win32 APIやDLL関数を呼び出すには? - @IT
http://www.atmarkit.co.jp/fdotnet/dotnettips/024w32api/w32api.html

(*1) コメントでご指摘いただき,new StringBuilder()new StringBuilder(1024) に訂正,その旨補足しました (2012-08-20)。