C#でLoadLibraryを使用してアンマネージDLLを使用する

C#(.NET)でアンマネージDLLを使用するには、DllImport属性を使用するのが記述量も少なく、面倒なことを考えずに済むので一番楽だと思います。

[DllImport("user32.dll")]
private static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
private void button1_Click(object sender, EventArgs e)
{
  MessageBox(IntPtr.Zero, "Hello World.", "Caption", 0);
}

ただ、DLLのインスタンスハンドルを持っておきたい場合や、DLLの解放(FreeLibrary)を任意のタイミングで行いたい場合などに、この方法では実現することができません。そこでC#でもLoadLibraryを使用してDLLを使用する方法を調べてみました。

LoadLibrary、FreeLibrary、GetProcAddressはとりあえずそのままインポートして使用します。

[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32", SetLastError = true)]
internal static extern bool FreeLibrary(IntPtr hModule);
[DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = false)]
internal static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
private void button2_Click(object sender, EventArgs e)
{
  IntPtr handle = LoadLibrary("user32.dll");
  IntPtr funcPtr = GetProcAddress(handle, "MessageBoxA");
  // ???
  FreeLibrary(handle);
}

関数のアドレスを取得するところまでは簡単にできました。が、この???の部分で少し悩みました。これがC/C++であれば、関数のプロトタイプ宣言をしてからキャストしてそのまま呼び出してやればいいのですが、C#ではそんなことはできません。

少し調べて、.NET Frameworkにも関数ポインタをデリゲートに変換する仕組みがあることがわかりました。System.Marshal.GetDelegateForFunctionPointerメソッドを使うと良いようです。

public delegate int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint type);
private void button2_Click(object sender, EventArgs e)
{
  IntPtr handle = LoadLibrary("user32.dll");
  IntPtr funcPtr = GetProcAddress(handle, "MessageBoxA");

  MessageBox messageBox = (MessageBox)Marshal.GetDelegateForFunctionPointer(funcPtr, typeof(MessageBox));
  messageBox(IntPtr.Zero, "Hello World.", "Caption", 0);

  FreeLibrary(handle);
}

かなり手間は増えましたが、一応目的は実現できました。こんな事にもちゃんと手段が用意されているとは、素晴らしいですね。Marshalクラスについては、もう少しちゃんと知っておいた方がいいかなと思いました。