您的位置:首页 > 编程语言 > C语言/C++

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

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

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

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

主要有如下几种转换:

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

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

类型对照:

BSTR---------StringBuilder

LPCTSTR---------StringBuilder

LPCWSTR---------IntPtr

handle---------IntPtr

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

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

int*-----------refint

int&-----------refint

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

unsignedchar*-----refbyte

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

CallBack回调函数需要封装在一个委托里,delegatestaticexternintFunCallBack(stringstr);

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

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

1.数据类型转换问题

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

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

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

int__stdcallFunctionName(unsignedcharparam1,unsignedshortparam2)

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

[DllImport(“COMDLLpath/file”)]

externstaticintFunctionName(byteparam1,ushortparam2)

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

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

Win32Types

CLRType

char,INT8,SBYTE,CHAR

System.SByte

short,shortint,INT16,SHORT

System.Int16

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

System.Int32

__int64,INT64,LONGLONG

System.Int64

unsignedchar,UINT8,UCHAR,BYTE

System.Byte

unsignedshort,UINT16,USHORT,WORD,ATOM,WCHAR,__wchar_t

System.UInt16

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

System.UInt32

unsigned__int64,UINT64,DWORDLONG,ULONGLONG

System.UInt64

float,FLOAT

System.Single

double,longdouble,DOUBLE

System.Double

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

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

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

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

int__stdcallFunctionName(unsignedchar¶m1,unsignedchar*param2)

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

[DllImport(“file”)]

externstaticintFunctionName(refbyteparam1,refbyteparam2)

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

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

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

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

[DllImport(“file”)]

externstaticintFunctionName(refbyteparam1[1],refbyteparam2)

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

C#中调用DLL

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

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

包含DllImport所在的名字空间

usingSystem.Runtime.InteropServices;

publicclassXXXX{

[DllImport(“MyDLL.dll")]

publicstaticexternintmySum(inta,intb);

}

[DllImport(“MyDLL.dll")]

publicstaticexternintmySum(inta,intb);

代码中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指示被调用方在从属性化方法返回之前是否调用SetLastErrorWin32API函数(C#中默认false)

int类型

[DllImport(“MyDLL.dll")]

//返回个int类型

publicstaticexternintmySum(inta1,intb1);

//DLL中申明

extern“C”__declspec(dllexport)intWINAPImySum(inta2,intb2)

{

//a2b2不能改变a1b1

//a2=..

//b2=...

returna+b;

}

//参数传递int类型

publicstaticexternintmySum(refinta1,refintb1);

//DLL中申明

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

{

//可以改变a1,b1

*a2=...

*b2=...

returna+b;

}

DLL需传入char*类型

[DllImport(“MyDLL.dll")]

//传入值

publicstaticexternintmySum(stringastr1,stringbstr1);

//DLL中申明

extern“C”__declspec(dllexport)intWINAPImySum(char*astr2,char*bstr2)

{

//改变astr2bstr2,astr1bstr1不会被改变

returna+b;

}

DLL需传出char*类型

[DllImport(“MyDLL.dll")]

//传出值

publicstaticexternintmySum(StringBuilderabuf,StringBuilderbbuf);

//DLL中申明

extern“C”__declspec(dllexport)intWINAPImySum(char*astr,char*bstr)

{

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

returna+b;

}

DLL回调函数

BOOLEnumWindows(WNDENUMPROClpEnumFunc,LPARAMlParam)



usingSystem;

usingSystem.Runtime.InteropServices;

publicdelegateboolCallBack(inthwnd,intlParam);//定义委托函数类型

publicclassEnumReportApp

{

[DllImport("user32")]

publicstaticexternintEnumWindows(CallBackx,inty);

publicstaticvoidMain(){

CallBackmyCallBack=newCallBack(EnumReportApp.Report);EnumWindows(myCallBack,0);

}

publicstaticboolReport(inthwnd,intlParam)

{

Console.Write("Windowhandleis");

Console.WriteLine(hwnd);returntrue;

}

}

DLL传递结构

BOOLPtInRect(constRECT*lprc,POINTpt);

usingSystem.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)]

publicstructPoint{

publicintx;

publicinty;

}

[StructLayout(LayoutKind.Explicit)]

publicstructRect

{

[FieldOffset(0)]publicintleft;

[FieldOffset(4)]publicinttop;

[FieldOffset(8)]publicintright;

[FieldOffset(12)]publicintbottom;

}

ClassXXXX{

[DllImport("User32.dll")]

publicstaticexternboolPtInRect(refRectr,Pointp);

}

DLL回调函数,传递结构想看的msdn里面都有专题介绍,看的我都是晕晕的:)

其他参考请搜索:

在C#程序设计中使用Win32类库

C#中调用C++托管Dll

如何在C#中加载自己编写的动态链接库

相关文章:CreatingaP/InvokeLibrary

能用上DLL以后感觉还是很好的,原来的C++代码只要修改编译通过就可以了,

高兴没多久,发现.net2005居然可以用VB,VC开发智能设备项目,可以创建MFC智能设备项目

晕晕,难道可以直接用MFC来开发smartphone的程序了,赶紧看看,,,


VisualC++使用__declspec(dllexport)从DLL导出(到C#)

由于各种的原因,如何把unmanaged的c++DLL转换成managedC#是一个问题。

方法有3个.

Ø使用.def文件

Ø可以不用.def文件,使用__declspec(dllexport)关键字,特别是针对VisualC++编译器的时候

Ø直接用MC++写

什么时候用.def文件?

.def的意思是module-definition,这个纯文本的文件定义了模块的信息。对于编译器来说,一个方法在编译之后的DLL文件里,存在的形式名字可能不是作者当时起的那个,例如好好的函数名字function()变成了?function2@@YAXXZ;可以用undname查看这个被编译器修饰掉的名字,原型是"void__cdeclfunction2(void)".大概使用的时候就会遇到类似”链接错误,未决的外部符号…”的错误.

.def文件主要的作用,就是”标注”出这个函数原来的样子,这样编译器在编译的时候,规则上就会以C编译器的规则来处理,修饰被去掉了,另外同时可以把导出函数的序号值手动的改高一点;还有一个优点(也是缺点)就是可以用NONAME来修饰函数,这样导出函数的序号值就变成了1~N,即第N个函数.所以调用GetProcAddress()的时候,可以直接用定义的序号值,而不用写函数的名字(但是名字就完全不可用了),更好的是,导出函数的这个DLL会变得比较小,
当然,MSDN强调了一点:仅你可以并有权更改这个.def文件内容的时候,你才可以用这个办法.

那么,什么时候考虑用.def文件呢?因为编译器不同,而产生的修饰名不同的话,这个文件就是必须的.

注意如果文件没有导出函数的话,这个文件可能降低运行效率。

.def文件的格式

LIBRARYFileNameWithoutExtension

EXPORTS

Function1@1

Function3@2

Function2@3NONAME

启用Enableit:Propertypages->ConfigurationProperties->C/C++->Linker->input->ModuleDefinitionFile

那不用.def呢?__declspec(dllexport)的作用

这个东西,可以给函数用,也可以给类用.声明大概这样子:

viewsource

print?

1
__declspec
(
dllexport
)
int
__stdcall
GetMid(vector<type>ve);
2
class
__declspec
(
dllexport
)
TestClass{
3
public
:
4
TestClass();
5
}
这牵扯到了一个东西就是__stdcall和__cdecl(还有__fastcall,不过很少用),其中__cdecl一般是C或者C++的缺省调用规范,但是最大的一个区别就是__stdcall在返回前自身清除堆栈,而__cdecl是调用方来做这个事情(可参考COM中的某些机制),另一个区别就是__stdcall对于可变参数的函数,玩不转.

反正今时今日,大家都在用__stdcall,所以这么写也没什么问题,但不是没有.VB里调用标记着__cdecl的方法,可能会得到一个异常BadDLLCallingConvention.解决方法也很简单:

原来的函数

viewsource

print?

1
long
_cdeclPassStr(
LPSTR
pStr)
2
{
return
1;
}
新的函数

viewsource

print?

1
long
_stdcallPassStrStdCall(
LPSTR
pStr)
2
{
return
PassStr(pStr);
}
问题是,如果这个函数原型,参数是可变的,那又怎么弄呢?

调用的时候,C#都是这么写的:

viewsource

print?

1
[DllImport(
""
,EntryPoint
=
@"?GetMid@@YAXXZ"
,CharSet=CharSet.Auto)]
2
private
static
extern
void
GetMid(...);
这个入口点名字还真别扭,看来去掉这个修饰还是蛮需要的,除了用.def文件,另一个办法就是用extern“C”.

Extern“C”

一句话总结:这个东西可以去掉修饰名。在不用.def文件的前提下,这个可以保证你的函数function()还是这个名字.

但是,这个东西对类不太起作用!

这个东西是这么用的:放到函数声明的最前面。就类似这样extern“C”void__declspec(dllexport)function(void);

对于类,一般的做法是,把它的内部方法(特别是实例方法,或变量),wrap出一个方法来。见下面的实例.

还要做什么?

当一个DLL被初始化的时候,它需要一个入口点,一般对于非MFCDLL来说,这样写就行了:

viewsource

print?

01
BOOL
APIENTRYDllMain(
HANDLE
hModule,
02
DWORD
ul_reason_for_call,
03
LPVOID
lpReserved
04
)
05
{
06
switch
(
ul_reason_for_call)
07
{
08
case
DLL_PROCESS_ATTACH:
09
case
DLL_THREAD_ATTACH:
10
case
DLL_THREAD_DETACH:
11
case
DLL_PROCESS_DETACH:
12
break
;
13
}
14
return
TRUE;
15
}
要注意的是这个入口点的名字必须是DllMain,如果不是需要修改linker的/entry选项.否则对于C的话可能会初始化失败.

************************************************************************

开始用导出函数PInvoke

为了好看一点,先约定一下:

viewsource

print?

1
#defineEXT_Cextern"C"
2
#defineDLLEXPORT__declspec(dllexport)
3
#defineEXT_C_DLLEXPORTEXT_CDLLEXPORT
4
#defineCALLBACK__stdcall
5
#defineWINAPI__stdcall
6
#defineAPIENTRYWINAPI
1.普通的函数

viewsource

print?

1
EXT_C_DLLEXPORT
void
WINAPI
Function();
2
3
[DllImport(
"filename.dll"
,
EntryPoint=
"Function"
)]
4
private
static
extern
void
Func();
2.ref或者out

viewsource

print?

1
EXT_C_DLLEXPORT
void
WINAPI
Function(Type**ty);
2
3
[DllImport(
"filename.dll"
,
EntryPoint=
"Function"
)]
4
private
static
extern
void
Func(
out
Type
ty);
3.指针函数和委托

viewsource

print?

1
Typedef
void
(CALLBACK
*pFunc)(
int
);
2
EXT_C_DLLEXPORT
void
WINAPI
Compare(
int
a,
int
b,
pFuncp);
3
private
delegate
int
CompareCallback(
int
a,
int
b);
4
[DllImport(
"filename.dll"
,EntryPoint=”Compare”)]
5
private
static
extern
int
Compare(
int
a,
int
b,
CompareCallbackcall);
4.类的处理.其实不是说不可以把类标记为DLLEXPORT,如果可以的话,当然是wrap比较好

C++里的原型

viewsource

print?

1
class
DLLEXPORTTest
2
{
3
public
:
4
Test();
5
~Test();
6
BOOL
function(
int
par)
7
};
类被export,函数调用时候注意用CallingConvention.ThisCall.

viewsource

print?

1
[DllImport(
"filename.dll"
,
EntryPoint=
@"??4Test@@QEAAAEAV0@AEBV0@@Z"
,CallingConvention=CallingConvention.ThisCall)]
2
private
static
extern
int
TestFunc(IntPtr
hwnd,
int
par);
采用了”迂回”策略,C++里先这样定义,同理,添加构造函数等,函数就变成了这个样子:

viewsource

print?

01
EXT_C_DLLEXPORT
BOOL
WINAPI
function_wrap(Test*t,
int
par)
02
{
03
return
t->function(par);
04
}
05
EXT_C_DLLEXPORTTest*Test_ctor()
06
{
07
Test*t=
new
Test();
08
return
t;
09
}
10
EXT_C_DLLEXPORT
void
Test_dector(Test*
t)
11
{
12
if
(NULL
==t)
13
{
14
delete
t;
15
t=NULL;
16
}
17
}
在C#里这样写,那么就和平时用没什么区别了.

viewsource

print?

01
public
class
Test
:IDisposable
02
{
03
private
IntPtr
instance;
04
public
Test()
05
{
06
instance=CreateInstance();
07
}
08
09
~Test()
10
{
11
Dispose(
false
);
12
}
13
14
#regionpinvoke
15
[DllImport(
"filename.dll"
,
EntryPoint=
@"Test_ctor"
)]
16
private
static
extern
IntPtr
CreateInstance();
17
[DllImport(
"filename.dll"
,
EntryPoint=
@"Test_dector"
)]
18
private
static
extern
void
DestroyInstance(IntPtr
hwnd);
19
[
return
:
MarshalAs(UnmanagedType.Bool)]
20
[DllImport(
"filename.dll"
,
EntryPoint=
@"function_wrap"
)]
21
private
static
extern
bool
function_wrap(
int
par);
22
#endregion
23
24
#regionIDisposableMembers
25
26
public
void
Dispose()
27
{
28
Dispose(
true
);
29
}
30
31
private
void
Dispose(
bool
bDisposing)
32
{
33
if
(instance
!=IntPtr.Zero)
34
{
35
DestroyInstance(instance);
36
}
37
38
if
(bDisposing)
39
{
40
GC.SuppressFinalize(
this
);
41
}
42
}
43
44
#endregion
45
}
5.Struct操作.

viewsource

print?

1
typedef
struct
Object_HANDLE
{
2
unsigned
long
dbch_size;
3
HANDLE
dbch_handle;
4
GUIDdbch_eventguid;
5
BOOL
res_flag;
6
}Object_Native_HANDLE;
首先在C#里严格定义这个,LayoutKind.Sequential用来保证内存分配的正常。

viewsource

print?

1
[StructLayout(LayoutKind.Sequential)]
2
public
struct
Object_Native_HANDLE
3
{
4
public
ulong
dbch_size;
5
public
IntPtr
dbch_handle;
6
public
Guid
dbch_eventGuid;
7
public
bool
res_flag;
8
}
Marshal的使用如下:

viewsource

print?

1
Object_Native_HANDLEobj=
new
Object_Native_HANDLE();
//初始化
2
int
amountToAllocate=Marshal.SizeOf(obj);
//获取大小
3
IntPtrobjPtr=Marshal.AllocHGlobal(amountToAllocate);
//分配并获取空的空间地址
4
Marshal.StructureToPtr(obj,objPtr,
false
);
//
值写入分配的空间
5
//操作...
6
Marshal.FreeHGlobal(objPtr);
//释放空间
最后提一句,unmagedcode中的错误,到managed以后,极大可能是捕捉不到的。所以错误需要分别处理。

花了不少时间,MC++平时用的不多,不写了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: