(转)C#调用非托管Win 32 DLL
转载学习收藏,原文地址http://www.cnblogs.com/mywebname/articles/2291876.html
背景
在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第三方通讯组件的时候,通过C#来开发应用软件时,就需要利用DllImport特性进行方法调用。本篇文章将引导你快速理解这个调用的过程。
步骤
1. 创建一个CSharpInvokeCPP的解决方案:

2. 创建一个C++的动态库项目:

3. 在应用程序设置中,选择“DLL”,其他按照默认选项:

最后点击完成,得到如图所示项目:

我们可以看到这里有一些文件,其中dllmain.cpp作为定义DLL应用程序的入口点,它的作用跟exe文件有个main或者WinMain入口函数是一样的,它就是作为DLL的一个入口函数,实际上它是个可选的文件。它是在静态链接时或动态链接时调用LoadLibrary和FreeLibrary时都会被调用。详细内容可以参考(http://blog.csdn.net/benkaoya/archive/2008/06/02/2504781.aspx)。
4. 现在我们打开CSharpInvokeCPP.CPPDemo.cpp文件:
现在我们加入以下内容:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// CSharpInvokeCPP.CPPDemo.cpp : 定义 DLL 应用程序的导出函数。 // #include "stdafx.h" extern "C" __declspec(dllexport) int Add(int x, int y) { return x + y; } extern "C" __declspec(dllexport) int Sub(int x, int y) { return x - y; } extern "C" __declspec(dllexport) int Multiply(int x, int y) { return x * y; } extern "C" __declspec(dllexport) int Divide(int x, int y) { return x / y; } |
extern "C" 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。而被extern "C"修饰的变量和函数是按照C语言方式编译和连接的。
__declspec(dllexport)的目的是为了将对应的函数放入到DLL动态库中。
extern "C" __declspec(dllexport)加起来的目的是为了使用DllImport调用非托管C++的DLL文件。因为使用DllImport只能调用由C语言函数做成的DLL。
5. 编译项目程序,最后在Debug目录生成CSharpInvokeCPP.CPPDemo.dll和CSharpInvokeCPP.CPPDemo.lib

我们用反编译工具PE Explorer查看下该DLL里面的方法:

可以发现对外的公共函数上包含这四种“加减乘除”方法。
6. 现在来演示下如何利用C#项目来调用非托管C++的DLL,首先创建C#控制台应用程序:

7. 在CSharpInvokeCSharp.CSharpDemo项目上新建一个CPPDLL类,编写以下代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class CPPDLL { [DllImport("CSharpInvokeCPP.CPPDemo.dll")] public static extern int Add(int x, int y); [DllImport("CSharpInvokeCPP.CPPDemo.dll")] public static extern int Sub(int x, int y); [DllImport("CSharpInvokeCPP.CPPDemo.dll")] public static extern int Multiply(int x, int y); [DllImport("CSharpInvokeCPP.CPPDemo.dll")] public static extern int Divide(int x, int y); } |
DllImport作为C#中对C++的DLL类的导入入口特征,并通过static extern对extern “C”进行对应。
8. 另外,记得把CPPDemo中生成的DLL文件拷贝到CSharpDemo的bin目录下,你也可以通过设置【项目属性】->【配置属性】->【常规】中的输出目录:

这样编译项目后,生成的文件就自动输出到CSharpDemo中了。
9. 然后在Main入口编写测试代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
static void Main(string[] args) { int result = CPPDLL.Add(10, 20); Console.WriteLine("10 + 20 = {0}", result); result = CPPDLL.Sub(30, 12); Console.WriteLine("30 - 12 = {0}", result); result = CPPDLL.Multiply(5, 4); Console.WriteLine("5 * 4 = {0}", result); result = CPPDLL.Divide(30, 5); Console.WriteLine("30 / 5 = {0}", result); Console.ReadLine(); } |
运行结果:

方法得到调用。
10. 以上的方法只能通过静态方法对于C++中的函数进行调用。那么怎样通过静态方法去调用C++中一个类对象中的方法呢?现在我在CPPDemo项目中添加一个头文件userinfo.h:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class UserInfo { private: char* m_Name; int m_Age; public: UserInfo(char* name, int age) { m_Name = name; m_Age = age; } virtual ~UserInfo(){ } int GetAge() { return m_Age; } char* GetName() { return m_Name; } }; |
在CSharpInvokeCPP.CPPDemo.cpp中,添加一些代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include "malloc.h" #include "userinfo.h" typedef struct { char name[32]; int age; } User; UserInfo* userInfo; extern "C" __declspec(dllexport) User* Create(char* name, int age) { User* user = (User*)malloc(sizeof(User)); userInfo = new UserInfo(name, age); strcpy(user->name, userInfo->GetName()); user->age = userInfo->GetAge(); return user; } |
这里声明一个结构,包括name和age,这个结构是用于和C#方面的结构作个映射。
注意:代码中的User*是个指针,返回也是一个对象指针,这样做为了防止方法作用域结束后的局部变量的释放。
strcpy是个复制char数组的函数。
11. 在CSharpDemo项目中CPPDLL类中补充代码:
|
1
2
3
4
5
6
7
8
9
10
11
|
[DllImport("CSharpInvokeCPP.CPPDemo.dll")] public static extern IntPtr Create(string name, int age); [StructLayout(LayoutKind.Sequential)] public struct User { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string Name; public int Age; } |
其中这里的结构User就和C++中的User对应。
12. 在Program.cs中补充代码:
|
1
2
3
|
IntPtr ptr = CPPDLL.Create("李平", 27); <STRONG><FONT color=#ff0000>CPPDLL.User user = (CPPDLL.User)Marshal.PtrToStructure(ptr, typeof(CPPDLL.User));</FONT></STRONG> Console.WriteLine("Name: {0}, Age: {1}", user.Name, user.Age); |
注意:红色字体部分,这里结构指针首先转换成IntPtr句柄,然后通过Marshal.PtrToStructrue转换成你所需要的结构。
运行结果:

转自liping13599168/archive/2011/03/31/2000320.html
以下是调用时的一些类型转换等注意事项:
C++
C#调用dll时的类型转换总结
|
C++(Win 32) |
C# |
|
char** |
作为输入参数转为char[],通过Encoding类对这个string[]进行编码后得到的一个char[] |
|
作为输出参数转为byte[],通过Encoding类对这个byte[]进行解码,得到字符串 |
|
|
C++ Dll接口: void CplusplusToCsharp(in char** AgentID, out char** AgentIP); C#中的声明: [DllImport("Example.dll")] public static extern void CplusplusToCsharp(char[] AgentID, byte[] AgentIP); C#中的调用: Encoding encode = Encoding.Default; byte[] tAgentID; byte[] tAgentIP; string[] AgentIP; tAgentID = new byte[100]; tAgentIP = new byte[100]; CplusplusToCsharp(encode.GetChars(tAgentID), tAgentIP); AgentIP[i] = encode.GetString(tAgentIP,i*Length,Length); |
|
|
Handle |
IntPtr |
|
Hwnd |
IntPtr |
|
int* |
ref int |
|
int& |
ref int |
|
void* |
IntPtr |
|
unsigned char* |
ref byte |
|
BOOL |
bool |
|
DWORD |
int 或 uint(int 更常用一些) |
|
枚举类型 |
Win32: BOOL MessageBeep(UINT uType // 声音类型); 其中的声音类型为枚举类型中的某一值。 C#: 用户需要自己定义一个枚举类型: public enum BeepType { SimpleBeep = -1, IconAsterisk = 0x00000040, IconExclamation = 0x00000030, IconHand = 0x00000010, IconQuestion = 0x00000020, Ok = 0x00000000, } C#中导入该函数: [DllImport("user32.dll")] public static extern bool MessageBeep(BeepType beepType); C#中调用该函数: MessageBeep(BeepType.IconQuestion); |
|
结构类型 |
Win32: 使用结构指针作为参数的函数: BOOL GetSystemPowerStatus( LPSYSTEM_POWER_STATUS lpSystemPowerStatus ); Win32中该结构体的定义: typedef struct _SYSTEM_POWER_STATUS { BYTE ACLineStatus; BYTE BatteryFlag; BYTE BatteryLifePercent; BYTE Reserved1; DWORD BatteryLifeTime; DWORD BatteryFullLifeTime; } SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS; C#: 用户自定义相应的结构体: struct SystemPowerStatus { byte ACLineStatus; byte batteryFlag; byte batteryLifePercent; byte reserved1; int batteryLifeTime; int batteryFullLifeTime; } C#中导入该函数: [DllImport("kernel32.dll")] public static extern bool GetSystemPowerStatus( ref SystemPowerStatus systemPowerStatus); C#中调用该函数: SystemPowerStatus sps; ….sps初始化赋值…… GetSystemPowerStatus(ref sps); |
|
字符串 |
对于字符串的处理分为以下几种情况: 1、 字符串常量指针的处理(LPCTSTR),也适应于字符串常量的处理,.net中的string类型是不可变的类型。 2、 字符串缓冲区的处理(char*),即对于变长字符串的处理,.net中StringBuilder可用作缓冲区 Win32: BOOL GetFile(LPCTSTR lpRootPathName); C#: 函数声明: [DllImport("kernel32.dll", CharSet = CharSet.Auto)] static extern bool GetFile ( [MarshalAs(UnmanagedType.LPTStr)] string rootPathName); 函数调用: string pathname; GetFile(pathname); 备注: DllImport中的CharSet是为了说明自动地调用该函数相关的Ansi版本或者Unicode版本 变长字符串处理: C#: 函数声明: [DllImport("kernel32.dll", CharSet = CharSet.Auto)] public static extern int GetShortPathName( [MarshalAs(UnmanagedType.LPTStr)] string path, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath, int shortPathLength); 函数调用: StringBuilder shortPath = new StringBuilder(80); int result = GetShortPathName( @"d:\test.jpg", shortPath, shortPath.Capacity); string s = shortPath.ToString(); |
|
struct |
具有内嵌字符数组的结构: Win32: typedef struct _TIME_ZONE_INFORMATION { LONG Bias; WCHAR StandardName[ 32 ]; SYSTEMTIME StandardDate; LONG StandardBias; WCHAR DaylightName[ 32 ]; SYSTEMTIME DaylightDate; LONG DaylightBias; } TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION; C#: [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct TimeZoneInformation { public int bias; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string standardName; SystemTime standardDate; public int standardBias; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string daylightName; SystemTime daylightDate; public int daylightBias; } |
|
具有回调的函数 |
Win32: BOOL EnumDesktops( HWINSTA hwinsta, // 窗口实例的句柄 DESKTOPENUMPROC lpEnumFunc, // 回调函数 LPARAM lParam // 用于回调函数的值 ); 回调函数DESKTOPENUMPROC的声明: BOOL CALLBACK EnumDesktopProc( LPTSTR lpszDesktop, // 桌面名称 LPARAM lParam // 用户定义的值 ); C#: 将回调函数的声明转化为委托: delegate bool EnumDesktopProc( [MarshalAs(UnmanagedType.LPTStr)] string desktopName, int lParam); 该函数在C#中的声明: [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern bool EnumDesktops( IntPtr windowStation, EnumDesktopProc callback, int lParam); |
该表对C#中调用win32函数,以及c++编写的dll时参数及返回值的转换做了一个小的总结,如果想进一步了解这方面内容的话,可以参照msdn中“互操作封送处理”一节。
(转)C#调用非托管Win 32 DLL的更多相关文章
- C#如何调用非托管的C++Dll
现在在Windows下的应用程序开发,VS.Net占据了绝大多数的份额.因此很多以前搞VC++开发的人都转向用更强大的VS.Net.在这种情况下,有很多开发人员就面临了如何在C#中使用C++开发好的类 ...
- 在VS2010上使用C#调用非托管C++生成的DLL文件
背景 在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第三方通讯组件的时候,通过C#来开发应用软件时,就需要利用DllImport特性进行方法调用.本篇文章将引导你快速理解这个调用 ...
- 在VS2017上使用C#调用非托管C++生成的DLL文件(图文讲解)
原文:在VS2010上使用C#调用非托管C++生成的DLL文件(图文讲解) 背景 在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第三方通讯组件的时候,通过C#来开发应用软件时,就 ...
- 【转】在VS2010上使用C#调用非托管C++生成的DLL文件(图文讲解)
原文:http://www.cyqdata.com/cnblogs/article-detail-35876# 背景 在项目过程中,有时候你需要调用非C#编写的DLL文件,尤其在使用一些第三方通讯组件 ...
- 关于C#调用非托管DLL,报“内存已损坏的”坑,坑,坑
因客户需求,与第三方对接,调用非托管DLL,之前正常对接的程序,却总是报“内存已损坏的异常”,程序进程直接死掉,折腾到这个点(2018-05-11 00:26),终于尘埃落定,直接上程序. 之前的程序 ...
- 关于C#调用非托管动态库方式的性能疑问
最近的项目中,因为一些原因,需要C#调用非托管(这里为C++)的动态库.网上喜闻乐见的方式是采用静态(DllImport)方式进行调用.偶然在园子里看到可以用动态(LoadLibrary,GetPro ...
- C#调用非托管dll
以C#开发周立功CAN举例,在官网下载了周立功的demo 一.C++头文件样子 //接口卡类型定义#define VCI_PCI5121 1 //一些结构体定义 typedef struct tagR ...
- .net4.0调用非托管DLL的异常捕获
转发: 由于有些非托管的DLL内部异常未有效处理,当托管程序调用到这样的DLL时,就引起托管程序意外退出. 托管程序使用通常的捕获try……catch块不起作用.原因是.NET 4.0里新的异常处理机 ...
- 托管程序调用非托管dll问题总结
托管程序Visual Basic.net, 非托管DLL标准C++程序(使用VC++编译) 函数调用定义 第一种写法: <DllImportAttribute("XXX.dll&quo ...
随机推荐
- ajax语法
js语言功能比较强大,但不能访问数据库 ajax来补充这一缺陷 特点:输出不用刷新页面,条件查询数据显示页面上一般不用它,因为需要造很多表格不如用嵌入php代码方式简单 ajax语法: $.ajax( ...
- [Hibernate] - one to many
事实上one to many 和 many to one是一样的,这是一个相互的过程. hibernate.cfg.xml <?xml version="1.0" encod ...
- 怎么优化JAVA程序的执行效率和性能?
现在java程序已经够快的了,不过有时写出了的程序效率就不怎么样,很多细节值得我们注意,比如使用StringBuffer或者StringBuilder来拼接或者操作字符串就比直接使用String效率高 ...
- JQ查找替换
resultStr = resultStr.replace(/\n/gi , "<br />"); //可以全部替换resultStr = resultStr.repl ...
- SPOJ #11 Factorial
Counting trailing 0s of n! It is not very hard to figure out how to count it - simply count how many ...
- 使用Lucene.Net实现全文检索
使用Lucene.Net实现全文检索 目录 一 Lucene.Net概述 二 分词 三 索引 四 搜索 五 实践中的问题 一 Lucene.Net概述 Lucene.Net是一个C#开发的开源全文索引 ...
- Oracle中in和exists的选择
在ORACLE 11G大行其道的今天,还有很多人受早期版本的影响,记住一些既定的规则, 1.子查询结果集小,用IN 2.外表小,子查询表大,用EXISTS 摘自:http://blog.chi ...
- 由csdn开源项目评选中闹出刷票问题想到投票程序的设计
帖子<#CSDN刷票门# 有没有人在恶意刷票?CSDN请告诉我!用24小时监控数据说话!> http://www.cnblogs.com/sanshi/p/3155946.html 网站投 ...
- NYOJ16 矩形嵌套(DAG最长路)
矩形嵌套 紫书P262 这是有向无环图DAG(Directed Acyclic Graph)上的动态规划,是DAG最长路问题 [题目链接]NYOJ16-矩形嵌套 [题目类型]DAG上的dp & ...
- Linux命令(20)查看当前网速
Linux查看网络即时网速 sar -n DEV 1 100 1代表一秒统计并显示一次 100代表统计一百次 还可以使用ntop工具