摘要:通过学习 microsoft .net 框架中某些特定而有用的类,可以减少您对 win32 api 调用的依赖。本文讨论的每个类都可以代替一个或多个 win32 api 调用,而在 microsoft visual basic 6.0 中,您必须调用一个或多个 win32 api 才能完成相同的任务。
目标
- 查找现有 win32 api 调用的特定替代品。
- 了解 registry 类。
- 使用 fileversioninfo 类。
- 使用环境信息和系统信息。
目录
避免使用 win32 api
如果您是一位 microsoft visual basic® 6.0 开发人员,您就无法避免调用 win32 api。开发人员有太多的任务需要完成,而 visual basic 却不能提供任何实现方法。例如,在 visual basic 6.0 中,您很难完成以下任务:
- 确定文件版本信息。
- 在注册表的任何位置进行读取和写入操作。
- 确定用户的特定文件夹,例如 microsoft windows® 收藏夹或个人文件夹。
- 检索所有可用驱动器的列表。
- 查找用户的登录名或计算机名。
- 检索所有打开窗口的列表。
如果仅使用 visual basic 6.0 中提供的工具,您不可能解决上述任何问题。对于每个问题,开发人员都需要使用 windows api。许多开发人员使用 windows api 已经找到了完成这些(以及许多其他)任务的方法。
windows api 存在什么问题?
为什么不继续在 .net 环境中使用 windows api 呢?如果使用 .net 平台调用服务(称为“p/invoke”),您当然可以这样做。从 visual basic 开发人员的角度来说,调用 windows api 并不比使用他们所熟悉的 declare 语句困难。不过,在 .net 环境中使用 windows api 存在一些比较严重的缺陷,您可能需要考虑采取任何可行的措施来避免这些问题。例如:
- .net 公共语言运行时不会受平台影响。当您使用 windows api 调用时,您将代码绑定到编写代码的特定平台上(即,相对于其他操作系统的某个特定 windows 版本或 windows 本身)。必要时,您需要将代码转换到另一个平台上,而这样做就需要修改使用 api 调用的每行代码。
- 从 .net 中调用 windows api(或 dll 中的任何非托管代码)不像在 visual basic 6.0 中那样简单。例如,对结构的工作方式的限制使得很难将结构传递给 api 调用。此外,由于数据类型的更改以及更严格的类型转换,visual basic 6.0 的 api 声明也需要进行更改。
- 根据语言的不同,使用 windows api(以及通常情况下使用的外部代码)的技巧也不尽相同。如果您打算在多 .net 语言环境中工作,则需要掌握各种语言的不同技巧。
- 调用 windows api 的代码要求调用这些代码的用户具有执行此操作的权限。这将影响应用程序的安全保护方案,您需要对此要求提前做出安排。
这个问题很简单:尽管您可以在 visual basic .net 应用程序中继续使用 windows api,但通常情况下,您应当尽可能寻找由 .net 框架提供的替代品。虽然 .net 框架的目的并不是要阻止您直接使用 windows 的功能,但框架的确提供了大量的类,可以帮助您放弃对 windows api 调用的依赖。
如果能够给出一个完整列表,列出 win32 api 调用以及在 .net 框架中完成相同任务的相应方法(如果有),可能会很方便,不过本文不涉及此任务。在本文中,您将了解到一些由 .net 框架提供的特定且非常有用的类,它们可以解决您的问题。在每个示例中,本文所讨论的类都可以用来替代一个或多个 win32 api 调用,而在 microsoft visual basic 6.0 中,您必须调用一个或多个 win32 api 才能完成相同的任务。
使用注册表
如果您与大多数 visual basic 6.0 开发人员一样,您会发现 microsoft visual basic for applications (vba) 中内置的 savesetting、getsetting、getallsettings 和 deletesetting 方法有点儿用处,但却很可能被它们的局限性弄得精疲力尽。所有这些方法都只能在注册表的 hkey_current_user\software\vb 和 vba program settings 下的项中使用。如果您要在注册表的其他地方读取或写入注册表项或注册表值,则必须使用复杂的 api 调用,或依靠别人的代码来处理此问题。
.net 框架在 microsoft.win32 名称空间中提供了一对功能强大的类(registry 和 registrykey),从而简化了注册表的使用,即不再需要 api 调用!
作为演示,请在示例项目的主窗体上单击 work with the registry(使用注册表)按钮。此窗体提供了 software\microsoft\windows\currentversion\run 项的 hkey_local_machine 配置单元中所有注册表值的列表。您可以右键单击列表中的任何项,然后选择插入新项,或者编辑或删除选定项,如图 1 所示。
提示:示例窗体也已经过设计,在列表框中按下 enter 键时,可以编辑当前选定的项。按下 delete 键可以删除选定项,按下 insert 键可以添加一个新值。这些项对应于列表框的上下文菜单中的项。

