精品主页 | 软件下载 | 系统下载 | 精品导航| 精彩图片 | 转帖工具 | 版主申请 | 影视下载
发新话题
打印

感悟 Visual Basic (十)

感悟 Visual Basic (十)

  


                  第十五话关于公用系统DLL的版本

用API写程序,经常少不了对Windows公用组件的调用,比如打开、保存对话框、选择文件夹对话框等,都是多数程序都使用的作为打开、保存文件时的标准对话框,因为这些组件是由Windows自己提供的,除了方便,还可以让自己的程序和其它Windows程序有相同的风格,方便用户使用。但是由于这些组件是由Windows提供,随着Windows的升级、系统软件的更新,这些组件可能也被更新了,就可能出现各台计算机上文件版本不同。一般地,软件针对低版本公用系统文件的设计,在高版本中使用是没问题,但是反过来就不一定,因为可能针对高版本的设计中使用到低版本中没有的功能,所以在设计程序的时候,根据文件的版本来做适当调整是很有需要的。这一话我要向你介绍一个获得公用组件版本的办法——使用DllGetVersion()。DllGetVersion并不是一个一定可以使用的API。微软在自己的新版本的公用系统DLL中使用了这个函数,但是早期版本的系统DLL中并没有,而这个函数并不是一个获得其它DLL的版本的函数,而是由DLL自己告诉你它的版本,所以你不可以把它当成一个一般API来看待。那么怎么使用它呢?为了得知要调用的DLL是否有这个函数,我找

到了这个方法:用GetProcAddress()。GetProcAddress()能访问一个DLL,返回一个指定的函数的入口地址。使用这个函数,还需要另外两个API:

LoadLibrary()和FreeLibrary()。以下是声明:

PrivateDeclareFunctionLoadLibraryLib"kernel32"Alias

"LoadLibraryA"(ByVallpLibFileNameAsString)AsLongPrivateDeclareFunctionFreeLibraryLib"kernel32"(ByValhLibModuleAsLong)AsLong

PrivateDeclareFunctionGetProcAddressLib"kernel32"(ByValhModuleAsLong,ByVallpProcNameAsString)AsLong

比如你要获得COMCTL32.DLL的版本(如果是其它系统DLL的话,也应该每个都声明),就要按下面这样声明(虽然你不知道是否有这个函数在该DLL中):

PrivateDeclareFunctionDllGetVersionLib"COMCTL32"(pdvi

AsDLLVERSIONINFO)AsLong

这个函数使用到了一个DLLVERSIONINFO的用户定义类型:

PrivateTypeDLLVERSIONINFO

cbSizeAsLong

dwMajorAsLong

dwMinorAsLong

dwBuildNumberAsLong

dwPlatformIDAsLong

EndType

DLLVERSIONINFO类型是由微软规定,无论你要访问哪个系统DLL,如果它有DllGetVersion这个函数,那么它的参数也是这个类型。

接下来就可以这么做:

DimhmodAsLong

DimlRAsLong

DimlptrDLLVersionAsLong

DimtDVIAsDLLVERSIONINFOhmod=LoadLibrary("comctl32.dll")If(hmod<>0)Then

lptrDLLVersion=GetProcAddress(hmod,"DllGetVersion")

If(lptrDLLVersion<>0)Then

tDVI.cbSize=Len(tDVI)

lR=DllGetVersion(tDVI)

If(lR=&H0)Then

Debug.Print"Major:"&tDVI.dwMajor,"Minor:"&

tDVI.dwMinor,"Build:"&tDVI.dwBuildNumber

EndIf

Else

Debug.Print"DllGetVersionFailed"

EndIf

FreeLibraryhmod

EndIf

上面这一段就是整个过程。首先用LoadLibrary把系统DLL装载进内存,如果成功就返回非0,然后对返回的句柄执行GetProcAddress,这时是在假设DLL中有DllGetVersion这个函数的情况下调用的,如果成功,GetProcAddress返回非0,如果失败(包括找不到函数),则返回0,所以我们就可以知道DllGetVersion()函数可不可以调用。

调用DllGetVersion()时,需要为DLLVERSIONINFO类型参数设置好cbSize,它告诉DLL你的参数占用多少内存。如果调用DllGetVersion成功,返回值是0,这时dwMajor、dwMinor和dwBuildNumber分别是主、副版本号和编译号,而dwPlatformID是DLL的使用平台:NT

(DLLVER_PLATFORM_NT)还是WIN9X

(DLLVER_PLATFORM_WINDOWS)。其中,DLLVER_PLATFORM_NT值是&H2,DLLVER_PLATFORM_WINDOWS值是&H1,这是API浏览器中找不到的,记下来吧。

最后注意要用FreeLibrary把系统DLL从内存中释放,不然它将一直占用系统资源。


  


                  






第十六话最后的礼物

这是《细水长流话API》的最后一节,从最早的《从消息说起》到上一期的《回调》,我们一起度过了许多时光,用到了许多可以说是使用API的必备知识,现在我们就来完成一个综合的题目,作为本次连载的压轴。

图1是大家经常看过的选择文件夹对话框,它是常用的Windows公用组件之一,当然,这里吸引你的并不只是如何显示这个对话框,还有图2,在图1的基础上为它初始化了选择的文件夹,并在上面添加了一个可以让你直接输入文件夹路径的文本框。它是怎么做的?相信这是许多人迫切想知道的。

最初我们需要用到以下的API和用户定义类型:

PublicDeclareFunctionSHBrowseForFolderLib"shell32"(lpbi

AsBrowseInfo)AsLong

PublicDeclareFunctionSHGetPathFromIDListLib"shell32"(ByValpidListAsLong,ByVallpBufferAsString)AsLong



PublicTypeBrowseInfo

hWndOwnerAsLong

pIDLRootAsLong

pszDisplayNameAsLong

lpszTitleAsLong

ulFlagsAsLong

lpfnCallbackAsLong

lParamAsLong

iImageAsLong

EndType

SHBrowseForFolder调用shell32.dll中的函数,它不同于CommonDialog等在comctl.dll中的函数,而是外壳函数,主要执行系统已经存在的一些功能。我很愿意向你逐一解释BrowseInfo的所有成员的作用,但是我觉得已经没有必要了,因为你已经学到了这里,是时候自己领悟一下了,所以我只说明示例中我认为需要说明的:

hWndOwner设置所属窗体句柄,它将一直在该窗体之上

lpszTitle指向标题的指针

ulFlags设置对话框的参数,例如:BIF_BROWSEFORCOMPUTER只返回计算机,选择其它文件夹将无法点击确定按钮

BIF_BROWSEFORPRINTER只返回打印,选择其它文件夹将无法点击确定按钮

BIF_EDITBOX添加一个输入路径的文本框,需要shell32.dll版本在

4.71以上

BIF_RETURNONLYFSDIRS只返回本系统的文件夹,选择的文件夹如果不在本机,将无法点击确定按钮

BIF_DONTGOBELOWDOMAIN不显示网络文件夹

我需要说明lpszTitle应如何使用。一般情况下,你可以通过我以前讲的办法给它传递一个字符串的指针,但是你马上会发现,假如你正确的使用StrPtr("Look!"),你却得到了错误的结果:只有一个“L”字母。更糟的是,如果你使用中文,它显示的是一堆乱码。



这是为什么?我说过,Windows9x和部分WindowsNT的API使用ANSI字符集,但是VB使用UNICODE。这个情况下,“Look!”在内存中表示成16进制是6C00

6F006F002100,API用NULL(即0)表示字符串结束,这就是原因,所以我们需要找一个让lpszTitle指向ANSI字符集的办法。

你可能见过一些人(包括许多国外程序员写的示例)使用这样一种方法:lstrcat("Look!",""),没错,lstrcat把两个字符串连接起来,并返回指针,因为字符串传递给API时已经由VB转换为ANSI字符了,所以它看起来似乎没问题。但是我要建议你不要这样做,因为它可以让你的指针成为无效指针,不信你试试在它返回指针之后,先用Debug.Print打印一次指针(或用其它方法访问这个变量一次)后再使用这个指针,你的指针的返回结果会变成没用的。那么你应该怎么做呢?

DimszTitle()AsByte

szTitle=StrConv("Look!",vbFromUnicode)

lpszTitle=VarPtr(szTitle(0))

上面就是我的办法。我用数组存放,并用StrConv把字符串转换为ANSI的。

在设置好所需要的值给BrowseInfo类型的变量后,就可以调用SHBrowseForFolder了。当选择对话框显示过后(你按了“确定”或“取消”),SHBrowseForFolder会返回一个值(取消则返回0),它又是一个指针,这个指针指向你选择的路径,不过不要试图用CopyMemory把这个指针指向的内容当成字符串般复制下来,你应该使用SHGetPathFromIDList取回(类似函数我已经讲过使用方法,这里不再说明)。

用这种方法调用的对话框,就如图1,如果你需要初始化路径,那么你要用到回调。lpfnCallback就是指向函数入口的指针。不过不幸的是,AddressOf是关键字,所以你无法把AddressOf得到的值直接赋给变量,怎么办?PublicFunctionMyAddressOf(AddressOfXAsLong)As

Long

MyAddressOf=AddressOfXEndFunction


  


                  




上面是MyAddressOf函数,我们变通一下,把AddressOf的得到的值传递给自己的一个函数,再由这个函数返回AddressOf的结果。通过MyAddressOf(AddressOffunctionA)就可以得到AddressOffunctionA的结果了。那么接下来是SHBrowseForFolder所要求的回调函数

的形式:

PublicFunctionBrowseForFoldersProc(ByValhWndAsLong,ByValuMsgAsLong,ByVallParamAsLong,ByVallpDataAsLong)AsLong

hWnd是对话框的句柄,uMsg是消息,lParam随着uMsg的不同,有时有作用,有时没有,而lpData,如以前所说过的一些回调函数,也是由你的程序来定,API把它传给回调函数,与之相对应的是BrowseInfo中的lParam。

这个回调函数在浏览文件夹对话框发送不同消息时被调用,BFFM_INITIALIZED消息在对话框初始化完成时被发送,在这个时候,我们就可以为对话框设置选择哪个文件夹:用SendMessage。

