この記事の目的は「アンマネージド DLL に親しむ」です。
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 ファイルを置けば準備完了です。プログラムをコマンドプロンプトで走らせてみてください。
文字列を扱う
文字列を受け渡しする次の 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()); } }
char *
は System.Text.StringBuilder
,const 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)。