穿越猎人当歌手的小说:在VB 中调用动态连接库

来源:百度文库 编辑:中财网 时间:2024/05/14 03:58:26

作 为 一 种 简 单 易 用 的Windows 开 发 环 境,Visual Basic 从 一 推 出 就 受 到 了 广 大 编 程 人 员 的 欢 迎。 它 使 程 序 员 不 必 再 直 接 面 对 纷 繁 复 杂 的Windows 消 息, 而 可 以 将 精 力 主 要 集 中 在 程 序 功 能 的 实 现 上, 大 大 提 高 了 编 程 效 率。 但 凡 事 有 利 必 有 弊。VB 中 高 度 的 封 装 和 模 块 化 减 轻 了 编 程 者 的 负 担, 同 时 也 使 开 发 人 员 失 去 了 许 多 访 问 低 层API 函 数 和 直 接 与Windows 交 互 的 机 会。 因 此, 相 比 而 言,VB 应 用 程 序 的 执 行 效 率 和 功 能 比C/C++ 或Delphi 生 成 的 程 序 要 差。 为 了 解 决 这 个 问 题, 在 一 个 大 型 的VB 开 发 应 用 中, 直 接 调 用Windows API 函 数 几 乎 是 不 可 避 免 的; 同 时, 还 有 可 能 需 要 程 序 员 自 己 用C/C++ 等 开 发 一 些 动 态 连 接 库, 用 于 在VB 中 调 用。 本 文 主 要 讨 论 在32 位 开 发 环 境Visual Basic 5.0 中 直 接 调 用Windows 95 API 函 数 或 用 户 生 成 的32 位 动 态 连 接 库 的 方 法 与 规 则。

---- Windows 动 态 连 接 库 是 包 含 数 据 和 函 数 的 模 块, 可 以 被 其 它 可 执 行 文 件(EXE、DLL、OCX 等) 调 用。 动 态 连 接 库 包 含 两 种 函 数: 输 出(exported) 函 数 和 内 部(internal) 函 数。 输 出 函 数 可 以 被 其 它 模 块 调 用, 而 内 部 函 数 则 只 能 在 动 态 连 接 库 内 部 使 用。 尽 管 动 态 连 接 库 也 能 输 出 数 据, 但 实 际 上 它 的 数 据 通 常 是 只 在 内 部 使 用 的。 使 用 动 态 连 接 库 的 优 点 是 显 而 易 见 的。 将 应 用 程 序 的 一 部 分 功 能 提 取 出 来 做 成 动 态 连 接 库, 不 但 减 小 了 主 应 用 程 序 的 大 小, 提 高 了 程 序 运 行 效 率, 还 使 它 更 加 易 于 升 级。 多 个 应 用 程 序 共 享 一 个 动 态 连 接 库 还 能 有 效 地 节 省 系 统 资 源。 正 因 为 如 此, 在Windows 系 统 中, 动 态 连 接 库 得 到 了 大 量 的 使 用。

---- 一 般 来 说, 动 态 连 接 库 都 是 以DLL 为 扩 展 名 的 文 件, 如Kernel32.dll 、commdlg.dll 等。 但 也 有 例 外, 如16 位Windows 的 核 心 部 件 之 一GDI.exe 其 实 也 是 一 个 动 态 库。 编 写 动 态 连 接 库 的 工 具 很 多, 如Visual C++、Borland C++、Delphi 等, 具 体 方 法 可 以 参 见 相 关 文 档。 下 面 只 以Visual C++ 5.0 为 例, 介 绍 一 下 开 发 应 用 于Visual Basic 5.0 的 动 态 连 接 库 时 应 注 意 的 问 题( 本 文 中 所 有 涉 及C/C++ 语 言 或 编 译 环 境 的 地 方, 都 以VC5 为 例; 所 有 涉 及Visual Basic 的 地 方 都 以VB5 为 例)。

---- 作 为 一 种32 位Windows 应 用 程 序 的 开 发 工 具,VB5 生 成 的exe 文 件 自 然 也 都 是32 位 的, 通 常 情 况 下 也 只 能 调 用32 位 的 动 态 连 接 库。 但 是, 并 不 是 所 有 的32 位 动 态 库 都 能 被VB 生 成 的exe 文 件 正 确 地 识 别。 一 般 来 说, 自 己 编 写 用 于VB 应 用 程 序 调 用 的 动 态 连 接 库 时, 应 注 意 以 下 几 个 方 面 的 问 题:

---- 1、 生 成 动 态 库 时 要 使 用__stdcall 调 用 约 定, 而 不 能 使 用 缺 省 的__cdecl 调 用 约 定;__stdcall 约 定 通 常 用 于32 位API 函 数 的 调 用。

---- 2、 在VC5 中 的 定 义 文 件(.def) 中, 必 须 列 出 输 出 函 数 的 函 数 名, 以 强 制VC5 系 统 将 输 出 函 数 的 装 饰 名(decorated name) 改 成 普 通 函 数 名; 所 谓 装 饰 名 是VC 的 编 译 器 在 编 译 过 程 中 生 成 的 输 出 函 数 名, 它 包 含 了 用 户 定 义 的 函 数 名、 函 数 参 数 及 函 数 所 在 的 类 等 多 方 面 的 信 息。 由 于 在VC5 中 定 义 文 件 不 是 必 需 的, 因 此 工 程 不 包 含 定 义 文 件 时VC5 就 按 自 己 的 约 定 将 用 户 定 义 的 输 出 函 数 名 修 改 成 装 饰 名 后 放 到 输 出 函 数 列 表 中, 这 样 的 输 出 函 数 在VB 生 成 的 应 用 程 序 中 是 不 能 正 确 调 用 的( 除 非 声 明 时 使 用Alias 子 句)。 因 此 需 要 增 加 一 个.def 文 件, 其 中 列 出 用 户 需 要 的 函 数 名, 以 强 制VC5 不 按 装 饰 名 进 行 输 出。

