我爱我的家园!

成功在于你是否努力,希望在于你是否相信自己!

 

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

动态加载 DLL 需要使用 Windows API 函数: LoadLibrary GetProcAddress 以及 FreeLibrary 。我们可以使用 DllImport C# 中使用这三个函数。
 
[DllImport("Kernel32")]
public static extern int GetProcAddress(inthandle, Stringfuncname);
 
[DllImport("Kernel32")]
public static extern int LoadLibrary(Stringfuncname);
 
[DllImport("Kernel32")]
public static extern int FreeLibrary(inthandle);
 
当我们在 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")]
    publicstaticexternintLoadLibrary(Stringfuncname);
 
    ///<summary>
    /// API GetProcAddress
    ///</summary>
    [DllImport("Kernel32")]
    publicstaticexternintGetProcAddress(inthandle, Stringfuncname);
 
    ///<summary>
    /// API FreeLibrary
    ///</summary>
    [DllImport("Kernel32")]
    publicstaticexternintFreeLibrary(inthandle);
 
    ///<summary>
    /// 通过非托管函数名转换为对应的委托 , by jingzhongrong
    ///</summary>
    ///<param name="dllModule"> 通过 LoadLibrary 获得的 DLL 句柄 </param>
    ///<param name="functionName"> 非托管函数名 </param>
    ///<param name="t"> 对应的委托类型 </param>
    ///<returns> 委托实例,可强制转换为适当的委托类型 </returns>
    publicstaticDelegateGetFunctionAddress(intdllModule, stringfunctionName, Typet)
    {
       intaddress = GetProcAddress(dllModule, functionName);
       if (address == 0)
           returnnull;
       else
           returnMarshal.GetDelegateForFunctionPointer(newIntPtr(address), t);
    }
 
    ///<summary>
    /// 将表示函数地址的 IntPtr 实例转换成对应的委托 , by jingzhongrong
    ///</summary>
    publicstaticDelegateGetDelegateFromIntPtr(IntPtraddress, Typet)
    {
       if (address == IntPtr.Zero)
           returnnull;
       else
           returnMarshal.GetDelegateForFunctionPointer(address, t);
    }
 
    ///<summary>
    /// 将表示函数地址的 int 转换成对应的委托,by jingzhongrong
    ///</summary>
    publicstaticDelegateGetDelegateFromIntPtr(intaddress, Typet)
    {
       if (address == 0)
           returnnull;
       else
           returnMarshal.GetDelegateForFunctionPointer(newIntPtr(address), t);
    }
}
 
通过这个类,我们这样调用 DLL
 
1 、声明相应的委托(正确声明很重要,否则不能调用成功,后面有详细介绍)。
 
2 、加载 DLL
int hModule = DLLWrapper.LoadLibrary(dllFilePath);
if (hModule == 0)
    returnfalse;
 
3 、获取相应的委托实例:
FOO foo = (FOO)DLLWrapper.GetFunctionAddress(hModule, "foo", typeof(FOO));
if (foo == null)
{
    DLLWrapper.FreeLibrary(hModule);
    returnfalse;
}
 
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)]
    structStructVersionInfo
    {
       publicintMajorVersion;
       publicintMinorVersion;
    }
另外,如果单独使用内部类型没有另外使用到字符串、结构、类,可以将结构在 C# 中声明为 class
    [StructLayout(LayoutKind.Sequential)]
    classStructVersionInfo
    {
       publicintMajorVersion;
       publicintMinorVersion;
    }
 
对应 C++ 中的声明:
    typedef struct _VERSION_INFO
    {
        intMajorVersion;
        intMinorVersion;
    } VERSION_INFO, *PVERSION_INFO;
 
如果结构中使用到了字符串,最好应指定相应的字符集:
    [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
 
部分常用的声明对应关系(在结构中):
C++ :字符串数组
    wchar_t Comments[120];
C#
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 120)]
    publicstringComments;
 
C++ :结构成员
    VERSION_INFO ver;
C#
    public StructVersionInfo ver;
 
C++ :函数指针声明
    PFOO pFoo; // 具体声明见文章前面部分
C#:
    public IntPtr pFoo;  // 也可以为 public int pFoo;
        // 不同的声明方法可以使用上面 DLLWrapper 类的相应函数获取对应的委托实例
 
如果在结构中使用到了 union ,那么可以使用 FieldOffset 指定具体位置。
 
3 、委托的声明:
 
C++ 编写的 DLL 函数需要通过指针传出将一个结构:如以下声明:
    void getVersionInfo( VERSION_INFO *ver);
对于在 C# 中声明为 class 的结构(当 VERSION_INFO 声明为 class
    delegate void getVersionInfo ( VERSION_INFO ver);
如果结构声明为 struct ,那么应该使用如下声明:
    delegate void getVersionInfo ( ref VERSION_INFO ver);
注意:应该使用 ref 关键字。
 
 
如果 DLL 函数需要传入一个字符串,比如这样:
    BOOL __stdcall jingzhongrong1(constwchar_t* lpFileName, int* FileNum);
那么使用委托来调用函数的时候应该在 C# 中如下声明委托:
    delegatebooljingzhongrong1(
       [MarshalAs(UnmanagedType.LPWStr)]StringFileName,
       refintFileNum);
注意:应该使用 [MarshalAs(UnmanagedType.LPWStr)] String 进行声明。
 
 
如果要在 DLL 函数中传出一个字符串,比如这样:
    void __stdcall jingzhongrong2(
    wchar_t* lpFileName, // 要传出的字符串
    intLength);
那么我们如下声明委托:
    // 使用委托从非托管函数的参数中传出的字符串,
    // 应该这样声明,并在调用前为 StringBuilder 预备足够的空间
    delegatevoidjingzhongrong2(
       [MarshalAs(UnmanagedType.LPWStr)] StringBuilderlpFileName,
       refintLength,
    );
在使用函数前,应先为 StringBuilder 声明足够的空间用于存放字符串:
    StringBuilder fileName = newStringBuilder(FileNameLength);

posted on 2008-09-02 11:56 死神 阅读(1935) 评论(1)  编辑  收藏 所属分类: C#学习

评论

# re: C#动态调用C++编写的DLL函数 2014-02-19 10:57 xyj

谢谢   回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

公告

欢迎大家来到我的个人世界!

常用链接

留言簿(3)

随笔分类(5)

随笔档案(9)

文章分类(37)

文章档案(41)

相册

语音技术

最新随笔

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