zoukankan      html  css  js  c++  java
  • C#动态调用C++编写的DLL函数

    [C#动态调用C++编写的DLL函数]

            by jingzhongrong  2008-05-08
    动态加载DLL需要使用Windows API函数:LoadLibrary、GetProcAddress以及FreeLibrary。我们可以使用DllImport在C#中使用这三个函数。
     
    [DllImport("Kernel32")]
    public static extern int GetProcAddress(int handle, String funcname);
     
    [DllImport("Kernel32")]
    public static extern int LoadLibrary(String funcname);
     
    [DllImport("Kernel32")]
    public static extern int FreeLibrary(int handle);
     
    当我们在C++中动态调用Dll中的函数时,我们一般的方法是:
    假设DLL中有一个导出函数,函数原型如下:
    BOOL __stdcall foo(Object &object, LPVOID lpReserved);
     
    1、首先定义相应的函数指针:
    typedef BOOL (__stdcall *PFOO)(Object &object, LPVOID lpReserved);
     
    2、调用LoadLibrary加载dll:
    HINSTANCE hInst = ::LoadLibraryW(dllFileName);
     
    3、调用GetProcAddress函数获取要调用函数的地址:
    PFOO foo = (PFOO)GetProcAddress(hInst,"foo");
    if(foo == NULL)
    {
        FreeLibrary(hInst);
        return false;
    }
     
    4、调用foo函数:
    BOOL bRet = foo(object,(LPVOID)NULL);
     
    5、使用完后应释放DLL:
    FreeLibrary(hInst);
     
    那么在C#中应该怎么做呢?方法基本上一样,我们使用委托来代替C++的函数指针,通过.NET Framework 2.0新增的函数GetDelegateForFunctionPointer来得到一个委托的实例:
     
    下面封装了一个类,通过该类我们就可以在C#中动态调用Dll中的函数了:
     
    public class DLLWrapper
    {
        ///<summary>
        /// API LoadLibrary
        ///</summary>
        [DllImport("Kernel32")]
        public static extern int LoadLibrary(String funcname);
     
        ///<summary>
        /// API GetProcAddress
        ///</summary>
        [DllImport("Kernel32")]
        public static extern int GetProcAddress(int handle, String funcname);
     
        ///<summary>
        /// API FreeLibrary
        ///</summary>
        [DllImport("Kernel32")]
        public static extern int FreeLibrary(int handle);
     
        ///<summary>
        ///通过非托管函数名转换为对应的委托, by jingzhongrong
        ///</summary>
        ///<param name="dllModule">通过LoadLibrary获得的DLL句柄</param>
        ///<param name="functionName">非托管函数名</param>
        ///<param name="t">对应的委托类型</param>
        ///<returns>委托实例,可强制转换为适当的委托类型</returns>
        public static Delegate GetFunctionAddress(int dllModule, string functionName, Type t)
        {
           int address = GetProcAddress(dllModule, functionName);
           if (address == 0)
               return null;
           else
               return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t);
        }
     
        ///<summary>
        ///将表示函数地址的IntPtr实例转换成对应的委托, by jingzhongrong
        ///</summary>
        public static Delegate GetDelegateFromIntPtr(IntPtr address, Type t)
        {
           if (address == IntPtr.Zero)
               return null;
           else
               return Marshal.GetDelegateForFunctionPointer(address, t);
        }
     
        ///<summary>
        ///将表示函数地址的int转换成对应的委托,by jingzhongrong
        ///</summary>
        public static Delegate GetDelegateFromIntPtr(int address, Type t)
        {
           if (address == 0)
               return null;
           else
               return Marshal.GetDelegateForFunctionPointer(new IntPtr(address), t);
        }
    }
     
    通过这个类,我们这样调用DLL:
     
    1、声明相应的委托(正确声明很重要,否则不能调用成功,后面有详细介绍)。
     
    2、加载DLL:
    int hModule = DLLWrapper.LoadLibrary(dllFilePath);
    if (hModule == 0)
        return false;
     
    3、获取相应的委托实例:
    FOO foo = (FOO)DLLWrapper.GetFunctionAddress(hModule, "foo", typeof(FOO));
    if (foo == null)
    {
        DLLWrapper.FreeLibrary(hModule);
        return false;
    }
     
    4、调用函数:
    foo(...);
     
    5、.NET并不能自动释放动态加载的DLL,因此我们在使用完DLL后应该自己释放DLL:
    DLLWrapper.FreeLibrary(hModule);
     
    下面我们将就委托应如何声明进行相应的讨论,在实际操作过程中,我发现使用DllImport方法和动态调用方法两者在C#中对DLL中函数原型的声明是有些区别的,下面我介绍动态调用中委托的声明:
     
    1、首先应该注意的是,C++中的类型和C#中类型的对应关系,比如C++中的long应该对应C#中的Int32而不是long,否则将导致调用结果出错。
     
    2、结构的声明使用StructLayout对结构的相应布局进行设置,具体的请查看MSDN:
     
    使用LayoutKind指定结构中成员的布局顺序,一般可以使用Sequential:
        [StructLayout(LayoutKind.Sequential)]
        struct StructVersionInfo
        {
           public int MajorVersion;
           public int MinorVersion;
        }
    另外,如果单独使用内部类型没有另外使用到字符串、结构、类,可以将结构在C#中声明为class:
        [StructLayout(LayoutKind.Sequential)]
        class StructVersionInfo
        {
           public int MajorVersion;
           public int MinorVersion;
        }
     
    对应C++中的声明:
        typedef struct _VERSION_INFO
        {
            int MajorVersion;
            int MinorVersion;
        } VERSION_INFO, *PVERSION_INFO;
     
    如果结构中使用到了字符串,最好应指定相应的字符集:
        [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
     
    部分常用的声明对应关系(在结构中):
    C++:字符串数组
        wchar_t Comments[120];
    C#:
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 120)]
        public string Comments;
     
    C++:结构成员
        VERSION_INFO ver;
    C#
        publicStructVersionInfo ver;
     
    C++:函数指针声明
        PFOO pFoo; //具体声明见文章前面部分
    C#:
        publicIntPtr pFoo; //也可以为 public int pFoo;
            //不同的声明方法可以使用上面DLLWrapper类的相应函数获取对应的委托实例
     
    如果在结构中使用到了union,那么可以使用FieldOffset指定具体位置。
     
    3、委托的声明:
     
    当C++编写的DLL函数需要通过指针传出将一个结构:如以下声明:
        void getVersionInfo(VERSION_INFO *ver);
    对于在C#中声明为class的结构(当VERSION_INFO声明为class)
        delegate voidgetVersionInfo(VERSION_INFO ver);
    如果结构声明为struct,那么应该使用如下声明:
        delegate voidgetVersionInfo(refVERSION_INFO ver);
    注意:应该使用ref关键字。
     
     
    如果DLL函数需要传入一个字符串,比如这样:
        BOOL __stdcall jingzhongrong1(const wchar_t* lpFileName, int* FileNum);
    那么使用委托来调用函数的时候应该在C#中如下声明委托:
        delegate bool jingzhongrong1(
           [MarshalAs(UnmanagedType.LPWStr)]String FileName,
           ref int FileNum);
    注意:应该使用[MarshalAs(UnmanagedType.LPWStr)]和String进行声明。
     
     
    如果要在DLL函数中传出一个字符串,比如这样:
        void __stdcall jingzhongrong2(
        wchar_t* lpFileName, //要传出的字符串
        int* Length);
    那么我们如下声明委托:
        //使用委托从非托管函数的参数中传出的字符串,
        //应该这样声明,并在调用前为StringBuilder预备足够的空间
        delegate void jingzhongrong2(
           [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFileName,
           ref int Length,
        );
    在使用函数前,应先为StringBuilder声明足够的空间用于存放字符串:
        StringBuilder fileName = new StringBuilder(FileNameLength);
     


    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jingzhongrong/archive/2008/05/08/2416623.aspx

    作者:观海看云个人开发历程知识库 - 博客园
    出处:http://www.cnblogs.com/zhangtao/
    文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    bzoj 2138: stone
    LOJ #6062. 「2017 山东一轮集训 Day2」Pair
    bzoj 5341: [Ctsc2018]暴力写挂
    UOJ #356. 【JOI2017春季合宿】Port Facility
    UOJ #357. 【JOI2017春季合宿】Sparklers
    UOJ #349. 【WC2018】即时战略
    bzoj 3600: 没有人的算术
    Codeforces 960G. Bandit Blues
    codeforces524E
    codeforces193B
  • 原文地址:https://www.cnblogs.com/zhangtao/p/1691909.html
Copyright © 2011-2022 走看看