2008-04-05

工作小记

花了两天时间,把单煤层储量计算程序升级成了多地层体积计算程序,主要是界面功夫,核心库没有变化。现在的界面比上次的要复杂几倍,但是设计得还好,没有陷入RAD的事件处理陷阱中。原先的C++核心库不是为多层设计的,在不修改API的情况下用于现在的程序虽然能工作,但有些冗余计算。如果要去修改库的话,真正的工作量还是在VB6这边,我的C++代码设计得很有弹性,可一旦修改了函数原型,又要在VB6中写一大堆代码,把数据搬来搬去,这种事做起来实在无聊

这次又体会到VB6的几个不顺手的特性:

1. 不能把变长数组声明为类模块的公开成员

不能作为类模块的公开成员的东西还有很多,比如字符串。这很扯,那么多限制,VB6的类模块还有多大用处?本来想用它的封装功能,后来还是选择了struct

2. 没有指针

我要交换两个结构的数据,其中的大头是动态数组。也许借助适当的SAFEARRAY的WIN32 API可以交换两个数组变量的“引用”(而非数组数据)?我不知道,我也没有用索引数组模拟指针的效果,现在就是直接交换数组数据。这么做虽然不至于影响用户界面的响应能力,但作为程序员,还是觉得这很罪恶,唯一的托辞是,给我多少钱,我就办多少事儿。

3. 语句表达能力弱

有时候在C里一行代码可以完成的事,在VB6里要用五六行。

这里的原因有一部分是语法层面上的,比如定义变量时不能初始化,没有+=运算符(重要的不是可以少写一遍变量名,而是那句代码可以作为表达式而非语句使用)等。

还有一部分原因是……语义?比如不对条件表达式进行短路求值。在C中,这么写是很普遍的:

if (index < 0 || list[index]...) ...

在VB6中,却得:

if index < 0 then
...
elseif list[index]... then
...
end if

4. 不直观的Combobox.Change/Click事件

无论是鼠标点击还是按上下方向键切换当前项目,都会触发Click事件——而不是Change事件。

Change事件的触发条件是:通过编程方式或界面输入设置其Text属性,修改ListIndex属性。

相比C#、C++,VB6简直就是个半成品,可是我却被限制只能用它,理想 & 现实?

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

下午安兴冲冲地打电话问我:你知不知道那个公司发给你的工资测算表中,还有一页是他们的工资等级表?

我没注意,只看到他们给我定的等级是试用C4转正C3。我以为C是部门类型,那样的话我进去之后,还有C2、C1两个等级的升级余地,两个太少了,也许有C0?

这个女人,我还没上班呢,就琢磨我的工资表了……听她一说才知道,我的位置是:

////
A1
A2
A3
A4
A5
B1
B2
B3
B4
B5
C1
C2
C3    <---- i'll be here...   :(
C4   
C5
D1
D2
D3
D4
D5
////

2008-04-02

VB6 ListBox有病

VB6 ListBox的第一个项目很特殊。 在以设计时或运行时的任何方式向ListBox添加项目之前,它的ListCount不出意料地为0,但它的第一个项目是存在的,值为空,并且可读写。更诡异的是,一旦向List(0)写入值,ListCount就为1——不需要AddItem

这是一个实验,在窗体上放一个ListBox在Form_load中:

Debug.Print List1.ListCount
Debug.Print List1.List(0) = Empty
List1.List(0) = "fuck"
Debug.Print List1.ListCount
Debug.Print List1.List(0)

输出

0 
True
1
fuck

好在,如果把这看成ListBox控件的一个BUG,那么这个BUG只在本身就有问题的代码中(访问尚未添加的项目)才会显现出来。

2008-03-31

VB6的Null

VB6的Null实在难以理解,它一般不能用在表达式中,但我确实知道有两个另外:

Dim myVar
' 情形1. 赋值
myVar = Null
' 情形2. 判断
myVar = IsNull(myVar) ' True

相当郁闷的是,if myVar = Null 是错误的,说“无效使用Null”——这到底算什么?!

但是如果把它跟C语言的void做个类比,心里能舒坦些:C的void是一个“伪”类型,只用来刻画没有返回值的函数,你不能声明一个类型为void的数据,void*之类的扩展类型除外。VB6Null是一个“伪”值,用来刻画值为“无”的变量,确切地说,用来刻画从数据库中读来的空字段。

绕人的是,变量的值怎么可能是绝对的“无”?有内存就有数据!——除非,值为Null的变量其实没有占用内存,只是一个符号?就是说,Dim myVar这句话只是引入了一个名称,而没有在堆栈上分配内存,也即相当于C语言的变量声明(而非定义)?可就算这样,也不足以妨碍if myVar = Null的合法性啊!写这样的代码是有歧义还是会死人啊!!

P.S 到现在为止,只在与数据库有关的编程中碰到这个问题,MS Excel或MS Access的空字段读到VB6中都是Null. 所以,仅仅知道有个IsNull函数可以用就够了,VB6这个过气的语言也不值得仔细追究了。

今天碰到的一个问题是:如何判断一个动态数组是否已经分配(ReDim)。

对于声明后尚未ReDim的数组,使用ubound函数会引发错误。我想知道除了On Error语句外还有没有更好的办法来检测,在CSDN论坛上找到yinweihong给出的解答:使用oleaut32.dll库的SafeArrayGetDim函数,它返回安全数组的维数。

当前问题是勉强解决了,但因这个问题发现了一大片未知的领域:VB与COM的关系。之前大致了解VB的对象(包括内置对象和类模块)实际上都是COM对象,现在认识到,它们的关系比我想象得要密切得多。

这就是VB,我不把它当回事,只是随便用用时,觉得它连简单都算不上,说它“简易”更合适些。比如经常发现它的语言设施缺胳膊少腿,再比如它的文档看起来远没有C#的周密严谨。而当我稍微留意一些问题时,发现它“隐藏得极深”。对于C#,我敢说其语言本身我是精通了,对.Net Framework也掌握得很好;对C++,我只能算熟悉(持续学习中,基本没有实践,所以熟悉但不熟练),但至少了解它的难度;而对于VB,我则完全没有估计出深浅。就这个意义来说,我其实是个VB门外汉。

刚才说问题解决得勉强,是因为我还没有完全理解SafeArrayGetDim函数的结果。现在可以肯定的是,对于未定义的数组,它返回0,但对于ReDim后的数组,它的返回值匪夷所思,是类似12048,35232这样的数字。我一开始怀疑这个现象会不会源自VB与C对整型数据的解释不同,因为VB中没有无符号整型。可当我看到同一条件不同次运行会得到不同的值,傻眼了。

时间有限,暂时不打算纠缠这个问题,有机会的话会看看AdamBear写的《vb真是想不到》系列。

此篇文章为《VB6传递数组参数到DLL过程》的继续讨论,原文章地址为:http://pzy84.blogbus.com/logs/10396097.html

1. 可以使用 __declspec(dllexport) 关键字导出函数定义。这比使用.def文件方便,但输出的函数名称是被修饰过的,可读性较差。

2. 使用 __stdcall 调用约定。

3. extern "C" 不是必须的。

4. 对于 const char* 参数,可以在 VB6 中声明为 byval string。如果要传入空串,使用 VB6 的 Empty 关键字。

5. 对于其它类型的数组参数,参考《VB6传递数组参数到DLL过程》

6. 按值传递时,VB6 的 Integer(16位) 与 C++ 的 int(32位) 似乎不兼容。在一次测试中,我把 C++ 的 int 参数在 VB6 中声明为 byval Integer,结果 VB6 传出的值到 C++ 中变成了一个莫名其妙(很大)的整数。可能与它们对数据的解释方式不同,所以即使是值参数,声明时也要保证尺寸一致。

======= 2008-1-22 更新 =========

* 在WIN32 DLL项目中,DEF文件似乎不起作用,但__declspec(dllexport)可以工作,在MFC DLL项目中,则都可以。

* __declspec(dllexport)可以导出函数、类、变量等。这些名称是修饰过的,看起来比较乱,但随同DLL一起生成的LIB同名文件包含了可用于重定位的信息。如果客户端代码是C++的,那么只要把LIB文件添加到工程中,就可以直接使用那些符号的原来的名称了。如果客户端代码是其它语言的,要么严格按修饰后的名称声明那些符号,要么在DLL中使用DEF文件导出符号。DEF和__declspec(dllexport)不冲突。

*指定LIB的方式有两个:
1、使用预处理指令 #pragma comment(lib, "libname.lib")
2、在IDE中设置,在VC8中,路径是“项目/属性/配置属性/链接器/输入/附加依赖项”

*需要注意库版本的一致,包括DEBUG/RELEASE,线程属性。

----------------- 2008-5-6 -----------------

VB6对导入dll的函数的声明区分大小写-_-