C#を使ってDLL内の関数を動的に呼び出す方法

ここでは、Reflectionによる動的なメソッド生成によってDLL内の関数を動的に実行する 方法について説明します。

手軽にやるならDefinePInvokeMethod()を使用する

方法については処々で話題になり色々な方法が考えれています。例えば、C++でアダプタを書いてそれをC#から利用する方法や、 GetProcAddressを使って取得した関数ポインタをIA-32アセンブリ言語を用いて直接実行する方法などです。 又、これはあまりメジャーではありませんが、.NET FrameworkではReflectionを通して動的にメソッドを定義できるので、 DefinePInvokeMethod()というメソッドを利用することができます。 この方法は非常に手軽に目的を達成できるので便利なのですが、 何故かライブラリの明示的な解放手段が提供されていません。 (若し存在する場合はご一報ください)これでは何かと困ることが出てくるかもしれません。 そこで、次のような方法を考えてみました。

Calliは関数ポインタを直接実行してくれる

一般的にC#ではCスタイルの関数ポインタ(デリゲートではない)を実行するこはできないとされています。 確かにC#という言語レヴェルでのサポートはありませんが、MSILではそれを可能にするオプコード(Calli)があります。 更にSystem.Reflection.Emit 名前空間にあるクラスを利用すれば、 動的にメタデータ及びMSILを生成することができるので、C#からでもCスタイルの関数ポインタを実行することは可能だということです。

次のようなヘルパークラスを作ってみました。

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Threading;


namespace DynamicallyPInvoke
{
    /// <summary>
    /// PInvokeMethodGen の概要の説明です。
    /// </summary>
    public class PInvokeMethodGen : IDisposable
    {
        #region Win32 API宣言
        
        [DllImport ("kernel32.dll", CharSet=CharSet.Auto)]
        private extern static IntPtr LoadLibrary(string lpFileName);

        [DllImport ("kernel32.dll")]
        private extern static bool FreeLibrary(IntPtr hModule);

        [DllImport("kernel32.dll", CharSet=CharSet.Ansi)]
        private extern static IntPtr GetProcAddress(IntPtr hModule,    string lpProcName);
        
        #endregion


        #region privateフィールド
        
        private bool blnDisposed = false;
        private IntPtr ipLib = IntPtr.Zero;
        
        #endregion

        #region コンストラクタ

        public PInvokeMethodGen(string libName)
        {
            ipLib = LoadLibrary(libName);
        }

        #endregion

        /// <summary>
        /// 指定されたデリゲートスタイルで動的にAPI関数実行メソッドを生成する
        /// </summary>
        /// <param name="methodType">デリゲートのタイプを指定</param>
        /// <param name="methodName">DLL内に定義されている関数名</param>
        /// <returns>関数実行メソッドに関連付けられたデリゲート</returns>
        public System.Delegate MethodGen(Type methodType, string methodName)
        {
            AppDomain ad = Thread.GetDomain();
            AssemblyName asmName = new AssemblyName();
            asmName.Name = "MyAsm";

            AssemblyBuilder asmb = ad.DefineDynamicAssembly(asmName,
                                                            AssemblyBuilderAccess.Run);
            ModuleBuilder modb = asmb.DefineDynamicModule("MyMod");
            TypeBuilder typb = modb.DefineType("MyTyp", TypeAttributes.Public);

            MethodInfo mi = methodType.GetMethod("Invoke");
            ParameterInfo [] pi = mi.GetParameters();
            Type [] parametersType = new Type[pi.Length];
            for(int i=0; i<pi.Length; i++)
                parametersType[i] = pi[i].ParameterType;
            MethodBuilder metb = typb.DefineMethod("MyMethod",
                                                    MethodAttributes.Public,
                                                    mi.ReturnType, parametersType);
            
            ILGenerator ilg = metb.GetILGenerator();
            // 1番目以降つまり実際関数に送られる引数を積む
            for(int i=1; i<=parametersType.Length; i++)
                ilg.Emit(OpCodes.Ldarg, i);
            // 関数ポインタを積む
            ilg.Emit(OpCodes.Ldc_I4, (int)GetProcAddress(ipLib, methodName));
            ilg.EmitCalli(OpCodes.Calli,
                          CallingConvention.StdCall, mi.ReturnType, parametersType);
            ilg.Emit(OpCodes.Ret);

            
            Type t = typb.CreateType();
            object o = Activator.CreateInstance(t);
            return Delegate.CreateDelegate(methodType, o, "MyMethod");
        }
        #region IDisposable メンバ

        public void Dispose()
        {
            if(!blnDisposed)
            {
                FreeLibrary(ipLib);
                blnDisposed = true;
            }
        }

        #endregion
    }
}

デリゲートを通して実行しているので、多少オーバーヘッドはあるでしょうが、 特に深刻な問題とはならないでしょう。又、インターフェース経由で実行するという手も考えられます。では使い方です。

delegate bool BeepDelegate(uint dwFreq, uint dwDuration);
private void button_Click(object sender, System.EventArgs e)
{
    using(PInvokeMethodGen pimg = new PInvokeMethodGen("kernel32.dll"))
    {
        BeepDelegate Beep = (BeepDelegate)pimg.MethodGen(typeof(BeepDelegate), "Beep");
        Beep(262, 500);
    }
}

フォーム上のボタンが押下されたときにBeep関数が呼び出されるというシナリオです。

最後に問題点ですが、上記ヘルパークラスではマーシャリングに対して完全な対応をしていません。 既定のマーシャリングは行われるのですが、MarshalAsAttributeなどで指定したものは無視しています。 従って、blittableでない型は自前で整形して渡してやる必要があります。

出会い喫茶 出会い系 評価 出会い系 優良 ベンツ 中古車 優良出会いサイト 中古車 ベンツ 出会い系サイト比較 出会い系 比較 携帯 出会い アクセスカウンター 出会い系サイト 完全無料 出会い系 出会い ランキング 出会い系 無料出会いサイト 完全無料出会い系サイト 出会い 比較 出会いサイト 無料