图 1:使用 registry 和 registrykey 类轻松检索和修改 windows 注册表中的信息
.net 框架提供了两个非常有用的类,使您可以轻松使用 windows 注册表。第一个类是 registry,它提供的字段与标准 registry 配置单元的各字段相对应:
- classesroot (hkey_classes_root)
- currentconfig (hkey_current_config)
- currentuser (hkey_current_user)
- dyndata (hkey_dyn_data)
- localmachine (hkey_local_machine)
- performancedata (hkey_performance_data)
- users (hkey_users)
要使用 registry 类,只需检索所需配置单元的引用。示例窗体的 loadlist 过程中包含如下代码,以便使用注册表中的 hkey_local_machine 配置单元:
imports microsoft.win32
dim reg as registrykey = registry.localmachine
另一个类是 registrykey,它可以完成所有工作。它提供了一组使用 registry 的方法。表 1 列出了 registrykey 类的所有有用方法。
表 1:registrykey 类方法
| 方法 | 说明 |
| createsubkey | 创建新子项或打开现有子项 |
| deletesubkey | 删除指定子项。 |
| deletesubkeytree | 以递归方式删除子项和该子项的所有子项。 |
| deletevalue | 从该项中删除指定项值。 |
| getsubkeynames | 检索包含所有子项名称的字符串数组。 |
| getvalue | 检索指定的值。 |
| getvaluenames | 检索包含与此项相关联的所有项值名称的字符串数组。 |
| opensubkey | 检索指定子项,具有可选的写入权限。 |
| setvalue | 设置指定的值。 |
registrykey 类还提供以下三个属性:
- name:检索项的名称。
- subkeycount:检索与该项相关联的子项的数量。
- valuecount:检索与该项相关联的项值的数量。
示例窗体的 listload 过程将检索所请求项中的所有值,并将检索到的值添加到窗体的列表框中:
private const conregkey as string = _
"software\microsoft\windows\currentversion\run"
private structure regdata
public value as string
public data as string
public overrides _
function tostring() as string
return me.value
end function
end structureprivate sub listload()
dim reg as registrykey = registry.localmachine
dim astrvalues() as string
dim strvalue as string
dim rd as regdata 清除列表框中的现有项。
lstitems.beginupdate()
lstitems.items.clear() 打开注册表项,然后使用 该项的值加载列表框。
reg = reg.opensubkey(conregkey)
astrvalues = reg.getvaluenames()
for each strvalue in astrvalues
rd.value = strvalue.tostring
rd.data = reg.getvalue(strvalue)
lstitems.items.add(rd)
next
lstitems.endupdate()
end sub
要编辑示例窗体中的值或添加新值,需要运行以下代码:
private sub addoredit
( _ byval rd as regdata, _ byval mode as frmaddvalue.accessmode)
dim reg as registrykey = registry.localmachine
dim frm as new frmaddvalue(mode)
frm.keyname = rd.value
frm.keydata = rd.data
if frm.showdialog() = dialogresult.ok then
if frm.keyname <> string.empty then
reg = reg.opensubkey(conregkey, true)
reg.setvalue(frm.keyname, frm.keydata)
listload()
end if
end if
end sub
此代码将再次打开注册表项,这次将请求写入项值的权限(此请求由 opensubkey 的第二个参数发出)。然后,代码将调用 setvalue 方法,传递图 1 所示的对话框窗体中的项名和项值。为简化工作,可以使用 setvalue 方法添加新值或修改现有值。如果项值不存在,setvalue 方法将添加一个项值。
要删除项值,示例窗体将调用以下代码:
private sub deletekey(byval rd as regdata)
dim strtext as string
dim reg as registrykey = registry.localmachine
if lstitems.selectedindex = -1 then
exit sub
end if 删除选定的项。
strtext = string.format(
_ "are you sure you want to delete ""{0}""?", _
rd.value)
if messagebox.show(strtext, _
"delete registry value", _
messageboxbuttons.yesno, _
messageboxicon.question) = dialogresult.yes
then 打开项,允许写入。
reg = reg.opensubkey(conregkey, true)
reg.deletevalue(rd.value) 重新加载列表框。
listload()
end if
end sub
此代码将打开项并请求对其执行写入操作,然后将调用 deletevalue 方法删除选定的值。
有了示例窗体提供的信息和 .net 框架附带的文档,便可以轻松地完成与注册表相关的任何任务,而不必使用 windows api。这是一个简单的对象模型,但它提供的功能比 visual basic 6.0 开发人员先前所拥有的功能更强大。
提示:如果具有必要的权限,您还可以使用远程计算机上的注册表。您可以调用 registrykey.openremotebasekey 方法检索其他计算机上的基本项,而不是简单地使用其中一个 registry 类属性,来代表您自己计算机上的 registry 配置单元。
使用常用对话框
windows 提供了一组常用对话框,使开发人员可以方便地请求用户信息。您肯定见过并且使用过打开和保存文件、颜色、打印机和字体设置等常用对话框。visual basic 6.0 开发人员在使用这些对话框时有两种选择。他们可以:
- 使用一个 microsoft activex® 控件,该控件提供了一个包含常用对话框的简单对象模型,但是由于控件有多个不同的版本且底层 dll 不同,因此该控件存在严重的部署问题。(对于很多 visual basic 6.0 开发人员来说,这个问题是最致命的 dll 问题。)
- 直接使用 windows api 发送消息并提供回叫功能,以管理各个常用对话框。
两个解决方案都不是完美无缺的,而且由于对 .net 框架进行了增补,这两个解决方案当前都不是必需的。查看 system.windows.forms 名称空间时,您会发现 colordialog、filedialog、fontdialog 和 printdialog 类。这些类都集成在框架中(也就是说,既不需要使用 api 调用,也不需要使用 activex 控件),这样可以方便地将这些标准功能合并到您的应用程序中。
每个类都提供了一系列属性,您可以使用类的 showdialog 方法在显示对话框之前设置这些属性。本文将不对每个类进行详细讨论,但在示例项目中使用了 filedialog 类,使您可以选择文件。如果您在主窗体上选择了 file version info(文件版本信息)按钮,则可以使用演示窗体上的 select a file(选择文件)按钮显示 file open(打开文件)对话框,如图 2 所示。

