程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#與C/C++的交互zz,

C#與C/C++的交互zz,

編輯:C#入門知識

C#與C/C++的交互zz,


C#與C++交互,總體來說可以有兩種方法:

  • 利用C++/CLI作為代理中間層

  • 利用PInvoke實現直接調用

第一種方法:實現起來比較簡單直觀,並且可以實現C#調用C++所寫的類,但是問題是MONO構架不支持C++/CLI功能,因此無法實現脫離Microsoft .NET Framework跨平台運行。

第二種方法:簡單的實現並不麻煩,只要添加DllImportAttribute特性即可以導入C/C++的函數,但是問題是PInvoke不能簡單的實現對C++類的調用。在Warensoft3D中為了可以使用MONO實現跨平台(當然DirectX是不能跨平台的),所以使用了本方法,下面將對本方法展開詳細的說明。

測試平台:

Windows7 64位,VS2010,.NET4.0

注意事項:

PInvoke從功能上來說,只支持函數調用,在被導出的函數前面一定要添加extern "C"來指明導出函數的時候使用C語言方式編譯和連接,這樣保證函數定義的名字和導出的名字相同,否則如果默認按C++方式導出,那個函數的名字就會變得亂七八糟,我們的程序就無法找到入口點了。

本文將說明以下幾點:

  • 互調的基本原理

  • 基本數據類型的傳遞

  • 指針的傳遞

  • 函數指針的傳遞

  • 結構體的傳遞

  • 基本數據類型的傳遞

  • 互調過程中,最基本要傳遞的無非是數值和字符,即:int,long,float,char等等,但是此類型非彼類型,C/C++與C#中有一些數據類型長度是不一樣的,下表中列出常見數據類型的異同:

    C/C++

    C#

    長度

    short

    short

    2Bytes

    int

    int

    4Bytes

    long(該類型在傳遞的時候常常會弄混)

    int

    4Bytes

    bool

    bool

    1Byte

    char(Ascii碼字符)

    byte

    1Byte

    wchar_t(Unicode字符,該類型與C#中的Char兼容)

    char

    2Bytes

    float

    float

    4Bytes

    double

    double

    8Bytes

    最容易弄混的是就是long,char兩個類型,在C/C++中long和int都是4個字節,都對應著C#中的int類型,而C/C++中的char類型占一個字節,用來表示一個ASCII碼字符,在C#中能夠表示一個字節的是byte類型。與C#中char類型對應的應該是C/C++中的wchar_t類型,對應的是一個2字節的Unicode字符。

    下面通過實例來說明調用過程:

    第一步:

    建立一個C++的Win32DLL,如下圖所示:

    這裡要注意選擇"Export symbols"導出符號。點擊完成。

    第二步:

    由於項目的名稱是"TestCPPDLL",因此,會自動生成TestCPPDLL.h和TestCPPDLL.cpp兩個文件,.h文件是要導出內容的聲明文件,為了能清楚的說明問題,我們將TestCPPDLL.h和TestCPPDLL.cpp兩個文件中的所有內容都刪除,然後在TestCPPDLL.h中添加如下內容:

    第一行代碼中定義了一個名為"TESTCPPDLL_API"的宏,該宏對應的內容是"__declspec(dllexport)"意思是將後面修飾的內容定義為DLL中要導出的內容。當然你也可以不使用這個宏,可以直接將"__declspec(dllexport)"寫在要導出的函數前面。

    第二行中的"EXTERN_C",是在"winnt.h"中定義的宏,在函數前面添加"EXTERN_C"等同於在函數前面添加extern "C",意思是該函數在編譯和連接時使用C語言的方式,以保證函數名字不變。

    第二行的代碼是一個函數的聲明,說明該函數可以被模塊外部調用,其定義實現在TestCPPDLL.cpp中,TestCPPDLL.cpp的代碼如下所示:

    第三步:

    在編譯C++DLL之前,需要做以下配置,在項目屬性對話框中選擇"C/C++"|"Advanced",將Compile AS 選項的值改為"C++"。然後確定,並編譯。

    生成的DLL文件如下圖所示:

    第四步:

    首先,添加一個C#的應用程序,如果要在C#中調用C++的DLL文件,先要在C#的類中添加一個靜態方法,並且使用DllImportAttribute對該方法進行修飾,代碼如下所示:

    DllImport中的第一個參數是指明DLL文件的位置,第二個參數"EntryPoint"用來指明對應的C/C++中的函數名稱是什麼。"extern"關鍵字表明該處聲明的這個Add方法是一個外部調用。

    該方法聲明完畢之後,就可以像調用一個普通的靜態方法一樣去使用了。

    下面是示例程序:

    class Program

    {

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "Add")]

    extern static int Add(int a, int b);

    static void Main(string[] args)

    {

    int c = Add(1,2);

    Console.WriteLine(c);

    Console.Read();

    }

    }

    在運行C#程序之前,先要修改C#的項目屬性,如下圖所示:

    將platform target設置為x86,並且允許非安全代碼(後面有用)。

    然後運行該C#程序,其結果如下圖所示:

    第五步:

    前面的Add方法中傳遞的是數值類型(int),其他的數據類型,如float,double,和bool類型的傳遞方式是一樣的,下面演示如何傳遞字符串。

    在TestCPPDLL.h中添加一個新的函數聲明,代碼如下:

    EXTERN_C TESTCPPDLL_API void __stdcall WriteString(wchar_t*content);

    這裡的參數是wchar_t類型的指針,對應著C#中的char類型。TestCPPDLL.cpp中添加如下代碼:

    TESTCPPDLL_API void __stdcall WriteString(wchar_t*content)

    {

        cout<<content;

    }

    該代碼的功能就是將輸入的字符串通過C++在控制台上輸出。下面是在C#中的聲明:

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "WriteString")]

    extern unsafe static void WriteString(char*c);

    調用過程如下所示:

    //因為使用指針,因為要聲明非安全域

    unsafe

    {

    //在傳遞字符串時,將字符所在的內存固化,

    //並取出字符數組的指針

    fixed (char* p = &("hello".ToCharArray()[0]))

    {

    //調用方法

    WriteString(p);

    }

    }

    其運行效果如下圖所示:

    3. 指針的傳遞

    根據前面介紹的數據類型對照表,我們可以直接在方法中傳遞指針,但是要注意的是我們常常需要將數組的指針(數據入口地址,第一個元素的地址),數據從C/C++到C#時問題不大,但是如果從C#到C/C++時一定要將數組先固化,然後再傳遞處理。

    下面演示如何傳遞指針,首先在TestCPPDLL.h中添加下列聲明:

    //傳入一個整型指針,將其所指向的內容加1

    EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i);

    //傳入一個整型數組的指針以及數組長度,遍歷每一個元素並且輸出

    EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength);

    //在C++中生成一個整型數組,並且數組指針返回給C#

    EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP();

    其實現寫在TestCPPDLL.cpp中,代碼如下所示:

    TESTCPPDLL_API void __stdcall AddInt(int *i)

    {

        (*i)++;

    }

    TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arrayLength)

    {

    int*currentPointer=firstElement;

    for (int i = 0; i < arrayLength; i++)

        {

            cout<<*currentPointer;

            currentPointer++;

        }

        cout<<endl;

    }

    int *arrPtr;

    TESTCPPDLL_API int* __stdcall GetArrayFromCPP()

    {

        arrPtr=new int[10];

    for (int i = 0; i < 10; i++)

        {

            arrPtr[i]=i;

        }

    return arrPtr;

    }

    對應調用的C#代碼如下所示:

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddInt")]

    extern unsafe static void AddInt(int* i);

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddIntArray")]

    extern unsafe static void AddIntArray(int* firstElement, int arraylength);

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "GetArrayFromCPP")]

    extern unsafe static int* GetArrayFromCPP();

    調用過程如下所示:

    unsafe

    {

    // 調用C++中的AddInt方法

    int i = 10;

    AddInt(&i);

    Console.WriteLine(i);

    //調用C++中的AddIntArray方法將C#中的數據傳遞到C++中,並在C++中輸出

    int[] CSArray = new int[10];

    for (int iArr = 0; iArr < 10; iArr++)

    {

    CSArray[iArr] = iArr;

    }

    fixed (int* pCSArray = &CSArray[0])

    {

    AddIntArray(pCSArray, 10);

    }

    //調用C++中的GetArrayFromCPP方法獲取一個C++中建立的數組

    int* pArrayPointer = null;

    pArrayPointer = GetArrayFromCPP();

    for (int iArr = 0; iArr < 10; iArr++)

    {

    Console.WriteLine(*pArrayPointer);

    pArrayPointer++;

    }

    }

    4. 函數指針的傳遞

    前面說明的都是簡單數據類型的及其指針的傳遞,利用PInvoke我們也可以實現函數指針的傳遞,C#中並沒有函數指針的概念,但是可以使用委托(delegate)來代替函數指針,關於C#中委托的說明,可以參考筆者前面的一個文章:《C#委托及事件》

    大家可能會問,為什麼要傳遞函數指針呢?利用PInvoke可以實現C#對C/C++函數的調用,反過來,我們能不能在C/C++程序運行的某一時刻,來調用一個C#對應的函數呢?(例如在C++中存在一個獨立線程,該線程可能在任意時刻觸發一個事件,並且需要通知C#)。這個時候,我們就有必要將一個C#中已經指向某一個函數的函數指針(委托)傳遞給C++。

    想要傳遞函數指針,首先要在C#中定義一個委托,並且在C++中定義一個函數指針,同時要保證委托和函數指針具備相同的函數原型,我們首先編寫C#的代碼,如下所示:

    //定義一個委托,返回值為空,存在一個整型參數

    public delegate void CSCallback(int tick);

    //定義一個用於回調的方法,與前面定義的委托的原型一樣

    //該方法會被C++所調用

    static void CSCallbackFunction(int tick)

    {

    Console.WriteLine(tick.ToString ());

    }

    //定義一個委托類型的實例,

    //在主程序中該委托實例將指向前面定義的CSCallbackFunction方法

    static CSCallback callback;

    在CS的主程序中讓callback指向CSCallbackFunction方法,代碼如下所示:

    //調用委托所指向的方法

    callback = CSCallbackFunction;

    然後在C/C++中定義一個函數指針,並且添加一個用於設置函數指針的函數,TestCPPDLL.h中的代碼如下所示:

    //定義一個函數指針

    typedef void (__stdcall *CPPCallback)(int tick);

    //定義一個用於設置函數指針的方法,

    //並在該函數中調用C#中傳遞過來的委托

    EXTERN_C TESTCPPDLL_API void SetCallback(CPPCallback callback);

    SetCallback函數的實現在TestCPPDLL.cpp中,代碼如下所示:

    TESTCPPDLL_API void SetCallback(CPPCallback callback)

    {

    int tick=rand();

    //下面的代碼是對C#中委托進行調用

    callback(tick);

    }

    在C#中添加SetCallback函數的聲明,代碼如下所示:

    //這裡使用CSCallback委托類型來兼容C++裡的CPPCallback函數指針

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")]

    extern static void SetCallback(CSCallback callback);

    在C#中的調用過程如下所示:

    //讓委托指向將被回調的方法

    callback = CSCallbackFunction;

    //將委托傳遞給C++

    SetCallback(callback);

    SetCallback方法被執行後,在C#中定義的CSCallbackFunction就會被C++所調用。

    5. 結構體的傳遞

    傳遞結構體的想法和傳遞一個int類型數據類似,struct中的數據是在內存中順序排列的,只要保證保證以下幾點,就可以直接傳遞結構體,甚至是結構體的指針:

    • 要傳遞的成員為公有的值類型字段

    • C#中結構體字段類型與C++結構體中的字段類型相兼容

    • C#結構中的字段順序與C++結構體中的字段順序相同,要保證該功能,需要將C#結構體標記為[StructLayout( LayoutKind.Sequential)]

    下面通過代碼進行說明,首先在C#中添加一個結構體,代碼如下所示:

    [StructLayout( LayoutKind.Sequential)]

    struct Vector3

    {

    public float X, Y, Z;

    }

    該結構體表示一個3D向量,包括X,Y,Z三個float類型的分量。

    然後在TestCPPDLL.h中也定義一個相同結構的結構體,代碼如下所示:

    struct Vector3

    {

    float X,Y,Z;

    };

    在TestCPPDLL.h中聲明一個用於傳遞Vector3結構體的一個函數,代碼如下所示:

    EXTERN_C TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector);

    在TestCPPDLL.cpp中將其實現,代碼如下所示:

    TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector)

    {

        cout<<"got vector3 in cpp,x:";

        cout<<vector.X;

        cout<<",Y:";

        cout<<vector.Y;

        cout<<",Z:";

        cout<<vector.Z;

    }

    在C#中添加對SendStructFromCSToCPP函數的聲明,代碼如下所示:

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")]

    extern static void SendStructFromCSToCPP(Vector3 vector);

    C#中的調用過程如下所示:

    //建立一個Vector3的實例

    Vector3 vector = new Vector3() { X =10,Y=20,Z=30 };

    //將vector傳遞給C++並在C++中輸出

    SendStructFromCSToCPP(vector);

    基輸出效果如下所示:

    完整的TestCPPDLL.h代碼如下所示:

    復制代碼

    #define TESTCPPDLL_API __declspec(dllexport)

    EXTERN_C TESTCPPDLL_API int __stdcall Add(int a,int b);

    EXTERN_C TESTCPPDLL_API void __stdcall WriteString(wchar_t*content);

    //傳入一個整型指針,將其所指向的內容加1

    EXTERN_C TESTCPPDLL_API void __stdcall AddInt(int *i);

    //傳入一個整型數組的指針以及數組長度,遍歷每一個元素並且輸出

    EXTERN_C TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arraylength);

    //在C++中生成一個整型數組,並且數組指針返回給C#

    EXTERN_C TESTCPPDLL_API int* __stdcall GetArrayFromCPP();



    //定義一個函數指針

    typedef void (__stdcall *CPPCallback)(int tick);

    //定義一個用於設置函數指針的方法,

    //並在該函數中調用C#中傳遞過來的委托

    EXTERN_C TESTCPPDLL_API void __stdcall SetCallback(CPPCallback callback);



    struct Vector3

    {

    float X,Y,Z;

    };

    EXTERN_C TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector);

    復制代碼

    完整的TestCPPDLL.CPP代碼如下所示:

    復制代碼

    #include "stdafx.h"

    #include <iostream>

    #include "TestCPPDLL.h"

    using namespace std;

    TESTCPPDLL_API int __stdcall Add(int a,int b)

    {

    return a+b;

    }

    TESTCPPDLL_API void __stdcall WriteString(wchar_t*content)

    {

    wprintf(content);

    printf("\n");

    }



    TESTCPPDLL_API void __stdcall AddInt(int *i)

    {

    (*i)++;

    }



    TESTCPPDLL_API void __stdcall AddIntArray(int *firstElement,int arrayLength)

    {

    int*currentPointer=firstElement;

    for (int i = 0; i < arrayLength; i++)

    {

    cout<<*currentPointer;

    currentPointer++;

    }

    cout<<endl;

    }

    int *arrPtr;

    TESTCPPDLL_API int* __stdcall GetArrayFromCPP()

    {

    arrPtr=new int[10];



    for (int i = 0; i < 10; i++)

    {

    arrPtr[i]=i;

    }



    return arrPtr;

    }



    TESTCPPDLL_API void __stdcall SetCallback(CPPCallback callback)

    {

    int tick=100;

    //下面的代碼是對C#中委托進行調用

    callback(tick);

    }



    TESTCPPDLL_API void __stdcall SendStructFromCSToCPP(Vector3 vector)

    {

    cout<<"got vector3 in cpp,x:";

    cout<<vector.X;

    cout<<",Y:";

    cout<<vector.Y;

    cout<<",Z:";

    cout<<vector.Z;

    }

    復制代碼

    完整的C#代碼如下所示:

    復制代碼

    using System;

    using System.Collections.Generic;

    using System.Runtime.InteropServices;

    using System.Text;



    namespace ConsoleApplication1

    {

    class Program

    {

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "Add")]

    extern static int Add(int a, int b);

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "WriteString")]

    extern unsafe static void WriteString(char* c);

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddInt")]

    extern unsafe static void AddInt(int* i);

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "AddIntArray")]

    extern unsafe static void AddIntArray(int* firstElement, int arraylength);

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "GetArrayFromCPP")]

    extern unsafe static int* GetArrayFromCPP();







    //定義一個委托,返回值為空,存在一個整型參數

    public delegate void CSCallback(int tick);

    //定義一個用於回調的方法,與前面定義的委托的原型一樣

    //該方法會被C++所調用

    static void CSCallbackFunction(int tick)

    {

    Console.WriteLine(tick.ToString());



    }

    //定義一個委托類型的實例,

    //在主程序中該委托實例將指向前面定義的CSCallbackFunction方法

    static CSCallback callback;





    //這裡使用CSCallback委托類型來兼容C++裡的CPPCallback函數指針

    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SetCallback")]

    extern static void SetCallback(CSCallback callback);



    [StructLayout(LayoutKind.Sequential)]

    struct Vector3

    {

    public float X, Y, Z;

    }



    [DllImport(@"E:\ex\TestCPPDLL\Debug\TestCPPDLL.dll", EntryPoint = "SendStructFromCSToCPP")]

    extern static void SendStructFromCSToCPP(Vector3 vector);



    static void Main(string[] args)

    {

    int c = Add(1, 2);

    Console.WriteLine(c);

    //因為使用指針,因為要聲明非安全域

    unsafe

    {

    //在傳遞字符串時,將字符所在的內存固化,

    //並取出字符數組的指針

    fixed (char* p = &("hello".ToCharArray()[0]))

    {

    //調用方法

    WriteString(p);

    }



    }

    unsafe

    {

    // 調用C++中的AddInt方法

    int i = 10;



    AddInt(&i);

    Console.WriteLine(i);



    //調用C++中的AddIntArray方法將C#中的數據傳遞到C++中,並在C++中輸出

    int[] CSArray = new int[10];

    for (int iArr = 0; iArr < 10; iArr++)

    {

    CSArray[iArr] = iArr;

    }

    fixed (int* pCSArray = &CSArray[0])

    {

    AddIntArray(pCSArray, 10);

    }

    //調用C++中的GetArrayFromCPP方法獲取一個C++中建立的數組

    int* pArrayPointer = null;

    pArrayPointer = GetArrayFromCPP();

    for (int iArr = 0; iArr < 10; iArr++)

    {

    Console.WriteLine(*pArrayPointer);

    pArrayPointer++;

    }

    }









    //讓委托指向將被回調的方法

    callback = CSCallbackFunction;

    //將委托傳遞給C++

    SetCallback(callback);



    //建立一個Vector3的實例

    Vector3 vector = new Vector3() { X = 10, Y = 20, Z = 30 };

    //將vector傳遞給C++並在C++中輸出

    SendStructFromCSToCPP(vector);





    Console.Read();

    }

    }

    }

    復制代碼

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