---- 3、VC5 中 的 编 译 选 项" 结 构 成 员 对 齐 方 式(structure member alignment)" 应 设 成4 字 节, 其 原 因 将 在 后 文 详 细 介 绍。

---- 4、 由 于 在C 中 整 型 变 量 是4 个 字 节, 而VB 中 的 整 型 变 量 依 然 只 有2 个 字 节, 因 此 在C 中 声 明 的 整 型(int) 变 量 在VB 中 调 用 时 要 声 明 为 长 整 型(long), 而C 中 的 短 整 型(short) 在VB 中 则 要 声 明 成 整 型(integer); 下 表 针 对 最 常 用 的 C 语 言 数 据 类 型 列 出 了 与 之 等 价 的 Visual Basic 类 型( 用 于 32 位 版 本 的 Windows)。

---- C 语 言 数 据 类 型 在Visual Basic 中 声 明 为 调 用 时 使 用 的 表 达 式

---- ATOM ByVal variable As Integer 结 果 为Integer 类 型 的 表 达 式

---- BOOL ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式

---- BYTE ByVal variable As Byte 结 果 为 Byte 类 型 的 表 达 式

---- CHAR ByVal variable As Byte 结 果 为 Byte 类 型 的 表 达 式

---- COLORREF ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式

---- DWORD ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式

---- HWND, HDC, HMENU ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式 等Windows 句 柄

---- INT, UINT ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式

---- LONG ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式

---- LPARAM ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式

---- LPDWORD variable As Long 结 果 为 Long 类 型 的 表 达 式

---- LPINT, LPUINT variable As Long 结 果 为 Long 类 型 的 表 达 式

---- LPRECT variable As type 自 定 义 类 型 的 任 意 变 量

---- LPSTR, LPCSTR ByVal variable As String 结 果 为 String 类 型 的 表 达 式

---- LPVOID variable As Any 任 何 变 量( 在 传 递 字 符 串 的 时 候 使 用ByVal)

---- LPWORD variable As Integer 结 果 为Integer 类 型 的 表 达 式

---- LRESULT ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式

---- NULL As Any 或 ByVal Nothing 或

---- ByVal variable As Long ByVal 0& 或 VBNullString

---- SHORT ByVal variable As Integer 结 果 为Integer 类 型 的 表 达 式

---- VOID Sub procedure 不 可 用

---- WORD ByVal variable As Integer 结 果 为Integer 类 型 的 表 达 式

---- WPARAM ByVal variable As Long 结 果 为 Long 类 型 的 表 达 式

---- 5、VB 中 进 行32 位 动 态 库 的 声 明 时, 函 数 名 是 大 小 写 敏 感 的。 在 获 得 了 需 要 的 动 态 连 接 库 之 后, 就 可 以 在VB 中 进 行 调 用 了。 但 是, 由 于VB 不 能 验 证 应 用 程 序 传 递 到 动 态 连 接 库 中 的 参 数 值 是 否 正 确, 因 此VB 程 序 中 大 量 的API 调 用 可 能 会 降 低 整 个 应 用 程 序 的 稳 定 性, 也 会 增 加 以 后 维 护 的 难 度。 所 以, 决 定 在VB 程 序 中 直 接 调 用API 函 数 时 要 慎 重, 但 适 当 的 使 用API 调 用 确 实 能 够 有 效 地 提 高VB 程 序 的 性 能。 这 之 间 的 平 衡 需 要 编 程 人 员 根 据 实 际 情 况 来 掌 握。 下 面 就 具 体 介 绍 一 下 在VB 中 调 用API 函 数 时 需 要 做 的 工 作。

---- 要 声 明 一 个DLL 过 程, 首 先 需 要 在 代 码 窗 口 的" 通 用(General)" 部 分 增 加 一 个Declare 语 句。 如 果 该 过 程 返 回 一 个 值, 应 将 其 声 明 为Function:

---- Declare Function publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])] As Type

---- 如 果 过 程 没 有 返 回 值, 可 将 其 声 明 为Sub:

---- Declare Sub publicname Lib "libname" [Alias "alias"] [([[ByVal] variable [As type] [,[ByVal] variable [As type]]...])]

---- 缺 省 情 况 下, 在 标 准 模 块 中 声 明 的DLL 过 程, 可 以 在 应 用 程 序 的 任 何 地 方 调 用 它。 在 其 它 类 型 的 模 块 中 定 义 的DLL 过 程 则 是 模 块 私 有 的, 必 须 在 它 们 前 面 声 明Private 关 键 字, 以 示 区 分。 下 面 分 别 介 绍 声 明 语 句 的 各 个 组 成 部 分。

---- 1、 指 定 动 态 库:

---- Declare 语 句 中 的 Lib 子 句 用 来 告 诉 Visual Basic 如 何 找 到 包 含 过 程 的 .dll 文 件。 如 果 引 用 的 过 程 属 于 Windows 核 心 库(User32、Kernel32 或 GDI32), 则 可 以 不 包 含 文 件 扩 展 名, 如:

---- Declare Function GetTickCount Lib "kernel32" Alias "GetTickCount" () As Long

---- 对 于 其 它 动 态 连 接 库, 可 以 在Lib 子 句 指 定 文 件 的 路 径:

---- Declare Function lzCopy Lib "c:windowslzexpand.dll" _

---- (ByVal S As Integer, ByVal D As Integer) As Long

---- 如 果 未 指 定 libname 的 路 径,Visual Basic 将 按 照 下 列 顺 序 查 找 该 文 件:

---- ①.exe 文 件 所 在 的 目 录

---- ② 当 前 目 录

---- ③Windows 系 统 目 录

---- ④Windows 目 录

---- ⑤Path 环 境 变 量 中 的 目 录

---- 下 表 中 列 出 了 常 用 的 操 作 系 统 环 境 库 文 件。

---- 动 态 链 接 库 描 述

