📄 46.asp
字号:
<DIR>
<P>Type POINTAPI ' POINTAPI 为一自定型别</P>
<P>x As Long</P>
<P>y As Long</P>
<P>End Type</P>
<P>Declare Function GetCursorPos Lib "user32" Alias "GetCursorPos"
(lpPoint As POINTAPI) As Long</P>
<P> </P>
</DIR>
</DIR>
<P>而呼叫的例子则是:</P>
<P> </P>
<DIR>
<DIR>
<P>Dim p As POINTAPI</P>
<P>ret = GetCursorPos ( p )</P>
<P>Print p.x, p.y ' p 传回滑鼠的位置</P>
<P> </P>
</DIR>
</DIR>
<P>如果 API 函数的参数中含有自订型别, 则除了 API 函数的宣告式之外, API
所使用的自订型别(例如 GetCursorPos 的 POINTAPI)也必须放到 VB 程式中, 此时
API 检视员的操作方法如下:</P>
<P> </P>
<DIR>
<P>1. 选取「API 类型」底下的「型态(Types)」, 接著「可选用的项目」底下所列示的不再是
API 函数的宣告式, 而是 API 函数的自订型别。</P>
<P> </P>
<P>2. 接下来是选取并且复制自订型别的定义到剪贴簿中, 然後再从剪贴簿复制到
VB 程式中。</P>
<P> </P>
</DIR>
<CENTER><P><FONT SIZE=+1>字串的传递</FONT>
<HR WIDTH="100%"></P></CENTER>
<P> </P>
<P>在 API 函数中, 字串参数的宣告只有「ByVal 参数名 As String」一种形式(没有「参数名
As String」形式), 按照 VB 习惯, 这是「传值」呼叫, 但实际上, API 对於所有字串参数的处理, 却一概以「传址」视之, 所以当我们传递字串到
API 函数时, 心理上应有的准备是「这个字串的内容可能會被 API 改变掉」, 以
GetWindowText(读取视窗的标题) API 为例, 其宣告式如下:</P>
<P> </P>
<DIR>
<P>Private Declare Function GetWindowText Lib "user32" Alias
"GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String,
ByVal cch As Long) As Long</P>
<P> </P>
</DIR>
<P>表面上 lpString 参数为传值呼叫, 但执行以下的呼叫式之後, S 的内容却會改变(将等於
Form1 视窗的标题):</P>
<P> </P>
<DIR>
<P>Dim S As String</P>
<P>S = "某字串................................."</P>
<P>ret = GetWindowText(Form1.hwnd, S, Len(S) )</P>
<P> </P>
</DIR>
<P>当然不是所有字串参数的内容都會被改变, 以下面的 SetWindowText 呼叫式为例, API
就只會读取字串 S 的内容, 而不會改变它:<BR>
</P>
<DIR>
<P>Dim S As String</P>
<P>S = "某字串................................."</P>
<P>ret = SetWindowText(Form1.hwnd, S)</P>
<P> </P>
</DIR>
<P>在呼叫含有字串参数的 API 函数以前, 我们必须先弄清楚 API 函数的本质, 确定
API 函数对於传入的参数是否會进行写入的动作, 因为两者在 VB 程式端应预先准备的工作并不相同。稍後笔者會进一步说明。</P>
<P> </P>
<CENTER><P><FONT SIZE=+1>Any </FONT>型别的参数传递
<HR WIDTH="100%"></P></CENTER>
<P> </P>
<P>所谓 Any 型别的参数, 指的是 VB 程式可以传入数值、字串、或自订型别…等资料的参数, 至於可以传入哪一种资料, 则与个别
API 函数有关系。Any 型别可说是 API 的参数类型中最为邪恶的一种, 因为如果传入的参数不正确, 轻则结果错误, 重则出现「这个程式执行的作业无效, 即将关闭」, 表示程式当掉了!</P>
<P> </P>
<P>有哪些 API 函数含有 Any 型别的参数呢?还好不多, 而且使用过一两个之後, 就能够掌握参数传递的原则, 未来本讲座所介绍的
API 函数若属於此一性质, 也會特别说明。</P>
<P> </P>
<H2>
<HR WIDTH="100%">VB 字串 vs. API 字串
<HR WIDTH="100%"></H2>
<P> </P>
<P>其实以上所介绍几种的参数类型中, 最大宗还是「字串」的传递, 而且对初学者来说, 也是最容易犯错的。</P>
<P> </P>
<CENTER><P><FONT SIZE=+1>VB </FONT>的字串
<HR WIDTH="100%"></P></CENTER>
<P> </P>
<P>VB 字串可分成「非固定长度」及「固定长度」两种, 如下:</P>
<P> </P>
<DIR>
<DIR>
<P>Dim S1 As String ' 非固定长度字串<BR>
Dim S2 As String*80 ' 固定长度字串</P>
<P> </P>
</DIR>
</DIR>
<P>两者的差别除了长度是否可变之外, 非固定长度字串會在字串的最尾端补上 Chr(0)
字元, 以 "VB5" 字串为例, 在记忆体内部其实含有 "VB5"+Chr(0)
共 4 个字元, 至於固定长度的字串则會先用「空白」字元补满整个字串, 然後才补上
Chr(0) 字元, 以下面的 S2 字串为例:</P>
<P> </P>
<DIR>
<DIR>
<P>Dim S2 As String * 80<BR>
S2 = "VB5"</P>
<P> </P>
</DIR>
</DIR>
<P>在 S2 的记忆体中所包含的字元则有 "VB5"+77个空白字元+Chr(0)。</P>
<P> </P>
<P>虽然 VB 會在字串的尾端补上 Chr(0) 字元, 但这个字元并不會计入字串的长度中, 以
"VB5" 为例, 虽然记忆体内部是 "VB5" + Chr(0), 但长度依然等於
3。其实在 VB 的字串中, 除了补上 Chr(0) 字元之外, 还會在字串的前面预留 4
个字元来记录著字串的长度, 结构如图-3:</P>
<P> </P>
<CENTER><P><IMG SRC="46-3.gif" HEIGHT=47 WIDTH=319></P></CENTER>
<CENTER><P>图-3 VB 字串的结构</P></CENTER>
<P> </P>
<P>当我们呼叫 Len() 函数时, VB 不是计算 Chr(0) 的位置来求取字串的长度, 而是直接取出记录於字串中的长度。</P>
<P> </P>
<CENTER><P><FONT SIZE=+1>API </FONT>的字串
<HR WIDTH="100%"></P></CENTER>
<P><FONT SIZE=+1> </FONT></P>
<P>相对於 VB 的字串, API 的字串并不會记录「字串的长度」, 而它计算字串长度的方法是判断
Chr(0) 字元, 其结构如图-4:</P>
<P> </P>
<CENTER><P><IMG SRC="46-4.gif" HEIGHT=73 WIDTH=273></P></CENTER>
<CENTER><P>图-4 API 字串的结构</P></CENTER>
<P><BR>
虽然 VB 的字串与 API 的字串结构不同, 但是当我们传递 VB 的字串到 API 时, VB
只會传入图-3「字串的内容+Chr(0)」的部分, 如此才可以配合 API 的字串结构。</P>
<P> </P>
<CENTER><P><FONT SIZE=+1>解决字串的不一致性问题</FONT>
<HR WIDTH="100%"></P></CENTER>
<P> </P>
<P>尽管 VB 已经把传入 API 的字串弄得跟 API 的字串结构一样, 但还是可能有问题, 直接来看例子, 请比较以下两个呼叫
SetWindowText 的例子:</P>
<P> </P>
<DIR>
<P>ret = SetWindowText(Form1.hwnd, "某字串")<BR>
Dim S As String * 80</P>
<P>S = "某字串"</P>
<P>ret = SetWindowText(Form1.hwnd, S)</P>
<P> </P>
</DIR>
<P>第一个 SetWindowText 呼叫式是直接传入「字串常数」, 结果没有问题, 但传入「固定长度」字串
S 的第二个 SetWindowText 呼叫式却有点小问题, 因为「S = "某字串"」其实表示「S
= "某字串"+77个空白字元+Chr(0)」, 所以它比 "某字串"
要多出 77 个空白字元, 如果希望 S 只传入 "某字串"+Chr(0), 则程式修正的方法如下:</P>
<P> </P>
<DIR>
<P>S = "某字串" + Chr(0)</P>
<P>ret = SetWindowText(Form1.hwnd, S)</P>
<P> </P>
</DIR>
<P>接下来让我们再来检视會改变字串内容的 API 函数, 例如:</P>
<P> </P>
<DIR>
<P>Dim S As String * 80</P>
<P>ret = GetWindowText(Form1.hwnd, S, 80 )</P>
<P> </P>
</DIR>
<P>以上的 GetWindowText 函数會把 Form1 视窗的标题填入 S 字串中, 然後传回来。(注:以上的参数三表示参数二的长度, 目的是告诉
GetWindowText 参数二的长度只有 80 个字元, 如此一来, 即使 Form1 的标题超过
80 个字元, GetWindowText 也不會把资料写出 80 个字元的范围, 如此可避免破坏其他资料)</P>
<P> </P>
<P>以上的呼叫式有没有问题呢?让我们假设 Form1 的标题是 "Form1", 然後来检视执行的结果, 由於
API 以 Chr(0) 为字串的结束字元, 因此当 GetWindowText 将 "Form1"
指定给 S 字串时, 实际的动作等於「S = "Form1" + Ch(0)」, 结果使得
S 变成「"Form1"+Ch(0)+74个空白字元+Chr(0)」, 这样的结果虽然不算错, 但请注意
S 字串中所记录的长度仍然等於 80, 如图-5(a), 而实际上我们期望的结果应该如图-5(b)。</P>
<P> </P>
<CENTER><P><IMG SRC="46-5.gif" HEIGHT=71 WIDTH=307></P></CENTER>
<CENTER><P>图-5 字串的结果不是我们期望的</P></CENTER>
<P> </P>
<P>为了让 S 能够得到我们期望的图-5(b), 呼叫 GetWindowText 之後, 应该将
S 第一个 Chr(0) 之前的字串指定给另一个非固定长度字串, 如下:</P>
<P> </P>
<DIR>
<P>Dim S As String * 80<BR>
Dim Sx As String</P>
<P>ret = GetWindowText(Form1.hwnd, S, 80 )</P>
<P>Sx = Left( S, InStr(S, Chr(0)) - 1 )</P>
<P> </P>
</DIR>
<P>则 Sx 就刚好等於 "Form1"。</P>
<P> </P>
<P>GetWindowText 是直接把 Chr(0) 补在字串参数中, 藉以告诉呼叫端传回字串的长度, 有些函数则會把字串的长度当成参数传回来, 以
RegQueryValue 为例:</P>
<P> </P>
<DIR>
<DIR>
<P>Declare Function RegQueryValue Lib "advapi32.dll" Alias "RegQueryValueA"
(ByVal hKey As Long, ByVal lpSubKey As String, ByVal lpValue As String,
lpcbValue As Long) As Long</P>
<P> </P>
</DIR>
</DIR>
<P>参数三 lpValue 是传入的字串, 参数四则是传回的字串长度, 若呼叫此类函数, 则呼叫之後可利用
Left 函数设定正确的传回字串, 如下:</P>
<P> </P>
<DIR>
<DIR>
<P>Dim L As Long<BR>
ret = RegQueryValue(hKey, KeyName, S, L)<BR>
Sx = Left( S, L )</P>
<P> </P>
</DIR>
</DIR>
<P><FONT SIZE=+1>传递的字串常见的错误</FONT></P>
<P> </P>
<P>其实我们可以把 API 的字串参数分成「唯读」及「可写入」两种, 以 SetWindowText
为例, API 會读取字串参数, 然後将参数内容设定给视窗的标题, 所以字串参数是唯读的, 以
GetWindowText 为例, API 會读取视窗的标题, 然後设定给字串参数, 所以字串参数是可写入的, 在使用上, 可写入的参数比较容犯以下两种错误:</P>
<P> </P>
<DIR>
<DIR>
<P>(1) </P>
<P>Dim S ' 未将 S 宣告成「字串」型别<BR>
S = Space(80)<BR>
ret = GetWindowText(Form1.hwnd, S, 80 )</P>
<P> </P>
<P>(2)</P>
<P>Dim S As String ' S 为一空字串<BR>
ret = GetWindowText(Form1.hwnd, S, 80 )</P>
<P> </P>
</DIR>
<P>◇ 错误(1):没有把 S 宣告成「字串」, 这将使得 S 无法正确地转换成 API
的字串, 所以呼叫 API 函数之後, 仍然无法得到正确的结果。<BR>
</P>
<P>◇ 错误(2):传入 API 函数的字串 S, 没有足够的空间容纳 API 函数所写入的字串, 此时
API 函数将會破坏 S 之後的资料, 而此一破坏资料的行为极可能让程式当掉。</P>
<P> </P>
</DIR>
<H2>
<HR WIDTH="100%">准备工作
<HR WIDTH="100%"></H2>
<P> </P>
<P>本期所介绍的内容只能算是 Windows API 的初步, 但也许这是您第一次接触
Windows API, 所以笔者并没有打算一开始就给您满汉大餐, 如果您有意继续跟著笔者进入
Windows API 的世界, 建议您先熟悉 API 检视员的使用, 然後试著写程式检测本期所介绍过的
API 函数, 在检测的过程中, 一定會遭遇不少挫折, 但这是正常的现象, 当然, 如果您想比对笔者所写的呼叫范例, 可进入网站下载, 下一期起笔者将會开始介绍
Windows API 中比较实用的函数。</P>
<P> </P>
<P> </P>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -