.NET程式員的C\C++情結(3)

來源:互聯網
上載者:User
摘要

這個系列是本人在工作或工作之餘開發和學習C\C++的一些筆記。本文涉及C++/CLI的一些內容。

本文為原創,首發於我的個人部落格:.NET程式員的C\C++情結(3)。歡迎交流指正。轉載請註明出處。

雖然現在主要從事.NET平台的開發,但是一直以來對C\C++有著那份難以割捨的情結。本文會涉及到託管C++的一些隨筆記錄。

當然,如果寫純.NET應用的話,C#無疑是最合適的語言的。但是託管C++在同時處理Native調用和託管調用上無疑是十分迷人的,往往用來作為託管世界和Native世界的橋樑。當然。你可以說用.NET的“平台叫用”特性同樣能夠勝任,蘿蔔青菜各有所愛吧。

 

託管C++基礎語言特性

在託管C++中需要像下面這樣定義一個託管類型

public ref class ARSession
{
public:
 property UInt32 FieldId;
}

預設情況下這樣的類是預設實現IDisposable的,原因很簡單,既然用到C++來封裝託管類型,那麼八成類型需要涉及到非託管對象,實現IDisposable減少了出錯的可能。可以同時實現兩種“解構函式”:

!ARSession(void)
~ARSession(void)

前者是好比Dispose(),後者是C++原生的解構函式。

可以同時引用託管的命名空間和C++命名空間

using namespace System;
using namespace System::Collections::Generic;
using namespace std;

也可以向普通C++一樣#include標頭檔,編譯的過程可以理解成跟本地C++的編譯過程一樣,只是在編譯的時候會有/clr開關,並至少引用相應的託管dll:mscorlib.dll

對於託管類型,在類型的標識右使用”^”標註,比如:

String^
array<String^>^
List<AREntry^>^

但注意,對於Nullable的實值型別,使用

Nullable<UInt32>

而不是

Nullable<UInt32>^

前者在C#中會看到是uint?,而後者在C#中會看到是ValueType

 

託管C++支援類似C#中的ref

Int32% totalMatch

out的話需要加一個Attribute

using namespace System::Runtime::InteropServices;   
void foo([Out] Bar^% x);

在本地堆中申請記憶體是使用new關鍵字,而在託管堆中申請記憶體,使用gcnew關鍵字:

ARException^ exception = gcnew ARException();

 

託管C++的記憶體管理

上面簡單介紹的一些語言特性是我實際碰到的,可能不全。與語言特性相比,更為重要的是記憶體管理帶來的複雜性。原生的C++只有一個由C運行庫管理的“本地堆”,而C++/CLI允許同時操作本地堆和託管堆。眾所周知,託管堆由CLR管理,在託管堆中的記憶體會隨時被CLR回收和壓縮,這意味著,如果使用C#的引用或者C++/CLI中的“Handle”(即由String^等“戴帽子的類型“聲明的變數)來操作託管堆的記憶體,不會有任何問題,因為CLR會自動更改引用或Handle指向的地址。然而,如果在本地堆或者棧上的本地指標來指向託管堆上的記憶體的話,CLR不會對壓縮記憶體帶來的地址修改負任何責任。如果發生這種情況的話,再次使用該指標將導致記憶體違規。下面這張圖可以解釋這個現象(圖片來源http://www.codeproject.com/Articles/17817/C-CLI-in-Action-Using-interior-and-pinning-pointer):

在中,本地指標指向的地址本來是Data,但是當CLR的GC工作後,Data可能被壓縮至託管堆的其他地方,而取而代之的是另外一塊記憶體。很典型的情況就是,我們要在託管的byte[]和非託管的usigned char*對象之間傳遞記憶體,下面這段代碼將String對象轉化成以UTF8編碼的位元組數組:

char* MarshalStringCopyToChar(String^ Source)
{
   if(String::IsNullOrEmpty(Source))
       return NULL;
   array<Byte>^ vText = System::Text::Encoding::UTF8->GetBytes(Source);
   pin_ptr<unsigned char> pText = &vText[0];
   char* Des = (char*)calloc(vText->Length+1,sizeof(char));
   memcpy(Des, pText, vText->Length);
   Des[vText->Length] = '\0';
   return Des;
}

