読者です 読者をやめる 読者になる 読者になる

DllImportを使わずにアンマネージドDLL関数を呼び出してみる

.NET

まったく使わないわけではないけど。参考にしたのはこのあたり。

同じようなインターフェイスを持つ、複数のアンマネージドDLL関数を呼び出す必要があったのでいろいろ調べていたら、 .NET 2.0 から用意された System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer を使えば関数ポインタをデリゲートに変換できることがわかった。
やり方としてはこんな感じ。

  1. アンマネージドDLLの関数定義を調べる。
  2. 関数定義にあわせてデリゲートを定義する。
  3. LoadLibrary でアンマネージドDLLをロードする。
  4. GetProcAddress で関数ポインタを取得する。
  5. GetDelegateForFunctionPointer で関数ポインタをデリゲートに変換する。
  6. デリゲートをコールする。
  7. FreeLibrary でアンマネージドDLLを解放する。

で、毎回同じようなコードになる部分をクラスにしてみたのでメモ。
サンプルでは MessageBoxW を呼び出してるけど、統合アーカイバとかにも応用できるはず。

using System;
using System.Runtime.InteropServices;

namespace LateBindingApplication {
    static class Program {
        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main() {
            using (LateBinding b = new LateBinding("user32.dll")) {
                MessageBox m = (MessageBox)b.GetDelegate("MessageBoxW", typeof(MessageBox));
                m(IntPtr.Zero, "call by c#", "LateBindingApplication", 0);
            }
        }
    }

    public delegate int MessageBox(IntPtr hwnd,
        [MarshalAs(UnmanagedType.LPWStr)]string text,
        [MarshalAs(UnmanagedType.LPWStr)]string Caption,
        int type);

    public class LateBinding : IDisposable {
        [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);

        [DllImport("kernel32", SetLastError = true)]
        private static extern bool FreeLibrary(IntPtr hModule);

        [DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = false)]
        private static extern IntPtr GetProcAddress(IntPtr hModule,
            [MarshalAs(UnmanagedType.LPStr)]  string lpProcName);

        private IntPtr _module;

        /// <summary>
        /// DLL名を指定して遅延バインディングを行うオブジェクトを生成します。
        /// </summary>
        /// <param name="filename">バインドするDLL名</param>
        public LateBinding(string filename) {
            _module = LateBinding.LoadLibrary(filename);
            if (_module != IntPtr.Zero) {
                return;
            }

            int result = Marshal.GetHRForLastWin32Error();
            throw Marshal.GetExceptionForHR(result);
        }

        /// <summary>
        /// 指定した名前を持つアンマネージ関数ポインタをデリゲートに変換します。
        /// </summary>
        /// <param name="procName">アンマネージ関数名</param>
        /// <param name="delegateType">変換するデリゲートのType</param>
        /// <returns>変換したデリゲート</returns>
        public Delegate GetDelegate(string procName, Type delegateType) {
            IntPtr ptr = LateBinding.GetProcAddress(_module, procName);
            if (ptr != IntPtr.Zero) {
                Delegate d = Marshal.GetDelegateForFunctionPointer(ptr, delegateType);
                return d;
            }

            int result = Marshal.GetHRForLastWin32Error();
            throw Marshal.GetExceptionForHR(result);
        }

        #region IDisposable メンバ

        public void Dispose() {
            if (_module != IntPtr.Zero) {
                LateBinding.FreeLibrary(_module);
            }
        }

        #endregion
    }
}