先看下下面这个结构体的定义:
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
有问题并不可怕,需要我们静下心来一一去分析原因,寻根知底。
友情提示:
后续的几篇文章对其应用作了更新和封装,可以在专栏里查看。
如果感觉对你有帮助,请支持肯定下博主的努力!