在C#调用C++的DLL简析(一)——生成非托管dll
经过一晚上的折腾,还是下点决心将些许的心得写下来,以免以后重复劳动。
C#与C/C++相 比,前者的优势在于UI,后者的优势在于算法,C++下的指针虽然恶心,若使用得当还是相当方便的,最重要的问题是,市面上很多流行的开发工具库,几乎没 有不支持C++的,但全面支持C#只能说是难得,在CPU发展到今天,若说C#的执行效率跟C++相比有很大的差距并不是那么靠谱,若非万不得已我还是宁 愿用C#来写代码,调试什么的也很方便。
不得已的情况下,要在C#下使用C++的函数或类,最好的方式就是使用动态 链接库(dll),至于COM什么的我是至今没弄明白其原理,也许主要是因为使用起来太麻烦了(还要注册什么的),使用dll的话,可以很方便将一个工程 细分到成为两部分,协同编程可以加速进展。当然,用C#的代码改写一遍也不是不可能的,可当你有现成的几万行代码那就真头痛得要命,还是安心的使用动态链 接库吧。
当你打开VS2012的时候,新建工程项目,也许你可能发现会有一个“CLR类库”的项目类型,直到今天我 才知道,CLR原来指的是托管C++,托管C++跟非托管C++虽然有一定的关系,但很多人更愿意将他俩看成为两种不同的程序语言,托管C++是一种很恶 搞的存在,它的唯一作用是用披着C++的马甲来写C#的内容,且最终是为C#服务的,使用CLR生成的dll可以直接在C#下引用,要用CLR还不如直接 用C#更简单一点(纯粹个人观点)。
这是最常见的技俩,网上的资料是一大票,但为了我这健忘脑袋,我还是逐步贴图讲明吧,据本人一通宵的成果,几经折腾,终于证明了想在native C++下导入类那是不可能的事,所以,以下讲的仅是如何导入函数而已——就如你们在网上看到的文章一样。
(1)建立生成dll的工程
我 用的是VS2012,不过好像跟前面的版本没什么太大的差别。打开VS,选择"新建项目"-“VC++”-"Win32"-"Win32项目",工程的名 字叫"MyNativeDll",配置如下图所示,因为我有可能用到MFC的类,所以我就勾选了“MFC”的选项,在此需要注意的是,如果你新建时没有勾 选MFC,但在后面却想动用MFC的内容,就会遇到“MFC apps must not #include <windows.h>”的Error,只是在工程的配置里修改是根本没有用的,必做要重建工程。

(2)实现dll导出的函数
新建好工程后,在VS的“解决方案资源管理器”中可以看到如下图的目录,其实你完全可以不用管这些默认的文件,如果你要用,可以在看一下MyNativeDll.h里的注释说明,大概能看得懂的。