图 2:使用 filedialog 类显示 windows 常用对话框
虽然 filedialog 类不像直接使用 windows api 那样灵活,但它提供了大量的属性,您可以使用这些属性控制对话框的操作。您可以决定文件的来源,选择一个或多个文件,还可以检索选定的文件名。表 2 列出了可能会用到的 filedialog 类的部分属性和方法。
表 2:filedialog 对象的属性和方法
| 属性/方法 | 说明 |
| addextension | 指示当用户省略扩展名时,对话框是否自动为文件添加一个扩展名。 |
| checkfileexists | 指示当用户指定的文件名不存在时,对话框是否显示警告信息。 |
| checkpathexists | 指示当用户指定的路径不存在时,对话框是否显示警告信息。 |
| defaultext | 默认文件扩展名。 |
| dereferencelinks | 指示对话框是否返回快捷方式所引用的文件的位置,或者是否返回快捷方式 (.lnk) 所在的位置。 |
| filename | 在文件对话框中选定的文件名。 |
| filenames | (只读)对话框中所有选定文件的文件名。 |
| filter | 当前文件名筛选字符串,确定对话框的“save as file type”(另存为文件类型)或“files of type”(文件类型)框中显示的选项。 |
| filterindex | 文件对话框中当前选定的筛选器的索引。 |
| initialdirectory | 文件对话框显示的初始目录。 |
| restoredirectory | 指示对话框在关闭之前是否还原当前目录。 |
| showhelp | 指示文件对话框中是否显示 help(帮助)按钮。 |
| title | 文件对话框标题。 |
| validatenames | 指示对话框是否只接受有效文件名。 |
| reset (method) | 将所有属性重置为默认值。 |
| showdialog (method) | 显示文件对话框。如果用户按 ok(确定),将返回 dialogresult.ok,否则返回 dialogresult.cancel。 |
单击 select a file(选择文件)时,示例窗体 frmfileversioninfo 将调用以下代码:
private sub btnselectfile_click( _
byval sender as system.object,
_ byval e as system.eventargs)
_ handles btnselectfile.click
dim ofd as openfiledialog = new openfiledialog()
ofd.filter = _
"executable files (*.exe;*.dll;*.ocx)|" & _
"*.exe;*.dll;*.ocx|" & _
"drivers (*.sys;*.drv;*.fnt)|" & _
"*.sys;*.drv;*.fnt|" & _
"all files (*.*)|*.*"
ofd.filterindex = 1
ofd.showreadonly = false
ofd.restoredirectory = true
if ofd.showdialog() = dialogresult.ok then
if ofd.filename.length > 0 then 显示文件版本信息。
displayfileinfo(ofd.filename)
end if end if
end sub
提示:如同使用 visual basic 6.0 commondialog activex 控件一样,您可以将 filedialog 类的 filter 属性设置为一个字符串,在其中包含一对以竖线分隔的值,如下所示:“description|filespec”。
在上面的示例中,一旦您选择了一个文件名,示例窗体就会在窗体的 listview 控件中显示有关该文件的信息。下一节将讨论 fileversioninfo 类,它可以实现上述操作。