上述代碼實際上是將託管堆中的一部分記憶體資料copy到非託管堆,使其奏效的關鍵就是pin_ptr<unsigned char>這個指標了。

在託管C++中也可以使用如下方法代替上面的實現:

std::string tmp = marshal_as<std::string>(Source);

但是,似乎在轉換過程中是以ANSI編碼來轉換的,具體沒有詳細研究。不過marshal_as是可以擴充的,詳見:http://msdn.microsoft.com/zh-cn/library/bb384865.aspx

 

C++運行庫的問題

在開發過程中碰到一個很怪異的_CrtIsValidHeapPointer錯誤,關於這個問題,需要瞭解Microsoft C運行庫以及其管理堆記憶體的一些原則:

首先,到目前為止,Microsoft C運行庫實際上已經有很多版本了,在應用程式執行期間,很可能在記憶體中存在多個版本的C運行庫,而且每個C運行庫版本維護自己的堆,這樣,如果在不同的運行庫之間引用堆記憶體,那麼在Debug模式下會有一個_CrtIsValidHeapPointer宏來防止這個操作(Release模式沒有驗證過是不是就沒有這個限制了)。那麼典型的情境就是,當我們在引用某個第三方動態連結程式庫時,如果這個第三方的動態連結程式庫所引用的C運行庫跟我們的主程式不一致,那麼將會在記憶體中同時存在兩個版本的運行庫,所以,如果主程式申請的堆記憶體,由其他dll來釋放,那麼就會報錯。所以,所謂的“誰申請誰釋放”的原則在這裡實際上也是適用的。上面這個錯誤就是在Debug模式下,協助開發人員發現這種跨運行庫的heap的指標引用的問題,尚不知道這種引用是否完全不合法,還是僅僅只有風險。

另外,如果以靜態連結的方式連結到C運行庫的話,即使是同一個版本的運行庫,在記憶體中也存在兩份copy,並有兩塊由不同運行庫維護的堆記憶體。

從上述這點看來,如果要自己開發一個dll的話,記得要提供堆記憶體釋放的函數,以避免出現不同運行庫的衝突。

 

 

C++模板

老是說C++的模板真心比C#的泛型在語言層面要複雜的多,使用模板並不難,但是要自己設計範本類,就出問題了。這裡簡單總結一些模板的基礎。

模板類的聲明如下:

template <typename T>
public class IntelligentARStructAR
{
private:
  T _Struct;
public:
  ~IntelligentARStructAR();
}

模板類的實現(定義):

template<typename T> IntelligentARStructAR<T>::~IntelligentARStructAR(){…}

模板類的具化:

編譯器在編譯過程中,需要等模板在原始碼中使用的時候,才會產生一個對應的類型,這個過程叫模板類的具化。

編譯器要產生一個模板的定義,必須同時能看到模板的聲明、模板的定義以及模板的具化要素,如果編譯器在編譯階段不能具化,那麼只能寄希望於連結器

來看個典型的錯誤:

  • template.h:裡面有模板的聲明
  • template.cpp:include template.h,裡面有模板的實現(定義)
  • main.cpp:include template.h,裡面有使用模板(即模板具化的要素)

編譯器在編譯template.cpp時,同時看到了模板聲明和模板定義,但是因為沒有模板的具化要素,編譯器無法產生模板類型(因為,在沒有要素的情況下,不可能知道T這個類型的結構大小,也就無法產生二進位代碼);在編譯main.cpp,能夠看到的是模板的聲明和模板的具化要素,但沒有模板的定義,於是無法編譯通過。

這個典型的使用就是:C++編譯器不能支援對模板的分離式編譯的原因。

解決這個問題的方法有如下幾種:

  1. 在具化要素時,讓編譯器看到模板定義。典型的方式是將模板的聲明和定義同時寫在標頭檔中。
  2. 用另外的編譯單元中顯示的具化。在另一個cpp檔案中顯示的使用模板,這樣連結器能夠在連結階段找到模板類型。
  3. export關鍵字。據說還沒有編譯器實現。
 歡迎訪問我的github首頁:http://pchou.info
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.