日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

.NET程序员的C\C++情结(3)

發布時間:2025/5/22 c/c++ 59 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .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):

在上圖中,本地指針指向的地址本來是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++編譯器不能支持對模板的分離式編譯的原因。

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

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

    轉載于:https://www.cnblogs.com/P_Chou/archive/2013/01/12/net-cpp-hobby-03.html

    總結

    以上是生活随笔為你收集整理的.NET程序员的C\C++情结(3)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。