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


摘要

這個系列是本人在工作或工作之余開發和學習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):

net-cpp-hobby-img0

在上圖中,本地指針指向的地址本來是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


免責聲明!

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



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