LoginSignup
51
56

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-06-10

一般的に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でキャッシュしておく等した方が良いかもしれません。

51
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
51
56