SendMessagehWnd,BFFM_SETSELECTIONA,1,ByVallpData

我事先为BrowseInfo类型变量中的lParam设置好要初始化的文件夹的路径(ANSI的),当回调函数被调用并且是发生BFFM_INITIALIZED消息时,我向对话框发送BFFM_SETSELECTIONA消息,使对话框选择某个文件夹。当然如果你不使用lpData,你也可以在要发送时再把路径的指针作为参数发送,它们没什么不同的。

最后,如果API成功返回了一个路径,我建议你调用一下CoTaskMemFree:

PublicDeclareSubCoTaskMemFreeLib"ole32.dll"(ByValhMemAsLong)

CoTaskMemFree的参数是指向一段内存的指针,由于SHBrowseForFolder返回了一个指向路径的指针,即是说它可能在内存中保留了一部分资源,CoTaskMemFree则可以把这资源回收。虽然如果你不这么做可能不会马上发现问题,但是小心一点总是好的。

好了,除去声明,我把我的整个调用过程列出如下:

ConstMAX_PATH=260

DimlpIDListAsLong

DimsBufferAsString

DimtBrowseInfoAsBrowseInfo

DimszTitle()AsByte

DimsPath()AsByte



szTitle=StrConv("你要选择哪个文件夹?"&vbNullChar,vbFromUnicode)

sPath=StrConv("C:\"&vbNullChar,vbFromUnicode)



WithtBrowseInfo

.hWndOwner=Me.hWnd

.lpszTitle=VarPtr(szTitle(0))

.ulFlags=BIF_RETURNONLYFSDIRSOr

BIF_DONTGOBELOWDOMAINOrBIF_EDITBOX

.lpfnCallback=MyAddressOf(AddressOf

BrowseForFoldersProc)

.lParam=VarPtr(sPath(0))EndWith

lpIDList=SHBrowseForFolder(tBrowseInfo)If(lpIDList)Then

sBuffer=Space(MAX_PATH)

SHGetPathFromIDListlpIDList,sBuffer

CoTaskMemFreelpIDList

sBuffer=Left(sBuffer,InStr(sBuffer,vbNullChar)-1)

MsgBoxsBuffer

EndIf

以下是标准模块中的回调函数:

PublicFunctionBrowseForFoldersProc(ByValhWndAsLong,ByValuMsgAsLong,ByVallParamAsLong,ByVallpDataAsLong)AsLong

SelectCaseuMsg

CaseBFFM_INITIALIZED

SendMessagehWnd,BFFM_SETSELECTIONA,1&,ByVallpData

CaseBFFM_SELCHANGED

’Selectionchanged

EndSelect



EndFunction


  


                  




注意看我使用字符串的那两个地方,记得结尾加上符串结束。好了,关于这一个的内容我就只讲这么多,有不明白的地方,可以参考上面我的调用过程。一些地方我没有讲,我相信你已经有能力自己进一步去探讨了。关于这个对话框的更复杂的使用,我想MSDN应该是最好的辅助工具书了。

由于API浏览器中查不到我给出的常量的值,所以我把这个API常用到的一些值帮你列在下面:

浏览文件夹的常量:

BIF_RETURNONLYFSDIRS=1

BIF_DONTGOBELOWDOMAIN=2

BIF_STATUSTEXT=4

BIF_RETURNFSANCESTORS=8

BIF_EDITBOX=&H10

BIF_VALIDATE=&H20

BIF_BROWSEFORCOMPUTER=&H1000

BIF_BROWSEFORPRINTER=&H2000

BIF_BROWSEINCLUDEFILES=&H4000对话框发出的消息:BFFM_INITIALIZED=1

BFFM_SELCHANGED=2

BFFM_VALIDATEFAILEDA=3

BFFM_VALIDATEFAILEDW=4发给对话框的消息:WM_USER=&H400

BFFM_SETSTATUSTEXTA=(WM_USER 100)BFFM_ENABLEOK=(WM_USER 101)BFFM_SETSELECTIONA=

(WM_USER 102)BFFM_SETSELECTIONW=(WM_USER 103)BFFM_SETSTATUSTEXTW=(WM_USER 104)


第十七话再见

很高兴你一直看到这里,但愿我在这次连载中没有出什么大差错。我也相信你开始喜欢上了API。API可以帮助VB完成许多别人认为不可能的事情。我不禁又要提起自己的NaviEdit,不仅我用它赚钱(笑),我更重视的是,它是我从初次接触API到熟练使用API的见证。例如正当许多人正不知从何入手制作OfficeXP风格的菜单的时候,我在去年年末就用VB为NaviEdit写出了这种风格的菜单(见图3),我靠的是API,同时也是靠一系列的方法。是谁说VB就不好呢?我就很喜欢用。如果你想得心应手的使用VB,那么API是必不可少的。虽然关于GDI方面,我未能在本次连载中讲到,但我相信你已经有独立进入更深一层研究的坚实基础。记住这些基础,继续学习,并应用到实际中去,加油吧!再见!

TOP

发新话题