回 帖 发 新 帖 刷新版面

主题:[转帖]系统的编程技巧(1998-1999收集整理)

API函数在VB开发中的应用
万树学 郑启双 
    VB作为快速开发Windows下的编程工具,已经为越来越多的开发者采用。但如果要开发出专业的Windows软件,还需采用大量的API函数,以下结合笔者开发管理软件的经验谈几点体会。

    程序中判定Windows的版本

    众所周知,Windows3.x各版本或多或少会有些差别,为了使开发程序避免出现莫名其妙的错误,最好在程序运行前自动判定Windows的版本。采用API提供的函数getversion很容易实现这一点。函数声明如下:

Declare Function GetVersion Lib"Kernel"() As Integer

    此函数没有参数,返回值为Windows的版本号,其中版本号的低位字节为Windows的主版本号,版本号的高位字节返回Windows的次版本号。判别过程如下:

Private Sub Form_Load ()

Dim ver As Integer

Dim major As Integer

Dim minor As Integer

Ver = GetVersion ()

major = ver And &HFF

minor = (ver And &HFF00) \ 256

If major < > 3 And minor < > 10 Then

MsgBox "版本不正确!"

Exit Sub

End If

End Sub

    程序中判断Windows的安装目录

    一般VB开发出来的程序包含vbrun300.dll等辅助文件和.vbx文件,它们均需安装到Windows目录(c:\windows)或Windows的系统目录(c:\windows\system)下,但因为用户安装Windows时可能会改变Windows的目录名(如c:\windows),使用安装软件后,不能正确运行.API中提供的GetwinDowsdirectory或GetSystemDirectory较好地解决了这个问题。函数声明如下:

 

Declare Function GetSystemDirectory Lib "Kernel"(ByVal lpBuffer As

String,ByVal nSize As Integer) As Integer

 

    其中参数lpbuffer为字串变量,将返回实际Windows目录或Windows的系统目录,nsize为lpbuffer的字串变量的大小,函数返回值均为实际目录的长度。检查函数如下:

 

Function checkdir() As Boolean

Dim windir As String * 200

Dim winsys As String * 200

Dim winl As Integer

Dim wins As Integer

Dim s1 As String

Dim s2 As String

winl = GetWindowsDirectory(windir,200)

winl = GetSystemDirectory(winsys,200)

s1 = Mid $(windir,1,winl)

s2 = Mid $(winsys,1,wins)

If Wins = 0 Or wins = 0 Then

checkdir = False

Exit Function

End If

If s1 < > "C:\WINDOWS" Or s2 < > "C:\WINDOWS\SYSTEM" Then

checkdir = False

Exit Function

End If

checkdir = True

End Function

 

shell 出现的问题

    通常编程时要调用外部程序,VB提供了shell()函数,但是如果shell调用的外部程序找不到,则运行的程序失去控制,VB给出提示"filenotfound",改变这种现象,要在程序中加入onerrorgoto,比较麻烦,API函数中的winexec很好地解决了这个问题。函数声明如下:

 

Declare Function WinExec Lib "Kernel"(ByVal lpCmdLine As String,

ByVal nCmdShow As Integer) As Integer

 

    其中lpCmdline为调用的外部文件名,NcmdShow为外部程序的运行状态,如隐藏窗口、最小化窗口等等。如返回值大于32表示执行功能,否则返回错误码。例程如下:

 

sub command1_click

ds i as integer

i=winexec("notepad.exe","c:\wst.txt",9)

'参数9 即SW_RESTORE,也就是激活并显示窗口

if i>32 then

msgbox "调用正确!!"

else

msgbox "调用错误!!"

end if

end sub

 

    通过实践发现,在使用VB开发应用程序时,灵活使用API函数,会更大地发挥VB的作用,使开发出的软件更专业,功能更强大。


 
VB中访问API函数之防错技巧
郭少越 

    功能强大的API(应用程序接口)函数对于VB程序员来说,不愧是很好的编程工具,然而使用API函数的程序员也许都遇到这样的现象。在VB集成环境下,程序运行后,出现一错误信息对话框,按确定键后系统自动退出VB集成环境,此时如果你的程序尚未存盘,那末很遗憾挽回损失已回天乏力。最后一次存盘之后的程序都不复存在。这是你对API函数使用不当引起的一般保护故障(GPF)。

    当一个GPF错发生时,你应允许Windows关闭你的应用。有些情况下你可能需要退出Windows或者重新引导系统。出错程度视哪里内存被破坏而定。DLL(动态链接库)函数中的类型不一致等错误是GPF错的主要原因。这些错误会导致GPF甚至使Windows系统完全崩溃(需要重新引导系统)。

    下面谈避免GPF的一些技巧。

    用别名来提供强类型检查是避免GPF的有效措施之一。有些情况下,DLL函数可以接受多种类型,LoadCursor函数就是这样一个例子,其定义如下:

HCursor LoadCursor(hInstance,lpCursorName)

    这里HCursor是一个指向光标对象的16位句柄,hInstance是一个16位实例句柄,lpCursorName是光标的名字或者是光标资源的32位整数ID。为了支持两种类型的lpCursorName参数。VB有必要包含如下两个声明:

DeclareFunction LoadCursor Lib"USER"(ByVal hInstance As Integer,ByVal lpCursorName As String)As Integer



DeclareFunction LoadCursor Lib"USER"(ByVal hInstance As Integer,ByVallpCursorName As Long)As Integer

    但是,这两个声明不能在一个程序中同时存在,因为Visual Basic会报重复声明错。我们知道,As Any声明可使得任何参数都可以传递给DLL函数,因此可以如下声明:

DeclareFunction LoadCursor Lib"USER"(ByVal hInstance As Integer ,ByVal lpCursorName As Any )As Integer

    上述声明意味着Visual Basic能支持一个参数可接受多种类型的DLL函数,然而这就可能带来各种灾难性的后果,每当偶然情况下用不正确的参数调用该函数时,都可以引发一个GPF,我们可以这样进行严格的类型检查并且帮助防止这类问题。

    这种方法就是在函数的声明中使用Alias技巧,看看下面的两个声明:

DeclareFunction LoadCursorByName Lib"USER"Alias "LoadCursor"(ByVal hInstance As Integer,ByVal lpCursorName As String)As Integer

DeclareFunction LoadCursorByID Lib"USER"Alias "LoadCursor"(ByVal hInstance As Integer ,ByVallpCursorName As Long)As Integer

    LoadCursorByName用字符串做lpCursorName参数访问DLL函数LoadCursor, 而LoadCursorByID访问同样的DLL函数LoadCursor,但是用长整型做lpCursorName参数,这两个函数都对lpCursorName参数进行严格的类型检查,使Visual Basic能在调用DLL函数之前识别出不正确的变量类型,最大限度地减少引起GPF或者导致系统崩溃的机会。

    除此之外,使用API函数时运行之前最好先存盘,仔细检查调用API函数的参数与声明的类型是否一致,以及严格检查参数是否有效都能减少引发GPF或者系统崩溃。


 
VB调用API函数技巧--快速选择全部项目 

    我们在使用 List 控件时,经常需要全部选择其中的项目,在项目较少时,我们可以逐项设置 Selected 来选择全部的项目,但当项目较多时,这样做就比较费时,其实,我们可以用 API 函数来简单实现此功能:

Dim nRet As Long

Dim bState as Boolean

bState=True

nRet = SendMessage(lstList.hWnd, LB_SETSEL, bState, -1)

函数声明:

Public Declare Function SendMessage Lib "User32" Alias "SendMessageA" ( ByVal hWnd As Long, ByVal wMsg As Integer, ByVal wParam As Long, ByVal lParam As Long) As Long

Public Const WM_USER = &H400

Public Const LB_SETSEL = (WM_USER + 6)


 

回复列表 (共2个回复)

沙发

