先看下下面這個結構體的定義:
C++下面的定義:
//#pragma pack(4)
typedef struct _KERNEL_DATA // 按8個字節對齊的話
{
unsigned long PortNumber; // 佔4個字節
union
{
unsigned long LongData;
unsigned short ShortData;
unsigned char CharData;
}; //佔4個字節
unsigned long ulPhysicalAddr; // 佔8個字節,原本4個字節
unsigned char* puchVirtualAddr;// 佔8個字節
unsigned long* __ptr64 pulVirtualAddr;// 佔8個字節
unsigned long ulNumberOfBytes;//佔8個字節,原本4個字節
}KERNEL_DATA, *PKERNEL_DATA;
int main()
{
KERNEL_DATA data;
data.PortNumber = 1;
data.LongData = 2;
data.ulPhysicalAddr = 0xFF000003;
data.ulNumberOfBytes = 0x20212223;
data.puchVirtualAddr = nullptr;
data.pulVirtualAddr = nullptr;
std::cout << "size of void* __ptr32 is " << sizeof(void* __ptr32) << std::endl;
std::cout << "size of void* __ptr64 is " << sizeof(void* __ptr64) << std::endl;
}
C#對應的定義:
[StructLayout(LayoutKind.Explicit, Size = 32)]
public struct _KERNEL_DATA // 在32位系統上
{
[FieldOffset(0)]
UInt32 PortNumber; //
[FieldOffset(4)]
UInt32 LongData;
[FieldOffset(4)]
UInt16 ShortData;
[FieldOffset(4)]
Byte CharData;
[FieldOffset(8)]
UInt32 ulPhysicalAddr; //
[FieldOffset(12)]
IntPtr puchVirtualAddr;//
[FieldOffset(20)]
UIntPtr pulVirtualAddr;//
[FieldOffset(28)]
UInt32 ulNumberOfBytes;//
};
簡單說下這個問題背景:
看到手機微信裏以前的大學同學,也是宿舍舍友,畢業後同事從事軟件開發行業。看到給我微信上發了這個問題,說C#調用C++這個結構體總是出錯。看了下粗略的問題,我也有較長時間沒怎麼用過C++了,但從我瞭解這個結構體的所佔字節數,一直認爲上面結構體sizeof()佔用了24個字節(在32位系統上),在64位系統上佔用32個字節。我老同學通過調試和對比,發現在64位系統上確實都是32字節,看起來應該沒啥問題。可以C#從C++中獲取到的數據總是不對,或者直接程序崩潰。
那如何去分析這個問題呢?
當結構體都確定下來,但對於結構體定義的變量爲什麼從表面看起來都是對的,但實際卻在使用中數據總是不對,甚至程序運行不對呢?
我第一個時間想到是的看C++上傳數據的內存分佈(C#中查看方式也是一樣的),下面簡說明如何在調試下查看變量的內存存儲的具體形式和數值。
調試模式下打開內存查看示圖:
如下圖所示:
從上圖可以看出在同學所說的在64位系統上是32個字節(按8字節對齊)。現在我們一個個來看內存與變量對應位置。
typedef struct _KERNEL_DATA // 在32位系統上
{
unsigned long PortNumber; // 佔4個字節
union
{
unsigned long LongData;
unsigned short ShortData;
unsigned char CharData;
}; //佔4個字節
unsigned long ulPhysicalAddr; // 佔4個字節
unsigned char* puchVirtualAddr;// 佔4個字節
unsigned long* __ptr64 pulVirtualAddr;// 佔8個字節
unsigned long ulNumberOfBytes;//佔8個字節,原本4個字節
}KERNEL_DATA, *PKERNEL_DATA;
現在我們看出原因了,發現unsigned long* __ptr64 pulVirtualAddr;// 佔8個字節,常理unsigned long* 是佔4個字節的。看來__ptr64起了指定佔字節數的作用。關於__ptr64的說明參見:https://docs.microsoft.com/zh-cn/previous-versions/aa985900(v=vs.110)?redirectedfrom=MSDN
文章中提及到
這個時候我們基本上發現了原因,分析並理解了問題,並解決了問題。
接下來我們把C#結構體偏移做下修改:
namespace WindowsFormsApplication3
{
[StructLayout(LayoutKind.Explicit, Size = 32)]
public struct _KERNEL_DATA // 在32位系統上
{
[FieldOffset(0)]
UInt32 PortNumber; //
[FieldOffset(4)]
UInt32 LongData;
[FieldOffset(4)]
UInt16 ShortData;
[FieldOffset(4)]
Byte CharData;
[FieldOffset(8)]
UInt32 ulPhysicalAddr; // 佔4個字節
[FieldOffset(12)]
IntPtr puchVirtualAddr;// 佔4個字節
[FieldOffset(16)]
UIntPtr pulVirtualAddr;// 佔8個字節
[FieldOffset(24)]
UInt32 ulNumberOfBytes;//佔4個字節
};
}
這裏就對齊簡單說明下:
這個時候我們再看下內存分佈:
內存的對齊是根據其自身的所佔字節數去取地址,比方unsigned long* __ptr64 pulVirtualAddr;// 佔8個字節,那它地址位必須是8*N(N爲非負整數,比如0,16,24,32,...)。所有上圖unsigned long ulNumberOfBytes;起始地址爲【16,20),由於它下一個成員變量爲8個字節,所地需在其後補齊4個字節。
這裏如果覺得unsigned long ulNumberOfBytes;本身4個字節,卻使用了8個字節。可以採用結構體字節對齊,指定對齊的字節數。
採用#pragma pack(4),這裏就不展開說了。
到這裏基本都解釋清楚了,這個在說明一點:
__ptr32,__ptr64 就是C++類型,和int類型一樣,是個關鍵字。
basetsd.h 中有其相關的信息
#if !defined(_MAC) && (defined(_M_MRX000) || defined(_M_AMD64) || defined(_M_IA64)) && (_MSC_VER >= 1100) && !(defined(MIDL_PASS) || defined(RC_INVOKED))
#define POINTER_64 __ptr64
typedef unsigned __int64 POINTER_64_INT;
#if defined(_WIN64)
#define POINTER_32 __ptr32
#else
#define POINTER_32
#endif
#else
#if defined(_MAC) && defined(_MAC_INT_64)
#define POINTER_64 __ptr64
typedef unsigned __int64 POINTER_64_INT;
#else
#if (_MSC_VER >= 1300) && !(defined(MIDL_PASS) || defined(RC_INVOKED))
#define POINTER_64 __ptr64
#else
#define POINTER_64
#endif
typedef unsigned long POINTER_64_INT;
#endif
#define POINTER_32
#endif
有問題並不可怕,需要我們靜下心來一一去分析原因,尋根知底。
友情提示:
後續的幾篇文章對其應用作了更新和封裝,可以在專欄裏查看。
如果感覺對你有幫助,請支持肯定下博主的努力!