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.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
}
}
デリゲートを通して実行しているので、多少オーバーヘッドはあるでしょうが、 特に深刻な問題とはならないでしょう。又、インターフェース経由で実行するという手も考えられます。では使い方です。
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でない型は自前で整形して渡してやる必要があります。