一般的にDLLを呼び出す場合はDLLImportを使用しますが、実行時にDLLが無いと起動出来なくなってしまうので、プラグイン的な使い方をする場合は、DLLを動的に呼び出したい場合があります。
DLLを動的に呼び出すには、LoadLibrary+GetProcAddressで関数ポインタを取得し、定義済みのDelegateに変換して呼び出す昔ながらの方法もありますが、今回紹介する方法は、TypeBuilder.DefinePInvokeMethod を使用して後から動的にDLLImport相当のメソッドを作成し、それを呼び出します。
この方法の利点としては、事前にDelegateを定義する必要が無いため、パラメータ設定次第で引数の数、型すらも自由に変更することが可能です。(そんな機会があるかと言えば、殆ど無いかもしれないが…)
コード
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.IO;
namespace ExecDllFuncTest
{
/// <summary>
/// PInvoke関数情報
/// </summary>
public class PInvokeProcInfo
{
/// <summary>
/// 関数名
/// </summary>
public string ProcName { get; set; }
/// <summary>
/// DLLファイル
/// </summary>
public string ModuleFile { get; set; }
/// <summary>
/// エントリポイント
/// </summary>
public string EntryPoint { get; set; }
/// <summary>
/// 戻り値の型(戻り値無しはSystem.Void)
/// </summary>
public Type ReturnType { get; set; } = typeof(void);
/// <summary>
/// 関数のパラメータの型
/// </summary>
public Type[] ParameterTypes { get; set; } = { };
/// <summary>
/// 呼び出し規約
/// </summary>
public CallingConvention CallingConvention { get; set; } = CallingConvention.StdCall;
/// <summary>
/// メソッドのキャラクターセット
/// </summary>
public CharSet CharSet { get; set; } = CharSet.Auto;
}
class Program
{
/// <summary>
/// PInvoke関数情報から、メソッドのメタデータを作成する。
/// </summary>
/// <param name="invInfo">PInvoke関数情報</param>
/// <returns>PInvoke関数メタデータ</returns>
public static MethodInfo CreateMethodInfo(PInvokeProcInfo invInfo)
{
string moduleName = Path.GetFileNameWithoutExtension(invInfo.ModuleFile).ToUpper();
AssemblyBuilder asmBld = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("Asm" + moduleName), AssemblyBuilderAccess.Run);
ModuleBuilder modBld = asmBld.DefineDynamicModule(
"Mod" + moduleName);
TypeBuilder typBld = modBld.DefineType(
"Class" + moduleName,
TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder methodBuilder = typBld.DefinePInvokeMethod(
invInfo.ProcName,
invInfo.ModuleFile,
invInfo.EntryPoint,
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PinvokeImpl | MethodAttributes.HideBySig,
CallingConventions.Standard,
invInfo.ReturnType,
invInfo.ParameterTypes.ToArray(),
invInfo.CallingConvention,
invInfo.CharSet);
methodBuilder.SetImplementationFlags(MethodImplAttributes.PreserveSig);
return typBld.CreateType().GetMethod(invInfo.ProcName);
}
private delegate int DlgMessageBox(IntPtr hWnd, string text, string caption, int buttonType);
static int Main(string[] args)
{
PInvokeProcInfo invInfo = new PInvokeProcInfo()
{
ProcName = "MessageBox",
EntryPoint = "MessageBoxW",
ModuleFile = "User32.dll",
ReturnType = typeof(Int32),
ParameterTypes = new Type[] { typeof(IntPtr), typeof(string), typeof(string), typeof(Int32) },
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Unicode
};
//Invokeで実行
MethodInfo method = CreateMethodInfo(invInfo);
method.Invoke(null, new object[] { IntPtr.Zero, "Run Invoke", "test1", 0});
//Delegateで実行
DlgMessageBox messageBox = (DlgMessageBox)method.CreateDelegate(typeof(DlgMessageBox));
messageBox(IntPtr.Zero, "Run Delegate", "test2", 0);
return 0;
}
}
}
実行結果
同じDLLから複数のメソッドを定義したり、同じメソッドを何度も使う場合は、作成済みのTypeBuilderやMethodInfoをDictionaryでキャッシュしておく等した方が良いかもしれません。