程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 在C#調用C++的DLL簡析(一)——生成非托管dll

在C#調用C++的DLL簡析(一)——生成非托管dll

編輯:關於C語言

經過一晚上的折騰,還是下點決心將些許的心得寫下來,以免以後重復勞動。


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的宏定義。

//Define.h
///////////////////////////////////////////
//////////////////////////////////////////
#ifndef _DEFINE_H_
#define _DEFINE_H_
#define _EXTERN_C_  extern "C"  _declspec(dllexport)
#endif


//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_


//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++的動態鏈接庫,我只能說,這是相當簡單的第一步在而已。


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.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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved