VB与WindowsAPI讲座(一)-进入WindowsAPI的热身运动VB没有提供这样的功能,必须呼叫WindowsAPI」,有时候笔者会这样回答读者的问题,虽然这麽回答有点偷懒,或者说不负责任,但这的确是事实,VB所提供的叙述、函数、物件…虽然也不在少数,但是都十分标准,或者说规矩,想变点花样,通常是行不通的,这是笔者决定开始撰写本文的主要原因。WindowsAPI是大家的感觉上VB程式要呼叫WindowsAPI是一件比较困难的事情,或者说比较麻烦的事情,但别忘了WindowsAPI是大家的,凡是在Windows工作环境底下执行的应用程式,都有权利呼叫WindowsAPI。Windows这个多工作业系统除了协调应用程式的执行、分配记忆体、管理系统资源…之外,她同时也是一个很大的服务中心,呼叫这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程式达到开启视窗、描绘图形、使用周边设备…等目的,由于这些函数服务的对象是应用程式(Application),所以便称之为ApplicationProgrammingInterface,简称API函数。但WindowsAPI与C语言最亲近虽然说呼叫WindowsAPI(以下简称API或API函数)是每一个应用程式的权利,但不可否认的API却与C语言最亲近,因为API函数在参数的传递上就是以C语言为标准。但这并不表示VB程式不能呼叫含有参数的API函数,如果传递的参数是单纯的资料型别,例如「整数」,则VB与C语言还是相通的,如果是特殊的资料型别(包含「字串」),则必须遵循一定的规范,否则不是无法得到正确的结果,就是因为违反规定而被踢出系统。如何正确地传递各种资料型别的参数,是VB程式呼叫API很重要的课题,当然也是本系列讲座的重点。物件vs.handle除了参数传递方式有所不同之外,要以VB程式呼叫API,还要具备Windows程式设计的handle观念。VB的程式设计模式是以物件为核心,但Windows的程式设计模式却是以handle为核心,以下笔者先举个简单的例子来说明,假设有一VB的表单Form1,若要改变此一表单的标题,则使用的方法是设定表单物件的Caption属性,叙述如下:Form1.Caption=新的标题若以API来执行相同的工作,则叙述如下:ret=SetWindowText(Form1.hwnd,新的标题)其中Form1.hwnd(hwnd是handleofwindow的缩写)代表的是Form1这个表单「视窗」的handle。以下是呼叫此一API函数的完整程式:PrivateDeclareFunctionSetWindowTextLib_user32AliasSetWindowTextA_(ByValhwndAsLong,ByVallpStringAsString)AsLongPrivateSubCommand1_Click()ret=SetWindowText(Me.hwnd,新的标题)EndSub由于笔者接下来的解说会继续使用此一函数,请将以上程式输入于表单的程式视窗中,并且在表单上布置一个Command1命令钮。(如果懒得输入,可进入笔者网站下载)handle是什麽?handle是什麽?让我们来检查看看,首先在SetWindowText之后增加以下叙述:PrintTypeName(Form1.hwnd)PrintForm1.hwnd结果TypeName印出Long,这表示handle的资料型别是Long,而接下来的Form1.hwnd则印出一个整数值。handle只是一个整数值吗?一个整数值能够做什麽呢?真实世界的handlehandel就字义来说,是器具的「把手」,以锅子为例,把手的用途是方便我们取用锅子里的食物,它本身虽然没什麽用,却可以帮我们取得有用的食物,再举个例子,车子的门把也叫做handle,虽然门把好像也没什麽大用处,但透过门把打开车门,可以让我们使用整部车子,也许有人会捧着锅子吃东西,或者打破车窗进入车子,但使用锅把取用食物及使用门把打开车门还是最方便的事情。handle者,存取Windows资源之识别码在Windows的世界里,充满着各种不同的系统资源,例如视窗、功能表、图片、记忆体、程式、程序…等,都算是系统资源,而Windows是这些资源的总管理者,为了能够管理这些资源,Windows必须给每一资源一个唯一的识别码,此一识别码便称为handle。Windows世界的handle与真实世界的把手在观念上很类似,由于每一个handle都是一个唯一的识别码,因此当程式要求Windows提供存取资源的服务时,须出具此一识别码,如此Windows便可以找到此一识别码所对应的资源,然后进行存取的工作,所以handle虽然只是一个整数值,但它就像是锅子的把手可用来取用锅子的食物一样,此一数值则可用来取用Windows的系统资源。handle最重要的特性是同一时间不会有两个资源的handle值是相同的,在前面的SetWindowTextAPI函数中,程式传入Form1表单视窗的handel,所以Windows便能够根据此一唯一的handle值,取得该handle所对应的视窗资源,进而将把标题设定给这个视窗。从handle到物件对很多VB的物件而言,都含有handle性质的属性,如图-1,当我们使用这类物件时,除了可以利用该物件的属性及方法来操作物件外,也可以利用其中handle性质的属性来呼叫API,直接启动Windows所提供的服务,以前面的SetWindowText(Form1.hwnd,新的标题)为例,hwnd便是附属于Form1的handle性质属性。图-1含有handle的物件简单地说,VB所提供的物件并没有把Windows的handle程式设计模式丢到一边,而是将handle封装起来,使之成为物件的一个属性。注:虽然说Windows的程式设计是以handle为核心,但仍然有不少API函数是与handle无关的,例如字串的复制,这类API函数通常不会使用到Windows所配置的系统资源,所以不需要使用handle。使用WindowsAPI的难处当我们要开始使用API时,必须知道叁件事情:(1)要呼叫哪一个API函数。(2)如何宣告API函数。(3)如何传递参数。这是以上叁件事情当中最困难的一件,主要的原因是Windows的API实在太多了,大约有1500个,这还不包含OLE、ODBC…等特殊的API,此外,如果我们把API按不同性质加以分类,则使用每一类API函数所应具备的背景知识亦各有不同,以系统注册区相关的API函数为例,就必须先了解Windows如何安排系统注册区,以及存取系统注册区的方式。不过也不必被1500++个函数给打退堂鼓了,因为不是所有的程式设计都要仰赖API,当我们面对一个问题时,首先还是寻求VB的解决方案,如果VB实在无法解决,才考虑使用API,当然,笔者接下来的讲座也不是API函数一个一个往下介绍,而会先过滤掉那些可用VB完成的API。如何宣告API函数在VB程式中,若要使用VB内建的叙述或函数,不必事先宣告直接呼叫即可,若要使用API函数,则必须在先把API函数的出处、函数名称、参数、传回值…等宣告在表单的一般区块或是一般模组(.bas档案)中,感觉上也是一件挺麻烦的工作,但由于VB提供有辅助程式,所以相对于另外两件事情,反倒是最轻松的。如何传递参数由于API采用了C语言的参数传递方式,而C语言的参数传递又与VB有着不小的差异,以致不少呼叫API所造成的错误都发生在参数传递时,所以本期我们将会有不少的篇幅放在如何传递参数上面。如何宣告WindowsAPIAPI函数的宣告并不困难,因为我们可以请VB的「API检视员」来帮忙,以下是使用「API检视员」的方法:要呼叫哪一个API函数1.首先选取VB功能表的「增益集/增益功能管理员」,然后在「增益功能管理员」交谈窗中核取「VBAPIViewer」,按下「确定」钮后,VB的「增益集」功能表栏底下就会出现「API检视员」,选取此一命令,即可启动「API检视员」。2.第一次执行API检视员时,须利用功能表的「档案/载入文字档」载入VBWinapi目录底下的Win32api.txt,接着在「可选项的项目」底下即会列出所有的API函数,若我们双按其中的函数,则该函数的宣告即会出现在「选取的项目」底下,此时再按下「复制」钮,可将选取的函数宣告复制到剪贴簿,过程如图-2,接着回到VB的程式视窗,再选取功能表的「编辑/复制」,即可将函数的宣告从剪贴簿中复制过来。图-2利用「API检视员」将API的宣告复制到剪贴簿接下来请注意API宣告式复制到VB程式的位置,此时您有两种选择:(1)先利用VB功能表的「专案/新增模组」新增一个一般模组(.bas档),然后将API宣告式复制到此一模组的程式视窗中,(2)将API宣告式复制到表单程式视窗的(一般)区块底下,但复制过来之后,必须在Declare前面加上Private保留字。将API宣告式放在一般模组与表单模组的差异是:放在一般模组的API函数,可供同一专案的所有程式使用,若放在表单模组,则只有宣告API函数的表单可以使用。提升载入API宣告式的速度如果您经常使用API检视员,就会发现载入Win32api.txt是蛮花时间的,API检视员允许我们将此一档案转换成mdb格式的资料库,此时所使用的命令是功能表的「转换文字档为资料库」,转换之后,Win32api.txt的所在目录会增加Win32api.mdb资料库档案,将来我们便可以利用功能表的「档案/载入资料库档案」来载入此一资料库档案,则载入的速度确实提升不少。不过API检视员实在有点「阿达」,因为载入Win32api.txt的API检视员具有「搜寻」API的功能,但如果载入的是Win32api.mdb资料库档案,则「搜寻」的功能反而不见了。笔者实在很不满,所以决定撰写一个改良版的API检视员,也取您阅读本文时,也经完成了,请随时注意笔者的网站。如何传递参数在API函数所定义的参数型别中,大致上可分成「数值」、「自订型别」、「字串」(String)、及「Any」四种资料型别,以下让笔者按照这四种资料型别来说明VB程式与API的参数传递方式:数值的传递数值型别在API的参数定义中可能有两种形式:「参数名As数值型别」及「ByVal参数名As数值型别」,所代表的意义分别是数值的「传址」及「传值」呼叫,与VB的习惯完全相同,以GetFileSize(读取档案的长度)API为例,其宣告式如下:DeclareFunctionGetFileSizeLibkernel32AliasGetFileSize(ByValhFileAsLong,lpFileSizeHighAsLong)AsLong而呼叫的例子如下:DimhFileAsLong,lenFileAsLonghFile=OpenFile(…)'OpenFile也是API函数ret=GetFileSize(hFile,lenFile)'lenFile将传回档案长度其中hFile参数为Long型别的「传值」呼叫,lenFile参数则是Long型别的「传址」呼叫,若呼叫成功,则GetFileSize会将档案的长度设定给lenFile。自订型别的参数传递对C语言而言,自订型别的参数只能以位址来传递,因此在API函数中,并没有「ByVal参数名As自订型别」的参数宣告,至于「参数名As自订型别」的传址呼叫,则与VB的习惯完全相同,以GetCursorPos(读取滑鼠的位置)为例,其宣告式如下:TypePOINTAPI'POINTAPI为一自定型别xAsLongyAsLongEndTypeDeclareFunctionGetCursorPosLibuser32AliasGetCursorPos(lpPointAsPOINTAPI)AsLong而呼叫的例子则是:DimpAsPOINTAPIret=GetCursorPos(p)Printp.x,p.y'p