最近,写了一个VB应用程序,这个APP主要是用来演示三个C++写的视频处理动态链接库的使用方式。第一次写VB的代码,确实遇到了很多问题,现在回想一下,真的是很多东西走了或多或少的弯路,现在把这些东西总结并记录下来,期望以后写兼容VB的动态库,可以注意接口的定义,并不是所有的接口定义方式,都方便VB使用,再者,就是在VB中使用的一些知识积累。
C++写的DLL接口定义需注意的事项
1. 接口中自定义数据类型不能ByVal传递
这次就遇到了这个问题,而且花了很长的时间去验证这个问题,在Dll中有一个函数,如下:
Struct Color
{
Long lBrightness;
Long lcontrast;
Long lhue;
Long lsaturation;
}Color;
BOOL SetColor(Color stuColor);
在VB下需调用此函数,则定义相应的自定义数据和函数:
Type Color
lBrightness As Long
lcontrast As Long
lhue As Long
lsaturation As Long
End Type
Declare Function SetColor lib”A.dll”(ByVal StuColor As Color)As Long
这样看似可以将自定义数据结构中的四个变量都传送下去,但是这在编译时是通过不了的。
最初的时候是期望不用修改dll的接口,只是变换VB的代码将结构中四个数据都传送下去,试了以下方式:
a. 采用ByRef的方式,这样编译可以通过了,但是每次传下去的都是结构的首地址,并没有把数据传下去;
b. 传送结构的地址(Varptr(color)),同方法一,不行;
c. 传送结构的首元素(Color.lBrightness),期望dll可以根据第一个参数的地址,自动查找其他的数据,可惜,并不如愿;
d. …
试了很多方法,结果都是不行,我就纳闷了,怎么就不行呢,于是开始在网上查找资料,看别人都是怎么办的,果然有很多文章介绍这种自定义数据类型的使用,但都是用ByRef的方式传送,到后来,经过头允许把dll的接口改成BOOL SetColor(Color * stuColor);才把这个问题给解决了。对于自定义数据类型,VB不提供直接传值的方式,所以遇到类似的问题,dll接口定义的时候,就应该用指针。
2. 接口中函数指针的传递
在注册回调函数时,需要将函数指针通过接口传送下去,在dll中有一个回调函数注册函数设计成,将三个回调函数指针放到一个结构体中,在用在指针传递,如下;
Struct FucntionPtr
{
BeginFunc fptFunctionPtr1;
ProcFunc fptFunctionPtr2;
EndFunc fptFunctionPtr3;
}CallbackFunc;
Bool SetCallBackFunction(CallbackFunc * pFunction);
在VB中就需要定义一个含有三个函数指针的自定义数据结构:
Type CallbackFunc
fptFunctionPtr1 As Long
fptFunctionPtr2 As Long
fptFunctionPtr3 As Long
End Type
Declare function SetCallbackFunction Lib”B.dll”(ByRef pCallbackFunc As CallbackFunc)As long
Dim pCallbackFunc As Calllbackfunction
pCallbackFunc. fptFunctionPtr1=定义在标准模块中的回调函数的指针;
VB中有一个函数AddressOf可获得函数的指针,但是这个函数,获取的指针只能直接作为函数参数,不能直接获取,赋给一个变量,如:
pCallbackFunc. fptFunctionPtr1=AddressOf BeginFunc;
是编译不通过的;
在网上搜了一篇资料介绍如何在VB中获取函数指针,但也非常复杂,所以将Dll中的注册函数的参数改为直接传递函数指针,而不是指针结构的指针,在以后编写兼容VB的动态库的时候也需要注意这一点。
3. VB中使用回调函数
因为回调函数的问题耽误了我很多的时间,大部分时间都在查找Debug下程序可以运行,但是EXE运行老是会报错,而且Debug下运行特别不稳定,调试呀调试,老是不行,有人说VB下用回调函数就是有问题,但是用定时器做了个试验,在定时器的回调函数中调用系统API,动态库的API都没有问题,但是在自己写的回调函数中调用这些函数都有问题。后来问我们的老大,他说是线程切换时出的问题,在微软的Support上说,VB的Callback函数中调用的API函数,在EXE运行时会出现“在 0x660bd3b1 指令引用 0x0000009c内存。 无法写入内存”错误,发生错误的原因是:不创建 Visual Basic 的一个线程调用回调函数。这些 API 函数使用一个忙线程模型,但 Visual Basic 仅支持 Apartment-模型线程处理。 此外,必须小心其他相关与哪些代码在回调函数可以执行。 在回调函数内以下的使用可能导致意外结果:
• |
file I/O。 |
• |
错误处理。 |
• |
固定大小数组。 |
• |
设置语句。 |
• |
COM 方法调用的返回 HRESULT (如任何 Visual Basic ActiveX 对象)。 |
• |
声明调用。 |
• |
全局对象 (如 Application 对象。 |
• |
Visual Basic run-time files of most。 |
于是,就将dll回送数据的方式改为消息的方式,就OK了,很稳定。
不知道在VB中创建一个线程,并在该线程中调用回调函数,EXE能不能跑,如果在回调函数中使用了上述的不稳定因素,程序会不会真的不稳定。
写VB兼容的dll一定要考虑callback的问题。
4. 接口中字符串的传递
Dll中很多函数都需要传递字符串,传递字符串不能用传址方式,如果用传址方式的话那么传递的是指向字符串指针的指针,API将不能返回数据,并且造成访问数据出错,所以需要用ByVal传递字符串指针。
在自定义结构体中的string的传递,声明为String,
5. VB中文件的处理
一开始不知道有Commondialog这个好用的DD来实现Openfile功能,就自己写了一个,那三个文件操作控件虽然好用,但是,比起系统提供的对话框,自己写的就难看了好多,后来就全改成commondialog了,浪费我时间,要是早点知道有这么个东东就好咯。
6. 还有很多细节,如果没有注意也会很浪费调试时间,VB中,默认的函数参数的传递方式是ByRel,如果什么都不写是传址,而不是传值,结果编程时忽略了这个问题,害得我以为Dll有问题,调了大半天的dll,结果偶然才发现,自己没写ByVal,晕死了!