|
|||||||
![]() |
![]() |
||||||
|
|||||||
|
调用 Windows 过程基本概念从 Win32Forth 中调用 Window 函数是很容易的 -- 只要您知道怎么做。这个教程告诉您与 DLL 接口的关键字,并通过一些简单的例子进行说明。 我们假设您使用的 Win32Forth 版本从内核级支持 CALL 和 PROC 语句。为了确认,可以打入 .LIBS 。 它的输出应该看起来像下面这样的(这就是说,在您的机器上,地址和库的列表可能与这里的不同,它们因操作系统和 Win32Forth 版本的不同而不同)。
基本的 DLLDLL ( 动态链接库 ) 和您在 PC 机上运行的程序类似,当然也有几个关键的差异。 DLL 通常没有一个可见的接口,也就是说,它们没有窗口。 DLL 可以在不同的进程间共享;不管有多少个程序使用它,只需要有一个拷贝驻留在存储器中。 DLL 通常为使用它的程序提供打包的功能,为了在程序中有效地使用 DLL ,您需要得到它们的文档。 在 Win32Forth 中使用的字调用外部的 DLL 需要有几个字,它们是: 调试字: .PROCS, .LIBS 定义字: PROC, WINLIBRARY 调用字: CALL, REL>ABS, ABS>REL 命名字: AS 步骤 1. 得到DLL的文档没有 DLL 的文档,您就不可能知道 DLL 包含的函数名字,也不可能知道它们的参数和返回值。主要的 Window DLL 文档可以在 msdn.microsoft.com 上得到,还有许多第三方的文档也包含了这些内容。对于其它的 DLL ,您需要参考与 DLL 一同来的文档。 步骤 2. 告诉 Win32Forth 使用 DLLWin32Forth 需要知道 DLL 的名字以便装载它们。语句 WINLIBRARY KERNEL32.DLL 允许 W32F 装入 DLL ,并准备执行输出子程序。 DLL 库的查找路径因操作系统 (95/98/ME/NT/2000/XP) 而不同,但是通常情况我们只需要使用位于系统目录、 Win32Forth 启动目录中的 DLL 库。大多数 DLL 可以通过这种方法找到,需要指定路径名的情况很少见。 WINLIBRARY 可以被重复指定,但是只有最后一个拷贝被装入。 .LIBS 命令显示了被指定和被装入的库。
这个字通常只从控制台输入。它显示出 TESTIT.DLL 已经指定但是还没有被装入。如果您在调用时发生 Win32F 找不到 DLL 的错误,而您又确定它应该在您的库中,请检查 handle 地址。如果这个地址仍然是 -noload- , 那么 W32F 就不能找到相应的库。参看 CALL 以得到更多的细节。 步骤 3. 告诉Win32Forth 外部子程序为了定义 DLL 中的函数,使用 PROC 语句。这里是一个从文档中来的 Windows 调用的例子:
这个函数可这样声明 0 PROC AllocConsole 也就是说,函数有 0 个参数,名字是 AllocConsole 。注意大小写 -- PROC 和 CALL 语句是大小写敏感的!您必须按上面显示的样子拼写。但是,参数的数目只是为了文档的需要。合法的参数数目可以从 0 到 127 -- 不要指定负数,因为这些负值用于指定特殊的系统过程。 这一步是可选的 -- 不需要非得写上一个 PROC 语句不可,因为 W32F 将在 CALL 语句中动态地定义 PROC 。 只有有限数目的过程可以动态定义,现在大约是 100 到 150 个。定义的过程可以使用 .PROCS 命令查看。限制输出是一个子串(大小写没有关系)
步骤 4. 调用函数现在已经我们定义了库,选择性地定义了过程,我们可以使用 CALL 语句调用一个函数: CALL AllocConsole 这里特别注意大小写 -- 必须与文档的大小写匹配。上面的调用执行之后,您应该得到一个 DOS 控制台和一个 0 在数据堆栈上,这个值是从函数返回的代码。 更高级的特性参数顺序Windows 调用的参数在堆栈上是按文档说明的相反顺序传递的。例如,如果文档说 BOOL Function (A, B, C); 则我们的编码应该是 C B A CALL Function 反序对于调用成功来说是基本的。 过程的名字有些函数显示出多达 3 个名字。例如,有一个函数 CharUpperBuff 、 CharUpperBuffA 和 CharUpperBuffW 。 事实上,它们只有两个名字。请看下面这个例子的输出
ProcEP 列显示了这个函数的入口点。 CharUpperBuff 和 CharUpperBuffA 函数是同一个函数,名字是 CharUpperBuffA , A 函数表示这个函数是一个访问 ASCII 字符的函数,字符宽度是 8 位。 W 函数是一个 UNICODE 或者 2 字节函数。通常,您应该使用 A 函数,因为 W32F 是一个基于字节的系统。任何 W32F PROC 或者 CALL 一个函数都有 A 和 W 的变形,不指明时就自动装入 A 版本。如果您真的想要 W 函数,则您必须指明: 2 PROC CharUpperBuffW . 返回值从函数返回的值通常都是在调用之后留在数据堆栈顶上的一个项目。大多数函数都是用 C 或者 C++ 编写的,不能返回多个值,如果需要返回多于一个的值,还必须使用其它的方法。 返回值可以是: BOOL (0 = TRUE 或者 OK, 1 = FALSE 或者 Not OK) 。为了把这个值转换成 Forth 的 TRUE / FALSE , 可在调用之后使用 0= 或者 ABORT" 比如 CALL AllocConsole 0= IF ( all is OK ) 或者 CALL FreeConsole ABORT" FreeConsole failed" INT 、 HANDLE 等,所有这些都是堆栈上的无符号或者有符号的 32 位值 (DWORD) 。可以把它们作为一般的 Forth 字使用。 VOID 堆栈上的 32 位值 (DWORD) ,没有意义,应该丢弃(使用 DROP )。 其它,请看下面内容。 解释函数参数函数的参数各不相同,为了使调用能够正确地工作,这些参数不仅需要有正确的顺序,还必须有正确的类型。这里给出了如何指定不同的参数。例如:
变成了:
HANDLE 、 BYTE 、 WORD 、 DWORD: 都是简单的堆栈值,或者是如上面例子中指定值的名字。如果一个 HANDLE 值是 13 ,那么 13 应该在堆栈上。 LPxxx 类型 ( 除 STR 外 ): 指向一个值的指针。如果您使用 VALUE <name> 作为 LPxxx 参数时请特别小心 – 它们通常不能正常工作,特别是当函数返回一个值时,比如在 BYTESREAD 的情况下。使用 VARIABLE ,它可以把地址放到堆栈上。 LPxSTR 类型 : Null (0) 结束的串。上面的方法可以使用,比如: : UPPERCASE ( caddr -- caddr ) 这个函数得到一个计数串,把它转成 (addr len) ,再转换到 (len addr) 。这种计数在后、串指针在先的格式对调用是必要的。函数在堆栈上返回一个值(在本例中是 n ),不过我们并不关心。实际上,上面的例子并不是一个真正的 null 结束串的调用,下面是一个: int lstrlen(LPCTSTR lpString); 这将计数串中的字符。于是, Z" ABCDEFG" CALL lstrlen 返回 7 ,这是串的长度。 在 W32F 中,下面这些串类型是 null (0) 结束的串,可以安全地用在调用中: C" – 计数的字节也被同样计算。也就是说, C" ABCDEFG" COUNT DROP 将指向开始的 A S" ABCDEFG" 把 (addr len) 放到堆栈上。地址指向一个 null 结尾的串。或者 DROP 或者 SWAP 依赖于您对计数的需要。 Z" ABCDEFG" 与 C" 相同,但是没有计数字节,所以 Z" 能够直接使用。 如果您希望创建自己的串缓冲区,这里是一个创建 null 结尾串的技术:
使用 ABS>REL 和 REL>ABS (旧的特性)ABS>REL 和 REL>ABS 是旧的特性。不要再使用了,如果您在现存的代码中见到它们,就简单的忽略。它们不做任何事情。 使用 AS有时我们可能有这样的 Forth 字,它需要得到过程的 XT (可执行标记,或称代码域地址);有时候是 DLL 的过程名字太长,或者意义隐晦,我们需要一个有意义的、短小的名字。我们可以这样: 1 PROC ExitThread AS EXIT-TASK 可以把 EXIT-TASK 加入到当前的字列表中。 AS 后面应该是过程的声明 ( 如果需要,可以在任何的注释之后 ) 。 危险的技巧这是一个危险的技巧,请在您对调用一个 DLL 函数更加熟悉之后再使用。对于函数 BOOL function(LPWORD param) ; ( 假设它返回 1, 并把一个 0 放到指向的区域 ) ,您可以像下面这样: 0 SP@ CALL function 在 CALL 之前,堆栈看起来是这样的: 0 | addr of previous cell | 在调用之后,堆栈应该是 10 | 1 。 不需要变量! ReadFile 调用看起来是这样的:
将在堆栈返回值 2 、读入的字节数和返回码,并按这里的顺序放置。 |
||||