<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.visual-prolog.com/index.php?action=history&amp;feed=atom&amp;title=Foreign_Language_Code%EF%BC%88%E5%A4%96%E8%AF%AD%E4%BB%A3%E7%A0%81%EF%BC%89</id>
	<title>Foreign Language Code（外语代码） - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.visual-prolog.com/index.php?action=history&amp;feed=atom&amp;title=Foreign_Language_Code%EF%BC%88%E5%A4%96%E8%AF%AD%E4%BB%A3%E7%A0%81%EF%BC%89"/>
	<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Foreign_Language_Code%EF%BC%88%E5%A4%96%E8%AF%AD%E4%BB%A3%E7%A0%81%EF%BC%89&amp;action=history"/>
	<updated>2026-05-19T18:07:36Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Foreign_Language_Code%EF%BC%88%E5%A4%96%E8%AF%AD%E4%BB%A3%E7%A0%81%EF%BC%89&amp;diff=4104&amp;oldid=prev</id>
		<title>Yiding: Created page with &quot;（以下内容译自:Category:Tutorials中的Foreign_Language_Code。）  “外语代码”指的是用非Visual Prolog语言的其它编程语言写的代码。Visual Prol...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Foreign_Language_Code%EF%BC%88%E5%A4%96%E8%AF%AD%E4%BB%A3%E7%A0%81%EF%BC%89&amp;diff=4104&amp;oldid=prev"/>
		<updated>2015-06-17T11:50:20Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;（以下内容译自&lt;a href=&quot;/index.php?title=Category:Tutorials&quot; title=&quot;Category:Tutorials&quot;&gt;Category:Tutorials&lt;/a&gt;中的Foreign_Language_Code。）  “外语代码”指的是用非Visual Prolog语言的其它编程语言写的代码。Visual Prol...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Foreign_Language_Code。）&lt;br /&gt;
&lt;br /&gt;
“外语代码”指的是用非Visual Prolog语言的其它编程语言写的代码。Visual Prolog可以直接调用外语代码，这里解释一些概念及细节。直接调用外语代码需要在很底层的二进制上与对应代码交互，简单的情况中这很容易，但复杂起来也不得了。不言而喻，处理复杂情况需要很多Visual Prolog及另外那种编程语言方面的知识，不过也用不着害怕，在多数情况下所需要的知识其实也是相当简单的。&lt;br /&gt;
&lt;br /&gt;
这里描述的原理还可以帮助我们调用Microsoft Win32平台的API，这能极大开阔我们编程的领域。 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==关键概念==&lt;br /&gt;
&lt;br /&gt;
外语编译器处理一些事情与Visual Prolog的不一样，一方面它是其他人开发的，另一方面也因为它必须处理的是不同语言的不同特性。对Visual Prolog来说，要与所有的外语代码交互是不可能的，因为很难了解任意其它编译器所使用的原则。因而，Visual Prolog要与外语代码打交道，则这个外语代码必须遵守某些规则。 &lt;br /&gt;
&lt;br /&gt;
Visual Prolog可以调用这样的代码：它是用C语言编写的并且是用Microsoft的C编译器编译的。它不能调用任何还需要编译的代码。这么说够严格了，只要有创造力，处理所谓“不可能”也是常事儿。 &lt;br /&gt;
&lt;br /&gt;
为使用外语代码，当然需要访问这些代码。这里分两种与代码打交道的情况：一种是把代码直接链接到自己的程序中，另一种是让代码在动态链接库（DLL）中。&lt;br /&gt;
&lt;br /&gt;
接下来需要定位代码，这是通过名称来实现的。如果代码直接链接到自己的程序中，必须要用链接（link）名；如果代码是放在DLL里则必须使用外引（export）名。不管是链接名还是外引名，在Visual Prolog代码中没有什么差别，不过要想在外语代码（或系统）中找相应的名称，那还是有差别的。说系统，是因为有时要使用的名称在代码里根本就没有，这样我们还是在概念上来使用链接名这个说法吧。 &lt;br /&gt;
&lt;br /&gt;
现在可以编写代码了，我们知道了它在什么地方。接着要传递输入参数，调用代码，代码执行后再获取输出。如此这般。有很多方法执行这个过程，很显然，调用者与被调用者在这个过程中应该有默契，也就是说有一致的调用协议。&lt;br /&gt;
&lt;br /&gt;
调用者与被调用者不仅在参数如何传递上要一致，两者对被传递字节的解释上也要一致。换言之，两者的数据表示法 必须一致 。&lt;br /&gt;
&lt;br /&gt;
最后，还必须要处理好内存的管理。调用程序与被调程序必须在分配内存与释放内存上相协调，在什么时间分配和释放上相协调。如果分配器与释放器不是同一个，释放器必须知道如何释放内存。 &lt;br /&gt;
&lt;br /&gt;
总之，调用外语代码有四个必须关照好的关键问题：&lt;br /&gt;
&lt;br /&gt;
*链接名 &lt;br /&gt;
*调用协议&lt;br /&gt;
*数据表示法&lt;br /&gt;
*内存管理&lt;br /&gt;
 &lt;br /&gt;
==调用协议与链接名==&lt;br /&gt;
&lt;br /&gt;
这里把调用协议与链接名放在一起来讨论，因为传统上一些编译器总是以某种调用协议配合使用某种链接名。 &lt;br /&gt;
&lt;br /&gt;
所谓链接名（或外引名）是用于标识要调用的外语代码的一个名称。不同的编译器使用不同的缺省链接名，而且很多编译器可以指定特殊的链接名。在Visual Prolog中，可以在谓词声明中用“as”限定符声明链接名： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    pppp : (integer) as &amp;quot;LinkName&amp;quot;.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在Visual Prolog程序中，上面谓词的名称是pppp，但其链接名是LinkName。&lt;br /&gt;
&lt;br /&gt;
注意，只有 类谓词 才有链接名，这意味着它必须是在类中声明的，或是在实现的类谓词段中声明的。&lt;br /&gt;
&lt;br /&gt;
Visual Prolog支持若干种不同的调用协议。调用协议也是在谓词声明中指定的，使用的限定符是language：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    qqqq : (integer) language c.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
对于这个协议，C编译器通过在C程序所使用的名称前加一个下划线来创建链接名。如果使用c调用协议而又没有指定链接名，Visual Prolog也使用这个方法（注意，build 6107及以后的编译器实际上使用了另一种命名策略，必须用as来得到这样的链接名）。例如，上面qqqq的链接名是_qqqq。如果指定了链接名，则会使用指定的那个名字： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    rrrr : (integer) language c as &amp;quot;LinkName&amp;quot;.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
rrrr将使用C调用协议，其链接名是LinkName（前面没有下划线）。&lt;br /&gt;
&lt;br /&gt;
C++编译器传统上使用C调用协议，但却不依赖C的链接名，因为C++是可以重载的。也就是说，在C++里同样的名称可以由不同的函数使用，只要这些函数有不同数量的参数或是其参数具有可区分的类型。这些不同的东西必须要有不同的链接名，因此C++编译器基于C++名称、参数数量及其类型创建精细的名称，这个过程称作名称分解。 &lt;br /&gt;
&lt;br /&gt;
不同的编译器使用不同的分解算法，它们之间通常并不兼容。因此，通常的做法或是对要在外语代码中访问的C++例程使用明确的链接名，或是在 export &amp;quot;C&amp;quot; 段包含一个声明，让编译器使用C命名协议。 &lt;br /&gt;
&lt;br /&gt;
Visual Prolog还支持所谓 stdcall 调用协议。这个协议由Microsoft Visual Basic用于Microsoft Win32平台的API调用，很多Pascal系列的编译器也使用了它，包括Borland Delphi。实际上，C和C++程序也常常在由外语代码访问的例程序中使用这个调用协议。Visual Prolog对 stdcall 使用了与c调用相同的命名协议（注意，build 6107及以后的编译器实际上使用了另一种命名策略，必须用as来得到这样的链接名）。也就是说，缺省情况下Prolog名前加一个下划线作为链接名，但如果指定了链接名，则使用指定的链接名。&lt;br /&gt;
&lt;br /&gt;
Microsoft Win32平台的API使用 stdcall 调用协议，但另有其命名协议。其名称修饰比c调用协议稍多一点儿，详细内容请参看Visual Prolog语言参考手册。 Visual Prolog有一个特殊的调用协议 apicall 用于支持这种命名协议。事实上， apicall 与 stdcall 调用协议是相同的，只不过名称修饰不一样。在 apicall 调用协议中，显式声明于as限定符的链接名也要被修饰。如果需要一个特殊修饰的名字，就必须使用 stdcall 调用协议并且自行指定一个修饰名。 &lt;br /&gt;
&lt;br /&gt;
==数据表示法==&lt;br /&gt;
由于内容太多，这里对数据表示法不做详细讨论，只对Visual Prolog如何表示各类数据作一概要介绍。 &lt;br /&gt;
&lt;br /&gt;
输入参数的个数用值来传递，也就是说这个值是直接压进调用栈的。输出参数的个数用指针传递，也就是说压进调用栈的是这个值的指针。调用栈中所有整数都占32比特，而实数都占64比特。&lt;br /&gt;
&lt;br /&gt;
字符由数字来表示。&lt;br /&gt;
&lt;br /&gt;
所有其它的数据由指向实际数据的指针来表示。输入参数通过压入调用栈的指针来传递，输出参数通过压入栈中的指向结果指针的指针来传递。 &lt;br /&gt;
&lt;br /&gt;
一个函子域的值由指向一片内存的指针来表示，内存中先是函子（用一个32比特的数表示），接着是各子构件，其表示方法如上述，或直接是数值，或是实际数据的指针。&lt;br /&gt;
&lt;br /&gt;
如果函子域只有一个项，则函子就省略了，因为它总是具有相同的值。（注意， Visual Prolog 5中函子是会有的，除非域声明中使用了struct关键字，但当前版本的Visual Prolog函子总是不会出现的）。&lt;br /&gt;
&lt;br /&gt;
使用align限定符，函子可以使用不同的表示法，请参看语言参考手册。 &lt;br /&gt;
&lt;br /&gt;
串由指向以零结尾的字符序列的指针来表示，如同在C语言中一样。&lt;br /&gt;
&lt;br /&gt;
二进制数由指向二进制数据的指针来表示。在数据本身之前，存放有数据的长度值加无符号数值的尺度（The value, which equals to the length of the data plus the size of unsigned, is stored immediately before the data）。 &lt;br /&gt;
&lt;br /&gt;
===例=== &lt;br /&gt;
设要在Visual Prolog程序中调用一个C例程。C中的声明是这样的：&lt;br /&gt;
&lt;br /&gt;
int myRoutine(wchar_t * TheString, int BufferLength, wchar_t * TheResult); &lt;br /&gt;
&lt;br /&gt;
“wchar_t *”是C中的 Unicode 串。这个例程中有三个参数： &lt;br /&gt;
&lt;br /&gt;
*TheString，这是一个串 &lt;br /&gt;
*Bufferlength，这是一个整数 &lt;br /&gt;
*TheResult，它也是一个串&lt;br /&gt;
&lt;br /&gt;
返回值是一个整数。相应的Visual Prolog声明如下（假设是在 实现 中声明的）： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;class predicates&lt;br /&gt;
    myRoutine : (string  TheString, integer BufferLength, string TheResult) -&amp;gt; integer language c.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