在工程里添加几个文件,Define.h,CFunction.h,CFunction.cpp,其内容如下所示:
//Define.h 用于导入dll的宏定义。
|
1
2
3
4
5
6
7
|
//Define.h/////////////////////////////////////////////////////////////////////////////////////#ifndef _DEFINE_H_#define _DEFINE_H_#define _EXTERN_C_ extern "C" _declspec(dllexport)#endif |
//CFunction.h 函数定义,这里我特意定义了一组结构,我的用意稍后再讲。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
//CFunction.h///////////////////////////////////////////////////////////////////////////////////////#ifndef _C_FUNCTION_H_#define _C_FUNCTION_H_#include "Define.h"#include <string>#include <istream>struct SystemTime{ int year; int month; int day; int hour; int minute; int second; int millsecond; SystemTime & operator= (SystemTime st) { this->year = st.year; this->month = st.month; this->day = st.day; this->hour = st.hour; this->minute = st.minute; this->second = st.second; this->millsecond = st.millsecond; return *this; }};_EXTERN_C_ int add(int x, int y);_EXTERN_C_ int sub(int x, int y);_EXTERN_C_ int testChar(char * src, char * res, int nCount);_EXTERN_C_ int testStruct(SystemTime & stSrc, SystemTime & stRes);#endif //_C_FUNCTION_H_ |
//CFunction.cpp dll函数的实现,简单的赋值而已,大家应该看得明白的。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//CFunction.cpp////////////////////////////////////////////////////////////////////////////////////////#include "stdafx.h"#include "CFunction.h"#include <stdio.h>int add(int x, int y){ return x + y;}int sub(int x, int y){ return x - y;}int testChar(char * src, char * res, int nCount){ memcpy(res, src, sizeof(char) * nCount); return 1;}int testStruct(SystemTime & stSrc, SystemTime & stRes){ stRes = stSrc; return 1;} |
添 加好代码之后,选择“生成”的选项,因在工程目录下的Debug文件就已经存在我们所需要的MyNativeDll.dll文件,一起的还有lib的静态 库文件(稍后要用到),及其他相关的调试文件,至此,我们已经成功的生成了native C++的动态链接库,我只能说,这是相当简单的第一步在而已。
(3)在C#工程下使用生成的dll
新建一个C#的窗口工程(我个人是很讨厌控制台的程序的),工程命名为“DllTest”,这就不教了。
在新建的窗体工程中添加一个CFunction.cs的类,这个类主要是用于导出上面dll里的函数,废话不多说,直接贴代码:
//CFunction.cs dll的函数接口
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Runtime.InteropServices;namespace DllTest{ [StructLayout(LayoutKind.Sequential)] public struct SystemTime { public int year; public int month; public int day; public int hour; public int minute; public int second; public int millsecond; public SystemTime(DateTime dt) { this.year = dt.Year; this.month = dt.Month; this.day = dt.Day; this.hour = dt.Hour; this.minute = dt.Minute; this.second = dt.Second; this.millsecond = dt.Millisecond; } public override string ToString() { return this.year.ToString() + "-" + this.month.ToString() + "-" + this.day.ToString() + " " + this.hour.ToString() + ":" + this.minute.ToString() + "-" + this.second.ToString() + "-" + this.millsecond.ToString(); } }; public class CFunction { [DllImport("MyNativeDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public extern static int add(int x, int y); [DllImport("MyNativeDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public extern static int sub(int x, int y); [DllImport("MyNativeDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public extern static int testChar(ref byte src, ref byte res, int nCount); [DllImport("MyNativeDll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public extern static int testStruct(ref SystemTime stSrc, ref SystemTime stRes); }} |
上面代码的格式看起来不好看,大家自己下附件里的文件看好了,上面的做法相当是作了一个CFunction的静态类而已。然后在Form1.cs窗体里直接写测试代码,我是直接写在Form1的初始化函数里,懒,没办法。
//Form1.cs 在C#的窗体初始化函数添加测试代码
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Diagnostics;using System.Drawing;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows.Forms;namespace DllTest{ public partial class Form1 : Form { public Form1() { InitializeComponent(); int a = CFunction.add(100, 50); int b = CFunction.sub(100, 50); Debug.WriteLine("add = " + a.ToString() + " b = " + b.ToString()); Debug.WriteLine("\r\n"); string src = "123456"; byte[] srcBytes = System.Text.Encoding.ASCII.GetBytes(src); byte[] resBytes = new byte[100]; a = CFunction.testChar(ref srcBytes[0], ref resBytes[0], src.Length); string res = (System.Text.Encoding.ASCII.GetString(resBytes, 0, resBytes.Length)).TrimEnd(); Debug.WriteLine(res.ToString()); Debug.WriteLine("\r\n"); SystemTime stSrc = new SystemTime(DateTime.Now); SystemTime stRes = new SystemTime(); a = CFunction.testStruct(ref stSrc, ref stRes); Debug.WriteLine(stRes.ToString()); Debug.WriteLine("\r\n"); } }} |
在你进行调试之前,务必记得要将在第二步生成的MyNativeDll.dll拷贝至C#工程下的bin\Debug\目录下,然后点击“调试”,看输出窗口,应该会有东西输出的,我就不贴出来了。
(4)总结
1)总体上来讲,生成一个native C++的dll不是很困难的事,重点在于在C#下的dll导出函数那里;
2)个人的经验来看,使用native C++可以导入函数,至于导出C++类,通过指针的方式并非不可能,可是方法过于费解,建议不要那么做;
3) 在书写dll导出函数时,变量的传递是关键,建议使用C++的基本类型,如int,float,double等,因为C#下指针的概念很纠结,在C++下 的引用符“&”,在C#中则使用ref的标识,需要紧记的一点是,C#与C++的类型并不全然通用(结构对齐问题),注意做变换。像上面的 testChar函数,原本string(C#)对应的是char*(C++),但可能由于各种Unicode或多字节的关系,我是没法返回正确的值,于 是我采用了byte的传入类型。关于C#与C++混编的类型问题,可以查看下面的文章:C++与C#的类型转换,文章2,文章3,在网上google到好的文章也是不容易的啊。
4) 观察我写的结构,在C++下使用的结构体,在C#必须要重新定义一次,使用 [StructLayout(LayoutKind.Sequential)]的标识用于结构的对齐,如果你变量中使用了string这样的类型,还需 要使用MarshalAs这样的方法支定义其长度——才可以跟char *相对应;
5)函数的返回值别用什么string了,我是找为到方法取得其正确的返回值,最好使用ref的引用方法回传回来。
6)指针的参数的传递在C#下使用IntPtr类型作转换,这我先不细说,网上相关文章还是不少的。
本文出自 “几缕萧雨锁清秋” 博客,请务必保留此出处http://joeyliu.blog.51cto.com/3647812/1289614
在C#调用C++的DLL简析(一)——生成非托管dll的更多相关文章
- 在C#调用C++的DLL方法(一)生成非托管dll
C#与C/C++相比,前者的优势在于UI,后者的优势在于算法,C++下的指针虽然恶心,若使用得当还是相当方便的,最重要的问题是,市面上很多流行的开发工具库,几乎没有不支持C++的,但全面支持C#只能说 ...
- C#调用非托管dll
以C#开发周立功CAN举例,在官网下载了周立功的demo 一.C++头文件样子 //接口卡类型定义#define VCI_PCI5121 1 //一些结构体定义 typedef struct tagR ...
- 托管程序调用非托管dll问题总结
托管程序Visual Basic.net, 非托管DLL标准C++程序(使用VC++编译) 函数调用定义 第一种写法: <DllImportAttribute("XXX.dll&quo ...
- 托管非托管Dll动态调用
原文:托管非托管Dll动态调用 最近经常看到有人问托管非托管Dll调用的问题.对于动态库的调用其实很简单.网上很多代码都实现了Dll的静态调用方法.我主要谈论下动态库的动态加载. 对于托管动态库,实现 ...
- 关于C#调用非托管DLL,报“内存已损坏的”坑,坑,坑
因客户需求,与第三方对接,调用非托管DLL,之前正常对接的程序,却总是报“内存已损坏的异常”,程序进程直接死掉,折腾到这个点(2018-05-11 00:26),终于尘埃落定,直接上程序. 之前的程序 ...
- 托管DLL和非托管DLL的区别
首先解释一下,托管DLL和非托管DLL的区别.狭义解释讲,托管DLL就在Dotnet环境生成的DLL文件.非托管DLL不是在Dotnet环 境生成的DLL文件. 托管DLL文件,可以在Dotnet环境 ...
- .Net 程序在自定义位置查找托管/非托管 dll 的几种方法
原文:.Net 程序在自定义位置查找托管/非托管 dll 的几种方法 一.自定义托管 dll 程序集的查找位置 目前(.Net4.7)能用的有2种: #define DEFAULT_IMPLEMENT ...
- ASP.NET与非托管DLL的那些事儿【转+增】
https://www.cnblogs.com/yeahgis/archive/2011/11/12/2246341.html ASP.NET与非托管DLL的那些事儿 环境VS2010 语言:ISO ...
- 关于Dll、Com组件、托管dll和非托管dll
转自:https://blog.csdn.net/black_bad1993/article/details/53906252 Com组件 1.线程模型是干嘛用的?解决"多个线程" ...
随机推荐
- 完全二叉树的高度为什么是对lgN向下取整
完全二叉树的高度为什么是对lgN向下取整呢? 说明一下这里的高度:只有根节点的树高度是0. 设一棵完全二叉树节点个数为N,高度为h.所以总节点个数N满足以下不等式: 1 + 21 + 22 +……+ ...
- vs2013编译boost库
打开vs2013>>visual studio tools>>VS2013 x64 本机工具命令提示 cd D:\lib\boost_1_55_0\boost_1_55_0 b ...
- Java Socket(3): NIO
NIO采取通道(Channel)和缓冲区(Buffer)来传输和保存数据,它是非阻塞式的I/O,即在等待连接.读写数据(这些都是在一线程以客户端的程序中会阻塞线程的操作)的时候,程序也可以做其他事情, ...
- ffmpeg/ffplay vc6 源码剖析
ffmpeg/ffplay是当今多媒体领域的王者,很多很多的人想研究学习ffmpeg/ffplay,但苦于ffmpeg/ffplay庞大的代码量,令人望而生畏.为帮助更多的人研习ffmpeg/ffpl ...
- Cocos2d提供的字体(图文并茂)
1.AppleGothic CCLabelTTF *myLabel = [CCLabelTTF labelWithString:@"AppleGothic" fontName:@& ...
- 【大数取模】HDOJ-1134、CODEUP-1086
1086: 大数取模 题目描述 现给你两个正整数A和B,请你计算A mod B.为了使问题简单,保证B小于100000. 输入 输入包含多组测试数据.每行输入包含两个正整数A和B.A的长度不超过1 ...
- MyBatis 如何接收参数
MyBatis的mapper接口不需要自己实现,框架会自动帮我们实现,到时候直接调用就可以了.定义的mapper接口中的方法可以有多个参数吗?答案是肯定.在Ibatis时代是自己通过代码实现如何调用x ...
- C 实现的算法篇
算法的定义:算法是解决实际问题的一种精确的描述方法,目前,广泛认同的定义是:算法的模型分析的一组可行的确定的和有穷的规则 算法的五个特性:有穷性,确切性,输入,输出,可行性.目前算法的可执行的步骤非常 ...
- NGUI学习笔记-UISprite
所有的Sprite使用前,得先准备个图集,然后选择里面的图片进行填充 UISprite里面有几个属性做个笔记: Type: Smple:除了显示内容从图集里面获取外,其他都和Texture一样的绘制 ...
- 最短路径算法(Dijkstra算法、Floyd-Warshall算法)
最短路径算法具体的形式包括: 确定起点的最短路径问题:即已知起始结点,求最短路径的问题.适合使用Dijkstra算法. 确定终点的最短路径问题:即已知终结结点,求最短路径的问题.在无向图中,该问题与确 ...