[C#] DLLImport를 사용하지 않고 DLL을 동적으로 호출

14739 단어 dllWindowsC#.NET
일반적으로 DLL을 호출하는 경우는 DLLImport를 사용합니다만, 실행시에 DLL이 없으면 기동할 수 없게 되어 버리므로, 플러그인적인 사용법을 하는 경우는, DLL를 동적으로 호출하고 싶은 경우가 있습니다.

DLL을 동적으로 호출하려면 LoadLibrary + GetProcAddress에서 함수 포인터를 가져 와서 미리 정의 된 Delegate로 변환하고 호출하는 구식 방법이 있습니다. 동적으로 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로 캐쉬 해 두는 등이 좋을지도 모릅니다.

좋은 웹페이지 즐겨찾기