如果谓词是在类声明中声明的，则需要去掉 class 关键字。&lt;br /&gt;
&lt;br /&gt;
==Externally和库==&lt;br /&gt;
&lt;br /&gt;
如果按上面说的声明一个谓词，编译器还是会给出错误消息说它找不到所声明谓词的子句。由于该谓词并非在Visual Prolog中实现的，这并不奇怪。所要做的是，告诉编译器这个谓词的代码应该在外部某个地方去找。之所以用externally这个字，因为正是要把它用在声明谓词的类实现中限定符resolve后面。如果类是叫作xxx，则它会是这样： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement xxx&lt;br /&gt;
    resolve&lt;br /&gt;
        myRoutine externally&lt;br /&gt;
    ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这样一来，编译器就会接受这个声明了，不过或许会碰到这样的情况，链接器还会出错，说_myRoutine没有定义。这是因为做这个工程时还缺包含有_myRoutine的库。&lt;br /&gt;
&lt;br /&gt;
如果_myRoutine已经在一个静态库（在一个LIB文件）里了，只需要把这个文件加到工程中去，链接器就可以从该LIB中提取相应的代码并把它加到程序中。&lt;br /&gt;
&lt;br /&gt;
如果_myRoutine在一个DLL里，则还是需要一个LIB文件，这个文件描述该DLL，然后把这个LIB文件加到工程中去。这种情况下链接器会在程序中加入描述该在哪个DLL中找该例程的信息。&lt;br /&gt;
&lt;br /&gt;
如果_myRoutine在一个DLL中，但却没有相应的LIB文件，也还是可以调用该例程的。这只需要在resolve限定符后写出该DLL的文件名，像这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement xxx&lt;br /&gt;
    resolve&lt;br /&gt;
        myRoutine externally from &amp;quot;myDLL.dll&amp;quot;&lt;br /&gt;
    ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在这种情况下，编译器会添加代码，这个代码在myRoutine第一次调用时动态加载该DLL。这个DLL直到程序退出都不会再卸载。 &lt;br /&gt;