---- Advapi32.dll 高 级 API 服 务, 支 持 大 量 的 API( 其 中 包 括 许 多 安 全 与 注 册 方 面 的 调 用)

---- Comdlg32.dll 通 用 对 话 框 API 库

---- Gdi32.dll 图 形 设 备 接 口 API 库

---- Kernel32.dll Windows 32 位 核 心 的 API 支 持

---- Lz32.dll 32 位 压 缩 例 程

---- Mpr.dll 多 接 口 路 由 器 库

---- Netapi32.dll 32 位 网 络 API 库

---- Shell32.dll 32 位 Shell API 库

---- User32.dll 用 户 接 口 例 程 库

---- Version.dll 版 本 库

---- Winmm.dll Windows 多 媒 体 库

---- Winspool.drv 后 台 打 印 接 口, 包 含 后 台 打 印 API 调 用。

---- 对 于Windows 的 系 统API 函 数, 可 以 利 用VB 提 供 的 工 具API Viewer 查 找 某 一 函 数 及 其 相 关 数 据 结 构 和 常 数 的 声 明, 并 复 制 到 自 己 的 程 序 中。

---- 2、 使 用 别 名:

---- Declare 语 句 中 的Alias 子 句 是 一 个 可 选 的 部 分, 用 户 可 以 通 过 它 所 标 识 的 别 名 对 动 态 库 中 的 函 数 进 行 引 用。 例 如, 在 下 面 的 语 句 中, 声 明 了 一 个 在VB 中 名 为MyFunction 的 函 数, 而 它 在 动 态 库Mydll.dll 中 最 初 的 名 字 是MyFunctionX。

---- Private Declare Function MyFunction Lib "Mydll.dll" _

---- Alias "MyFunctionX" ( ) As Long

---- 需 要 注 意 的 是,Alias 子 句 中 的 函 数 名 是 大 小 写 敏 感 的, 也 就 是 说, 必 须 与 函 数 在 生 成 时 的 声 明( 如 在C 源 文 件 中 的 声 明) 一 致。 这 是 因 为32 位 动 态 库 与16 位 动 态 库 不 同, 其 中 的 函 数 名 是 区 分 大 小 写 的。 同 样 道 理, 如 果 没 有 使 用Alias 子 句, 那 么 在Function( 或Sub) 后 的 函 数 名 也 是 区 分 大 小 写 的。

---- 通 常 在 以 下 几 种 情 况 时 需 要 使 用Alias 子 句:

---- A. 处 理 使 用 字 符 串 的 系 统 Windows API 过 程

---- 如 果 调 用 的 系 统 Windows API 过 程 要 使 用 字 符 串, 那 么 声 明 语 句中 必 须 增 加 一 个 Alias 子 句, 以 指 定 正 确 的 字 符 集。 包 含 字 符 串 的 系 统 Windows API 函 数 实 际 有 两 种 格 式:ANSI 和 Unicode( 关 于ANSI 和 Unicode 两 种 字 符 集 的 区 别 将 在 后 面 详 细 阐 述)。 因 此, 在 Windows 头 文 件 中, 每 个 包 含 字 符 串 的 函 数 都 同 时 有 ANSI 版 本 和 Unicode 版 本。 例 如, 下 面 是 SetWindowText 函 数 的 两 种 C 语 言 描 述。 可 以 看 到, 第 一 个 描 述 将 函 数 定 义 为 SetWindowTextA, 尾 部 的"A" 表 明 它 是 一 个 ANSI 函 数:

---- WINUSERAPI BOOL WINAPI SetWindowTextA(HWND hWnd, LPCSTR lpString);

---- 第 二 个 描 述 将 它 定 义 为 SetWindowTextW, 尾 部 的"W" 表 明 它 是 一 个 Unicode 函 数:

---- WINUSERAPI BOOL WINAPI SetWindowTextW(HWND hWnd, LPCWSTR lpString);

---- 因 为 两 个 函 数 实 际 的 名 称 都 不 是"SetWindowText", 要 引 用 正 确 的 函 数 就 必 须 增 加 一 个 Alias 子 句:

Private Declare Function SetWindowText Lib "user32" _
Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal _
lpString As String) As Long

---- 应 当 注 意, 对 于VB 中 使 用 的 系 统Windows API 函 数, 应 该 指 定 函 数 的 ANSI 版 本, 因 为 只 有 Windows NT 才 支 持 Unicode 版 本, 而 Windows 95 不 支 持 这 个 版 本。 仅 当 应 用 程 序 只 运 行 在 Windows NT 平 台 上 的 时 候 才 可 以 使 用 Unicode 版 本。

---- B. 函 数 名 是 不 标 准 的 名 称

---- 有 时, 个 别 的 DLL 过 程 的 名 称 不 是 有 效 的 标 识 符。 例 如, 它 可 能 包 含 了 非 法 的 字 符( 如 连 字 符), 或 者 名 称 是 VB 的 关 键 字( 如 GetObject)。 在 这 种 情 况 下, 可 以 使 用 Alias 关 键 字。 例 如, 操 作 环 境 DLLs 中 的 某 些 过 程 名 以 下 划 线 开 始。 尽 管 在 VB 标 识 符 中 允 许 使 用 标 识 符, 但 是 下 划 线 不 能 作 为 标 识 符 的 第 一 个 字 符。 为 了 使 用 这 种 过 程, 必 须 先 声 明 一 个 名 称 合 法 的 过 程, 然 后 用 Alias 子 句 引 用 过 程 的 真 实 名 称:

Declare Function lopen Lib "kernel32" Alias "_lopen" _
(ByVal lpPathName As String, ByVal iReadWrite _
As Long) As Long

---- 在 上 例 中,lopen 是 VB 中 使 用 的 过 程 名 称。 而 _lopen 则 是 动 态 连 接 库 中 可 以 识 别 的 名 称。

---- C. 使 用 序 号 标 识 DLL 过 程

