最近有一項目,是和另外一家公司合作,需要接收對方發來的結構訊息,然後填充好後發回。
涉及到利用socket傳輸和接收struct。
一般情況下我們只需要利用C#提供的序列化和還原序列化即可,將class/struct聲明為可序列化的。
然後利用BinaryFormatter之類的方法進行序列化及還原序列化操作~自己可以Google一下:C#序列化
但是假如對方平台為C++或其它非.NET平台,這樣做就不行了。由於不同平台類型之間的差異,
所以有不小的麻煩。
先附上C++與C#之間的類型對應關係:
C++ 輸入輸出 C#
==================================
char chr[255] O StringBuilder
KCA_DIR I int
LPCSTR I string
int I int
LPSTR O StringBuilder
int* O out int
DWORD I int
DWORD* O out int
BOOL I bool
Rc_DBMgr I IntPtr
long* O out long
| API與C#的資料類型對應關係表 |
| API資料類型 |
類型描述 |
C#類型 |
API資料類型 |
類型描述 |
C#類型 |
| WORD |
16位不帶正負號的整數 |
ushort |
CHAR |
字元 |
char |
| LONG |
32位不帶正負號的整數 |
int |
DWORDLONG |
64位長整數 |
long |
| DWORD |
32位不帶正負號的整數 |
uint |
HDC |
裝置描述表控制代碼 |
int |
| HANDLE |
控制代碼,32位整數 |
int |
HGDIOBJ |
GDI物件控點 |
int |
| UINT |
32位不帶正負號的整數 |
uint |
HINSTANCE |
執行個體控制代碼 |
int |
| BOOL |
32位布爾型整數 |
bool |
HWM |
視窗控制代碼 |
int |
| LPSTR |
指向字元的32位指標 |
string |
HPARAM |
32位訊息參數 |
int |
| LPCSTR |
指向常字元的32位指標 |
String |
LPARAM |
32位訊息參數 |
int |
| BYTE |
位元組 |
byte |
WPARAM |
32位訊息參數 |
int |
Wtypes.h 中的非託管類型 |
非託管 C 語言類型 |
託管類名 |
說明 |
HANDLE |
void* |
System.IntPtr |
32 位 |
BYTE |
unsigned char |
System.Byte |
8 位 |
SHORT |
short |
System.Int16 |
16 位 |
WORD |
unsigned short |
System.UInt16 |
16 位 |
INT |
int |
System.Int32 |
32 位 |
UINT |
unsigned int |
System.UInt32 |
32 位 |
LONG |
long |
System.Int32 |
32 位 |
BOOL |
long |
System.Int32 |
32 位 |
DWORD |
unsigned long |
System.UInt32 |
32 位 |
ULONG |
unsigned long |
System.UInt32 |
32 位 |
CHAR |
char |
System.Char |
用 ANSI 修飾。 |
LPSTR |
char* |
System.String 或 System.StringBuilder |
用 ANSI 修飾。 |
LPCSTR |
Const char* |
System.String 或 System.StringBuilder |
用 ANSI 修飾。 |
LPWSTR |
wchar_t* |
System.String 或 System.StringBuilder |
用 Unicode 修飾。 |
LPCWSTR |
Const wchar_t* |
System.String 或 System.StringBuilder |
用 Unicode 修飾。 |
FLOAT |
Float |
System.Single |
32 位 |
DOUBLE |
Double |
System.Double |
64 位元 |
需要注意的是非託管的BOOL在C#中對應System.Int32。而在API調用時直接用bool即可。
socket傳輸的是byte[].所以我們需要把struct轉化為byte[]. 有高人為我們提供了如下方法。
//struct轉換為byte[]
static byte[] StructToBytes(object structObj)
{
int size = Marshal.SizeOf(structObj);
IntPtr buffer = Marshal.AllocHGlobal(size);
try
{
Marshal.StructureToPtr(structObj, buffer, false);
byte[] bytes = new byte[size];
Marshal.Copy(buffer, bytes, 0, size);
return bytes;
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
//byte[]轉換為struct
static object BytesToStruct(byte[] bytes, Type strcutType)
{
int size = Marshal.SizeOf(strcutType);
IntPtr buffer = Marshal.AllocHGlobal(size);
try
{
Marshal.Copy(bytes, 0, buffer, size);
return Marshal.PtrToStructure(buffer, strcutType);
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
一般情況下到此就結束了。但是假如struct裡面除了基礎資料型別 (Elementary Data Type)int,long,byte之外,還有 char*.比如:
typedef struct{ char szStatus[924]; char szError[196]; BYTE bEmergent;} CHECK_STATUS_PARAM
為了確保資料轉送和讀取的正確性,應該固定字串的長度。
此處就涉及到了:字串的封送處理。見:http://msdn.microsoft.com/zh-cn/library/s9ts558h(VS.80).aspx
在不同的情況下我們需要採用不同的封送選項。
結構中使用的字串
字串是結構的有效成員;但是,StringBuilder 緩衝區在結構中是無效的。下表顯示當字串資料型別被作為欄位封送時該類型的封送處理選項。MarshalAsAttribute 屬性提供了若干個 UnmanagedType 枚舉值,以便將字串封送到欄位。
枚舉類型 非託管格式的說明
UnmanagedType.BStr
具有預設長度和 Unicode 字元的 COM 樣式的 BSTR。
UnmanagedType.LPStr
指向 ANSI 字元的空終止數組的指標。
UnmanagedType.LPTStr
指向平台相關的字元的空終止數組的指標。
UnmanagedType.LPWStr
指向 Unicode 字元的空終止數組的指標。
UnmanagedType.ByValTStr
定長的字元數組;數組的類型由包含數組的結構的字元集確定。
項目要求採用ANSI編碼,於是C#對應的stuct為:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct CHECK_STATUS_PARAM
{
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 924 )]
public string szStatus;
[MarshalAs( UnmanagedType.ByValTStr, SizeConst = 196 )]
public string szError;
public byte bEmergent;
}
為了保證正確性,使對象的各個成員在非託管記憶體中的精確位置被顯式控制。我們也可以使用FieldOffsetAttribute指示該欄位在類型中的位置。此方法只有在LayoutKind設定為Explicit時使用。 。