VB应用小集----优化OLE的调用频率 

    在VB5.0中,OLE调用功能大大增强,程序员的调用次数也自然地随之增加,显然,优化OLE的调用对提高系统效率有着不可低估的意义。下面提供三则优化OLE调用技巧供参考。

    1.预先定义专用对象,减少OLE调用次数,提高程序效率

    在VB中进行OLE功能调用时,一般需要重复使用“对象、属性”这类语句,有时连接对象很多,语句较长。比如,要想获得某个数据库中数据表字段名称的OLE功能调用语句可能这样:

        DBEngine,Workspaces(0).DataBases(0).RecordSet.Fields(0).Name

    上面语句中的每个点(.)都需要进行一次OLE功能调用。如果我们想列出所有的字段名称,一般情况下程序如下:

        ForI%=0ToDBEngine.WorkSpaces(0).Database.RecordSet.Fields.Count-1

        PrintDBEngine.WorkSpaces(0).Database.RecordSet.Fields(I%).Name

        Next

    上面这段程序用循环的方式将所有字段名称列出来,每一个循环需要有10次OLE调用(10个点),假设有10个字段,则完成这个循环需要进行100次OLE调用。从上面的程序中我们可以看到OLE调用语句的前面部分对象都是一样的,我们可以考虑将其预先定义好,这样就可以减少OLE调用次数,提高效率,将程序改写如下:

        DimDBAsDatabase

        DimSAsFields

        DimFAsField

        SetDB=DBEngine.Workspaces(0).OpenDatabase("biblio.mdb")

        SetX=DBEngine.Workspaces(0).Databases(0).TableDefs(0).Fields

        ForEachFInX

        PrintF.Name

        Next

    改写后的程序在完成定义对象时使用6次OLE调用,循环过程需要10次(10×1),完成整个任务只需要16次,和上面的程序比起来效率大大提高了。


    2.利用WITH语句来减少OLE调用次数

    比如,我们通过OLE功能定义某个字段的属性语句可能这样:

        DBEngine.WorkSpaces(0).Database.RecordSet.Fields(0).Name="Book"

        DBEngine.WorkSpaces(0).Database.RecordSet.Fields(0).Required=True

        DBEngine.WorkSpaces(0).Database.RecordSet.Fields(0).Size=20

        DBEngine.WorkSpaces(0).Database.RecordSet.Fields(0).Type=db Text

        DBEngine.WorkSpaces(0).Database.RecordSet.Fields(0).Value="MSDOS"

    上面的语句运行效率不高,且书写繁琐,利用WITH语句改写如下:

        WithDBEngine.WorkSpaces(0)

        .Database.RecordSet.Fields(0)

        .Name="BOOK"

        .Required=True

        .Size=20

        .Type=dbText

        .Value="MSDOS"

        EndWith

    改写后的程序书写简单、效率也高(OLE调用次数少)。


    3.利用服务方程序的宏来减少OLE功能调用次数

    如果要从VB中通过OLE调用WORD中的某些功能来完成某项任务,首先在WORD中将所要使用的功能用记录宏的方式记录成一个宏(根据实际情况可修改记录后的宏代码),然后再用VB的OLE功能调用WORD来执行这个宏,这样可避免反复用VB的OLE功能调用WORD来完成任务,简化了程序,提高了效率。


 
监视程序的运行情况 


    我们使用 VB 提供的 Shell 方法调用外部程序时,Shell 方法执行后会继续执行下面的代码,而我们不能确定外部程序什么时候调用结束。我们现在可以通过以下方法来实现对被调用程序的监视:

    (1)首先在工程中添加一个模块,然后声明 OpenProcess 及 CloseHandle 这两个 API 函数;

    (2)然后加入下面的函数:

      Public Function StillRun(ByVal ProgramID) As Boolean

      Dim lHProgram As Long

      Dim lReturn As Long

      hProgram = OpenProcess(0, False, ProgramID)

      If Not hProgram = 0 Then

      StillRun = True

      Else

      StillRun = False

      End If

      CloseHandle hProgram

      End Function

    说明:ProgramID 是 Shell 函数返回的那个值, 那是一个进程号;lHProgram 是被测程序的进程句柄;lReturn是进程的返回码。本程序只是在WIN32下有效,使用重要注意一个问题, 就是代码的重入问题。可以用类似以下的代码来解决。

      While StillRun(id)

      DoEvents

      Wend


板凳


Shell & Wait 的程序怎么写? 