---- 除 了 使 用 名 称 之 外, 还 可 以 使 用 序 号 来 标 识 DLL 过 程。 某 些 动 态 连 接 库 中 不 包 含 过 程 的 名 称, 在 声 明 它 们 包 含 的 过 程 时 必 须 使 用 序 号。 同 使 用 名 称 标 识 的DLL 过 程 相 比, 如 果 使 用 序 号, 在 最 终 的 应 用 程 序 中 消 耗 的 内 存 将 比 较 少, 而 且 速 度 会 快 些。 但 是, 一 个 具 体 的API 的 序 号 在 不 同 的 操 作 系 统 中 可 能 是 不 同 的。 例 如 GetWindowsDirectory 在 Win95 下 的 序 号 为 432, 而 在 Windows NT 4.0 下 为 338。 总 而 言 之, 如 果 希 望 应 用 程 序 能 够 在 不 同 的 操 作 系 统 下 运 行, 那 么 最 好 不 要 使 用 序 号 来 标 识 API 过 程。 如 果 过 程 不 属 于API, 或 者 应 用 程 序 使 用 的 范 围 很 有 限, 那 么 使 用 序 号 还 是 有 好 处 的。

---- 要 使 用 序 号 来 声 明 DLL 过 程,Alias 子 句 中 的 字 符 串 需 要 包 含 过 程 的 序 号, 并 在 序 号 的 前 面 加 一 个 数 字 标 记 字 符 (#)。 例 如,Windows kernel 中 的 GetWindowsDirectory 函 数 的 序 号 为 432; 可 以 用 下 面 的 语 句 来 声 明 该 DLL 过 程:

Declare Function GetWindowsDirectory Lib "kernel32" _
Alias "#432" (ByVal lpBuffer As String, _
ByVal nSize As Long) As Long

---- 在 这 里, 可 以 使 用 任 意 的 合 法 名 称 作 为 过 程 的 名 称,VB 将 用 序 号 在 DLL 中 寻 找 过 程。

---- 为 了 得 到 要 声 明 的 过 程 的 序 号, 可 以 使 用 Dumpbin.exe 等 实 用 工 具(Dumpbin.exe 是 Microsoft Visual C++ 提 供 的 一 个 实 用 工 具, 它 的 使 用 说 明 可 以 参 见VC 的 文 档)。 利 用 Dumpbin, 可 以 提 取 出 .dll 文 件 中 的 各 种 信 息, 例 如 DLL 中 的 函 数 列 表, 它 们 的 序 号 以 及 与 代 码 有 关 的 其 它 信 息。

---- 3、 使 用 值 或 引 用 传 递

---- 在 缺 省 的 情 况 下,VB 以 引 用 方 式 传 递 所 有 参 数(ByRef)。 这 意 味 着 并 没 有 传 递 实 际 的 参 数 值,VB 只 传 递 了 数 据 的 32 位 地 址。 另 外 有 许 多 DLL 过 程 要 求 参 数 以 值 方 式 传 递(ByVal)。 这 意 味 着 它 们 需 要 实 际 的 数 据, 而 不 是 数 据 的 内 存 地 址。 如 果 过 程 需 要 一 个 传 值 参 数, 而 传 递 给 它 的 参 数 是 一 个 指 针, 那 么 由 于 得 到 了 错 误 的 数 据, 该 过 程 将 不 能 正 确 地 工 作。

---- 要 使 参 数 以 使 用 值 方 式 传 递, 在 Declare 语 句 中 需 要 在 参 数 声 明 的 前 面 加 上 ByVal 关 键 字。 例 如InvertRect 过 程 要 求 第 一 个 参 数 用 传 值 方 式 传 递, 而 第 二 个 用 引 用 方 式 传 递:

Declare Function InvertRect Lib "user32" Alias _
"InvertRectA" (ByVal hdc As Long, lpRect As RECT) As Long

---- 动 态 连 接 库 的 参 数 传 递 是 一 个 复 杂 的 问 题, 也 是VB 中 调 用 动 态 连 接 库 时 最 容 易 出 现 错 误 的 地 方。 参 数 类 型 或 传 递 方 式 的 声 明 错 误 都 可 能 导 致 应 用 程 序 出 现GPF( 通 用 保 护 错 误), 甚 至 使 操 作 系 统 崩 溃, 因 此 我 们 将 在 后 面 专 门 详 细 地 讨 论 这 个 问 题。

---- 4、 灵 活 的 参 数 类 型

---- 某 些 DLL 过 程 的 同 一 个 参 数 能 够 接 受 多 种 数 据 类 型。 如 果 需 要 传 递 多 种 类 型 的 数 据, 可 以 将 参 数 声 明 为 As Any, 从 而 取 消 类 型 限 制。 例 如, 下 面 的 声 明 中 的 第 三 个 参 数 (lppt As Any) 既 可 以 传 递 一 个 POINT 结 构 的 数 组, 也 可 以 传 递 一 个 RECT 结 构:

Declare Function MapWindowPoints Lib "user32" Alias _
"MapWindowPoints" (ByVal hwndFrom As Long, _
ByVal hwndTo As Long, lppt As Any, _
ByVal cPoints As Long) As Long

---- As Any 子 句 提 供 了 一 定 的 灵 活 性, 但 是, 由 于 它 不 进 行 任 何 的 类 型 检 查, 风 险 也 随 之 增 加。 因 此 在 使 用 As Any 子 句 时, 必 须 仔 细 检 查 所 有 参 数 的 类 型。

---- 正 确 的 函 数 声 明 是 在VB 中 调 用 动 态 连 接 库 的 前 提, 但 要 想 在VB 中 用 对、 用 好 动 态 库 中 的 函 数, 仅 仅 有 声 明 还 是 远 远 不 够 的。 前 面 已 经 说 过, 由 于VB 不 能 验 证 应 用 程 序 传 递 到 动 态 连 接 库 中 的 参 数 值 是 否 正 确, 因 此 就 要 求 程 序 员 应 对 参 数 类 型 有 非 常 详 细 的 了 解, 否 则 很 容 易 引 起 应 用 程 序 发 生 通 用 保 护 错 或 导 致 潜 在 的Bug, 降 低 软 件 的 可 靠 性。 下 面 将 参 数 类 型 分 为 简 单 数 据 类 型、 字 符 串、 和 用 户 自 定 义 类 型 三 种 分 别 进 行 讨 论。

---- 1、 简 单 数 据 类 型:

---- 简 单 数 据 类 型 是 指Numeric 数 据 类 型( 包 括Integer、Long、Single、Double、Currency 类 型)、Byte 数 据 类 型 和Boolean 数 据 类 型。 它 们 的 共 同 的 特 点 是 结 构 简 单, 操 作 系 统 在 处 理 时 不 必 进 行 特 殊 的 转 换。

---- 简 单 数 据 类 型 参 数 的 传 递 比 较 简 单。 我 们 知 道, 在VB 中 传 递 参 数 的 方 式 有 两 种: 传 值(Byval) 和 传 址(ByRef), 缺 省 的 方 式 是 传 址。 所 谓 传 值, 就 是 对 一 个 变 量 的 具 体 值 进 行 传 递; 而 传 址 则 是 传 递 变 量 的 地 址。 例 如, 在VB 程 序 中 需 要 将 一 个 整 型 变 量m=10 的 值 传 进 动 态 库, 如 果 用 传 值 方 式, 那 么 传 进 动 态 库 的 值 就 是10, 而 在 传 址 方 式 下, 传 入 的 则 是 变 量 m 的 地 址, 相 当 于C/C++ 中 &m 的 值。 需 要 注 意 的 是, 以 传 值 方 式 传 进 动 态 连 接 库 的 变 量, 其 值 在 动 态 库 中 是 不 能 被 改 变 的; 如 果 需 要 在 动 态 连 接 库 中 修 改 传 入 参 数 的 值, 则 必 须 使 用 传 址 方 式。 一 般 来 说, 在VB 和 动 态 连 接 库 之 间 传 递 单 个 的 简 单 数 据 类 型, 只 要 注 意 了 以 上 几 个 方 面 就 可 以 了。 当 需 要 将 一 个 简 单 数 据 类 型 的 整 个 数 组 传 进 动 态 库 时, 必 须 将 相 应 参 数 声 明 为 传 址 方 式, 然 后 把 数 组 的 第 一 个 元 素 作 为 参 数 传 入, 这 样 在 动 态 连 接 库 中 就 得 到 了 数 组 的 首 地 址, 从 而 可 以 对 整 个 数 组 进 行 访 问。 例 如, 声 明 了 一 个 名 为ReadArray 的DLL 过 程, 要 求 传 入 一 个 整 型 数 组aArray:

Declare Function ReadArray Lib "mydll.dll" _
(aArray As Integer) As Integer
在 调 用 时 可 以 采 用 如 下 方 式:
Dim ret,I(5) as Integer
… …
ret = ReadArray(I(0)) ‘ 将 整 个 数 组 传 入 动 态 连 接 库

---- 2、 字 符 串 参 数 的 传 递:

---- 与 简 单 数 据 类 型 相 比, 字 符 串 类 型(String、String * n) 的 参 数 传 递 要 复 杂 得 多,这 主 要 是Windows 95 API 和VB 使 用 的 字 符 串 类 型 不 同 的 缘 故。VB 使 用 被 称 为 BSTR 的 String 数 据 类 型, 它 是 由 自 动 化( 以 前 被 称 为 OLE Automation) 定 义 的 数 据 类 型。 一 个 BSTR 由 头 部 和 字 符 串 组 成, 头 部 包 含 了 字 符 串 的 长 度 信 息, 字 符 串 中 可 以 包 含 嵌 入 的 null 值。 大 部 分 的 BSTR 是 Unicode 的, 即 每 个 字 符 需 要 两 个 字 节。BSTR 通 常 以 两 字 节 的 两 个 null 字 符 结 束。 下 图 表 示 了 一 个BSTR 类 型 的 字 符 串。

( 前 缀) a T e s t {content}
头 部 BSTR指向数据的第一个字节

---- 另 一 方 面, 大 部 分 的DLL 过 程( 包 括 Windows 95 API 中 的 所 有 过 程) 使 用 LPSTR 类 型 字 符 串, 这 是 指 向 标 准 的 以 null 结 束 的 C 语 言 字 符 串 的 指 针, 它 也 被 称 为 ASCIIZ 字 符 串。LPSTR 没 有 前 缀。 下 图 显 示 了 一 个 指 向 ASCIIZ 字 符 串 的 LPSTR。

---- a T e s t {content}

---- LPSTR 指 向 一 个 以null 结 尾 的 字 符 串 数 据 的 第 一 个 字 节

---- 如 果 DLL 过 程 需 要 一 个 LPSTR( 指 向 以 null 结 束 的 字 符 串 的 指 针) 作 为 参 数, 可 以 在VB 中 将 一 个 字 符 串 以 传 值 的 方 式 传 递 给 它。 因 为 指 向 BSTR 的 指 针 实 际 指 向 以 null 值 结 束 的 字 符 串 的 第 一 个 数 据 字 节, 所 以 对 于 DLL 过 程 来 说, 它 就 是 一 个 LPSTR。 这 样 传 入 动 态 连 接 库 的 字 符 串,DLL 过 程 也 可 以 对 它 进 行 修 改, 尽 管 它 是 以 传 值 方 式 传 入 的。 只 有 当DLL 过 程 需 要 一 个 指 向LPSTR 的 指 针 时, 才 以 传 址 的 方 式 传 入 字 符 串, 这 时DLL 过 程 得 到 的 是 一 个 指 向 字 符 串 指 针 的 指 针( 相 当 于C/C++ 中 的char * *), 而 不 是 通 常 所 用 的 字 符 串 的 首 地 址( 相 当 于C/C++ 中 的char *)。

---- 当 需 要 把 一 个 字 符 串 数 组 整 个 传 入 动 态 连 接 库 时, 情 况 就 变 得 复 杂 多 了, 用 传 递 简 单 数 据 类 型 数 组 的 方 式 来 传 递 字 符 串 数 组 是 行 不 通 的。 当 我 们 以 传 值 的 方 式 将 一 个 字 符 串 数 组 的 第 一 个 元 素 传 进 动 态 连 接 库 时,DLL 过 程 得 到 的 实 际 上 是 该 元 素 压 入 堆 栈 段 后 的 地 址, 而 不 是 数 据 段 中 整 个 数 组 的 首 地 址。 也 就 是 说, 这 时DLL 过 程 只 能 得 到 数 组 的 第 一 个 元 素, 而 无 法 访 问 整 个 数 组。 而 以 传 址 方 式 传 入 第 一 个 元 素 时,DLL 过 程 只 能 得 到 指 向 该 元 素 在 堆 栈 段 中 地 址 的 指 针, 同 样 无 法 访 问 整 个 数 组。 这 不 能 不 说 是VB 的 一 个 不 足。 因 此, 在 程 序 设 计 中, 如 果 确 实 需 要 将 整 个 字 符 串 数 组 传 入 动 态 库, 就 必 须 采 取 其 它 方 法。

---- 我 们 知 道, 在VB 中, 有 一 种Byte 数 据 类 型。 每 个Byte 型 变 量 占 一 个 字 节, 不 含 符 号 位, 因 此 所 能 表 示 的 范 围 为0 到255。 这 种 数 据 类 型 是 专 门 用 于 存 放 二 进 制 数 据 的。 为 了 将 整 个 字 符 串 数 组 传 进 动 态 库, 可 以 用 字 节 数 组 来 保 存 字 符 串。 由 于Byte 是 一 种 简 单 数 据 类 型, 因 此 字 节 数 组 的 传 递 是 非 常 简 单 的。 首 先, 需 要 把 一 个 字 符 串 正 确 地 转 变 成 一 个 字 节 数 组。 这 要 涉 及 一 些 字 符 集 的 知 识。Windows 95 和VB 使 用 不 同 的 字 符 集,Windows 95 API 使 用 的 是ANSI 或DBCS 字 符 集, 而VB 使 用 的 则 是Unicode 字 符 集。 所 谓ANSI 字 符 集, 是 指 每 个 字 符 都 用 一 个 字 节 表 示, 因 此 最 多 只 能 有28=256 个 不 同 的 字 符, 这 对 于 英 语 来 说 已 经 足 够 了, 但 不 能 完 全 支 持 其 它 语 言。DBCS 字 符 集 支 持 很 多 不 同 的 东 亚 语 言, 如 汉 语、 日 语 和 朝 鲜 语, 它 使 用 数 字 0-255 表 示 ASCII 字 符, 其 它 大 于255 或 小 于0 的 数 字 表 明 该 字 符 属 于 非 拉 丁 字 符 集; 在 DBCS 中,ASCII 字 符 的 长 度 是 一 个 字 节, 而 汉 语、 日 语 和 其 它 东 亚 字 符 的 长 度 是 2 个 字 节。 而Unicode 字 符 集 则 完 全 用 两 个 字 节 表 示 一 个 字 符, 因 此 最 多 可 以 表 示216=65536 个 不 同 字 符。 也 就 是 说,ANSI 字 符 集 中 所 有 的 字 符 都 只 占 一 个 字 节,DBCS 字 符 集 中ASCII 字 符 占 一 个 字 节, 汉 字 占 两 个 字 节,Unicode 字 符 集 中 每 个 字 符 都 占 两 个 字 节。 由 于VB 与Windows API 使 用 的 字 符 集 不 同, 因 此 在 进 行 字 符 串 到 字 节 数 组 的 转 换 时, 当 用Asc 函 数 取 得 一 个 字 符 的 字 节 码 后, 需 要 判 断 它 是 否 是 一 个ASCII 字 符; 如 果 是ASCII 字 符, 则 在 转 换 后 的 字 节 数 组 中 就 只 占 一 个 字 节, 否 则 要 占 两 个 字 节。 如 下 流 程 图 表 示 了 转 换 一 个 字 符 串 的 过 程。

---- 下 面 给 出 了 转 换 函 数:GetCharByte 得 到 一 个 字 符 的 高 字 节 或 低 字 节, 它 的 第 一 个 参 数 是 一 个 字 符 的ASCII 码, 第 二 个 参 数 是 标 志 取 高 字 节 还 是 低 字 节;StrToByte 按DBCS 或ANSI 格 式 将 一 个 字 符 串 转 换 成 一 个 字 节 数 组, 第 一 个 参 数 是 待 转 换 的 字 符 串, 第 二 个 参 数 是 转 换 后 的 一 个 定 长 字 节 数 组, 若 该 数 组 长 度 不 足 以 存 放 整 个 字 符 串, 则 截 去 超 长 的 部 分;ChangeStrAryToByte 利 用 前 两 个 函 数 将 字 符 串 数 组 转 换 成 字 节 数 组, 第 一 个 参 数 是 定 长 的 字 符 串 数 组, 其 中 每 个 元 素 都 是 一 个 字 符 串( 各 个 元 素 包 含 的 字 符 数 可 以 不 同), 第 二 个 参 数 是 一 个 变 长 的 字 节 数 组, 保 存 转 换 后 的 结 果。

---- Function GetCharByte(ByVal OneChar As Integer, ByVal IsHighByte As Boolean) As Byte ‘ 该 函 数 获 得 一 个 字 符 的 高 字 节 或 低 字 节

If IsHighByte Then
If OneChar >= 0 Then
GetCharByte = CByte(OneChar 256)
‘右移8位,得到高字节
Else
GetCharByte = CByte((OneChar
And &H7FFF) 256) Or &H80
End If
Exit Function
Else
GetCharByte = CByte(OneChar And &HFF)
‘屏蔽掉高字节,得到低字节
Exit Function
End If
End Function

Sub StrToByte(StrToChange As String, ByteArray() As Byte)
‘该函数将一个字符串转换成字节数组
Dim LowBound, UpBound As Integer
Dim i, count, length As Integer
Dim OneChar As Integer

count = 0
length = Len(StrToChange)
LowBound = LBound(ByteArray)
UpBound = UBound(ByteArray)

For i = LowBound To UpBound
ByteArray(i) = 0 ‘初始化字节数组
Next

For i = LowBound To UpBound
count = count + 1
If count <= length Then
OneChar = Asc(Mid(StrToChange, count, 1))

If (OneChar > 255) Or (OneChar < 0) Then
‘该字符是非ASCII字符
ByteArray(i) = GetCharByte(OneChar, True) ‘得到高字节
i = i + 1
If i <= UpBound Then ByteArray(i)
= GetCharByte(OneChar, False)
‘得到低字节
Else
‘该字符是ASCII字符
ByteArray(i) = OneChar
End If
Else
Exit For
End If
Next
End Sub

Sub ChangeStrAryToByte(StrAry()
As String, ByteAry() As Byte)
‘将字符串数组转换成字节数组
Dim LowBound, UpBound As Integer
Dim i, count, StartPos, MaxLen As Integer
Dim TmpByte() As Byte

LowBound = LBound(StrAry)
UpBound = UBound(StrAry)
count = 0
ReDim ByteAry(0)

For i = LowBound To UpBound
MaxLen = LenB(StrAry(i))
ReDim TmpByte(MaxLen + 1)
ReDim Preserve ByteAry(count + MaxLen + 1)
Call StrToByte(StrAry(i), TmpByte) ‘转换一个字符串
StartPos = count
Do
ByteAry(count) = TmpByte(count - StartPos)
count = count + 1
If ByteAry(count - 1) = 0 Then Exit Do
Loop ‘将每一个字符串对应
的字节数组按顺序填入结果数组中
ReDim Preserve ByteAry(count - 1)
Next i
End Sub

---- 下 面 看 一 个 转 换 的 例 子:

Dim ResultAry() as Byte
Dim SomeStr(2) as String
SomeStr(0) = " 测 试1"
SomeStr(1) = " 测 试222"
SomeStr(2) = " 测 试33"
Call ChangeStrAryToByte
(SomeStr,ResultAry) ‘ 转 换 字 符 串 数 组

---- 当 转 换 完 成 以 后, 查 看 字 节 数 组ResultAry, 其 中 包 含 了21 个 元 素, 依 次 是:178,226,202,212,49,0,178,226,202,212,50,50,50,0,178,226,202,212,51,51,0。 其 中,[178,226] 是" 测" 的 字 节 码,[202,112] 是" 试" 的 字 节 码,49,50,51 分 别 为 字 符1、2、3 的ASCII 码。 可 见, 经 过 转 换 后, 字 符 串 数 组 中 的 各 个 元 素 按 顺 序 放 在 了 字 节 数 组 中, 相 互 间 以 终 止 符0 分 隔。

---- 这 样, 字 符 串 数 组 就 全 部 转 换 成 了 字 节 数 组, 然 后 只 要 将 字 节 数 组 的 第 一 个 元 素 以 传 址 的 方 式 传 入 动 态 连 接 库,DLL 过 程 就 可 以 正 确 地 访 问 数 组 中 的 所 有 字 符 串 了。 但 是, 使 用 这 种 方 法, 当DLL 过 程 处 理 结 束 返 回VB 时,VB 得 到 的 仍 然 是 字 节 数 组。 如 果 需 要 在VB 中 再 次 得 到 该 字 节 数 组 表 示 的 字 符 串, 还 要 把 整 个 字 节 数 组 重 新 以0 为 分 割 符 分 成 多 个 子 数 组( 每 个 子 数 组 都 对 应 原 来 字 符 串 数 组 中 的 一 个 元 素), 然 后 使 用VB 函 数StrConv 将 每 个 子 数 组 转 换 成 字 符 串( 转 换 时 第 二 个 参 数 选vbUnicode), 就 可 以 显 示 或 进 行 其 它 操 作 了。 例 如, 其 中 一 个 子 数 组 的 名 字 是SubAry, 则 函 数StrConv(SubAry,vbUnicode) 就 返 回 了 它 所 对 应 的 字 符 串。

---- 总 之,VB 应 用 程 序 和 动 态 库 间 字 符 串 参 数 的 传 递 是 一 个 比 较 复 杂 的 过 程, 使 用 时 要 非 常 谨 慎。 同 时 应 尽 可 能 避 免 传 递 字 符 串 数 组 类 型 的 参 数, 因 为 这 很 容 易 引 起 下 标 越 界、 堆 栈 溢 出 等 严 重 错 误。

---- 3、 用 户 自 定 义 类 型(User-defined Type) 参 数 的 传 递

---- 用 户 自 定 义 类 型 在VB 中 是 一 种 重 要 的 数 据 类 型, 它 为 编 程 者 提 供 了 很 大 的 灵 活 性, 使 开 发 人 员 可 以 根 据 需 要 构 造 自 己 的 数 据 结 构。 它 相 当 于C/C++ 中 的 结 构 类 型(structure)。 在VB 中, 允 许 程 序 员 以 传 址 的 方 式 将 自 定 义 数 据 类 型 参 数 传 入 动 态 库,DLL 过 程 也 可 以 将 修 改 后 的 参 数 返 回VB 程 序。 但 是, 在VB 中 仍 然 不 支 持 以 传 值 的 方 式 传 递 用 户 自 定 义 类 型 参 数。

---- 传 递 用 户 自 定 义 类 型 参 数 时, 必 须 确 保VB 中 的 数 据 类 型 的 成 员 与 动 态 库 中 的 结 构 成 员 是 一 一 对 应 的, 所 占 空 间 也 必 须 严 格 一 致。 这 里 所 说 的 一 一 对 应, 不 仅 是 指VB 中 的 所 有 结 构 成 员 在 动 态 库 的 结 构 中 都 必 须 有 对 应 的 元 素, 而 且 它 们 在 数 据 结 构 中 定 义 的 顺 序 也 必 须 严 格 一 致, 这 是VB 中 使 用 的" 数 据 结 构 成 员 对 齐 方 式" 决 定 的。 在VB 中, 数 据 结 构 使 用 双 字 对 齐 方 式(4-byte alignment), 因 此, 在 用 户 自 己 生 成 用 于VB 调 用 的 动 态 连 接 库 时, 也 必 须 把 编 译 选 项"structure member alignment" 设 为4 字 节( 如 前 文 所 述)。

---- 所 谓 结 构 成 员 对 齐 方 式 是 指 一 个 数 据 结 构 内 部, 其 成 员 的 排 列 方 式。 譬 如, 在VB 中, 其 对 齐 方 式 是4 字 节, 这 就 好 象 在 一 个 数 据 结 构 内 部 分 成 了 很 多 个4 字 节 大 小 的 小 单 元, 如 果 相 邻 两 个 或 多 个 数 据 成 员 的 大 小 可 以 放 在 一 个 单 元 中, 那 么 就 放 在 一 起; 否 则 这 些 小 单 元 中 可 能 会 出 现 未 用 的 空 字 节。 我 们 来 看 下 面 一 个 数 据 类 型:

Type TestType
m1 as Integer
m2 as Byte
m3 as Long
End Type

---- 它 的 三 个 成 员 的 大 小 加 起 来 是2+1+4=7。 但 是, 由 于m1 和m2 的 字 节 总 长 度 是3, 小 于4, 它 们 就 存 放 于 一 个 单 元 中; 但 该 单 元 剩 下 的 一 个 字 节 不 足 以 放 下 一 个Long 型 的 成 员m3, 于 是m3 就 被 放 在 下 一 个 单 元 中, 它 们 之 间 就 有 了 一 个 未 用 的 空 字 节; 因 此, 整 个 结 构 所 占 实 际 长 度 是8 字 节。 同 理, 如 果 将m3 和m2 的 位 置 交 换 一 下, 它 所 占 的 尺 寸 就 变 成 了9 字 节。 可 见, 成 员 在 结 构 中 的 声 明 顺 序 也 是 非 常 重 要 的。

---- 通 常, 当 一 个 用 户 自 定 义 类 型 中 不 包 含 字 符 串 时, 向 动 态 连 接 库 中 传 递 该 类 型 的 参 数 是 没 有 什 么 问 题 的。 如 果 只 传 递 一 个 自 定 义 类 型 变 量, 则 既 可 以 传 递 该 变 量 名, 也 可 以 传 递 该 变 量 的 第 一 个 成 员, 它 们 的 效 果 是 一 样 的, 都 是 将 该 变 量 的 地 址 传 进 了 动 态 库; 同 样, 如 果 要 传 递 一 个 自 定 义 类 型 的 数 组, 则 既 可 以 传 递 该 数 组 的 第 一 个 元 素, 也 可 以 传 递 第 一 个 元 素 的 第 一 个 成 员。 但 是, 如 果 用 户 自 定 义 类 型 中 包 含 字 符 串 类 型 时, 又 该 如 何 与 动 态 连 接 库 传 递 参 数 呢 ? 答 案 是 令 人 遗 憾 的: 在VB 中, 你 无 法 将 一 个 包 含 字 符 串 成 员 的 用 户 自 定 义 类 型 变 量 或 数 组 安 全、 正 确 地 传 入 动 态 库 中。 如 果 你 这 样 做 了, 即 使 某 次 侥 幸 得 到 了 正 确 的 结 果, 在 其 背 后 也 隐 藏 着 许 多 致 命 的 危 险。 因 此, 如 果 一 定 要 在 用 户 自 定 义 类 型 中 包 含 字 符 串 变 量, 并 且 该 类 型 的 变 量 又 要 作 为 参 数 传 入 动 态 库 时, 你 最 好 修 改 类 型 定 义, 把 其 中 的 字 符 串 成 员 用 相 应 的 字 节 数 组 类 型 替 换 掉( 转 换 方 法 可 参 见 前 文), 这 样 就 可 以 在VB 和 动 态 库 间 传 递 这 种 类 型 的 参 数 了。

---- 另 外, 在VB 中 还 可 以 把 一 个 函 数 的 指 针 传 递 到 动 态 库 中, 方 法 也 并 不 复 杂。 但 笔 者 强 烈 建 议 最 好 不 要 这 么 做, 因 为 这 样 一 来VB 应 用 程 序 就 几 乎 完 全 丧 失 了 它 所 应 有 的 安 全 性。 如 果 确 实 需 要 传 递 函 数 指 针 的 话, 那 么 还 是 编 一 个C/C++ 的 程 序 来 完 成 这 项 工 作 吧。

---- 总 之, 在VB 中 调 用DLL 过 程 是 一 个 比 较 复 杂 的 问 题, 编 程 人 员 必 须 很 好 地 把 握, 才 能 达 到 既 提 高 了 程 序 效 率, 开 拓 了 程 序 功 能, 又 不 降 低 程 序 安 全 性 的 目 的。 另 外 需 要 特 别 指 出 的 一 点 是, 在 本 文 中 提 到 的 所 有 动 态 连 接 库, 都 是 指 没 有 使 用 自 动 化(OLE Automation) 技 术 的 动 态 库,Windows API 和 大 多 数 用 户 自 编 的 动 态 连 接 库 都 是 这 种 类 型 的。 对 于 使 用 了OLE Automation 技 术 的 动 态 连 接 库, 其 参 数 传 递 的 方 式 有 所 不 同, 读 者 可 以 参 阅 有 关OLE 技 术 的 书 籍, 在 此 不 再 涉 及。