求助: VC++ 中 DLL编译时结构体成员对齐的问题

求助: VC++ 中 DLL编译时结构体成员对齐的问题

现在我们有一个需求, 要求我们用VC++编写一个DLL, 叫GT2MQ.dll, 输出一些函数供一种叫GRAPHTALK(GT)的语言使用, 在这些函数的实现里调用了另一个其他厂商提供的中件间产品的DLL函数(adapter.dll).

GT对DLL函数的调用是有要求的, 其中最重要的是必须在编译时指定结构成员按1字节对齐, 否则GT在调用这个DLL的函数时会出现非法操作.

在GT2MQ里的方法调用adapter.dll的一个方法时, 需要传入一个结构体的指针做为参数, 这个结构体的定定义如下:
/*消息结构*/
typedef struct BusMessage
{
 char messageId[50];
 char correlId[50];
 char appMessageId[30];
 char version[30];
 char bodyType[20];
 char bodyCategory[20];
 char timeStampCreated[30];
 char timeStampExpired[30];
 char srcLogicalId[30];
 char dstLogicalId[30];
 char authenticationId[30];
 char commandMode[30];
 char txnScope[30];
 char *standardBody;
 char *body;
 char priority[20];
 char persistence[10];
 char expiry[10];
 char traceLevel[10];
 char publish[10];
 char backup[10];
 char messageName[384];
 char encoding[30];
 char msgCharset[30];

 long msglen;/*发送消息的长度*/
 long rcvlen;/*接收消息需要的内存*/
 
}BusMessage, *ptrBusMessage;

当我们的DLL函数新建了这个结构体, 清零, 并填写了必要的字段后, 把结构的指针做为参数调用adapter.dll中的一个函数, 然而这时后发现无论如何, 这个方法调用都不成功. 返回的错误是说msglen的字段没有填值. 但那个字段明明是填入了值的.

由于adapter.dll没有源码, 只好通过汇编代码来debug, 才发现这个结构体在我们的dll里和在adapter.dll里对字段寻址时地址不一样, 我们的DLL对msglen的寻址是msg+932, 而adapter.dll对同一个字段的寻址是msg+936!

通过手工计算, 确定msglen成员的偏移值应取932, 但是如是果同一个结构体定义放在一个新建的win32工程中时, 查看汇编代码, 就发现是按936寻址的.

后来再进一步查找, 发现问题出在下面两个字段的定址上:standardBody和msglen, 这两个字段在定址时都向后移动了两个字节, 造成了一共四个字节的偏移, 这使得成员访问数据时出错.

我们怀疑是由于我们的GT2MQ.dll项目里的那个结构成员对齐选项设为"1字节对齐"(/Zp1)造成的, 但是无论我们把这个参数设为多少, 都不会消除这个问题:我们的DLL始终不会为那两个字段增加两个字节的偏移. 何况我们项目的要求是GT2MQ.dll必须指定/Zp1选项, 即使这样解决了也无法满足要求.

我们目前的解决办法是: 在结构体里在standardBody和msglen之前各增加了一个字段: char Reserved1[2]和char Reserved2[2]来强迫我们的DLL为之后的成员定址时加一. 但是感觉这样做并不是很妥当, 必竟我们修改了厂商提供的头文件, 而同一头文件在其他项目里是不会出现问题的.

诚求更好的解决方案, 或是知道如何调整项目设置的高手请赐教!

posted @ 2005-09-05 10:02 HAL9000 阅读(1241) 评论(5)  编辑 收藏 所属分类: 技术

  回复  引用  查看    
#1楼 2005-09-05 10:08 | liujun      
我以前有用过
#pragma pack(...)

不知道在你这里管用么
  回复  引用  查看    
#2楼 2005-09-05 10:37 | dudu      
不要在首页求助!!
  回复  引用  查看    
#3楼 [楼主]2005-09-05 10:48 | HAL9000      
唔, 不好意思. 我是想这虽然是个求助贴, 但是总算也有分析过程和解决方案, 也算得上是技术贴, 就放在这里了.

  回复  引用  查看    
#4楼 [楼主]2005-09-05 11:25 | HAL9000      
已经初步搞好了, 在结构之前加了个:
#ifdef WIN32
#pragma pack(push, 4)
#endif

在之后加了个:
#ifdef WIN32
#pragma pack(pop)
#endif

一切正常了, 但是下一个问题是: 为何我设定项目的参数:/Zp1到/Zp16都没有对项目的生成产生影响呢? 在MSDN里对ZP参数的说明是:

“结构成员对齐”(/Zpn) 选项控制如何将结构成员封装到内存中,并为模块中的所有结构指定相同的封装方式。当指定此选项时,第一个结构成员后的每个成员将在成员类型大小或 n 字节边界(其中 n 为 1、2、4、8 或 16)两者中较小的一个处存储。

较小的一个? 我怎么觉得是较大的一个? liujun老兄, 能否给在下解释一下?
  回复  引用  查看    
#5楼 [楼主]2005-09-06 10:24 | HAL9000      
我自已搞明白了MSDN里的解释. 下面转一贴.

来自csdn的帖子:
主  题: 探讨:内存对齐
作  者: typedef_chen ((名未定)(我要骗人))
等  级:
信 誉 值: 100
所属论坛: C/C++ C++ 语言
问题点数: 50
回复次数: 1
发表时间: 2005-04-02 22:53:27


朋友帖了如下一段代码:
  #pragma pack(4)
  class TestB
  {
  public:
    int aa;
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestB);
  这里nSize结果为12,在预料之中。

  现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;
    short b;
    char c;
  };
  int nSize = sizeof(TestC);
  按照正常的填充方式nSize的结果应该是8,为什么结果显示nSize为6呢?

事实上,很多人对#pragma pack的理解是错误的。
#pragma pack规定的对齐长度,实际使用的规则是:
结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma
pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
也就是说,当#pragma
pack的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。
而结构整体的对齐,则按照结构体中最大的数据成员 和 #pragma
pack指定值 之间,较小的那个进行。

具体解释
#pragma pack(4)
  class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
这个类实际占据的内存空间是9字节
类之间的对齐,是按照类内部最大的成员的长度,和#pragma pack规定的值之中较小的一个对齐的。
所以这个例子中,类之间对齐的长度是min(sizeof(int),4),也就是4。
9按照4字节圆整的结果是12,所以sizeof(TestB)是12。


如果
#pragma pack(2)
class TestB
  {
  public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
//可以看出,上面的位置完全没有变化,只是类之间改为按2字节对齐,9按2圆整的结果是10。
//所以 sizeof(TestB)是10。

最后看原贴:
现在去掉第一个成员变量为如下代码:
  #pragma pack(4)
  class TestC
  {
  public:
    char a;//第一个成员,放在[0]偏移的位置,
    short b;//第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
    char c;//第三个,自身长为1,放在[4]的位置。
  };
//整个类的大小是5字节,按照min(sizeof(short),4)字节对齐,也就是2字节对齐,结果是6
//所以sizeof(TestC)是6。

感谢 Michael 提出疑问,在此补充:

当数据定义中出现__declspec( align())时,指定类型的对齐长度还要用自身长度和这里指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然后取其中较大的。

可以这样理解, __declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
__declspec( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:
在__declspec( align() )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec( align() )。
当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,取其中较大的作为整个结构的对齐长度。
特别的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作用。


标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2005-09-08 08:51 编辑过


相关链接: