2011-10-24

VC和BCB那点事——DLL导出函数的结构体参数

还是那个项目,VC写一个DLL,导出若干C函数,BCB来调用。实际情况比这个复杂,不过与我这次要讲的这个问题无关,所以在此省略了。

在解决了上次遇到的虚函数表顺序问题后,继续往下调试,又碰到了一个很怪的问题。有一个导出函数,一调用便崩溃。进去一看吧,崩溃点的代码别的导出函数也在用,没什么不对。虽然改改代码,可以做到不抛出异常,但该导出函数返回的值又不正常。反正它就是干不了想干的活。
该函数本身很简单,因此怀疑不是内部逻辑导致的问题。通过对比,发现该函数与其它工作正常的导出函数有一个明显的区别:它返回了一个自定义的结构体作为返回值。函数的声明大概是这样子的:
LONDATEEX __stdcall GetDataDate( USHORT sMarket);
由于别的可能性被一一排除,因此焦点慢慢移到这个情况上来。查了查网上的信息,发现有一篇文章(被墙,由此可见GFW的反动性质)提到了跨模块调用时的内存管理问题,并据此总结了几条规则。其中一条,便是不要在跨模块函数调用中使用类或结构体作为参数或返回值类型。

该文章在这一点上略有阐述:因为内存管理模式不一样,所以类和结构体这种可能会进行内存分配、回收的数据类型,一旦用作跨模块调用的参数或返回值,就会导致不确定的后果。我认为这个解释是合理的。我之前也已经想到了这一点,不过接下来还有。
该文章还提出了改进建议——如果一定要作为参数或返回值传递,那么应该采用所谓的纯结构体,即只包含了简单数据类型的结构体,构造、析构时不涉及内存分配和释放。该文认为,这种纯结构体的数据类型,可以用于DLL导出函数的参数或返回值。碰巧了,我这次这个结构体,就是个纯结构体。那么按照这篇文章的理论,不应该有问题才对。

不弄明白这个问题,始终是不甘心,于是我动手做实验了。先用VC写了一个很简单的DLL,声明了一个最简单的结构体,包含两个int类型的变量。然后用BCB来调用。并且,在做这个实验的时候,我特地注意避免了字节对齐不一致的问题。
嗯,结果是什么呢?调用完成了,可无论传进去的参数值还是传出来的返回值,都不对。同样的函数声明方式,都用了__stdcall的另一个只包含简单数据类型作为参数和返回值的DLL导出函数,工作正常。

两相对照,显然,就算是所谓纯结构体,也不见得能够放心地用于跨模块的函数调用。至少,在VC和BCB5之间,是会有问题的。别人说的,不一定就是对的,至少不一定全对。尽信书不如无书。

解决方案也很简单,指针是一个简单数据类型,把结构体的指针作为参数传进去就行了。比如:
BOOL __stdcall GetDataDate( USHORT sMarket, LONDATEEX* pDate);
还有别的办法也行,比如利用COM,或者通过操作系统级别管理的对象来传递数据。总之别让两种编译器编译出来的东西去分别猜对方是怎么管理内存的就行。

没有评论:

发表评论