【转】C#程序调用非托管C++ DLL文件的方法
2011-03-13 17:40
447 查看
08年写的一篇文章,当时项目用C#开发,但是有一些希望重用之前的C++代码,于是研究了如何在C#中调用C++的DLL。
viewsourceprint?
extern”C”
通常来说,C++编译器可能会改变函数和变量的名字,从而导致严重的链接程序问题。例如,假设使用C++编写一个DLL,当创建DLL时,Microsoft的编译器就会改变函数的名字。函数名将被设置一个前导下划线,再加上一个@符号的前缀,后随一个数字,表示作为参数传递给函数的字节数。例如,下面的函数是作为DLL的输出节中的_MyFunc@8输出的:
viewsourceprint?
如果用另一个供应商的工具创建了一个可执行模块,它将设法链接到一个名叫MyFunc的函数,该函数在Microsoft编译器已有的DLL中并不存在,因此链接将失败。
使用extern“C”关键字可以使编译器按照C语言的方式编译DLL文件,即编译时不改变函数名。
__declspec(dllexport)
在32位编译器版本中,可以使用__declspec(dllexport)关键字从DLL导出数据、函数、类或类成员函数。__declspec(dllexport)会将导出指令添加到对象文件中,因此不需要使用.def文件。
若要导出函数,__declspec(dllexport)关键字必须出现在调用约定关键字的左边(如果指定了关键字)。例如:
viewsourceprint?
__stdcall
表明被调用方清理堆栈。
viewsourceprint?
usingSystem.Runtime.InteropServices;
System.Runtime.InteropServices命名空间提供各种各样支持COMinterop及平台调用服务的成员,使程序可以与非托管代码进行交互操作。
[DllImport(“dllfilepath”)]
代码中DllImport关键字作用是告诉编译器入口点在哪里,并将打包函数捆绑在这个类中。在声明的时候还可以添加几个属性:
viewsourceprint?
EntryPoint:指定要调用的DLL入口点。默认入口点名称是托管方法的名称。
CharSet:控制名称重整和封送String参数的方式(默认是UNICODE)
CallingConvention指示入口点的函数调用约定(默认WINAPI)
注意:必须在标记为”static”和”extern”的方法上指定”DllImport”属性。
示例:
C#代码:
viewsourceprint?
C++代码:
viewsourceprint?
注意:在DLL中更改字符串的值,C#中的值也会改变。
缺点:无法改变字符串的长度,建议使用第3种方法。
C#代码:
viewsourceprint?
C++代码:
viewsourceprint?
C#代码:
viewsourceprint?
C++代码:
viewsourceprint?
注意:在DLL改变结构体成员的值,C#中随之改变。
C#代码:
viewsourceprint?
C++代码:
viewsourceprint?
C++中的函数声明
1 | extern "C" __declspec ( dllexport ) int __stdcalltestfunc( char *astr, int *a); |
通常来说,C++编译器可能会改变函数和变量的名字,从而导致严重的链接程序问题。例如,假设使用C++编写一个DLL,当创建DLL时,Microsoft的编译器就会改变函数的名字。函数名将被设置一个前导下划线,再加上一个@符号的前缀,后随一个数字,表示作为参数传递给函数的字节数。例如,下面的函数是作为DLL的输出节中的_MyFunc@8输出的:
1 | __declspec ( dllexport ) LONG __stdcallMyFunc( int a, int b); |
使用extern“C”关键字可以使编译器按照C语言的方式编译DLL文件,即编译时不改变函数名。
__declspec(dllexport)
在32位编译器版本中,可以使用__declspec(dllexport)关键字从DLL导出数据、函数、类或类成员函数。__declspec(dllexport)会将导出指令添加到对象文件中,因此不需要使用.def文件。
若要导出函数,__declspec(dllexport)关键字必须出现在调用约定关键字的左边(如果指定了关键字)。例如:
1 | __declspec ( dllexport ) void __cdeclFunction1( void ); |
表明被调用方清理堆栈。
C#中的函数声明
1 | using System.Runtime.InteropServices; |
2 | … |
3 |
4 | public class Program |
5 | { |
6 | [DllImport( @"E:\Projects\testdll\debug\testdll.dll" )] |
7 | public static extern int testfunc(StringBuilderabuf, ref int a); |
8 | } |
System.Runtime.InteropServices命名空间提供各种各样支持COMinterop及平台调用服务的成员,使程序可以与非托管代码进行交互操作。
[DllImport(“dllfilepath”)]
代码中DllImport关键字作用是告诉编译器入口点在哪里,并将打包函数捆绑在这个类中。在声明的时候还可以添加几个属性:
1 | [DllImport( "MyDLL.dll" , |
2 | EntryPoint= "mySum" , |
3 | CharSet=CharSet.Auto, |
4 | CallingConvention=CallingConvention.StdCall)] |
CharSet:控制名称重整和封送String参数的方式(默认是UNICODE)
CallingConvention指示入口点的函数调用约定(默认WINAPI)
注意:必须在标记为”static”和”extern”的方法上指定”DllImport”属性。
数据传递方法
1.基本数据类型的传递
函数参数和返回值可以是C#和C++的各种基本数据类型,如int,float,double,char(注意不是char*)等。示例:
C#代码:
01 | using System; |
02 | using System.Text; |
03 | using System.Runtime.InteropServices; |
04 |
05 | class Program |
06 | { |
07 | [DllImport( @"E:\Projects\testdll\debug\testdll.dll" )] |
08 | public static extern int testfunc( int a, float b, double c, char d); |
09 |
10 | static void Main( string []args) |
11 | { |
12 | int a=1; |
13 | float b=12; |
14 | double c=12.34; |
15 | char d= 'A' ; |
16 | testfunc(a,b,c,d); |
17 | Console.ReadKey(); |
18 | } |
19 | } |
01 | <PRE class =brush:cpp>#include<iostream> |
02 | using namespace std; |
03 |
04 | extern "C" |
05 | { |
06 | _declspec( dllexport ) int __stdcalltestfunc( int a, float b, double c, char d) |
07 | { |
08 | cout<<a<< "," <<b<< "," <<c<< "," <<d<<endl; |
09 | return 0; |
10 | } |
11 | } |
12 | </PRE> |
2.向DLL传入字符串
C#中使用string定义字符串,将字符串对象名传给DLL。注意:在DLL中更改字符串的值,C#中的值也会改变。
缺点:无法改变字符串的长度,建议使用第3种方法。
C#代码:
01 | using System; |
02 | using System.Text; |
03 | using System.Runtime.InteropServices; |
04 |
05 | class Program |
06 | { |
07 | [DllImport( @"E:\Projects\testdll\debug\testdll.dll" )] |
08 | public static extern int testfunc( string a); |
09 |
10 | static void Main( string []args) |
11 | { |
12 | string a= "HelloWorld!" ; |
13 | testfunc(a); |
14 | Console.ReadKey(); |
15 | } |
16 | } |
01 | #include<iostream> |
02 | using namespace std; |
03 |
04 | extern "C" |
05 | { |
06 | _declspec( dllexport ) int __stdcalltestfunc( char *astr) |
07 | { |
08 | cout<<astr<<endl; |
09 | *astr= 'A' ; //更改字符串的数据 |
10 | cout<<astr<<endl; |
11 | return 0; |
12 | } |
13 | } |
3.DLL传出字符串
C#中使用StringBuilder对象创建变长数组,并设置StringBuilder的Capacity为数组最大长度。将此对象名传递给DLL,使用char*接收。C#代码:
01 | using System; |
02 | using System.Text; |
03 | using System.Runtime.InteropServices; |
04 |
05 | class Program |
06 | { |
07 | [DllImport( @"E:\Projects\testdll\debug\testdll.dll" )] |
08 | public static extern int testfunc(StringBuilderabuf); |
09 |
10 | static void Main( string []args) |
11 | { |
12 | StringBuilderabuf= new StringBuilder(); |
13 | abuf.Capacity=100; //设置字符串最大长度 |
14 | testfunc(abuf); |
15 | Console.ReadKey(); |
16 | } |
17 |
18 | } |
01 | #include<iostream> |
02 | using namespace std; |
03 |
04 | extern "C" |
05 | { |
06 | _declspec( dllexport ) int __stdcalltestfunc( char *astr) |
07 | { |
08 | *astr++= 'a' ; |
09 | *astr++= 'b' ; //C#中abuf随astr改变 |
10 | *astr= '\0' ; |
11 |
12 | return 0; |
13 | } |
14 | } |
4.DLL传递结构体(需要在C#中重新定义,不推荐使用)
C#中使用StructLayout重新定义需要使用的结构体。注意:在DLL改变结构体成员的值,C#中随之改变。
C#代码:
01 | using System; |
02 | using System.Text; |
03 | using System.Runtime.InteropServices; |
04 |
05 | [StructLayout(LayoutKind.Sequential)] |
06 | public struct Point |
07 | { |
08 | public double x; |
09 | public double y; |
10 | } |
11 |
12 | class Program |
13 | { |
14 | [DllImport( @"E:\Projects\testdll\debug\testdll.dll" )] |
15 | public static extern int testfunc(Pointp); |
16 |
17 | static void Main( string []args) |
18 | { |
19 | Pointp; |
20 | p.x=12.34; |
21 | p.y=43.21; |
22 | testfunc(p); |
23 | Console.ReadKey(); |
24 | } |
25 | } |
01 | #include<iostream> |
02 | using namespace std; |
03 |
04 | struct Point |
05 | { |
06 | double x; |
07 | double y; |
08 | }; |
09 |
10 | extern "C" |
11 | { |
12 | _declspec( dllexport ) int __stdcalltestfunc(Pointp) |
13 | { |
14 | cout<<p.x<< "," <<p.y<<endl; |
15 | return 0; |
16 | } |
17 | } |
相关文章推荐
- 【好文章要分享】C#程序调用非托管C++ DLL文件的方法
- C#程序调用非托管C++ DLL文件的方法
- C#程序调用非托管C++ DLL文件的方法
- C#程序调用非托管C++ DLL文件的方法
- c# 反射调用程序集方法、接口实例
- C# 调用其它程序 Process.Start 方法说明
- C#动态方法调用 提高程序的扩展性
- 从C#程序中调用非受管DLLs的方法
- C#中Winform程序调用WebService方法
- 浅谈C++调用C#的DLL程序方法
- C# 使用 Environment.GetCommandLineArgs 方法 制作*.exe和参数 供其他程序调用
- 在C# winform程序中直接执行javascript,调用javascript方法
- 给C#程序加壳(C# 调用嵌入资源的EXE文件方法)
- C# 使用 Environment.GetCommandLineArgs 方法 制作*.exe和参数 供其他程序调用
- C# 调用其它程序 Process.Start 方法说明
- C# 程序开始主要是写类和方法 的基本步骤和调用方法
- C# 两个程序如何获得同用内存,一程序调用另外一程序内的方法或函数
- C#调用exe文件,IIS发布后无法掉用本地exe程序的解决方法
- C# 使用MarshalByRefObject跨程序调用方法
- C# 调用其它程序 Process.Start 方法