希望某一 VB 程序利用 Shell 执行某一个外部程序(假设是 notepad.exe)之后,就一直等到此一程序结束执行时, 才回到 VB 程序继续执行, 该怎么办到呢?

当我们调用 Shell 时, 会传回一个数值, 此一数值称为 Process Id, 利用此一 Process Id, 我们可以调用 OpenProcess API 取得 Process Handle, 然后再利用 Process Handle 调用 WaitForSingleObject, 即可等待被 Shell 执行的程序执行完毕, 才继续向下执行。细节如下:

1. API 的声明:

Const SYNCHRONIZE = &H100000
Const INFINITE = &HFFFFFFFF
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long

注:如果以上的声明放在「一般模块」底下, 应将 Declare 之前的 Private 保留字去掉, 并且在 Const 之前加上 Public 保留字。

2. 程序范例:(以执行 Notepad 程序为例)

Dim pId As Long, pHnd As Long ' 分别声明 Process Id 及 Process Handle 变数

pId = Shell("Notepad", vbNormalFocus) ' Shell 传回 Process Id

pHnd = OpenProcess(SYNCHRONIZE, 0, pId) ' 取得 Process Handle
If pHnd <> 0 Then
    Call WaitForSingleObject(pHnd, INFINITE) ' 无限等待,直到程序结束
    Call CloseHandle(pHnd)
End If

 
如何结束 Shell 所启动的程序? 

如果被 Shell 所启动的程序还没有结束, 我们就想主动结束它,该怎么做呢?

此时应调用的 Windows API 是 TerminateProcess, 细节如下:

1. API 的声明:

Const SYNCHRONIZE = &H100000
Const INFINITE = &HFFFFFFFF
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function TerminateProcess Lib "kernel32" Alias "TerminateProcess" (ByVal hProcess As Long, ByVal uExitCode As Long) As Long

注:如果以上的声明放在「一般模块」底下, 应将 Declare 之前的 Private 保留字去掉, 并且在 Const 之前加上 Public 保留字。

2. Shell 的程序范例:(以执行 MS-DOS 为例)

Dim pId As Long, pHnd As Long ' 分别声明 Process Id 及 Process Handle 变数

pId = Shell("Command.com", vbNormalFocus) ' Shell 传回 Process Id
pHnd = OpenProcess(SYNCHRONIZE, 0, pId) ' 取得 Process Handle
...
Call TerminateProcess( pHnd, 0 ) ' TerminateProcess 所传入的是 Process Handle
Call CloseHandle( pHnd )
但以上的方案只适用于 Shell 所启动的程序, ShellExecute 则不适用, 原因是 ShellExecute 函数是通过资源管理器来启动程序, 而资源管理器启动程序之后,并没有将 Process ID 或 Process Handle 传回来。


 
如何在 Windows 启动时自动执行某一个程序? 

此一问题比较普通的解决方法是在「开始功能表 -> 程序集 -> 启动」资料夹中放置可执行文件或建立可执行文件的捷径,若采用此一方法, 则使用者仍然可以在开机时按住 Shift 键, 让 Windows 忽略「启动」资料夹中的可执行文件,也就是不执行它们。 是不是有什么方法可以让程序一定会被执行呢?

答案是把可执行文件的资讯写入登录资料库(Registry)的以下 SubKey底下:

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

举例来说,如果写入以下 Value,则 Notepad 程序会在 Windows 启动时被执行:

Value名称 Value值
"记事本" "Notepad"

写入时, 「Value值」的部分可以是完整路径档名或可执行文件名, 但如果只写入可执行文件名(未含完整路径),则该可执行文件必须位于Windows的目录、 Windows的System目录、 目前工作目录、或PATH环境变数所定义的目录底下, Windows才能够加以执行, 以Notepad.exe程序为例,由于是位于Windows的目录底下, 所以只要书写可执行文件名即可。

至于「Value名称」的部分则没什么限制,只要不会与其他Value名称相冲突即可。

注:有关登录资料库的存取方法请叁阅 Run!PC 第 50 期。

 

我来回复

您尚未登录,请登录后再回复。点此登录或注册