经过一晚上的折腾,还是下点决心将些许的心得写下来,以免以后重复劳动。

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类型作转换,这我先不细说,网上相关文章还是不少的。

(5)示例文件的下载

本文出自 “几缕萧雨锁清秋” 博客,请务必保留此出处http://joeyliu.blog.51cto.com/3647812/1289614

在C#调用C++的DLL简析(一)——生成非托管dll的更多相关文章

  1. 在C#调用C++的DLL方法(一)生成非托管dll

    C#与C/C++相比,前者的优势在于UI,后者的优势在于算法,C++下的指针虽然恶心,若使用得当还是相当方便的,最重要的问题是,市面上很多流行的开发工具库,几乎没有不支持C++的,但全面支持C#只能说 ...

  2. C#调用非托管dll

    以C#开发周立功CAN举例,在官网下载了周立功的demo 一.C++头文件样子 //接口卡类型定义#define VCI_PCI5121 1 //一些结构体定义 typedef struct tagR ...

  3. 托管程序调用非托管dll问题总结

    托管程序Visual Basic.net, 非托管DLL标准C++程序(使用VC++编译) 函数调用定义 第一种写法: <DllImportAttribute("XXX.dll&quo ...

  4. 托管非托管Dll动态调用

    原文:托管非托管Dll动态调用 最近经常看到有人问托管非托管Dll调用的问题.对于动态库的调用其实很简单.网上很多代码都实现了Dll的静态调用方法.我主要谈论下动态库的动态加载. 对于托管动态库,实现 ...

  5. 关于C#调用非托管DLL,报“内存已损坏的”坑,坑,坑

    因客户需求,与第三方对接,调用非托管DLL,之前正常对接的程序,却总是报“内存已损坏的异常”,程序进程直接死掉,折腾到这个点(2018-05-11 00:26),终于尘埃落定,直接上程序. 之前的程序 ...

  6. 托管DLL和非托管DLL的区别

    首先解释一下,托管DLL和非托管DLL的区别.狭义解释讲,托管DLL就在Dotnet环境生成的DLL文件.非托管DLL不是在Dotnet环 境生成的DLL文件. 托管DLL文件,可以在Dotnet环境 ...

  7. .Net 程序在自定义位置查找托管/非托管 dll 的几种方法

    原文:.Net 程序在自定义位置查找托管/非托管 dll 的几种方法 一.自定义托管 dll 程序集的查找位置 目前(.Net4.7)能用的有2种: #define DEFAULT_IMPLEMENT ...

  8. ASP.NET与非托管DLL的那些事儿【转+增】

    https://www.cnblogs.com/yeahgis/archive/2011/11/12/2246341.html ASP.NET与非托管DLL的那些事儿 环境VS2010 语言:ISO ...

  9. 关于Dll、Com组件、托管dll和非托管dll

    转自:https://blog.csdn.net/black_bad1993/article/details/53906252 Com组件 1.线程模型是干嘛用的?解决"多个线程" ...

随机推荐

  1. How to install JDK (Java Development Kit) on Linux

    This tutorial will guide you on how to install JDK (Java Development Kit) on Linux. Since I use Cent ...

  2. objcopy

    objcopy objcopy [options] infile [outfile] Copy the contents of the input object file to another fil ...

  3. pfx 转 snk

    最近用 fody 加在c#工程内,但是签名只认snk ,好像是mono cecil的问题,都不认pfx,重新生成snk文件,publishkey又要变了, 底层dll引用的地方太多,要改好多cspro ...

  4. sql server压缩数据库和日志文件

    DBCC SHRINKDATABASE 功能:压缩数据库 用法:DBCC SHRINKDATABASE tb_115sou_com 注意:只有产生许多未使用空间的操作(如截断表或删除表操作)后,执行收 ...

  5. Lost connection to MySQL server at 'reading initial communication packet' 错误解决

    Lost connection to MySQL server at 'reading initial communication packet' 错误解决 上次解决了这个问题,今天又碰到,突然失忆, ...

  6. Android之 环境搭建

    一. 使用ADT Bundle多合一下载包 下载地址:链接:http://pan.baidu.com/s/1gepNRjX  密码: ozdi 说      明:多合一下载包,里面包含了:sdk + ...

  7. 开源的rtsp实现

    开源的rtsp实现                            ============== -- by BeagleTam                                  ...

  8. 强制将IE8设置为IE7兼容模式来解析网页

    强制将IE8设置为IE7兼容模式来解析网页 英文原文:http://msdn.microsoft.com/en-us/library/cc288325(VS.85).aspx 文件兼容性用于定义让IE ...

  9. ecshop init.php文件分析

    1.  ecshop init.php文件分析 2.  <?php  3.   4.  /**  5.  * ECSHOP 前台公用文件  6.  * ===================== ...

  10. 《Python 学习手册4th》 第十五章 文档

    ''' 时间: 9月5日 - 9月30日 要求: 1. 书本内容总结归纳,整理在博客园笔记上传 2. 完成所有课后习题 注:“#” 后加的是备注内容 (每天看42页内容,可以保证月底看完此书) “重点 ...