&lt;br /&gt;
pfc/application/useDll 包可以用来动态地加载和卸载DLL。如何（及为什么要）用这个包这里就不介绍了。&lt;br /&gt;
&lt;br /&gt;
==内存管理==&lt;br /&gt;
&lt;br /&gt;
数字和字符是通过调用栈直接传递的，或是直接写到需要的一片内存里，对于这些类型的内存处理是很容易的，没问题。 &lt;br /&gt;
&lt;br /&gt;
不直接依靠调用栈传递的数据，就是问题了。因为只要例程（调用程序或被调程序）还要用那个数据，数据占用的那片内存就都得是活的。但是，一旦它们不要用那个数据了，这片内存就得收回以免造成内存泄漏。&lt;br /&gt;
&lt;br /&gt;
通常，只能是那个内存片的分配者来收回该内存，因为别人不知道怎样回收。当然，大家都使用相同机制来解决这个问题也是可以的，一方外露自己的机制给别人（如外露例程）。 &lt;br /&gt;
&lt;br /&gt;
Visual Prolog中用的内存分配程例程已经由（在Vip6kernel.dll中的）运行时系统外露了，可以在外语代码中调用。 &lt;br /&gt;
&lt;br /&gt;
为了处理好何时内存需要收回的问题，程序与库代码都要使用协调一致的方法。&lt;br /&gt;
&lt;br /&gt;
===典型解决方案===&lt;br /&gt;
&lt;br /&gt;
只要不涉及COM和.Net，下面就是解决内存处理问题最普通的方法。 &lt;br /&gt;
&lt;br /&gt;
原理很简单：&lt;br /&gt;
&lt;br /&gt;
*输入参数只在调用时有效，因此如果被调程序以后还需要用某些输入参数，就应自行拷贝它们到一片内存中，这片内存由被调程序自己控制。 &lt;br /&gt;
*输出拷贝到内存缓冲区，缓冲区是由调用程序提供的，这样，就没有被调程序分配的内存传递给调用程序。 &lt;br /&gt;
&lt;br /&gt;
上述myRoutine是这种例程的一个典型例子：输出写到TheResult中，这是个调用程序分配内存的串，它对被调程序是个输入，所以才需要传递缓冲区的长度BufferLength。 &lt;br /&gt;
&lt;br /&gt;
调用上面代码的Visual Prolog 6 的代码大致可以是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    p(TheString) = TheResult :-&lt;br /&gt;
        TheResult = string::create(bufferLength),&lt;br /&gt;
        RetCode = myRoutine(TheString, bufferLength, TheResult),&lt;br /&gt;
        checkRetCode(RetCode).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
