Chinaunix首页 | 论坛 | 博客
  • 博客访问: 508619
  • 博文数量: 60
  • 博客积分: 1445
  • 博客等级: 上尉
  • 技术积分: 507
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-14 19:15
文章分类

全部博文(60)

文章存档

2012年(1)

2011年(7)

2010年(52)

我的朋友

分类: C/C++

2010-06-01 11:59:22

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

动态加载 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);

    retur n 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

{

    ///

    /// API LoadLibrary

    ///

    [DllImport("Kernel32")]

    public static extern int LoadLibrary(String funcname);


    ///

    /// API GetProcAddress

    ///

    [DllImport("Kernel32")]

    public static extern int GetProcAddress(int handle, String funcname);


    ///

    /// API FreeLibrary

    ///

    [DllImport("Kernel32")]

    public static extern int FreeLibrary(int handle);


    ///

    /// 通过非托管函数名转换为对应的委托 , by jingzhongrong

    ///

    /// 通过 LoadLibrary 获得的 DLL 句柄

    /// 非托管函数名

    /// 对应的委托类型

    /// 委托实例,可强制转换为适当的委托类型

    public static DelegateGetFunctionAddress(int dllModule, String functionName, Typet)

    {

       int address = GetProcAddress(dllModule, functionName);

       if (address == 0)

           retur nnull;

       else

           retur nMarshal.GetDelegateForFunctionPointer(new intPtr(address), t);

    }


    ///

    /// 将表示函数地址的 intPtr 实例转换成对应的委托 , by jingzhongrong

    ///

    public static DelegateGetDelegateFrom intPtr(intPtr address, Typet)

    {

       if (address == intPtr.Zero)

           retur nnull;

       else

           retur nMarshal.GetDelegateForFunctionPointer(address, t);

    }


    ///

    /// 将表示函数地址的 int  转换成对应的委托,by jingzhongrong

    ///

    public static DelegateGetDelegateFromintPtr(int address, Typet)

    {

       if (address == 0)

           retur nnull;

       else

           retur nMarshal.GetDelegateForFunctionPointer(new intPtr(address), t);

    }

}


通过这个类,我们这样调用 DLL :


1 、声明相应的委托(正确声明很重要,否则不能调用成功,后面有详细介绍)。


2 、加载 DLL :

int  hModule = DLLWrapper.LoadLibrary(dllFilePath);

if (hModule == 0)

    retur nfalse;


3 、获取相应的委托实例:

FOO foo = (FOO)DLLWrapper.GetFunctionAddress(hModule, "foo", typeof(FOO));

if (foo == null)

{

    DLLWrapper.FreeLibrary(hModule);

    retur nfalse;

}


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

    {

       public int MajorVersion;

       public int MinorVersion;

    }

另外,如果单独使用内部类型没有另外使用到字符串、结构、类,可以将结构在 C# 中声明为 class :

    [StructLayout(LayoutKind.Sequential)]

    classStructVersionInfo

    {

       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#

    public  StructVersionInfo ver;


C++ :函数指针声明

    PFOO pFoo; // 具体声明见文章前面部分

C#:

    public  int Ptr 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)]String FileName,

       refint FileNum);

注意:应该使用 [MarshalAs(UnmanagedType.LPWStr)] 和 String  进行声明。



如果要在 DLL 函数中传出一个字符串,比如这样:

    void __stdcall jingzhongrong2(

    wchar_t* lpFileName, // 要传出的字符串

    int * Length);

那么我们如下声明委托:

    // 使用委托从非托管函数的参数中传出的字符串,

    // 应该这样声明,并在调用前为 String Builder 预备足够的空间

    delegatevoidjingzhongrong2(

       [MarshalAs(UnmanagedType.LPWStr)] String BuilderlpFileName,

       refint Length,

    );

在使用函数前,应先为 String Builder 声明足够的空间用于存放字符串:

    String Builder fileName = new String Builder(FileNameLength);

阅读(5848) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~