1. 程式人生 > >C# 與C++的資料轉換

C# 與C++的資料轉換

在整合工作中,經常會有用c#程式碼呼叫c++的dll,這裡難免會有型別轉化。在呼叫中經常出現的問題有;

一、型別轉化

下面重點羅列下常用的型別轉化。

C++型別

C#型別

備註說明

Int

Int16、Int32

沒有懸念,直接轉化

Uint

UInt16、Uint32、int

在程式中,不太清楚是,就可以直接對應為int

Long

Int32

Long相對int就定型了,對應的就是Int32

DWORD(unsigned long)

Uint32

WORD(unsigned short)

Uint16

這是對WORD的認知。

Byte(Unsigned char)

Byte

DECIMAL

Decimal

位數轉化

BOOL

bool

char

char

這種沒有加指標,比較容易,直接對應入座

Handle(void *)

Intptr

函式中為視窗控制代碼,c#就是預設的為Intptr

HMODULE

Intptr

同上

HISTANCE

Intptr

同上

Int *、long *

Ref int、ref long

這種整形指標,在程式中是為了引用,所以在c#中對應的是ref,所以在關鍵詞之前加入ref.

Int &.long &

Ref int 、ref long

解釋同上,當然在關鍵詞加入 out,也是可以的

Char *(LPSTR、Pchar)、const char *(LPCSTR)、

String

也是為了獲取資料,在c#中string在應用中就是引用,所以直接改為string即可。Intptr也可以,不提倡。

Byte *

Ref byte、byte[]

程式byte * 是為了獲取型別為byte資料,在C#則用byte陣列獲取儲存資料。String也可以,但是不提倡,關鍵看看獲取的資料,做什麼用

GUID

Guid

Char[],byte[],in[]

可對應找指標型別對一個的轉化

Char[],byte[],int,這種在c++中應用時,先初始化陣列,然後定義一個指標指向資料地址,然後訪問,所以在轉化是,在對應為char*》string , int[]>int * > intptr

Char **,byte **

Intptr

這種雙指標的呼叫,一般是訪問二位陣列,所以我們直接處理為Intptr,當然intptr和之前不一樣,需要處理,可以看見例子說明。

結構體 * 變數名、結構體 &變數名。

Ref 結構體 變數名、intptr

在結構體引用,或者傳入值c++一般是用指標,在c#中,用ref代替。當然intptr也可以,但是不太方便。

通常在只要你選擇在win32執行環境中找到相匹配的CLR(公共語言執行庫,負責資源管理:記憶體分配和回收,並保證應用和底層作業系統之間有必要分離)型別,就可應正常工作。當然也有例外:BOOL在c++中發現其實為int型,所以轉化為int,而不是bool。

指標引數,在winAPI許多函式中將指標作為一個或者多個引數。指標的作用是儲存資料的地址,而不是資料。指標的加入,增加了資料的複雜性,同時增加資料靈活性,實現資料的傳入傳出,如果只是值型別應用只能是傳入資料。在應用中如果沒有指標,您可以直接通過值線上程堆疊中傳遞資料。有了指標,可以通過引用傳遞資料,將資料的記憶體地址推入到執行緒堆疊中,然後函式通過記憶體地址間接訪問資料。在c#用ref、out定義為類似指標作用關鍵詞。out是ref一個引數規範,實際上他們在執行中產生相同的機器碼,out作用為了讓呼叫者明白,資料只是傳出,ref表明資料傳入也是獲取。託管程式碼中ref、out引數另一個很好用的是,可以作為結構體、類、陣列提供一個地址供呼叫。只有在發現ref或out引數不符合需要情況下,才會封裝成更復雜的CLR型別。

在windows API中會有視窗控制代碼的獲取或者賦值,其方法的傳遞是不透明的,如handle、void *、histance等。

少數情況下,API 函式也將不透明指標定義為 PVOID 或 LPVOID 型別。在 Windows API 的定義中,這些型別意思就是說該指標沒有型別。當 一個不透明指標返回給您的應用程式(或者您的應用程式期望得到一個不透明指標)時,您應該將引數或返回值封送為 CLR 中的一種特殊型別 — System.IntPtr。當您使用 IntPtr 型別時,通常不使用 out 或 ref 引數,因為 IntPtr 意為直接持有指標。

