关于VC6-DLL与BCB5-EXE连结运行时静态库函数存在问题的研究
测试环境:
XP-Pro + VC6/SP5/SP6 + BCB5/UPD
问题现象:
以前在BCB5上开发了script.dll,并与BCB5同平台上开发的hce.exe运行很稳定,没有发现什么明显问题。
本月测试了STL/Boost相关内容,随便改造了script.dll,主要是更换本人自行开发容器模板为STL类型,由于仅更换了容器模板,算法本身未作什么大的更改,因此计算结果应与现有的数据完全一致。
经过简单的表达式和脚本测试,结果无误,并发布了一个免费的计算器程序,以进行更广泛的公开测试。
为了进行大数据量测试,本人将其与原BCB5开发的 hce.exe 动态连接运行(即:替换原BCB5开发的script.dll库),突然发现计算结果不甚一致,有一定的误差,虽然不大,但理论上不应有任何不同。
于是深入分析了一下script.dll与hce.exe的源代码,结果发现hce.exe中格式化数据的函数是sprintf,但sprintf函数在与两种开发工具产生的 script.dll 动态连接运行时,得到的结果不一样!!!!
比如:浮点数0.031025,在程序中需要保留5位小数,hce.exe+BCB版DLL得到的结果为0.03103,而hce.exe+VC6版得到的结果却为0.03102。很明显VC6版存在一定的问题。
为了搞清楚原因,先是google了“四舍五入”,发现有一种“四舍六入五成双”的算法,确实后者更为科学一些。但后来又发现VC6版DLL中对 0.001105 取5位小数得到的结果为 0.00111,很显然也不符合“四舍六入五成双”。
但一想VC6也不简单,怎么会犯这么低级的错误。于是动手写了个测试程序,用VC6-CL.exe编译运行
#include <cstdio>
void main(void)
{
printf("%.*f\n", 5, 0.031025);
}
得到结果为:0.03103,很正确! 可见VC6本身没有这个问题。再用BCB5编译,结果同样正确。说明二者独立运行时没有这样的问题。
于是开始怀疑,VC6版的DLL与BCB5版的EXE动态连结运行时,存在某种潜在的“库”连结问题!
这了验证这个猜测,于是考虑设计两个简单的示例程序,一个将在VC6下编译为script.dll文件,其中随便export一个函数即可。另一个将在BCB5下编译为.exe文件,并动态载入前面这个script.dll,并在载入点前后用相同的源代码向std::cout打印结果。
下面是DLL测试程序(script.cpp):
#include "windows.h"
#include
int WINAPI DllEntryPoint(HINSTANCE hinstDLL, unsigned long fdwReason, void * lpvReserved)
{
hinstDLL;fdwReason;lpvReserved;return TRUE;
}
#define DLLEXPORT __declspec( dllexport )
DLLEXPORT int __stdcall __dll_func()
{
char buf[64]; memset(buf,0,sizeof(buf));
sprintf(buf, "%.*f\n", 5, 0.031025);
return 0;
}
用VC6的命令行编译
cl.exe /LD script.cpp
得到 script.dll 文件
下面是EXE测试程序script_test.cpp:
#include <cstdio>
#include <iostream>
#include <cmath>
int main(void)
{
char buf[64]; memset(buf,0,sizeof(buf));
sprintf(buf, "%.*f\n", 5, 0.031025); //得到0.03103
std::cout
HINSTANCE _hInst=::LoadLibrary("script.dll");//只需要load一下dll,不需调用任何函数
sprintf(buf, "%.*f\n", 5, 0.031025); //得到0.03102 !!!!!
std::cout
::FreeLibrary(_hInst);
return 0;
}
用BCB5命令行编译
bcc32 script_test.cpp
得到 script_test.exe 文件
然后运行 script_test.exe ,得到结果却为:
0.03103
0.03102
测试结果与预料的结果完全一致! 可见现在已经找到了问题重现的方法。
为了进一步测试,将两个.cpp文件交换编译器再进行测试,即互换编译器,BCB5产生DLL,VC6产生EXE,然后运行,结果为:
0.03103
0.03103
是正确的!
再次交换顺序,即用同一种编译器分别生成两套.dll与.exe,再运行,均得到正确的结果。
由此可见,问题应在VC6产生的DLL身上,因为只有VC6产生的DLL与BCB5产生的EXE无法正确运行(而反之则行)。
现在开始从VC6制造DLL的选项入手,看看是否能通过编译选项解决这个问题。
首先在VC6的命令行上加上编译选项 /MT,静态连接,
cl.exe /LD /MT script.cpp
生成script.dll,用BCB5生成的.exe测试,结果还是不正确。
再换 /MD 选项,
cl.exe /LD /MD script.cpp
生成script.dll,用BCB5生成的.exe一测试,结果正确了!!!!!这正是期望看到的结果。
看来问题就出在这里了。根据以上复杂的组合过程可以得到以下结论:
1、VC6生成的.dll与BCB5生成的.exe动态连结运行时,可能会引起sprintf()等函数运行不正常。
2、鉴于以上问题,有可能 stdio.h 中描述的函数如sprintf()在该情况下均可能出问题。
3、为了避免这个问题,在VC6的dll编译选项中必须添加上 /MD 选项。
其实本人在这里还是存在一些疑问,比如:在loadlibrary()载入.dll后,怎么会直接影响到.exe中的静态库函数呢?
由于目前时间和精力水平有限,没有继续深入。目前只能记住存在这样的问题及相应的解决方法。在此作个记录,以免日后忘了。