• 在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(" ");
                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(" ");
                SystemTime stSrc = new SystemTime(DateTime.Now);
                SystemTime stRes = new SystemTime();
                a = CFunction.testStruct(ref stSrc, ref stRes);
                Debug.WriteLine(stRes.ToString());
                Debug.WriteLine(" ");
            }
        }
    }

    在你进行调试之前,务必记得要将在第二步生成的MyNativeDll.dll拷贝至C#工程下的binDebug目录下,然后点击“调试”,看输出窗口,应该会有东西输出的,我就不贴出来了。

    (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

  • 相关阅读:
    java程序员面试金典--i++
    JavaScript 装逼指南
    轻松学习 JavaScript——第 8 部分:JavaScript 中的类
    轻松学习 JavaScript——第 7 部分:对象属性描述符
    轻松学习 JavaScript——第 6 部分:JavaScript 箭头函数
    轻松学习 JavaScript——第 5 部分:简化函数提升
    轻松学习 JavaScript——第 4 部分:函数中的 arguments 对象
    轻松学习 JavaScript——第 3 部分:函数中的默认参数
    布隆过滤器
    决策树(ID3、C4.5、CART)
  • 原文地址:https://www.cnblogs.com/zhaoxinshanwei/p/3983051.html
Copyright © 2020-2023  润新知