C#时常需要调用C++DLL

C#时常需要调用C++DLL
 
文章出处:DIY部落(http://www.diybl.com/course/3_program/c/c_js/20100101/186047.html)

C#时常需要调用C++DLL

www.diybl.com 时间:2010-01-01 作者:匿名 编辑:小张 点击: 107 [评论]

-

-

在合作开发时,C#时常需要调用C++DLL,当传递参数时时常遇到问题,尤其是传递和返回字符串是,现总结一下,分享给大家:

 

VC++中主要字符串类型为:LPSTR,LPCSTR, LPCTSTR, string, CString, LPCWSTR, LPWSTR等

但转为C#类型却不完全相同。

 

主要有如下几种转换:

 

 

将string转为IntPtr:IntPtr System.Runtime.InteropServices.Marshal.StringToCoTaskMemAuto(string)

 

将IntPtr转为string:string System.Runtime.InteropServices.MarshalPtrToStringAuto(IntPtr)

 

类型对照:

 

BSTR ---------  StringBuilder

 

LPCTSTR --------- StringBuilder

 

LPCWSTR ---------  IntPtr

 

handle---------IntPtr

 

hwnd-----------IntPtr

 

char *----------string

 

int * -----------ref int

 

int &-----------ref int

 

void *----------IntPtr

 

unsigned char *-----ref byte

 

Struct需要在C#里重新定义一个Struct

 

CallBack回调函数需要封装在一个委托里,delegate static extern int FunCallBack(string str);

 

注意在每个函数的前面加上public static extern +返回的数据类型,如果不加public ,函数默认为私有函数,调用就会出错。

 

 

在C#调用C++ DLL封装库时会出现两个问题:

 

 

1. 数据类型转换问题 

2. 指针或地址参数传送问题 

 

    首先是数据类型转换问题。因为C#是.NET语言,利用的是.NET的基本数据类型,所以实际上是将C++的数据类型与.NET的基本数据类型进行对应。 

 

    例如C++的原有函数是: 

 

int __stdcall FunctionName(unsigned char param1, unsigned short param2) 

 

    其中的参数数据类型在C#中,必须转为对应的数据类型。如: 

 

[DllImport(“ COM DLL path/file ”)] 

extern static int FunctionName(byte param1, ushort param2) 

 

    因为调用的是__stdcall函数,所以使用了P/Invoke的调用方法。其中的方法FunctionName必须声明为静态外部函数,即加上extern static声明头。我们可以看到,在调用的过程中,unsigned char变为了byte,unsigned short变为了ushort。变换后,参数的数据类型不变,只是声明方式必须改为.NET语言的规范。 

 

    我们可以通过下表来进行这种转换: 

 

Win32 Types 

CLR Type 

 

char, INT8, SBYTE, CHAR 

System.SByte 

 

short, short int, INT16, SHORT 

System.Int16 

 

int, long, long int, INT32, LONG32, BOOL , INT 

System.Int32 

 

__int64, INT64, LONGLONG 

System.Int64 

 

unsigned char, UINT8, UCHAR , BYTE 

System.Byte 

 

unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t 

System.UInt16 

 

unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT 

System.UInt32 

 

unsigned __int64, UINT64, DWORDLONG, ULONGLONG 

System.UInt64 

 

float, FLOAT 

System.Single 

 

double, long double, DOUBLE 

System.Double 

 

 

    之后再将CLR的数据类型表示方式转换为C#的表示方式。这样一来,函数的参数类型问题就可以解决了。 

 

    现在,我们再来考虑下一个问题,如果要调用的函数参数是指针或是地址变量,怎么办? 

 

    对于这种情况可以使用C#提供的非安全代码来进行解决,但是,毕竟是非托管代码,垃圾资源处理不好的话对应用程序是很不利的。所以还是使用C#提供的ref以及out修饰字比较好。 

 

    同上面一样,我们也举一个例子: 

 

int __stdcall FunctionName(unsigned char &param1, unsigned char *param2) 

 

    在C#中对其进行调用的方法是: 

 

[DllImport(“ file ”)] 

extern static int FunctionName(ref byte param1, ref byte param2) 

 

    看到这,可能有人会问,&是取地址,*是传送指针,为何都只用ref就可以了呢?一种可能的解释是ref是一个具有重载特性的修饰符,会自动识别是取地址还是传送指针。 

 

    在实际的情况中,我们利用参数传递地址更多还是用在传送数组首地址上。 

如:byte[] param1 = new param1(6); 

 

    在这里我们声明了一个数组,现在要将其的首地址传送过去,只要将param1数组的第一个元素用ref修饰。具体如下: 

 

[DllImport(“ file ”)] 

extern static int FunctionName(ref byte param1[1], ref byte param2) 

  

文章出处:DIY部落(http://www.diybl.com/course/3_program/c++/cppjs/200886/134816.html)

 

 

C# 中调用DLL 

为了能用上原来的C++代码,只好研究下从C# 中调用DLL

首先必须要有一个声明,使用的是DllImport关键字: 

包含DllImport所在的名字空间 

using System.Runtime.InteropServices; 

public class XXXX{

 

[DllImport(“MyDLL.dll")] 

public static extern int mySum (int a,int b); 

 

 

[DllImport(“MyDLL.dll")] 

public static extern int mySum (int a,int b); 

代码中DllImport关键字作用是告诉编译器入口点在哪里,并将打包函数捆绑在这个类中 

在调用的时候 

在类中的时候 直接   mySum(a,b);就可以了 

在其他类中调用: XXXX. mySum(a,b); 

 

[DllImport(“MyDLL.dll”)]在申明的时候还可以添加几个属性 

[DllImport(“MyDLL.dll", EntryPoint=" mySum ",CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall) 

EntryPoint: 指定要调用的 DLL 入口点。默认入口点名称是托管方法的名称 。 

CharSet: 控制名称重整和封送 String 参数的方式 (默认是UNICODE) 

CallingConvention指示入口点的函数调用约定(默认WINAPI)(上次报告讲过的) 

SetLastError 指示被调用方在从属性化方法返回之前是否调用 SetLastError Win32 API 函数 (C#中默认false ) 

 

 

int 类型 

[DllImport(“MyDLL.dll")] 

//返回个int 类型 

public static extern int mySum (int a1,int b1); 

//DLL中申明 

extern “C” __declspec(dllexport)  int WINAPI mySum(int a2,int b2) 

//a2 b2不能改变a1 b1

//a2=..

//b2=...

 return a+b; 

}

 

//参数传递int 类型 

public static extern int mySum (ref int a1,ref int b1); 

//DLL中申明 

extern “C” __declspec(dllexport)  int WINAPI mySum(int *a2,int *b2) 

//可以改变 a1, b1

*a2=...

*b2=...

 return a+b; 

 

 

DLL 需传入char *类型 

[DllImport(“MyDLL.dll")] 

//传入值 

public static extern int mySum (string  astr1,string bstr1); 

//DLL中申明 

extern “C” __declspec(dllexport)  int WINAPI mySum(char * astr2,char * bstr2) 

//改变astr2 bstr 2  ,astr1 bstr1不会被改变

 return a+b; 

 

 

DLL 需传出char *类型 

[DllImport(“MyDLL.dll")] 

// 传出值

public static extern int mySum (StringBuilder abuf, StringBuilder bbuf ); 

//DLL中申明 

extern “C” __declspec(dllexport)  int WINAPI mySum(char * astr,char * bstr) 

//传出char * 改变astr bstr -->abuf, bbuf可以被改变

 return a+b; 

 

DLL 回调函数 

 

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam) 

 

 

 

using System; 

using System.Runtime.InteropServices; 

public delegate bool CallBack(int hwnd, int lParam); //定义委托函数类型 

public class EnumReportApp 

[DllImport("user32")] 

public static extern int EnumWindows(CallBack x, int y); 

public static void Main() { 

CallBack myCallBack = new CallBack(EnumReportApp.Report); EnumWindows(myCallBack, 0); 

public static bool Report(int hwnd, int lParam) 

Console.Write("Window handle is "); 

Console.WriteLine(hwnd); return true; 

 

 

DLL  传递结构  

BOOL PtInRect(const RECT *lprc, POINT pt); 

 

using System.Runtime.InteropServices; 

[StructLayout(LayoutKind.Sequential)] 

public struct Point {

 public int x; 

public int y;

 } 

[StructLayout(LayoutKind.Explicit)] 

 public struct Rect 

 { 

[FieldOffset(0)] public int left; 

[FieldOffset(4)] public int top;

[FieldOffset(8)] public int right; 

[FieldOffset(12)] public int bottom;

 } 

Class XXXX { 

 [DllImport("User32.dll")] 

public static extern bool PtInRect(ref  Rect r, Point p); 

 } 

 

/////////////////////////////////////////////////////////////

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

时间:2010-01-13 15:38来源:未知 作者:admin 点击: 71次我要投稿

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

///////////////////////////////////////////////

.net实例:使用C++调用C#的DLL

 

资料来源:学网(www.xue5.com),原文地址:http://www.xue5.com/itedu/200802/108459.html

1 创建C# DLL,需要指定应用类型为“类库”,代码:

namespace CSLib

{

    public class Class1

    {

        private string name;

 

        public string Name

        {

            get

            {

                return name;

            }

            set

            {

                name = "Your Name: " + value;

            }

        }

    }

}

 

2 C++客户程序,是个控制台应用,代码:

#using "..\debug\CSLib.dll"

using namespace CSLib;

 

int _tmain(int argc, _TCHAR* argv[])

{

 Class1 ^c = gcnew Class1();

 

 c->Name = "zzj";

 

 printf("%s\n", c->Name);

 

 return 0;

}

 

3 几点要记住:

 1 使用#using引用C# DLL,而不是#include。我就是想当然的使用了后者,所以浪费了一上午的时间;

 2 别忘了using namespace CSLib;

 3 使用C++/clr语法,采用正确的访问托管对象,即:使用帽子''^'',而不是星星''*''。 

 

posted @ 2010-02-03 20:43  星释天狼  阅读(231)  评论(0编辑  收藏  举报