2012-3-22 AI程序设计 1
第 15章 与其他编程语言接口
2004.11.3 AI程序设计 2
第 15章 与其他编程语言接口
? 本章介绍 Visual Prolog与其他编程语言的接口, 帮助
读者学会如何在 Visual Prolog中调用 WIN32 API,打
开更广阔的编程世界的大门 。
2004.11.3 AI程序设计 3
第 15章 与其他编程语言接口
15.1 外部代码
15.2 关键问题
15.3 调用约定和链接名
15.4 数据表示
15.5 存储管理
15.6 Win32 API函数
2004.11.3 AI程序设计 4
15.1 外部代码
? 所谓, 外部代码, 是指用其它编程语言 ( 而不是用 Visual Prolog)
所编写的代码 。
? Visual Prolog能直接调用其它语言代码 。 本章就来解释这些概念
和细节 。 直接调用外部代码是一种二进制级的底层调用, 而非高级
语言层面的高级调用 。 这在简单的例程中相当简单, 但也可能出奇
的复杂 。 可以肯定的一点是:处理复杂调用需要非常熟悉 Visual
Prolog和其它编程语言 。 但是不要担心, 实际上, 在许多例程中
所需要的交互是相当简单的
2004.11.3 AI程序设计 5
15.2 关键问题
? 其它语言编译器和 Visual Prolog编译器有很大的不同, 这是由于
它们是由不同的人所制作的, 而且它们必须支持不同的语言特性 。
Visual Prolog不可能和所有的外部语言代码交互, 因为它不可能
知道其他编译器所采用的规则 。 所以, 要实现 Visual Prolog和其
他语言代码的交互, 就要求这些代码必须遵循规定的方式 。
? 为了调用外部代码 (异种语言代码 ),我们就必须访问这些代码 。 本
章我们要处理的代码是直接链接到程序里, 或者位于一个动态连接
库 DLL中 。
2004.11.3 AI程序设计 6
15.2 关键问题
? 首先, 我们必须查找这些代码的位置 。 这通过一个名字来实现 。 如
果代码是直接链接到你的程序里, 就必须使用链接名;如果代码位
于一个 DLL,则可以使用导出名 。 无论是用链接名还是用导出名,
对 Visual Prolog来说没有区别, 但是当你试图在外部代码 ( 或系
统 ) 中寻找这个名字时, 就有了区别 。 我们这里提到系统, 是因为
有时你必须使用的名字在代码中根本不存在 。 因此, 对于这个概念,
我们仅使用链接名 。
2004.11.3 AI程序设计 7
15.2 关键问题
? 其次,在我们已经确定了外部代码的位置之后,接着必须传递输入
参数并调用代码,代码被执行后,还必须获取其输出等等。有许多
不同的途径去完成此过程。显然,调用者和被调用者必须在这一点
达成一致,即双方必须有调用约定。
? 第三,关于数据表示。但是,不仅调用双方必须在参数等的传递上
相一致,更重要的是双方要以相同方式解释所传递的字节。换句话
说就是,双方的数据表示必须相同。
2004.11.3 AI程序设计 8
15.2 关键问题
? 最后要注意的一点是内存管理。调用者和被调用者必须明确在必要
时由哪一方分配和释放内存。如果释放内存和分配内存不是由同一
方进行,那么释放内存一方必须以正确的方式释放内存。
? 总之,调用外部代码有四个关键问题需要考虑,
? 链接名
? 调用约定
? 数据表示
? 存储管理
2004.11.3 AI程序设计 9
15.3 调用约定和链接名
? 链接名(或导出名)用于识别你想要调用的代码。不同的编译器用
不同的缺省链接名,许多编译器有多种指定链接名的途径。在
Visual Prolog中,你可以用保留字,as”为一个谓词声明一个链接
名。如,
predicates
pppp, (integer) as "LinkName",
? 在 Visual Prolog程序中,上面的谓词被命名为,pppp”,但是它的
链接名是,LinkName”。
2004.11.3 AI程序设计 10
15.3 调用约定和链接名
? 注意:仅类谓词有一个链接名,这意味着它必须在一个类中进行声
明或在一个类实现的类谓词( class predicates)段进行声明。
? Visual Prolog支持大量不同的调用约定。这些约定也在谓词中声
明,但用的是 language保留字。
predicates
qqqq, (integer) language c,
2004.11.3 AI程序设计 11
15.3 调用约定和链接名
? 按照 C编译器的调用约定,编译器在 C程序中的名字前添加一个下
划线来创建链接名。如果你用 C调用约定但不提供链接名,Visual
Prolog也将使用这个约定。注意:一直到 build 6107编译器实际
上使用的是另一种命名策略,也就是说你必须用 as来获取链接名。
上例中 qqqq的链接名是,_qqqq”。如果你声明一个链接名,就必
须严格遵守这个规则,
predicates
rrrr, (integer) language c as "LinkName",
? rrrr将用这个 C调用约定,并有一个链接名 LinkName(即没有下
划线)。
2004.11.3 AI程序设计 12
15.3 调用约定和链接名
? C++编译器通常用的是 C调用约定, 但是它们不依赖 C链接名, 因
为 C++允许重载 。 也就是说, 在 C++中相同的名字只要变量数目
不同或者和类型不同, 就可以看作为不同的函数使用 。 这些不同变
量必须有不同的链接名 。 因此 C++编译器具有完善的命名机制,
这种命名基于 C++名字, 变量的数目和类型 。 这个过程可称为
,命名熨烫 ( name mangling), 。
? 不同的编译器采用不同的命名熨烫算法, 从而互不兼容 。 通常情况
下, 在被作为外部代码访问的 C++程序中要使用明确的链接名,
或者在一个输出, C”部分封装这个声明, 使编译器使用 C命名约定 。
2004.11.3 AI程序设计 13
15.3 调用约定和链接名
? Visual Prolog也支持 stdcall调用约定 。 Microsoft Visual Basic
使用 stdcall作为 Microsoft Win32平台 API调用的调用约定, 许多
Pascal家族编译器, 包括 Borland Delphi也使用 stdcall。 实际上
C和 C++程序和其他语言的程序接口时, 通常也使用 stdcall调用
约定 。 Visual Prolog对 stdcall调用约定使用与 C调用相同的命名
约定 。
? 注意:直到 build 6107编译器实际上使用的是另一种命名策略,
因此必须用 as来获取链接名 。 即在 Prolog名字前面加前缀下划线
来创建链接名, 但是如果指定了一个链接名, 它将被严格使用 。
2004.11.3 AI程序设计 14
15.3 调用约定和链接名
? Microsoft Win32 API使用特殊的 stdcall调用约定, 它较 C调用约
定多了一些名字修饰 。 可参阅 Visual Prolog语言参考手册 。
Visual Prolog有一个专用的调用约定 apicall来支持这个特殊的
stdcall。 apicall和 stdcall仅在名字修饰上有不同之处 。 有了
apicall,用 as声明的外部链接名也要被修饰 。 若需要一个有不同
修饰的名字, 就必须用 stdcall并自己指定修饰名 。
2004.11.3 AI程序设计 15
15.4 数据表示
? 所输入的数字参数按其值进行传递,即数值直接压入调用堆栈。输
出数字参数通过引用传递,即结果指针被压入调用堆栈。调用堆栈
中整型数占 32位,实型数占 64位。
? 字符也可以用数字表示。
? 其他数据用一个指针来指向。通过直接将指针压入调用堆栈来传递
输入参数。通过压入结果指针的指针(即指向指针的指针)来传递
输出参数。
2004.11.3 AI程序设计 16
15.4 数据表示
? 一个算符的论域由指向一块内存的指针来表示,这块内存首先保存
了算符,随后是每一个子部件。这些子部件直接由一个数字或指向
真实数据的指针来表示。
? 如果一个算符的论域只有一种选择,这种情况下通常具有相同的值,
所以跳过这个算符。注意:在 Visual Prolog 5中,除非该论域用
struct关键字声明,否则该算符出现;而在 Visual Prolog 6中,
该算符永远不会出现。
2004.11.3 AI程序设计 17
15.4 数据表示
? 用 align限定符可以使算符表示有所不同,请参阅语言参考的相关
部分。
? 字符串由一个指针表示,它指向由零值空字符终止的字符序列,与
C语言一样。
? 二进制论域由一个指针指向其二进制数据。其值等于数据长度加
unsigned类型的大小,这个值紧接着存储在数据之前。
2004.11.3 AI程序设计 18
15.4.1 举例
? 假设想在 Visual Prolog程序中调用一个 C例程。在 C中的声明如下,
int myRoutine(
wchar_t * TheString,
int BufferLength,
wchar_t * TheResult );
,wchar_t *”是 C语言中的 unicode字符串。
? 这个函数有 3个变量,
? TheString是一个字符串
? Bufferlength是一个整型数
? TheResult是一个字符串
? 该函数返回一个整型值。
2004.11.3 AI程序设计 19
15.4.1 举例
? 相应的 Visual Prolog 6声明如下(假定它被声明在一个类中),
class predicates
myRoutine, (
string TheString,
integer BufferLength,
string TheResult)
-> integer
language c,
? 若谓词在类中声明,则,class”应去掉。
2004.11.3 AI程序设计 20
15.4.2 外部链接库
? 如果声明的谓词如上例所示, 编译器就提示一个错误:找不到声明
谓词的子句 。
? 因为这个谓词根本不能在 Visual Prolog中进行实现 。 这时必须通
知编译器这个谓词的代码在外部 ( externally) 某处 。 这里我们之
所以使用 externally这个单词, 是因为它完全可以准确地表达在声
明该谓词的类的实现中用一个所谓的求解限定符所要表达的意思 。
2004.11.3 AI程序设计 21
15.4.2 外部链接库
? 如果这个类命名为 xxx,则它应该是,
implement xxx
resolve
myRoutine externally
,.,
? 这样编译器就接受了这个声明,但还可能出现链接错误,
_myRoutine没有定义。
? 这是因为包含 _myRoutine的库还没有链接。
2004.11.3 AI程序设计 22
15.4.2 外部链接库
? 如果 _myRoutine位于一个静态库 ( 一个 LIB文件 ), 则要把这个
文件加入项目, 链接器就会提取相关代码并加入程序 。
? 如果 _myRoutine位于一个 DLL库, 仍有一个 LIB文件用以描述这
个 DLL,接着就把这个文件加入项目 。 在这种情况下, 链接器在你
的程序中放置信息, 这些信息告知在哪里可以找到 DLL例程 。
2004.11.3 AI程序设计 23
15.4.2 外部链接库
? 如果 _myRoutine位于一个 DLL库,但是没有对应的 LIB文件,可
以通过下面方式调用该例程,
implement xxx
resolve
myRoutine externally from "myDLL.dll"
,.,
? 这样,当 myRoutine被首次调用时,编译器就增加代码,动态加
载 DLL库。这个 DLL库在程序退出之前不卸载。
? pfc/application/useDll可以用于动态加载和卸载 DLL库
2004.11.3 AI程序设计 24
15.5 存储管理
? 数字和字符参数被直接压栈或写入内存 。 这种情况比较简单 。
? 然而, 不直接压栈的数据涉及许多内存管理问题 。 因为只要调用双
方的任一方要访问这个数据, 数据都必须存在于内存 。 一旦数据不
再需要, 就必须回收内存以防止内存漏掉 。
? 通常情况下, 谁分配内存谁才能去回收这些内存, 因为另一方并不
知道如何回收 。 当然我们可以让双方采用相同的机制 。 例如, 一方
可以让另一方看到它的机制 ( 如作为导出例程 ) 。
? Visual Prolog 6使用的内存分配例程是采用运行时间系统
( Vip6kernel.dll) 来实现的, 并且可以从外部代码进行调用 。
? 程序和库代码在处理何时回收内存时, 通常采用一些事先已约定的
方法 。
2004.11.3 AI程序设计 25
15.5.1 典型解决方案
? 这一节介绍解决内存管理问题的通用方法。不涉及 COM和,NET,
这是我们认为最为通用的方法。原理很简单,
? 输入参数仅在调用期间存活,所以,如果被调用者需要存储某一输
入参数以备后用,它就必须将这个参数复制到自己的内存区。
? 输出被复制到调用者的内存缓冲区,所以被调用者分配的内存不会
传送给调用者。
? 前面的 myRoutine就是一个典型的例子。输出写入 TheResult
(由调用者分配的一个字符串),这就是为什么它看起来既是例程
的输入又要传递 BufferLength的原因。
2004.11.3 AI程序设计 26
15.5.1 典型解决方案
? Visual Prolog 6程序调用 myRoutine例程,
clauses
p(TheString) = TheResult,-
TheResult = string::create(bufferLength),
RetCode = myRoutine(TheString,bufferLength,
TheResult),
checkRetCode(RetCode),
? sring::creat为字符串分配内存, myRoutine将它的结果复制到
TheResult( Visual Prolog程序不涉及这些 ), 它不回收任何已
分配的内存 。
2004.11.3 AI程序设计 27
15.5.2 垃圾收集和全局堆栈
? 如果不采用上面的典型方案, Visual Prolog 6还提供一种全局性
的 G-堆栈, 并且用一个垃圾收集箱来管理堆 。
? 关于 G-堆栈, 在这里我们建议:如果不采用典型解决方案, 就不
要迁移 G-堆栈存储器 。 如果你不确定数据是否在 G-堆栈, 那么可
以用谓词 memory::toPersistentStorage来获得一个副本, 以确
保不处于 G-堆栈之中 。
2004.11.3 AI程序设计 28
15.5.2 垃圾收集和全局堆栈
? 应该注意到, 垃圾收集箱是一种循环回收机制:当某块内存不再需
要时, 不应该立即回收, 而要看一看在外部代码中是否还要访问它 。
通常这都会自动处理, 不存在什么问题 。 但是垃圾收集箱不知道
( 也不必知道 ) 数据在外部代码中何时存在 。 因此数据在 Visual
Prolog程序中存在的时间要长于在外部代码中存在的时间 。
? 一种典型的保留内存的方法是将它插入事实段, 然后在外部代码不
再需要它时回收它 。
2004.11.3 AI程序设计 29
15.6 Win32 API函数
? 也许你的程序从来不会调用其它编程语言的代码, 因此你会认为学
习这一部分没有必要 。 但要知道整个操作系统对于你的程序来说都
是外部的, 因此学习 Win32 API很有必要 。
? 操作系统提供成千上万有趣的外部例程, 你可以利用本章的原则来
调用它们 。 这些例程称为 Microsoft Windows XXX API函数 。 不
同平台提供的 API有所不同, 例如 Windows XP比 NT 4.0提供更
多的例程 。
? 各平台 API的在线文档可参见 MSDN库 。
2004.11.3 AI程序设计 30
15.6 Win32 API函数
? 从 Prolog的观点来看, 这个文档有一个问题:它用到许多字符常
量, 但却没有定义这些常量的值 。 例如, 例程 PlaySound的文档
告诉我们可以用标志参数 SND_ASYNC在程序中异步播放声音
( 即在应用程序继续执行期间播放声音 ) 。 SNC_ASYNC实际上
是一个常数, 但文档中并没有说明这个常数的值 。
? 从平台 SDK更新处下载 SDK C/C++,在其中的 *.h头文件中可以
找到这些常量的宏定义 。
2004.11.3 AI程序设计 31
15.6 Win32 API函数
? 至此,你应该能编写类似下面的代码了,
implement playSoundFile
open core
resolve playSound externally
domains
soundFlag = unsigned32,
class predicates
playSound, (string Sound,
pointer HModule,
soundFlag SoundFlag)
-> booleanInt Success
language apicall,
constants
snd_sync, soundFlag = 0x0000,
/* play synchronously (default) */
snd_async, soundFlag = 0x0001,
/* play asynchronously */
snd_nodefault, soundFlag = 0x0002,
2004.11.3 AI程序设计 32
15.6 Win32 API函数
/* silence (!default) if sound not found */
snd_memory, soundFlag = 0x0004,
/* Sound points to a memory file */
snd_loop, soundFlag = 0x0008,
/* loop the sound until next sndplaysound */
snd_nostop, soundFlag = 0x0010,
/* don't stop any currently playing sound */
snd_nowait, soundFlag = 0x00002000,
/* don't wait if the driver is busy */
snd_alias, soundFlag = 0x00010000,
/* name is a registry alias */
snd_alias_id, soundFlag = 0x00110000,
/* alias is a predefined id */
snd_filename, soundFlag = 0x00020000,
/* name is file name */
snd_resource, soundFlag = 0x00040004,
2004.11.3 AI程序设计 33
15.6 Win32 API函数
/* name is resource name or atom */
snd_purge, soundFlag = 0x0040,
/* purge non-static events for task */
snd_application, soundFlag = 0x0080,
/* look for application specific association */
clauses
run(),-
console::init(),
_ = playSound("tada.wav",
null,snd_nodefault+snd_filename),
end implement playSoundFile
goal
mainExe::run(playSoundFile::run),
2004.11.3 AI程序设计 34
本章小结
? 对于现有主流平台上的任何一种编程语言, 应该能够与其他编程语
言无缝地接口, 以实现混合语言编程功能 。 这是现代编程语言的一
种共同特色 。
? 本章介绍 Visual Prolog与其他编程语言的接口, 旨在帮助读者学
会如何在 Visual Prolog程序中调用 WIN32 API,以解决 Visual
Prolog语言与其他编程语言之间的接口或混合语言编程问题 。
2004.11.3 AI程序设计 35
习 题 十 五
1,从 Visual Prolog调用其他编程语言时,必须理解哪些方面的问题?
2,何谓“外部代码”?从 Visual Prolog调用“外部代码”需要解决
哪些关键问题?
3,在 Visual Prolog中,数据表示有何特点?
4,在进行 Visual Prolog与其他高级语言混合编程时,在内存使用问
题上需要注意哪些方面?
5,在 Visual Prolog中,如何调用 Win32 API
第 15章 与其他编程语言接口
2004.11.3 AI程序设计 2
第 15章 与其他编程语言接口
? 本章介绍 Visual Prolog与其他编程语言的接口, 帮助
读者学会如何在 Visual Prolog中调用 WIN32 API,打
开更广阔的编程世界的大门 。
2004.11.3 AI程序设计 3
第 15章 与其他编程语言接口
15.1 外部代码
15.2 关键问题
15.3 调用约定和链接名
15.4 数据表示
15.5 存储管理
15.6 Win32 API函数
2004.11.3 AI程序设计 4
15.1 外部代码
? 所谓, 外部代码, 是指用其它编程语言 ( 而不是用 Visual Prolog)
所编写的代码 。
? Visual Prolog能直接调用其它语言代码 。 本章就来解释这些概念
和细节 。 直接调用外部代码是一种二进制级的底层调用, 而非高级
语言层面的高级调用 。 这在简单的例程中相当简单, 但也可能出奇
的复杂 。 可以肯定的一点是:处理复杂调用需要非常熟悉 Visual
Prolog和其它编程语言 。 但是不要担心, 实际上, 在许多例程中
所需要的交互是相当简单的
2004.11.3 AI程序设计 5
15.2 关键问题
? 其它语言编译器和 Visual Prolog编译器有很大的不同, 这是由于
它们是由不同的人所制作的, 而且它们必须支持不同的语言特性 。
Visual Prolog不可能和所有的外部语言代码交互, 因为它不可能
知道其他编译器所采用的规则 。 所以, 要实现 Visual Prolog和其
他语言代码的交互, 就要求这些代码必须遵循规定的方式 。
? 为了调用外部代码 (异种语言代码 ),我们就必须访问这些代码 。 本
章我们要处理的代码是直接链接到程序里, 或者位于一个动态连接
库 DLL中 。
2004.11.3 AI程序设计 6
15.2 关键问题
? 首先, 我们必须查找这些代码的位置 。 这通过一个名字来实现 。 如
果代码是直接链接到你的程序里, 就必须使用链接名;如果代码位
于一个 DLL,则可以使用导出名 。 无论是用链接名还是用导出名,
对 Visual Prolog来说没有区别, 但是当你试图在外部代码 ( 或系
统 ) 中寻找这个名字时, 就有了区别 。 我们这里提到系统, 是因为
有时你必须使用的名字在代码中根本不存在 。 因此, 对于这个概念,
我们仅使用链接名 。
2004.11.3 AI程序设计 7
15.2 关键问题
? 其次,在我们已经确定了外部代码的位置之后,接着必须传递输入
参数并调用代码,代码被执行后,还必须获取其输出等等。有许多
不同的途径去完成此过程。显然,调用者和被调用者必须在这一点
达成一致,即双方必须有调用约定。
? 第三,关于数据表示。但是,不仅调用双方必须在参数等的传递上
相一致,更重要的是双方要以相同方式解释所传递的字节。换句话
说就是,双方的数据表示必须相同。
2004.11.3 AI程序设计 8
15.2 关键问题
? 最后要注意的一点是内存管理。调用者和被调用者必须明确在必要
时由哪一方分配和释放内存。如果释放内存和分配内存不是由同一
方进行,那么释放内存一方必须以正确的方式释放内存。
? 总之,调用外部代码有四个关键问题需要考虑,
? 链接名
? 调用约定
? 数据表示
? 存储管理
2004.11.3 AI程序设计 9
15.3 调用约定和链接名
? 链接名(或导出名)用于识别你想要调用的代码。不同的编译器用
不同的缺省链接名,许多编译器有多种指定链接名的途径。在
Visual Prolog中,你可以用保留字,as”为一个谓词声明一个链接
名。如,
predicates
pppp, (integer) as "LinkName",
? 在 Visual Prolog程序中,上面的谓词被命名为,pppp”,但是它的
链接名是,LinkName”。
2004.11.3 AI程序设计 10
15.3 调用约定和链接名
? 注意:仅类谓词有一个链接名,这意味着它必须在一个类中进行声
明或在一个类实现的类谓词( class predicates)段进行声明。
? Visual Prolog支持大量不同的调用约定。这些约定也在谓词中声
明,但用的是 language保留字。
predicates
qqqq, (integer) language c,
2004.11.3 AI程序设计 11
15.3 调用约定和链接名
? 按照 C编译器的调用约定,编译器在 C程序中的名字前添加一个下
划线来创建链接名。如果你用 C调用约定但不提供链接名,Visual
Prolog也将使用这个约定。注意:一直到 build 6107编译器实际
上使用的是另一种命名策略,也就是说你必须用 as来获取链接名。
上例中 qqqq的链接名是,_qqqq”。如果你声明一个链接名,就必
须严格遵守这个规则,
predicates
rrrr, (integer) language c as "LinkName",
? rrrr将用这个 C调用约定,并有一个链接名 LinkName(即没有下
划线)。
2004.11.3 AI程序设计 12
15.3 调用约定和链接名
? C++编译器通常用的是 C调用约定, 但是它们不依赖 C链接名, 因
为 C++允许重载 。 也就是说, 在 C++中相同的名字只要变量数目
不同或者和类型不同, 就可以看作为不同的函数使用 。 这些不同变
量必须有不同的链接名 。 因此 C++编译器具有完善的命名机制,
这种命名基于 C++名字, 变量的数目和类型 。 这个过程可称为
,命名熨烫 ( name mangling), 。
? 不同的编译器采用不同的命名熨烫算法, 从而互不兼容 。 通常情况
下, 在被作为外部代码访问的 C++程序中要使用明确的链接名,
或者在一个输出, C”部分封装这个声明, 使编译器使用 C命名约定 。
2004.11.3 AI程序设计 13
15.3 调用约定和链接名
? Visual Prolog也支持 stdcall调用约定 。 Microsoft Visual Basic
使用 stdcall作为 Microsoft Win32平台 API调用的调用约定, 许多
Pascal家族编译器, 包括 Borland Delphi也使用 stdcall。 实际上
C和 C++程序和其他语言的程序接口时, 通常也使用 stdcall调用
约定 。 Visual Prolog对 stdcall调用约定使用与 C调用相同的命名
约定 。
? 注意:直到 build 6107编译器实际上使用的是另一种命名策略,
因此必须用 as来获取链接名 。 即在 Prolog名字前面加前缀下划线
来创建链接名, 但是如果指定了一个链接名, 它将被严格使用 。
2004.11.3 AI程序设计 14
15.3 调用约定和链接名
? Microsoft Win32 API使用特殊的 stdcall调用约定, 它较 C调用约
定多了一些名字修饰 。 可参阅 Visual Prolog语言参考手册 。
Visual Prolog有一个专用的调用约定 apicall来支持这个特殊的
stdcall。 apicall和 stdcall仅在名字修饰上有不同之处 。 有了
apicall,用 as声明的外部链接名也要被修饰 。 若需要一个有不同
修饰的名字, 就必须用 stdcall并自己指定修饰名 。
2004.11.3 AI程序设计 15
15.4 数据表示
? 所输入的数字参数按其值进行传递,即数值直接压入调用堆栈。输
出数字参数通过引用传递,即结果指针被压入调用堆栈。调用堆栈
中整型数占 32位,实型数占 64位。
? 字符也可以用数字表示。
? 其他数据用一个指针来指向。通过直接将指针压入调用堆栈来传递
输入参数。通过压入结果指针的指针(即指向指针的指针)来传递
输出参数。
2004.11.3 AI程序设计 16
15.4 数据表示
? 一个算符的论域由指向一块内存的指针来表示,这块内存首先保存
了算符,随后是每一个子部件。这些子部件直接由一个数字或指向
真实数据的指针来表示。
? 如果一个算符的论域只有一种选择,这种情况下通常具有相同的值,
所以跳过这个算符。注意:在 Visual Prolog 5中,除非该论域用
struct关键字声明,否则该算符出现;而在 Visual Prolog 6中,
该算符永远不会出现。
2004.11.3 AI程序设计 17
15.4 数据表示
? 用 align限定符可以使算符表示有所不同,请参阅语言参考的相关
部分。
? 字符串由一个指针表示,它指向由零值空字符终止的字符序列,与
C语言一样。
? 二进制论域由一个指针指向其二进制数据。其值等于数据长度加
unsigned类型的大小,这个值紧接着存储在数据之前。
2004.11.3 AI程序设计 18
15.4.1 举例
? 假设想在 Visual Prolog程序中调用一个 C例程。在 C中的声明如下,
int myRoutine(
wchar_t * TheString,
int BufferLength,
wchar_t * TheResult );
,wchar_t *”是 C语言中的 unicode字符串。
? 这个函数有 3个变量,
? TheString是一个字符串
? Bufferlength是一个整型数
? TheResult是一个字符串
? 该函数返回一个整型值。
2004.11.3 AI程序设计 19
15.4.1 举例
? 相应的 Visual Prolog 6声明如下(假定它被声明在一个类中),
class predicates
myRoutine, (
string TheString,
integer BufferLength,
string TheResult)
-> integer
language c,
? 若谓词在类中声明,则,class”应去掉。
2004.11.3 AI程序设计 20
15.4.2 外部链接库
? 如果声明的谓词如上例所示, 编译器就提示一个错误:找不到声明
谓词的子句 。
? 因为这个谓词根本不能在 Visual Prolog中进行实现 。 这时必须通
知编译器这个谓词的代码在外部 ( externally) 某处 。 这里我们之
所以使用 externally这个单词, 是因为它完全可以准确地表达在声
明该谓词的类的实现中用一个所谓的求解限定符所要表达的意思 。
2004.11.3 AI程序设计 21
15.4.2 外部链接库
? 如果这个类命名为 xxx,则它应该是,
implement xxx
resolve
myRoutine externally
,.,
? 这样编译器就接受了这个声明,但还可能出现链接错误,
_myRoutine没有定义。
? 这是因为包含 _myRoutine的库还没有链接。
2004.11.3 AI程序设计 22
15.4.2 外部链接库
? 如果 _myRoutine位于一个静态库 ( 一个 LIB文件 ), 则要把这个
文件加入项目, 链接器就会提取相关代码并加入程序 。
? 如果 _myRoutine位于一个 DLL库, 仍有一个 LIB文件用以描述这
个 DLL,接着就把这个文件加入项目 。 在这种情况下, 链接器在你
的程序中放置信息, 这些信息告知在哪里可以找到 DLL例程 。
2004.11.3 AI程序设计 23
15.4.2 外部链接库
? 如果 _myRoutine位于一个 DLL库,但是没有对应的 LIB文件,可
以通过下面方式调用该例程,
implement xxx
resolve
myRoutine externally from "myDLL.dll"
,.,
? 这样,当 myRoutine被首次调用时,编译器就增加代码,动态加
载 DLL库。这个 DLL库在程序退出之前不卸载。
? pfc/application/useDll可以用于动态加载和卸载 DLL库
2004.11.3 AI程序设计 24
15.5 存储管理
? 数字和字符参数被直接压栈或写入内存 。 这种情况比较简单 。
? 然而, 不直接压栈的数据涉及许多内存管理问题 。 因为只要调用双
方的任一方要访问这个数据, 数据都必须存在于内存 。 一旦数据不
再需要, 就必须回收内存以防止内存漏掉 。
? 通常情况下, 谁分配内存谁才能去回收这些内存, 因为另一方并不
知道如何回收 。 当然我们可以让双方采用相同的机制 。 例如, 一方
可以让另一方看到它的机制 ( 如作为导出例程 ) 。
? Visual Prolog 6使用的内存分配例程是采用运行时间系统
( Vip6kernel.dll) 来实现的, 并且可以从外部代码进行调用 。
? 程序和库代码在处理何时回收内存时, 通常采用一些事先已约定的
方法 。
2004.11.3 AI程序设计 25
15.5.1 典型解决方案
? 这一节介绍解决内存管理问题的通用方法。不涉及 COM和,NET,
这是我们认为最为通用的方法。原理很简单,
? 输入参数仅在调用期间存活,所以,如果被调用者需要存储某一输
入参数以备后用,它就必须将这个参数复制到自己的内存区。
? 输出被复制到调用者的内存缓冲区,所以被调用者分配的内存不会
传送给调用者。
? 前面的 myRoutine就是一个典型的例子。输出写入 TheResult
(由调用者分配的一个字符串),这就是为什么它看起来既是例程
的输入又要传递 BufferLength的原因。
2004.11.3 AI程序设计 26
15.5.1 典型解决方案
? Visual Prolog 6程序调用 myRoutine例程,
clauses
p(TheString) = TheResult,-
TheResult = string::create(bufferLength),
RetCode = myRoutine(TheString,bufferLength,
TheResult),
checkRetCode(RetCode),
? sring::creat为字符串分配内存, myRoutine将它的结果复制到
TheResult( Visual Prolog程序不涉及这些 ), 它不回收任何已
分配的内存 。
2004.11.3 AI程序设计 27
15.5.2 垃圾收集和全局堆栈
? 如果不采用上面的典型方案, Visual Prolog 6还提供一种全局性
的 G-堆栈, 并且用一个垃圾收集箱来管理堆 。
? 关于 G-堆栈, 在这里我们建议:如果不采用典型解决方案, 就不
要迁移 G-堆栈存储器 。 如果你不确定数据是否在 G-堆栈, 那么可
以用谓词 memory::toPersistentStorage来获得一个副本, 以确
保不处于 G-堆栈之中 。
2004.11.3 AI程序设计 28
15.5.2 垃圾收集和全局堆栈
? 应该注意到, 垃圾收集箱是一种循环回收机制:当某块内存不再需
要时, 不应该立即回收, 而要看一看在外部代码中是否还要访问它 。
通常这都会自动处理, 不存在什么问题 。 但是垃圾收集箱不知道
( 也不必知道 ) 数据在外部代码中何时存在 。 因此数据在 Visual
Prolog程序中存在的时间要长于在外部代码中存在的时间 。
? 一种典型的保留内存的方法是将它插入事实段, 然后在外部代码不
再需要它时回收它 。
2004.11.3 AI程序设计 29
15.6 Win32 API函数
? 也许你的程序从来不会调用其它编程语言的代码, 因此你会认为学
习这一部分没有必要 。 但要知道整个操作系统对于你的程序来说都
是外部的, 因此学习 Win32 API很有必要 。
? 操作系统提供成千上万有趣的外部例程, 你可以利用本章的原则来
调用它们 。 这些例程称为 Microsoft Windows XXX API函数 。 不
同平台提供的 API有所不同, 例如 Windows XP比 NT 4.0提供更
多的例程 。
? 各平台 API的在线文档可参见 MSDN库 。
2004.11.3 AI程序设计 30
15.6 Win32 API函数
? 从 Prolog的观点来看, 这个文档有一个问题:它用到许多字符常
量, 但却没有定义这些常量的值 。 例如, 例程 PlaySound的文档
告诉我们可以用标志参数 SND_ASYNC在程序中异步播放声音
( 即在应用程序继续执行期间播放声音 ) 。 SNC_ASYNC实际上
是一个常数, 但文档中并没有说明这个常数的值 。
? 从平台 SDK更新处下载 SDK C/C++,在其中的 *.h头文件中可以
找到这些常量的宏定义 。
2004.11.3 AI程序设计 31
15.6 Win32 API函数
? 至此,你应该能编写类似下面的代码了,
implement playSoundFile
open core
resolve playSound externally
domains
soundFlag = unsigned32,
class predicates
playSound, (string Sound,
pointer HModule,
soundFlag SoundFlag)
-> booleanInt Success
language apicall,
constants
snd_sync, soundFlag = 0x0000,
/* play synchronously (default) */
snd_async, soundFlag = 0x0001,
/* play asynchronously */
snd_nodefault, soundFlag = 0x0002,
2004.11.3 AI程序设计 32
15.6 Win32 API函数
/* silence (!default) if sound not found */
snd_memory, soundFlag = 0x0004,
/* Sound points to a memory file */
snd_loop, soundFlag = 0x0008,
/* loop the sound until next sndplaysound */
snd_nostop, soundFlag = 0x0010,
/* don't stop any currently playing sound */
snd_nowait, soundFlag = 0x00002000,
/* don't wait if the driver is busy */
snd_alias, soundFlag = 0x00010000,
/* name is a registry alias */
snd_alias_id, soundFlag = 0x00110000,
/* alias is a predefined id */
snd_filename, soundFlag = 0x00020000,
/* name is file name */
snd_resource, soundFlag = 0x00040004,
2004.11.3 AI程序设计 33
15.6 Win32 API函数
/* name is resource name or atom */
snd_purge, soundFlag = 0x0040,
/* purge non-static events for task */
snd_application, soundFlag = 0x0080,
/* look for application specific association */
clauses
run(),-
console::init(),
_ = playSound("tada.wav",
null,snd_nodefault+snd_filename),
end implement playSoundFile
goal
mainExe::run(playSoundFile::run),
2004.11.3 AI程序设计 34
本章小结
? 对于现有主流平台上的任何一种编程语言, 应该能够与其他编程语
言无缝地接口, 以实现混合语言编程功能 。 这是现代编程语言的一
种共同特色 。
? 本章介绍 Visual Prolog与其他编程语言的接口, 旨在帮助读者学
会如何在 Visual Prolog程序中调用 WIN32 API,以解决 Visual
Prolog语言与其他编程语言之间的接口或混合语言编程问题 。
2004.11.3 AI程序设计 35
习 题 十 五
1,从 Visual Prolog调用其他编程语言时,必须理解哪些方面的问题?
2,何谓“外部代码”?从 Visual Prolog调用“外部代码”需要解决
哪些关键问题?
3,在 Visual Prolog中,数据表示有何特点?
4,在进行 Visual Prolog与其他高级语言混合编程时,在内存使用问
题上需要注意哪些方面?
5,在 Visual Prolog中,如何调用 Win32 API