用C++/CLI搭建C++和C#之間的橋梁


一、簡單用法

C#和C++是非常相似的兩種語言,然而我們卻常常將其用於兩種不同的地方,C#得益於其簡潔的語法和豐富的類庫,常用來構建業務系統。C++則具有底層API的訪問能力和拔尖的執行效率,往往用於訪問底層模塊和構建有性能要求的算法。

這兩種場景看起來有較大的差異,大多數的時候可以各行其道。但還是有很多時候會出現融合的情況。當我們構建分布式系統的時候,由於RPC機制一般都是語言無關的,我們大可以將其各盡所長,按需划分在最能發揮其長處的位置。然而,一旦我們需要構建融合兩者需求的集中式系統的時候,就會頭痛無比。

此時,我們可以使用C++/CLI搭建C++和.Net之間的橋梁,C++/CLI是一個比較有意思的兩棲模塊,它具有如下特點

  1. 既可以訪問.Net類庫,也可以訪問C++原生類庫
  2. 既可以被.Net程序引用,也可以被C++原生程序引用

使用C++/CLI,我們可以使用C++編寫算法,用C#編寫界面,也可以使用.Net Framework類庫增強C++程序功能,各取所長。關於的優點,園子里有篇文章介紹的比較詳細,值得一讀:從C++到C++/CLI

下面我們就以一個簡單的例子來演示一下它的用法:

Calculator.h:

    #pragma once

    namespace CppCliTest
    {
        public ref class Calculator
        {
        public:
            int Add(int a, int b);
        };
    }

 

Calculator.cpp

    #include "stdafx.h"
    #include "Calculator.h"

    namespace CppCliTest
    {
        int Calculator::Add(int a, int b)
        {
            return a + b;
        }
    }

 

main.cpp

    #include "stdafx.h"
    #include "Calculator.h"

    using namespace System;
    using namespace CppCliTest;

    int main(array<System::String ^> ^args)
    {
        Calculator^ calculator = gcnew Calculator();
        int result = calculator->Add(3, 2);

        Console::WriteLine(L"Result is {0}", result);
        return 0;
    }

 

