[C#] DLLImportを使わずDLLを動的に呼び出す

一般的にDLLを呼び出す場合はDLLImportを使用しますが、実行時にDLLが無いと起動出来なくなってしまうので、プラグイン的な使い方をする場合は、DLLを動的に呼び出したい場合があります。

DLLを動的に呼び出すには、LoadLibrary+GetProcAddressで関数ポインタを取得し、定義済みのDelegateに変換して呼び出す昔ながらの方法もありますが、今回紹介する方法は、TypeBuilder.DefinePInvokeMethod を使用して後から動的にDLLImport相当のメソッドを作成し、それを呼び出します。

この方法の利点としては、事前にDelegateを定義する必要が無いため、パラメータ設定次第で引数の数、型すらも自由に変更することが可能です。（そんな機会があるかと言えば、殆ど無いかもしれないが…）

#コード
```cs
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;
        }
    }
}
```

#実行結果
![実行結果](https://i.imgur.com/LcD2N32.png "実行結果")


同じDLLから複数のメソッドを定義したり、同じメソッドを何度も使う場合は、作成済みのTypeBuilderやMethodInfoをDictionaryでキャッシュしておく等した方が良いかもしれません。