DLL 呼び出し
プラットフォーム呼び出しサービス
.NET プラットフォームは、一つのシステムとして完成しています
その仕組みは、GUI や ネットワーク環境など、システムとして独立できる能力があり
.NET 標準クラスライブラリを使えば、GUI アプリケーションを作成することも簡単です
しかし、.NET はネイティブプラットフォームの機能を呼び出す手段も提供しています
Win32 API プログラマは、独自の DLL を C/C++ で作成する能力があるでしょう
.NET からプラットフォームを呼び出すことができれば、便利なのは間違いありません
ただし、システムを呼び出した場合 .NET アプリケーションはそのシステムに依存してしまいます
DLL を呼び出すことができれば、事実上 Windows の全ての機能を引き出すことができます
しかし、.NET の思想は OS を抽象化することであることを忘れてはいけません
.NET におけるシステム呼び出しサービスを PInvoke と呼ぶこともあります
システムを呼び出すには、コンパイラに API の位置を知らせる必要があります
これには、System.Runtime.InteropServices.DllImport 属性を用います
public sealed class DllImportAttribute : Attribute
この属性クラスのコンストラクタは次のように定義されています
コンストラクタ引数は、位置パラメータとして指定しなければなりません
public DllImportAttribute(string dllName);
dllName には、DLL の名前を指定します
DLL の本体は、システムが発見できる位置になければなりません
この属性が指定されたメソッドは、指定した DLL に存在すると解釈されます
Windows API は C 言語から呼び出せるように設計されていて
オブジェクト思考の立場から見れば、それは常に public static なメソッドです
呼び出す API のエントリポイントは、コンパイラがメソッド名から判断します
また、C# 言語では外部メソッドを呼び出す場合、
必ず、メソッドに extern 修飾子を指定しなければなりません
この extern キーワードは、一般的に DllImport 属性とセットで用いられます
どの DLL を呼び出すかは、プログラマが判断する作業です
DLL の知識、及び Win32 API のネイティブな型情報を知っている必要があります
using System;
using System.Runtime.InteropServices;
class Win32 {
[DllImport("USER32.DLL")]
public static extern int MessageBoxA(
int hWnd , String lpText , String lpCaption , uint uType
);
public const int MB_OK = 0x00000000;
public const int MB_OKCANCEL = 0x00000001;
public const int MB_ABORTRETRYIGNORE = 0x00000002;
public const int MB_YESNOCANCEL = 0x00000003;
public const int MB_YESNO = 0x00000004;
public const int MB_RETRYCANCEL = 0x00000005;
public const int MB_ICONHAND = 0x00000010;
public const int MB_ICONQUESTION = 0x00000020;
public const int MB_ICONEXCLAMATION = 0x00000030;
public const int MB_ICONASTERISK = 0x00000040;
}
class Test {
public static void Main() {
Win32.MessageBoxA(0 , "Kitty on your lap" ,
"MsgBox" , Win32.MB_YESNO | Win32.MB_ICONEXCLAMATION);
}
}
このプログラムは、Win32 の MesssgeBox() ファンクションを呼び出します
プログラムが実行されると USER32.DLL がメモリにロードされ
API が呼び出され、Kitty on your lap と書かれたダイアログが出現します
DllImport 属性が指定された Win32.MessageBoxA() メソッドは
USER32.DLL ライブラリの MessageBoxA() というエクスポート関数であることを表しています
DllImport の EntryPoint 名前付きパラメータを使用すれば
メソッド名ではなく、このパラメータで DLL の関数の位置を指定することができます
こうすれば、独自の新しい名前で DLL の関数を C# から呼び出すことができるでしょう
using System;
using System.Runtime.InteropServices;
class Win32 {
[DllImport("USER32.DLL" , EntryPoint="MessageBoxA")]
public static extern int Dialog(
int hWnd , String lpText , String lpCaption , uint uType
);
public const int MB_OK = 0x00000000;
public const int MB_OKCANCEL = 0x00000001;
public const int MB_ABORTRETRYIGNORE = 0x00000002;
public const int MB_YESNOCANCEL = 0x00000003;
public const int MB_YESNO = 0x00000004;
public const int MB_RETRYCANCEL = 0x00000005;
public const int MB_ICONHAND = 0x00000010;
public const int MB_ICONQUESTION = 0x00000020;
public const int MB_ICONEXCLAMATION = 0x00000030;
public const int MB_ICONASTERISK = 0x00000040;
}
class Test {
public static void Main() {
Win32.Dialog(0 , "Kitty on your lap" ,
"MsgBox" , Win32.MB_YESNO | Win32.MB_ICONEXCLAMATION);
}
}
このプログラムの Win32.Dialog() メソッドは
DllImport 属性で USER32.DLL の MessageBoxA() 関数を呼び出すことを表しています
プログラムを実行すれば、先ほどのプログラムとまったく同じ結果を得ることができます
ところで、C# の int 等の数値型は、単純な数値型ではありません
この実体は構造体ですし、String 型も単純な 1 バイトの配列とは異なります
Win32 API において、文字列とは単純な配列であり、クラスや構造体ではありません
C# から DLL を呼び出す場合、このギャップはどのように解決しているのでしょうか
実は、.NET はマーシャリングと呼ばれる方法でこれを解決します
.NET 型を DLL 関数に直接渡すことは、互換性がないためできません
そこで、.NET は DLL を呼び出す時、マーシャリングと呼ばれる処理を行うことによって
.NET 型の値をネイティブ型に変換し、DLL に情報を渡しているのです
そもそも、.NET の型にはネイティブ型の情報が属性として含まれているため
String 型を DLL に渡そうとすれば、Win32 の LPSTR 型に変換される仕組みを持っているのです
より詳しくこの実装を見たければ、MarshalAs 属性を調べるとよいでしょう
この属性も System.Runtime.InteropServices 名前空間に含まれています