在CLR型別系統中intptr是一種特殊的屬性,沒有固定的大小,在執行時再繫結,依據作業系統的正常指標而定。這意味著在 32 位的 Windows 中,IntPtr 變數的寬度是 32 位的,而在 64 位的 Windows 中,實時編譯器編譯的程式碼會將 IntPtr 值看作 64 位的值。當在託管程式碼和非託管程式碼之間封送不透明指標時,這種自動調節大小的特點十分有用。然而,當使用 Windows API 函式時,因為指標應是不透明的,所以除了儲存和傳遞給外部方法外,不能將它們另做它用。這種“只限儲存和傳遞”規則的兩個特例是當您需要向外部方法傳遞 null 指標值和需要比較 IntPtr 值與 null 值的情況。為了做到這一點,您不能將零強制轉換為 System.IntPtr,而應該在 IntPtr 型別上使用 Int32.Zero 靜態公共欄位,以便獲得用於比較或賦值的 null 值。

封送文字,主要是指在獲取資料時,資料可能是儲存在char陣列中,如果我們用string接收時,有可能為亂碼。所以在函式呼叫過程中,當char *,char[]是作為輸入資料時,可以改為string。當作為資料傳出時,則要好好考慮了,有時需要改為char []。在c+程式中,就是在c中字串實際上是隻是一個字元值陣列,通常為null,大多數windows API函式是按照對於ansi,將其作為字元值陣列(比較常用),對於unicode,將其作為寬字元值陣列。有時獲取的資料為亂碼時,可能就是需要轉為unicode,就解決了。大多數windows API函式都帶有LPTSTR或者LPCTSTR值。他們分別是可修改和不可修改的緩衝區,包含以null結束的字元陣列。“C”代表const,意味資料不會傳遞到函式外部。“T”代表該引數可以是Unicode和ANSI,在CLR執行中取決你選擇的字符集和底層作業系統的字符集.。所以函式宣告時,加上DllImportAttribute 為CharSet.Auto就可以了。如果字串引數只用作輸入,則使用 System.String 型別。在託管程式碼中,字串是不變的,適合用於不會被本機 API 函式更改的緩衝區。如 果字串引數可以用作輸入和/或輸出,則使用 System.StringBuilder 型別。StringBuilder 型別是一個很有用的類庫型別,它可以幫助您有效地構建字串,也正好可以將緩衝區傳遞給本機函式,由本機函式為您填充字串資料。一旦函式呼叫返回,您只 需要呼叫 StringBuilder 物件的 ToString 就可以得到一個 String 物件。

CharSet的各變數對char以及char[]的影響如下:
ANSI:char以及char[]佔一個位元組
AUTO:char以及char[]佔兩個位元組
UNICODE:char以及char[]佔兩個位元組

總體原則可以總結為:

1、在c++常用的基本型別(數值型別、位元組型別)直接轉化到c#中的數值型別。(原則是位元組數,確定好)

2、在c++常用的指標型別(數值型別*、位元組型別*)則轉化為c#中的ref 數值型別、ref 位元組型別。但是在常用時,針對char * ,轉化為string。

3、在c++常用的構造型別(結構體、陣列、列舉型別、共用體)行對比較複雜。列舉和共用體直接複製就可以用,結構體的宣告隨後重點講,在函式呼叫時,如果為結構體 * 變數名,則為 ref 結構體 變數名。陣列在 函式呼叫,可以直接寫為陣列名,也可以寫為Intptr。

二、結構體

1、結構體的重定義。

在c++中會有很多結構體,結構體內有各種各樣的資料型別,所以就牽涉到資料型別的轉化,同時在通過結構體獲取到資料後,也牽涉到編碼轉化問題。

結構體型別和類型別在語法上有很多相似之處,他們都是一種資料結構,都可以包括資料成員和方法成員。

結構體和類區別:

1、結構體是值型別,它在棧中分配空間;類是引用型別,他在堆中分配空間,棧儲存的是引用。

2、結構體型別直接儲存成員資料,類中資料型別存在堆中,然後通過在棧中引用,訪問資料。因為結構體是值型別,直接儲存,因此當物件的主要成員為資料切不大時,使用結構體效率更高。

3、結構體直接包含自己的資料,每個結構體都儲存一份資料,在程式宣告兩個結構體物件,改變其中一個,另一個數據不變,但是類是引用,則另一個數據會改變。

4、結構體是值型別,不能初始化為null,複製時則資料全部複製。;類中的複製是引用的複製,資料較大時,結構體複製則效果不是很好。

結構和類的適用場合分析:

  1、當堆疊的空間很有限,且有大量的邏輯物件時,建立類要比建立結構好一些;

  2、對於點、矩形和顏色這樣的輕量物件,假如要宣告一個含有許多個顏色物件的陣列,則CLR需要為每個物件分配記憶體,在這種情況下,使用結構的成本較低;

  3、在表現抽象和多級別的物件層次時,類是最好的選擇,因為結構不支援繼承。

  4、大多數情況下,目標型別只是含有一些資料,或者以資料為主。

結構體的宣告、初始化、引用在c#還是很重要的,下面根據程式碼進行分析。

//C++的結構體
//錄影索引列表檔案
typedef struct tagINDEX_INFO
{
	DWORD dwStartTime;				//錄影開始時間
	DWORD dwEndTime;				//錄影停止時間
	BYTE  btFileType;					//檔案型別
	BYTE  btFileStatus;				//檔案狀態
	BYTE  Reserved[2];				//預留,LMC向NVR請求回放時Reserved[0]標識NVR傳送速度,Reserved[1]存放錄影倒放標誌
	
	BYTE  btMAC[6];					//裝置MAC地址
	WORD  wChan;					//裝置通道
	DWORD dwIP1;					//裝置IP1
	DWORD dwIP2;					//裝置IP2,公網模式下,儲存此檔案所在的NVR的IP
	DWORD dwIP3;					//裝置IP3,V3061用來儲存錄影片段在錄影檔案的偏移量,勿動
	DWORD dwIP4;					//裝置IP4,V3061用來錄影檔名,勿動
	DWORD dwFileOffset;				//檔案偏移,用於檔案下載時斷點續傳和定位
	DWORD dwReserved;				//預留,3070有用到,不要動它
}INDEX_INFO, *LPINDEX_INFO;

C#結構體

 //錄影索引列表檔案
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi,Pack = 1)]
        public struct NET_INDEX_INFO
        {
            public UInt32 dwStartTime;//錄影開始時間
            public UInt32 dwEndTime;//錄影停止時間
            public byte btFileType;//檔案型別
            public byte btFileStatus;//檔案狀態
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
            public byte [] Reserved;//預留,LMC向NVR請求回放時Reserved[0]標識NVR傳送速度,Reserved[1]存放錄影倒放標誌
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
            public byte [] btMAC;//裝置MAC地址
            public UInt16 wChan;//裝置通道
            public UInt32 dwIP1;//裝置IP1
            public UInt32 dwIP2;//裝置IP2,公網模式下,儲存此檔案所在的NVR的IP
            public UInt32 dwIP3;//裝置IP3,V3061用來儲存錄影片段在錄影檔案的偏移量,勿動
            public UInt32 dwIP4;//裝置IP4,V3061用來錄影檔名,勿動
            public UInt32 dwFileOffset;//檔案偏移,用於檔案下載時斷點續傳和定位
            public UInt32 dwReserved;//預留,3070有用到,不要動它
        }

上面成員前面必須新增public,因為預設是private。

2、StructLayout特性

公共語言執行庫利用StructLayoutAttribute控制類或結構的資料欄位在託管記憶體中的物理佈局,即類或結構需要按某種方式排列。如果要將類傳遞給需要指定佈局的非託管程式碼,則顯式控制類佈局是重要的。它的建構函式中用 LayoutKind值初始化 StructLayoutAttribute 類的新例項。 LayoutKind.Sequential 用於強制將成員按其出現的順序進行順序佈局。 System.Runtime.InteropServices.StructLayout   允許的值有StructLayout.Auto   StructLayout.Sequential   StructLayout.Explicit.  在應用中為了資料順利傳入,一般是用StructLayout.Sequential,意味著結構體體內資料傳入的格局按照宣告一樣。 StructLayout.Explicit需要用FieldOffset()設定每個成員的位置這樣就可以實現類似c的公用體的功能。 [StructLayout(LayoutKind.Explicit)] 
struct S1
{
  [FieldOffset(0)]
  int a;
  [FieldOffset(0)]
  int b;
}
這樣a和b在記憶體中地址相同

StructLayout特性支援三種附加欄位:CharSet、Pack、Size。     
·   CharSet定義在結構中的字串成員在結構被傳給DLL時的排列方式。可以是Unicode、Ansi或Auto。     
  預設為Auto,在WIN   NT/2000/XP中表示字串按照Unicode字串進行排列,在WIN   95/98/Me中則表示按照ANSI字串進行排列。     
·   Pack定義了結構的封裝大小。可以是1、2、4、8、16、32、64、128或特殊值0。特殊值0表示當前操作平臺預設的壓縮大小。 

3.MarshalAs的使用

MarshalAs屬性指示如何在託管程式碼和非託管程式碼之間封送資料。 [MarshalAs(UnmanagedType unmanagedType, 命名引數)]

常用的UnmanagedType列舉值:(詳細內容查MSDN)

BStr   長度字首為雙位元組的 Unicode 字串;

LPStr  單位元組、空終止的 ANSI 字串。;

LPWStr  一個 2 位元組、空終止的 Unicode 字串;

ByValArray 用於在結構中出現的內聯定長字元陣列,應始終使用MarshalAsAttribute的SizeConst欄位來指示陣列的大小。常用的是陣列對應的為ByAvlArray,string對應的是ByValStr。