善意提醒

如果您打开本站很慢,布局排版混乱,并且看不到图片,那么可能是因为您还没有掌握用科学的方法上网的本领。
显示标签为“BCB”的博文。显示所有博文
显示标签为“BCB”的博文。显示所有博文

2014-05-07

BCB5 在 Win7 x64 上启动时报错「1 transfer item(s) contain syntax errors」

由于 WinXP 已经被微软官方宣告服务终止,最近把工作环境升级到了 Win7,并且安装的是 x64 版本。装了之后发现,BCB5 启动的时候会弹出一个报错对话框,里面的信息很奇怪:
1 transfer item(s) contain syntax errors
点「确定」关闭对话框之后,BCB5 使用起来也没有什么问题。但每次启动都会弹框,很讨厌。那么,这是什么情况呢?

一般来讲,Win7 与 WinXP 之间,出现类似兼容性问题的原因大致有:
  • 管理员权限问题
  • 注册表键值问题
  • 系统目录问题
  • DEP 问题
在 x64 系统上,目录问题尤其突出。Program Files 现在还有个 Program Files (x86)。System32 那边也有个 SysWOW64。后者一般跟应用软件关系还不太大,但前者常常会导致很多问题。我就见过有的软件安装包都会运行出现问题。

这次的情况其实也类似。照例,先上国外网站的链接。
http://codeverge.com/embarcadero.cppbuilder.install/at-start-up-1-transfer-item-s/1096695
最后那个回复,把操作步骤写得很详尽。做 C/C++ 开发的,英语阅读一般还是不会有问题,我就不翻译了。

总之呢,这个问题就是因为 Program Files (x86) 直接引起的。另一个回复里面说把 BCB5 卸载后重新安装在 Program Files 下也能解决。当然,有问题的地方其实只有一处,所以完全没必要如此大动干戈。

2013-01-16

关于 BCB 中 Package 的两点注意事项

BCB 通过 Package 实现了自定义控件的能力,用起来的确很方便。很容易地就可以扩展 IDE 的能力,设计出更为强大的软件。但在实际使用中也发现有两个值得注意的地方。

1. Runtime packages

如果打上了 Build with runtime packages 复选框的勾,那么 BCB 在 Link 的时候将会把 Runtime packages 中列出的 Package 以动态链接的方式 Link 到 Project 的输出文件(EXE 或 DLL 等)中。在发行的时候,必须带上这些 Package 对应的 BPL 文件,EXE(或 DLL)才能正常工作。

Runtime packages 是一个分号分隔的 Package 名列表。没有在列表中的 Package,会静态链接到 Project 输出文件。如果不选择 Build with runtime packages,则所有的 Package 都会静态链接到 Project 输出文件。这大致相当于在 VC 中选择 Use MFC in a Static Library。

2. Package 设置的归属

一直以为 BCB 的 Components->Install Packages 里面的内容是个全局的设置。后来才发现原来是属于 Project 里面的 Options 之一。准确地说,是其中的 Runtime packages 部分的设置属于 Project。

也就是说,如果在某一个 Project 中设置了一个 Runtime packages 列表。那么这个设置只会应用在这个 Project 的编译结果中。对于别的 Project,依然保有并使用各自的设置。

2012-10-25

头一次埋彩蛋哩

在改某个 Bug 的时候,需要通过已知的 HWND 来判断该子窗口是不是一个 DateTimePicker 控件。办法很简单,只要给这个子窗口发一个 DateTimePicker 控件的特定消息,如果返回值正确,那么就认为猜对了。

但是在选择这个「特定消息」的时候,我犯了难。DateTimePicker 支持的消息本来就不多。为了避免对正在编辑的数据带来扰动,只能选择 GETXXX 之类的消息。并且这个消息还得有定义良好的返回值,用于鉴别消息是不是发对了人。

一开始我选的是 DTM_GETSYSTEMTIME,这货能返回当前编辑的时间。但我发现控件一旦响应了这个消息,键盘输入就像按过回车一样被 COMMIT 过了,导致年份之类的多位数字根本输不完整。回头看了看 DTM_GETMCCOLOR,又无法确定返回值是不是能够鉴别出来。有个 DTM_GETDATETIMEPICKERINFO 倒是看上去挺好,可惜只支持 VISTA 往上。最后我选择了 DTM_GETRANGE。

DTM_GETRANGE 可以返回设计者在 IDE 上给 DateTimePicker 控件定下的最大/最小值。所以我只要把最小值设一个特定的日子,然后看看返回值正不正确就 OK。实验下来对输入也没有扰动,是个很好的选择。那么,日子选哪一天呢?

……我敲入了 1989-06-04

2012-10-19

一个关于浮点运算的坑

这两天遇到一个很奇怪的问题,后来通过排除法终于把故障点缩小到一句代码,但还是很奇怪:

某个同事以前用 VC 写了一个 DLL 用于提供某类通用计算,相当于一个计算模块,由我这边写 BCB 程序来调用。不过在这次的问题中发现,一旦线程调用过 pow() 这个 C 库函数来计算过一个非整数指数的幂,那么这个计算模块接下来同样的参数再次用就会得出不一样的结果。调用过 pow() 之前是一种结果,调用之后是另一种结果,现象相当稳定。

诡异就诡异在 pow() 的返回值既没有错,也没有被采用过。比如仅仅一句:
pow(2.0, 3.1);
函数返回值根本没有用来干过任何事情,相当于直接丢掉了。那么按道理来说这行代码应该不会对之前或之后的代码造成任何影响。但它确确实实影响了。
然而指数如果是整数,就不会,比如:
pow(2.0, 3.0);

那个写 DLL 的同事对此也是一头雾水。怀疑点一度被放在 VC / BCB 身上,因为它们的 CRT 不一样。但因为正好有一个测试工具,稍加改造便可以针对计算过程输出详细的结果报表,因此用来比较了一下,发现了问题:出问题的地方,有两个本来应该用来比较的 double 型输入数据正好是相等的。在 pow() 调用之前,它们的确被判断为相等。但调用 pow() 之后,计算结果显示它们被判断为不相等了。

这种事情对于常写浮点运算相关代码的程序员而言是很容易引起警惕的。浮点数不能直接用 == 之类来比较,必须去判断两数相减的绝对值是否小于某个精度。所以将这个测试结果提交给写 DLL 的同事之后,很快就定位并解决了问题。
但这里我更关注的是以下几个问题:

  1. pow(2.0, 3.0) 和 pow(2.0, 3.1) 的不同,使我相信 CRT 肯定对前者作了优化。这种事情,想得通,但后果可能是个坑。
  2. 很明显,CRT 在 pow() 被调用之后,处理浮点数时的行为模式改变了。通过观察 _statusfp() 的返回值,我发现调用前为 0,调用后变成了 0x20。但这个 0x20 是一个 Undocumented 的东西,哪怕是最新的 MSDN 上也找不到。并且我通过 _fpreset() 将状态字变回了 0,但计算模块仍然会出错,说明应该还有别的东西也被改了。这个坑是不是 VC 和 BCB 联合挖的,目前还不知道。
  3. 如果不知道有这个坑,就可能导致一些大麻烦。在特定的代码逻辑中,这个问题很难通过黑盒测试发现。它可能在很长一段时间内都能工作得很好,直到某一次有个用户算了一个指数带小数点的幂,然后……一切就不一样了。这简直就是逻辑炸弹嘛!让我想起了《深渊上的火》里面可怜的蓝荚和绿茎。
  4. MSDN 真心不是完全靠得住的。

在本文最后,还要对提供过重要帮助的 Libin Yan 表示感谢!并感谢所有关注和评论过这个 PO 的 G+ 网友!

2011-11-29

掌握了更好的 BCB 程序调试手段

Borland C++ Builder 5 编译的程序没有 PDB,因此要通过 Dump 文件来分析故障原因就太坑爹了。没有符号文件的话,汇编看起来相当痛苦。
上次查过,有个办法是使用 map2dbg,把 map 文件转换成 dbg 文件,这样 windbg 也能够加载符号文件用于调试。不过 map 里面只有函数名称,没有代码的行号,所以调试起来还是不是很方便。如果断点是在一个长长的函数里面,而且没有嵌套调用什么函数,那么对于汇编功底不深的我也是一样的郁闷。

今天下了点决心要解决这个问题,否则调试效率太低了。
感谢万能的 Google,这次我知道了有个开源项目叫 tds2dbg。用法和 map2dbg 类似,生成的 dbg 文件的确可用,而且在正确的行号上指出了我遇到的问题。这样,Crash 就不再是一个问题了。

值得注意的是:BCB 编译选项中,必须打开 Compiler->Debugging->Line Number Information,以及 Linker 里面的 Create debug information。否则生成的 dbg 文件没法用。

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,或者通过操作系统级别管理的对象来传递数据。总之别让两种编译器编译出来的东西去分别猜对方是怎么管理内存的就行。

2011-10-21

VC 和 BCB 那点事——虚函数的顺序问题

因为工作需要,编写了一个 C++ 类,供一个 VC 下的 DLL 使用。用法类似回调对象,将 C++ 对象的指针传给 DLL 导出的函数。类里面要被用到的函数全是虚函数,父类是个彻底的抽象类,由 DLL 的开发人员提供 .h 头文件。VC 和 BCB 在虚函数表的实现上是一致的,因此可以跨模块调用。

大部分虚函数的调用测试都很顺利,但在一个函数上遇到了麻烦。不,应该说是两个。在测试过程中发现,当 DLL 想要调用我提供的 C++ 类的 FunA 函数时,总是错误地调用到了 FunB 函数。反过来也一样,结果就好像是 DLL 把 A 函数和 B 函数搞反了。
class A
{
    ……
    virtual FunA() = 0;
    virtual FunB() = 0;
    ……
};
既然是按照虚函数表来调用,那就跟函数名无关。这一点很快就得到了证实。现在唯一剩下的就是顺序问题。根据资料,虚函数表里面的顺序是根据函数的声明顺序来排布的,也就是说 .h 头文件决定了顺序。调换了一下顺序,果然就正常了,可为什么会反呢?
其实不能只看这么一点儿代码,这个问题和上下文有关:
class A
{
    ……
    virtual FunB(int J) = 0;
    virtual FunA() = 0;
    virtual FunB() = 0;
    ……
};
最后是 CSDN 上一篇博文揭示了这个问题:VC 会把重载函数给排在一起,不管中间有没有插队者。参见:http://blog.csdn.net/doudouhuy/article/details/4348531
也就是说,最终的顺序实际上是:
class A
{
    ……
    virtual FunB(int J) = 0;
    virtual FunB() = 0;
    virtual FunA() = 0;
    ……
};
而 BCB 大概不会做这个改动,因此调用到的函数就和预期的不一样了。

最后说一下。为了避免遇到类似问题,建议在进行跨模块的虚函数调用的时候,彻底避开重载函数出现的情况。把所有函数的名称都声明得不同,就不会轮到编译器来干扰了。