• 在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,但是在工程的配置里修改是根本没有用的,必做要重建工程。

    image

    注意到上图中的预编译头文件的复选框是默认的,具体的内容参见:


    (2)实现dll导出的函数

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

    image

    在工程里添加几个文件,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 1; 
    } 
    int testStruct(SystemTime & stSrc, SystemTime & stRes) 
    { 
        stRes = stSrc; 
        return 1; 
    }

    添加好代码之后,选择“生成”的选项,因在解决方案目录下的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(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++类,通过指针的方式并非不可能,可是方法过于费解,建议不要那么做,下篇讲导出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类型作转换,这我先不细说,网上相关文章还是不少的。

  • 相关阅读:
    linux各文件夹的作用
    CodeIgniter的URL传过来的中文参数处理错误的修复
    syn_ack攻击
    分治排序
    Linux Shell学习笔记
    sql题型
    jquery ajax
    json 字符串与对象之间的转换
    常用的VIM命令列表 移动光标
    visual c++ 2012 内存泄漏检测方法
  • 原文地址:https://www.cnblogs.com/stemon/p/4246052.html
Copyright © 2020-2023  润新知