string::create用于分配串所使用的内存；myRoutine拷贝自己的结果到TheResult中（Visual Prolog程序什么也不做），它没有返回任何由它分配的内存。&lt;br /&gt;
&lt;br /&gt;
===垃圾收集===&lt;br /&gt;
&lt;br /&gt;
如果不依靠上面这个“典型”解决方案，可以考虑这个情况：Visual Prolog使用垃圾收集器管理它的堆（heap）。 &lt;br /&gt;
&lt;br /&gt;
垃圾收集器其实做的就是一轮一轮的回收工作：内存不再要用时，你不用管它，让它活着好像外语代码还要用它一样。一般情况下这没问题，因为它完全是自动工作的。不过垃圾收集器未必能知道外语代码部分中的数据何时是存活的，所以编程者应该保证让它在Visual Prolog代码部分中存活的时间比在外语代码部分中存活的时间要长。 &lt;br /&gt;
&lt;br /&gt;
使一内存片存活的一个典型的办法就是把它插入到一个事实中，当外语代码不再要用它时，再把它从事实中撤消。&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 6 还用了叫做G栈的一些内存，但Visual Prolog 7.0 开始不再用G栈了。&lt;br /&gt;
&lt;br /&gt;
==Microsoft Win32平台的API==&lt;br /&gt;
&lt;br /&gt;
你可能会想：我不会去写什么外语代码，所以我用不着这个。前一半你说了算，后一半的结论却未必对。原因是：操作系统就是“外语”。 &lt;br /&gt;
&lt;br /&gt;
操作系统提供了数千个有意思的外语例程，可以按这里说的从Prolog程序中调用它们。这些例程被称为Microsoft Windows XXX 平台 API。不同的平台例程不一样，尤其是平台越新例程越多（XP的就比NT 4.0的多得多）。 &lt;br /&gt;
&lt;br /&gt;
各平台的API可以参看Microsoft的MSDN库。&lt;br /&gt;
&lt;br /&gt;
不过对我们来说，上述文档有一个问题：它说了很多常数，都说的是名字，但具体是什么值，没说。比如，PlaySound例程的文档中说，可以用一个叫做SND_ASYNC的标志，异步地由应用程序播放声音（也就是说一边播放声音一边应用程序做其它的什么事情）。SND_ASYNC 是一个数值，但具体是多少，文档中没说。 &lt;br /&gt;
&lt;br /&gt;
要找到这样常数的值，可以下载该平台C/C++的SDK，在C/C++头文件（*.h）里可以找到常数值。 &lt;br /&gt;
&lt;br /&gt;
按照得到的这些信息，可以自己创建类似下面这样的代码： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement playSoundFile&lt;br /&gt;
    open core&lt;br /&gt;
 &lt;br /&gt;
domains&lt;br /&gt;
    soundFlag = unsigned32.&lt;br /&gt;
 &lt;br /&gt;
class predicates&lt;br /&gt;
    playSound : (string Sound, pointer HModule, soundFlag SoundFlag) -&amp;gt; booleanInt Success&lt;br /&gt;
        language apicall.&lt;br /&gt;
resolve playSound externally&lt;br /&gt;
 &lt;br /&gt;
constants&lt;br /&gt;
    snd_sync : soundFlag = 0x0000.&lt;br /&gt;
         /* 同步播放（缺省） */&lt;br /&gt;
    snd_async : soundFlag = 0x0001.&lt;br /&gt;
         /* 异步播放 */&lt;br /&gt;
    snd_nodefault : soundFlag = 0x0002.&lt;br /&gt;
         /* 静音（如果没有找到声音文件它就是缺省项！） */&lt;br /&gt;
    snd_memory : soundFlag = 0x0004.&lt;br /&gt;
         /* 声音指向一个内存文件 */&lt;br /&gt;
    snd_loop : soundFlag = 0x0008.&lt;br /&gt;
         /* 循环播放声音直到出现另一个sndplaysound */&lt;br /&gt;
    snd_nostop : soundFlag = 0x0010.&lt;br /&gt;
         /* 不停止任何正在播放的声音 */&lt;br /&gt;
    snd_nowait : soundFlag = 0x00002000.&lt;br /&gt;
         /* 若驱动器忙则不等待 */&lt;br /&gt;
    snd_alias : soundFlag = 0x00010000.&lt;br /&gt;
         /* 名称是一个注册别名 */&lt;br /&gt;
    snd_alias_id : soundFlag = 0x00110000.&lt;br /&gt;
         /* 别名是一个预定义的ID */&lt;br /&gt;
    snd_filename : soundFlag = 0x00020000.&lt;br /&gt;
         /* 名称是文件名 */&lt;br /&gt;
    snd_resource : soundFlag = 0x00040004.&lt;br /&gt;
         /* 名称是资源名或原子（atom） */&lt;br /&gt;
    snd_purge : soundFlag = 0x0040.&lt;br /&gt;
         /* 清除任务非静态事件 */&lt;br /&gt;
    snd_application : soundFlag = 0x0080.&lt;br /&gt;
         /* 查找应用程序指定的关联 */&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    run() :-&lt;br /&gt;
        console::init(),&lt;br /&gt;
        _ = playSound(&amp;quot;tada.wav&amp;quot;, null, snd_nodefault+snd_filename).&lt;br /&gt;
 &lt;br /&gt;
end implement playSoundFile&lt;br /&gt;
 &lt;br /&gt;
goal&lt;br /&gt;
    mainExe::run( playSoundFile::run ).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==参考==&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
</feed>