在C#调用C++的DLL方法(一)生成非托管dll
C#与C/C++相比,前者的优势在于UI,后者的优势在于算法,C++下的指针虽然恶心,若使用得当还是相当方便的,最重要的问题是,市面上很多流行的开发工具库,几乎没有不支持C++的,但全面支持C#只能说是难得。在CPU发展到今天,若说C#的执行效率跟C++相比有很大的差距,并不是那么靠谱,若非万不得已我还是宁愿用C#来写代码,调试什么的也很方便。
不得已的情况下,要在C#下使用C++的函数或类,最好的方式就是使用动态链接库(dll),至于COM什么的我是至今没弄明白其原理,也许主要是因为使用起来太麻烦了(还要注册什么的),使用dll的话,可以很方便将一个工程细分到成为两部分,协同编程可以加速进展。当然,用C#的代码改写一遍也不是不可能的,可当你有现成的几万行代码需要重写,那就真头痛得要命,还是安心的使用动态链接库吧。
要使用非托管的C++代码有两种形式:导入非托管C++的函数和导入非托管C++的类。下面先说一下第一种:导入非托管C++代码的函数:
(1)建立生成dll的工程
我用的是VS2012,不过好像跟前面的版本没什么太大的差别。打开VS,选择"新建项目"----“Visual C++”----"Win32"----"Win32项目",工程的名字叫"MyNativeDll",配置如下图所示,因为我有可能用到MFC的类,所以我就勾选了“MFC”的选项。在此需要注意的是,如果你新建时没有勾选MFC,但在后面却想动用MFC的内容,就会遇到“MFC apps must not #include ”的Error,但是在工程的配置里修改是根本没有用的,必做要重建工程。
注意到上图中的预编译头文件的复选框是默认的,具体的内容参见:
(2)实现dll导出的函数
新建好工程后,在VS的“解决方案资源管理器”中可以看到如下图的目录,其实你完全可以不用管这些默认的文件,如果你要用,可以在看一下MyNativeDll.h里的注释说明,大概能看得懂的。
在工程里添加几个文件,Define.h,CFunction.h,CFunction.cpp,其内容如下所示:
//Define.h 用于导入dll的宏定义。
//Define.h
///////////////////////////////////////////
//////////////////////////////////////////
#ifndef _DEFINE_H_
#define _DEFINE_H_
#define _EXTERN_C_ extern "C" _declspec(dllexport)
#endif
__declspec(dllexport) extern “C”
__declspec(dllexport)将一个函数声明为导出函数,就是说这个函数将被包含它的程序之外的函数调用。
extern “C”使得在C++中使用C编译方式成为可能,在C++下定义C函数需要加extern “C”关键词。使用extern “C”来指明该函数使用C编译方式。这样一来输出的函数就可以从C代码里调用了。
cpp文件在编译为obj文件时,要对函数进行重命名,C语言会把函数name重命名为_name,而C++会重命名为_name@@decoration。
extern “C”指示编译器用C语言的方法给函数重命名。
在制作DLL导出函数的时候,由于C++存在函数重载,因此__declspec(dllexport) function(int ,int)在DLL中会被decorate。例如被decorate成function_int_int,而且不同的编译器decorate的方法不同,造成了在用GetProcAddress取得function地址时,找不到DLL中函数的地址。在使用extern “C”时,上面说的decorate就不会再发生了,这样就能很容易的找到要调用的函数的地址。也正是因为没有了decorate,所有C编译方式没有函数重载,但是如此一来,被extern “C”修饰的函数,就不具备了重载的能力。所以extern和extern “C”不是一回事。
//CFunction.h 函数声明,这里我特意声明了一组结构,我的用意稍后再讲。
//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_</istream></string>
//CFunction.cpp dll函数的实现,简单的赋值而已,大家应该看得明白的。
//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 ;
}
int testStruct(SystemTime & stSrc, SystemTime & stRes)
{
stRes = stSrc;
return ;
}
添加好代码之后,选择“生成”的选项,因在解决方案目录下的Debug文件就已经存在我们所需要的MyNativeDll.dll文件,一起的还有lib的静态库文件(稍后要用到),及其他相关的调试文件,至此,我们已经成功的生成了native C++的动态链接库,我只能说,这是相当简单的第一步在而已。(native C++就是非托管的C++)
(3)在C#工程下使用生成的dll
新建一个C#的窗口工程,工程命名为“DllTest”。
在新建的窗体工程中添加一个CFunction.cs的类,这个类主要是用于导出上面dll里的函数,废话不多说,直接贴代码:
//CFunction.cs dll的函数接口
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#的窗体初始化函数添加测试代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Diagnostics;
namespace DllTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
int a = CFunction.add(, );
int b = CFunction.sub(, );
Debug.WriteLine("add = " + a.ToString() + " b = " + b.ToString());
Debug.WriteLine("\r\n");
string src = "";
byte[] srcBytes = System.Text.Encoding.ASCII.GetBytes(src);
byte[] resBytes = new byte[];
a = CFunction.testChar(ref srcBytes[], ref resBytes[], src.Length);
string res = (System.Text.Encoding.ASCII.GetString(resBytes, , 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++类,通过指针的方式并非不可能,可是方法过于费解,建议不要那么做,下篇讲导出C++类的方法;
3)在书写dll导出函数时,变量的传递是关键,建议使用C++的基本类型,如int,float,double等,因为C#下指针的概念很纠结,在C++下的引用符“&”,在C#中则使用ref的标识,需要紧记的一点是,C#与C++的类型并不全然通用(结构对齐问题),注意做变换。像上面的testChar函数,原本string(C#)对应的是char*(C++),但可能由于各种Unicode或多字节的关系,我是没法返回正确的值,于是我采用了byte的传入类型。
4)观察我写的结构,在C++下使用的结构体,在C#必须要重新定义一次,使用 [StructLayout(LayoutKind.Sequential)]的标识用于结构的对齐,如果你变量中使用了string这样的类型,还需要使用MarshalAs这样的方法支定义其长度——才可以跟char *相对应;
5)函数的返回值别用什么string了,我是找为到方法取得其正确的返回值,最好使用ref的引用方法回传回来。
6)指针的参数的传递在C#下使用IntPtr类型作转换,这我先不细说,网上相关文章还是不少的。
在C#调用C++的DLL方法(一)生成非托管dll的更多相关文章
- 在C#调用C++的DLL简析(一)——生成非托管dll
经过一晚上的折腾,还是下点决心将些许的心得写下来,以免以后重复劳动. C#与C/C++相 比,前者的优势在于UI,后者的优势在于算法,C++下的指针虽然恶心,若使用得当还是相当方便的,最重要的问题是, ...
- 托管DLL和非托管DLL的区别
首先解释一下,托管DLL和非托管DLL的区别.狭义解释讲,托管DLL就在Dotnet环境生成的DLL文件.非托管DLL不是在Dotnet环 境生成的DLL文件. 托管DLL文件,可以在Dotnet环境 ...
- 关于Dll、Com组件、托管dll和非托管dll
转自:https://blog.csdn.net/black_bad1993/article/details/53906252 Com组件 1.线程模型是干嘛用的?解决"多个线程" ...
- C#调用非托管dll
以C#开发周立功CAN举例,在官网下载了周立功的demo 一.C++头文件样子 //接口卡类型定义#define VCI_PCI5121 1 //一些结构体定义 typedef struct tagR ...
- 托管非托管Dll动态调用
原文:托管非托管Dll动态调用 最近经常看到有人问托管非托管Dll调用的问题.对于动态库的调用其实很简单.网上很多代码都实现了Dll的静态调用方法.我主要谈论下动态库的动态加载. 对于托管动态库,实现 ...
- .Net 程序在自定义位置查找托管/非托管 dll 的几种方法
原文:.Net 程序在自定义位置查找托管/非托管 dll 的几种方法 一.自定义托管 dll 程序集的查找位置 目前(.Net4.7)能用的有2种: #define DEFAULT_IMPLEMENT ...
- 关于C#调用非托管DLL,报“内存已损坏的”坑,坑,坑
因客户需求,与第三方对接,调用非托管DLL,之前正常对接的程序,却总是报“内存已损坏的异常”,程序进程直接死掉,折腾到这个点(2018-05-11 00:26),终于尘埃落定,直接上程序. 之前的程序 ...
- 将WinForm程序(含多个非托管Dll)合并成一个exe的方法
原文:将WinForm程序(含多个非托管Dll)合并成一个exe的方法 开发程序的时候经常会引用一些第三方的DLL,然后编译生成的exe文件就不能脱离这些DLL独立运行了. ILMerge能把托管dl ...
- 托管程序调用非托管dll问题总结
托管程序Visual Basic.net, 非托管DLL标准C++程序(使用VC++编译) 函数调用定义 第一种写法: <DllImportAttribute("XXX.dll&quo ...
随机推荐
- 确定比赛名次(map+邻接表 邻接表 拓扑结构 队列+邻接表)
确定比赛名次 Time Limit : 2000/1000ms (Java/Other) Memory Limit : 65536/32768K (Java/Other) Total Submis ...
- atitit.软件开发GUI 布局管理优缺点总结java swing wpf web html c++ qt php asp.net winform
atitit.软件开发GUI 布局管理优缺点总结java swing wpf web html c++ qt php asp.net winform 1. Absoluti 布局(经常使用) 1 2. ...
- KMP模式匹配 三(弦)
原文请訪问我的博客:xiaoshig.sinaapp.com KMP模式匹配 三(串) Time Limit:1000MS Memory Limit:131072KB 64bit IO ...
- CSS3实战开发 表单发光特效实战开发
首先,我们先准备好html代码: <!doctype html> <html> <head> <meta charset="utf-8"& ...
- NHibernate 3.3
今天试了一下NHibernate 3.3比之前的版本简单,只需要引入两个dll,这两个dll分别是:Iesi.Collections.dll和NHibernate.dll 通过 http://nhfo ...
- ASP.Net连接SQLServer 连接字符串
引用命名空间 using System.Data; using System.Data.SqlClient; 连接字符串 // windows 验证方式数据库中存在单一实例 string connec ...
- 关于nvarchar与varchar的区别
varchar(x), nvarchar(x)这里面的x指的是最大的列宽 如果存储的字符串没达到最大列宽 那么他也只获得对应的列宽的存储空间 并不意味着系统就会给它分配x的空间给它 varch ...
- SQL Server表和字段说明的增加和更新
1. 增加字段说明 EXEC sp_addextendedproperty 'MS_Description', 'some description', 'user', ...
- TCP/IP详解之:ICMP协议
ICMP协议: ICMP是IP层的一个组成部分,ICMP报文是在IP数据报内部被传输的,用于在IP主机.路由器之间传递控制消息.控制消息是指网络不通.主机是否可达.路由是否可用等网络本身的消息.这些控 ...
- 为什么析构函数要加visual?
一般来说,有使用visual的类,其析构函数都需要加上visual,这是为什么呢? 因为使用多态时,一般都是由基类指针来使用派生类的,在删除这个派生类对象时也一样,由基类指针来调用, 可以想象,如果析 ...