この記事の目的は「アンマネージド 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)。