從這個例子中,我們可以簡單的管中窺豹的看看C++/CLI是在C++的基礎上擴充了一套語法,使其具有訪問.Net原始的功能,這里用到的有:

  • 使用ref class聲明CLI引用類型(C#中的class)
  • 使用^(例如如這里的String ^)來定義CLI引用類型
  • 使用gcnew創建CLI的引用類型

具體的功能我將在后面的文章中再做介紹,MSDN中也有文檔詳細的介紹了這些語法:https://msdn.microsoft.com/zh-cn/library/ms235289.aspx

雖然C++/CLI同時具有兩者的功能,但它使得本就比較復雜的C++語法變得更加復雜了(特別是初期的版本,非常復雜,現在已經簡化了不少了),並且長期沒有得到VisualStudio這宇宙第一IDE的較好支持(在VS2010的時候還不支持智能提示),是無法與擁有大量語法糖的C#比開發效率的。加上大多數需求場景可以通過分布式系統解決,這些都導致了它一直沒有得到太多的關注。但是,微軟還是在積極的改進它的,加上C++11的支持,現在已經比之前好用多了,如果用在合適的位置,是絕對能讓你的開發如魚得水的。

二、復雜用法

對於如下C#代碼:

System.Object x = new System.Object();

其在C++/CLI中的等價代碼如下:

 

System::Object^ x = gcnew System::Object();

和傳統的C++創建的語法比較下,

   

 P* x = new P();

 

我們不難發現,對於托管對象,主要引入了如下兩個語法:

  1. 用gcnew代替new實現托管對象的創建
  2. 用^代替*實現托管對象的指針

這種方式創建的對象是可以直接被CLR支持的,可以在C#中使用。托管對象指針使用的方式和傳統的對象指針還是比較類似的,直接使用->即可:

    System::Object^ x = gcnew System::Object();
    auto str = x->ToString();

另外,C++/CLI也有一種類似於C++的對托管對象的引用的語法:

    System::Object^ x = gcnew System::Object();
    System::Object% y = *x;
    auto str = y.ToString();

由於這種方式在C#里沒有對應的語法,用起來感覺怪怪的,也不方便於其它.net語言集成。

 

托管類型的定義

我們也可以自定義托管類型,在CLR中,托管類型是分為引用類型(class)和值類型(struct)的,在C++/CLI中的分別定義方式如下:

引用類型:

    public ref class MyClass
    {
    };

值類型:

    public value class MyClass
    {
    };

在ISO C++中類定義中加上了ref或value標記為托管類型,還算比較容易使用。

 

枚舉

枚舉的定義和C++11的enum class一樣,它像數字那樣可以同時應用於托管類型和非托管類型。

    

public enum class SomeColors { Red, Yellow, Blue };

或者更精確的表示:

public enum class SomeColors : char { Red, Yellow, Blue };

 

數組

C++/CLI中新增了array<T> ^的方式定義數組。

array<int> ^a = gcnew array<int>(100) { 1, 2, 3 };

或者使用它的完整版:

cli::array<int> ^a = gcnew cli::array<int> {1, 2, 3};

不定參數

對於C#中的不定參數的語法:

void foo(params string[] args)

在C++/CLI中對應的版本為:

void foo(... array<String^>^ args)

 三、基本類型

數值類型

對於基本的數值類型,在C++/CLI中是可以直接映射為托管類型的數值的,可以同時應用於托管類型和非托管類型,編譯器會將其自動轉換。

基本類型

System命名空間中對應的類

注釋/用法

bool

System::Boolean

bool dirty = false;

char

System::SByte

char sp = ' ';

signed char

System::SByte

signed char ch = -1;

unsigned char

System::Byte

unsigned char ch = '\0';

wchar_t

System::Char

wchar_t wch = ch;

short

System::Int16

short s = ch;

unsigned short

System::UInt16

unsigned short s = 0xffff;

int

System::Int32

int ival = s;

unsigned int

System::UInt32

unsigned int ui = 0xffffffff;

long

System::Int32

long lval = ival;

unsigned long

System::UInt32

unsigned long ul = ui;

long long

System::Int64

long long etime = ui;

unsigned long long

System::UInt64

unsigned long long mtime = etime;

float

System::Single

float f = 3.14f;

double

System::Double

double d = 3.14159;

long double

System::Double

long double d = 3.14159L;

 

字符串

字符串CLI已經內置了:System::String,但C++的常用字符串有char*、wchar_t*、std::string等好多種,編譯器提供了char*、wchar_t*到System::String的自動轉換:

    System::String^ s = "hello worold";
    System::String^ s2 = L"hello worold";

另外,也可以使用gcnew創建托管字符串:

    System::String^ s = gcnew String("hello worold");

但是,對於System::String轉char*,系統沒有直接的語法支持。方法有很多種,我通常使用如下方式來轉換:

    IntPtr ip = Marshal::StringToHGlobalAnsi(str);
    const char* ch = static_cast<const char*>(ip.ToPointer());
    //do something with ch
    Marshal::FreeHGlobal(ip);

這里有個需要注意的地方是在使用完轉換出來的const char*后需要釋放掉轉換過程中的Intptr,如果沒有太多需要考慮性能的地方,大可以使用一個std::string將其拷貝走,寫成如下函數形式:  

#include <string>

    using namespace std;
    using namespace System;
    using namespace System::Runtime::InteropServices;

    string cast_to_string(String^ str)
    {
        IntPtr ip = Marshal::StringToHGlobalAnsi(str);
        const char* ch = static_cast<const char*>(ip.ToPointer());
        string stdStr = ch;
        Marshal::FreeHGlobal(ip);

        return stdStr;
    }

四、網絡資源

關於C++/CLI的基礎,我前面已經寫過了幾篇文章介紹過一些了,不過這些基本上都是管中窺豹,如果要詳細了解C++/CLI,MSDN無疑是最好的教程。

如果需要在MFC中使用.net控件的話,可以參考如下三篇文章:

  1. 在 MFC 對話框中承載 Windows 窗體用戶控件
  2. 以 MFC 視圖的形式承載 Windows 窗體用戶控件
  3. 以 MFC 對話框的形式承載 Windows 窗體用戶控件

此外,MSDN文章如何實現 C++ 互操作 上有更加詳細的文章索引。后面有空的話,我會繼續繼寫一些相關的介紹文章的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM