<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.visual-prolog.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Yiding</id>
	<title>wiki.visual-prolog.com - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.visual-prolog.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Yiding"/>
	<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Special:Contributions/Yiding"/>
	<updated>2026-04-13T04:32:12Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4360</id>
		<title>Usage of inherits in Chinese</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4360"/>
		<updated>2017-03-23T00:40:53Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;下面这个文档是有关inherits的基本用法，用了一个实例做说明。&lt;br /&gt;
&lt;br /&gt;
[http://www.visual-prolog.com/download/75/documentation/Usage_of_inherits_chinese.pdf VisualProlog中“继承”的应用]&lt;br /&gt;
&lt;br /&gt;
[[Category:Chinese]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4353</id>
		<title>Usage of inherits in Chinese</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4353"/>
		<updated>2017-03-17T07:16:02Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;下面这个文档是有关inherits的基本用法，用了一个实例做说明。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
《VisualProlog中“继承”的应用》&lt;br /&gt;
[[Category:Chinese]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4352</id>
		<title>Usage of inherits in Chinese</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4352"/>
		<updated>2017-03-17T07:15:23Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;下面这个文档是有关inherits的基本用法，用了一个实例做说明。&lt;br /&gt;
《VisualProlog中“继承”的应用》&lt;br /&gt;
[[Category:Chinese]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4351</id>
		<title>Usage of inherits in Chinese</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4351"/>
		<updated>2017-03-17T05:20:29Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;有朋友来信问我inherits的使用方法，我也不会用。琢磨一段时间后知道了基本用法，可再也联系不上那位朋友。于是写了这个东西放在这里，希望能有用。&lt;br /&gt;
&lt;br /&gt;
[[Category:Chinese]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4350</id>
		<title>Usage of inherits in Chinese</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4350"/>
		<updated>2017-03-17T05:15:31Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;有朋友来信问我inherits的使用方法，我也不会用。琢磨一段时间后知道了基本用法，可再也联系不上那位朋友。于是写了这个东西放在这里，希望能有用。&lt;br /&gt;
&lt;br /&gt;
[[:Category:Chinese]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4349</id>
		<title>Usage of inherits in Chinese</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4349"/>
		<updated>2017-03-17T05:02:09Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;有朋友来信问我inherits的使用方法，我也不会用。琢磨一段时间后知道了基本用法，可再也联系不上那位朋友。于是写了这个东西放在这里，希望能有用。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[:Category:Chinese]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4348</id>
		<title>Usage of inherits in Chinese</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Usage_of_inherits_in_Chinese&amp;diff=4348"/>
		<updated>2017-03-17T04:56:35Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;有朋友来信问我inherits的使用方法，我也不会用。琢磨一段时间后知道了基本用法，可再也联系不上那位朋友。于是写了这个东西放在...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;有朋友来信问我inherits的使用方法，我也不会用。琢磨一段时间后知道了基本用法，可再也联系不上那位朋友。于是写了这个东西放在这里，希望能有用。&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Introduction_to_Classes_and_Objects%EF%BC%88%E7%B1%BB%E4%B8%8E%E5%AF%B9%E8%B1%A1%EF%BC%89&amp;diff=4347</id>
		<title>Introduction to Classes and Objects（类与对象）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Introduction_to_Classes_and_Objects%EF%BC%88%E7%B1%BB%E4%B8%8E%E5%AF%B9%E8%B1%A1%EF%BC%89&amp;diff=4347"/>
		<updated>2017-02-28T01:48:47Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* 继承 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Introduction to Classes and Objects。）&lt;br /&gt;
&lt;br /&gt;
本文的目的是概要介绍Visual Prolog中的面向对象的概念，并对相关语法做些举例说明。 &lt;br /&gt;
&lt;br /&gt;
==对象模型==&lt;br /&gt;
在Visual Prolog的 object model（对象模型） 中，主要的语义实体是 objects（对象）、对象类型及类。而涉及这些实体的主要语法概念则是接口、类的声明和实现。 &lt;br /&gt;
&lt;br /&gt;
interface（接口） 是一组命名了的谓词声明，它描述了对象的“界面”（因而称之为interface），也就是从对象外部访问对象的通道。接口就代表了对象类型（object types）。 &lt;br /&gt;
&lt;br /&gt;
来看下面这个接口定义：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;interface person&lt;br /&gt;
predicates&lt;br /&gt;
    getName : () -&amp;gt; string Name.&lt;br /&gt;
    setName : (string Name).&lt;br /&gt;
end interface person&amp;lt;/vip&amp;gt;&lt;br /&gt;
这是一个叫做person的接口的定义。所有person类型的对象都有两个谓词：getName 和 setName，如同上面声明的那样。&lt;br /&gt;
&lt;br /&gt;
接口只是定义对象的类型，而类才创建对象。类有declaration（声明）和implementation（实现）。构造（constructs）person对象的类，可以像这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;class person : person&lt;br /&gt;
constructors&lt;br /&gt;
    new : (string Name).&lt;br /&gt;
end class person&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这是名为person的类的声明，这个类构造person类型的对象。 该类有个名为new的构造器（constructor），只要给它一个 Name，它就会构造一个（person类型的）对象。&lt;br /&gt;
&lt;br /&gt;
类还需要有实现，可以是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement person&lt;br /&gt;
facts&lt;br /&gt;
    name : string.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    new(Name) :- name := Name.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    getName() = name.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    setName(Name) :- name := Name.&lt;br /&gt;
end implement person&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
它是person类的实现。实现必须为各公共谓词及构造器提供定义，在这里就是 new、getName 和 setName。实现也可以局部地声明和定义额外的实体，这些实体只能在实现内部访问。这个例子中，类声明了一个名为name的事实变量，它用于保存人的姓名。 &lt;br /&gt;
&lt;br /&gt;
每个对象有自己的事实变量name的实例，而上面子句的代码会引述特定的事实变量实例。我们称这样的谓词是对象谓词（object predicates）而那个事实是对象事实（object fact）。 &lt;br /&gt;
&lt;br /&gt;
==类实体==&lt;br /&gt;
&lt;br /&gt;
类还可以有类中所有对象共享的实体。 我们仍用上面的例子，再扩充一下，加上一些代码对创建了多少个person对象来计数。每创建一个对象，计数就加一。&lt;br /&gt;
&lt;br /&gt;
先扩充一下类的声明，增加一个谓词 getCreatedCount，它会返回当前的计数值。 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;class person : person&lt;br /&gt;
constructors&lt;br /&gt;
    new : (string Name).&lt;br /&gt;
 &lt;br /&gt;
predicates&lt;br /&gt;
    getCreatedCount : () -&amp;gt; unsigned Count.&lt;br /&gt;
end class person&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
注意，公用的类谓词（class predicates）是在类声明中声明的，而公用的对象谓词（object predicates）则是在接口中声明的。这是一个没有特例的规则：不可能在类声明中声明对象谓词，也不可能在接口中声明类谓词。&lt;br /&gt;
&lt;br /&gt;
增加的谓词还需要有类实现中的定义，这里还需要一个事实来保存计数值，这个事实必须是类事实（class fact，它是由所有对象共享的）。在类的实现（implementation）中可以声明和定义私有的对象实体及私有的类实体。要声明类实体，需要把相应的声明段加上class关键字。总之，类person的实现可以是这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement person&lt;br /&gt;
class facts&lt;br /&gt;
    createdCount : unsigned := 0.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    getCreatedCount() = createdCount.&lt;br /&gt;
 &lt;br /&gt;
facts&lt;br /&gt;
    name : string.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    new(Name) :-&lt;br /&gt;
        name := Name,&lt;br /&gt;
        createdCount := createdCount+1.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    getName() = name.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    setName(Name) :- name := Name.&lt;br /&gt;
end implement person&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
上面，增加了一个被初始化为零的类事实createdCount；增加了一个返回createdCount当前值的谓词getCreatedCount；还在构造器中增加对createdCount加一的代码。&lt;br /&gt;
&lt;br /&gt;
注意一下构造器，两个赋值形式上是一样的，但一个更新的是对象的状态而另一个则改变的是类的状态。 &lt;br /&gt;
&lt;br /&gt;
==模块==&lt;br /&gt;
&lt;br /&gt;
类有一个特殊的变种，它根本不产生对象，这样的类更像是一个模块。不构造对象的类（或简单说模块），声明时就不需要对象类型了：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;class io % 这里没有类型&lt;br /&gt;
predicates&lt;br /&gt;
    write : (string ToWrite).&lt;br /&gt;
    write : (unsigned ToWrite).&lt;br /&gt;
end class io&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这样不产生对象的类显然不能有对象实体，也不能有构造器。 &lt;br /&gt;
&lt;br /&gt;
==创建与访问==&lt;br /&gt;
&lt;br /&gt;
利用上面的代码，再建立一个可创建对象并用io类（这里不用管它是如何实现的）写出person姓名的目标（goal）。 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;goal&lt;br /&gt;
    P = person::new(&amp;quot;John&amp;quot;),&lt;br /&gt;
    Name = P:getName(),&lt;br /&gt;
    io::write(Name).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
上面的第一行，调用person类中的构造器 new ，创建的对象约束给变量P。再下一行，将在P上调用对象谓词 getName 的结果约束变量 Name。最后一行，调用io类中的类谓词来写。&lt;br /&gt;
&lt;br /&gt;
注意一下，类中的名称引用时用双冒号，如 person::new；而引用对象谓词时用单个冒号，如 P:getName 。 &lt;br /&gt;
&lt;br /&gt;
还需要注意，尽管构造器的声明不像是个函数，但它是一个返回对象的函数，而返回类型则包含在类声明中。&lt;br /&gt;
&lt;br /&gt;
==接口是对象的类型==&lt;br /&gt;
&lt;br /&gt;
上面说过，接口就是对象的类型。 This should be taken literally, you can use interfaces in the same places where you can use non-object types. 例如，在下面的谓词声明中： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;class mail&lt;br /&gt;
predicates&lt;br /&gt;
    sendMessage : (person Recipient, string Message).&lt;br /&gt;
end class mail&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
谓词 mail::sendMessage 以 person 和 string 作为参数。&lt;br /&gt;
&lt;br /&gt;
==多重实现==&lt;br /&gt;
&lt;br /&gt;
可以建立若干完全不同的类，这些类都可以创建person对象，这只需要声明和实现多个类来构造person对象。类的实现可以大不同，如，创建一个类把person保存在一个数据库中。它可以这样声明： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;class personInDB : person&lt;br /&gt;
constructors&lt;br /&gt;
    new : (string DatabaseName, string Name).&lt;br /&gt;
end class personInDB&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在这里，我们不去关心这样或那样的实际实现，不过我们还是给出下面的代码，显示对某个对象类型，可以有完全不同的实现。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement personInDB&lt;br /&gt;
facts&lt;br /&gt;
    db : myDatabase.&lt;br /&gt;
    personID : unsigned.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    new(DatabaseName, Name) :-&lt;br /&gt;
        db := myDatabase::getDB(DatabaseName),&lt;br /&gt;
        personID := db:storePerson(Name).&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    getName() = db:getPersonName(personID).&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    setName(Name) :- db:setPersonName(personID, Name).&lt;br /&gt;
end implement personInDB&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
可以看到，不光是内部的行为完全不同了，而且内部的状态也有了完全不同的结构与内容。&lt;br /&gt;
&lt;br /&gt;
==包容多态==&lt;br /&gt;
&lt;br /&gt;
相同类型的对象可以用在相同的关联中，而不用去管各自的实现有多大差别。例如，可以用上面的mail类给某个person发送消息，不管这个person是由person类构造的还是personInDB类构造的都行： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;goal&lt;br /&gt;
    P1 = person::new(&amp;quot;John&amp;quot;),&lt;br /&gt;
    mail::sendMessage(P1, &amp;quot;Hi John, ...&amp;quot;),&lt;br /&gt;
    P2 = personInDB::new(&amp;quot;Paul&amp;quot;),&lt;br /&gt;
    mail::sendMessage(P2, &amp;quot;Hi Paul, ...&amp;quot;).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这个特性就是所谓包容：一个类构造的对象与另一个类构造的对象都可以适应特定关联，只要这两个对象都具有关联所要求的类型。&lt;br /&gt;
&lt;br /&gt;
可以看到，谓词 mail::sendMessage 可以用任一个person类的对象，因而从某种意义上说它是多态的（包容多态）。 &lt;br /&gt;
&lt;br /&gt;
==支持 （类型的扩展）==&lt;br /&gt;
&lt;br /&gt;
我们再来假设一下，程序要处理特殊的一类人：程序用户。用户有姓名，而且他们还有口令。我们可以对用户创建一个新的对象类型/接口，它可以声明说用户是带有口令的person。为此，要使用supports（支持）限定符： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;interface user supports person&lt;br /&gt;
predicates&lt;br /&gt;
    trySetPassword : (string Old, string New, string Confirm) determ.&lt;br /&gt;
    validatePassword : (string Password) determ.&lt;br /&gt;
end interface user&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
它说：user supports（支持） person ，这有两个功效： &lt;br /&gt;
&lt;br /&gt;
*这意味着 user 对象要提供在 person 接口中声明的谓词（即 getName 和 setName）&lt;br /&gt;
*这还意味着user类型的对象也是person类型的对象，因而也就可以用在需要person对象的关联中。假设我们有如下的user类：&lt;br /&gt;
 &lt;br /&gt;
&amp;lt;vip&amp;gt;class user : user&lt;br /&gt;
constructors&lt;br /&gt;
    new : (string Name, string Password).&lt;br /&gt;
end class user&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
那么，这个类的对象也可以被 mail::sendMessage 使用： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;goal&lt;br /&gt;
    P = user::new(&amp;quot;Benny&amp;quot;, &amp;quot;MyCatBobby&amp;quot;),&lt;br /&gt;
    mail::sendMessage(P, &amp;quot;Hi Benny, ...&amp;quot;).&amp;lt;/vip&amp;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;
supports 限定符会产生一个亚层，我们称 user 是 person 的子类。 &lt;br /&gt;
&lt;br /&gt;
==对象：根对象 超类型==&lt;br /&gt;
&lt;br /&gt;
一个接口没有显式地支持任何其它接口时，隐含地意味支持接口object。object是一个隐含定义的接口，没有内容（也就是没有谓词）。任何对象都直接或间接地支持object接口，所以任何对象都具有object类型。因此，object是所有对象类型的超类。&lt;br /&gt;
&lt;br /&gt;
==继承==&lt;br /&gt;
&lt;br /&gt;
当实现user类时，当然想要利用已有的那个person类。假设我们想要user类与person类差不多，只不过是还需要处理口令。这样，我们可以让user类从person类中 inherit （继承）person那部分的实现。下面的代码就可以做到这个要求： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement user&lt;br /&gt;
    inherits&lt;br /&gt;
    person&lt;br /&gt;
 &lt;br /&gt;
facts&lt;br /&gt;
    password : string.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    new(Name, Password) :-&lt;br /&gt;
        person::new(Name),&lt;br /&gt;
        password := Password.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    trySetPassword(Old, New, Confirm) :-&lt;br /&gt;
        validatePassword(Old),&lt;br /&gt;
        New = Confirm,&lt;br /&gt;
        password := New.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    validatePassword(Password) :-&lt;br /&gt;
        password = Password.&lt;br /&gt;
end implement user&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
上面的实现说它 inherits person，如此一来，就会有如下效果：&lt;br /&gt;
&lt;br /&gt;
*每个构造出来的user对象都内含了person对象 &lt;br /&gt;
*user类可以直接由person类继承所有person接口的谓词。继承谓词时，并不是直接说它的实现，而是说要继承的实现所在的类。&lt;br /&gt;
 &lt;br /&gt;
The inheritance can to a certain extend be explained as syntactic sugaring. At least I could have achieved exactly the same effect with the following code (the clauses for the password predicates remains as above): &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement user&lt;br /&gt;
facts&lt;br /&gt;
    person : person.&lt;br /&gt;
    password : string.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    new(Name, Password) :-&lt;br /&gt;
        person := person_class::new(Name),&lt;br /&gt;
        password := Password.&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    getName() = person:getName().&lt;br /&gt;
 &lt;br /&gt;
clauses&lt;br /&gt;
    setName(Name) :- person:setName(Name).&lt;br /&gt;
 &lt;br /&gt;
    ...&lt;br /&gt;
end implement user_class&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
上面的代码，我们没有继承person类的什么东西，而是自行创建 person 对象并把它保存在事实变量中。也没有继承 getName 和 setName 的代码而是直接实现了这些谓词，它们只不过是把工作委托给了事实变量中的对象。带有关键字 delegate 的段可以用来便捷这样的委托。 &lt;br /&gt;
&lt;br /&gt;
这个代码功能上很相似，但还是有显著的差别： &lt;br /&gt;
&lt;br /&gt;
*首先，代码还是多写了些 &lt;br /&gt;
*person对象没有内含在user对象中，而是引用了它（还有，这里有两个内存分配块而不是一个）。&lt;br /&gt;
*在后面这个情况下，我们可以 动态地 改变事实变量中的值为其它的对象，这只需要给事实变量赋值为新的对象，比如，类personInDB的一个对象。要注意，第二种实现在调用过程中有间接的一层。Visual Prolog处理这样的间接关系是相当有效率的，不过它处理继承会更有效率。&lt;br /&gt;
 &lt;br /&gt;
Visual Prolog具有多重继承特性，也就是说，可以同时继承多个类。&lt;br /&gt;
&lt;br /&gt;
==小结==&lt;br /&gt;
上面，我们介绍了Visual Prolog中对象系统的基本概念。对象系统中还有其它一些有意思的特征，这里没有说到，比如：&lt;br /&gt;
&lt;br /&gt;
*对象可以在实现中进一步支持接口 &lt;br /&gt;
*用于内存回收的终结程序 &lt;br /&gt;
*对象谓词值，a seamless counterpart to C# delegates. &lt;br /&gt;
&lt;br /&gt;
==参考==&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=DelayCall(window)_(%E5%BB%B6%E6%97%B6%E8%B0%83%E7%94%A8%EF%BC%89&amp;diff=4163</id>
		<title>DelayCall(window) (延时调用）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=DelayCall(window)_(%E5%BB%B6%E6%97%B6%E8%B0%83%E7%94%A8%EF%BC%89&amp;diff=4163"/>
		<updated>2015-08-23T23:50:04Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Motivating examples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的DelayCall (window)。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
事件可能发生得过于频繁而超过处理的需要,&amp;lt;vp&amp;gt;window::delayCall&amp;lt;/vp&amp;gt; 提供了一种解决这个问题的方法。&lt;br /&gt;
&lt;br /&gt;
== 起因 ==&lt;br /&gt;
&lt;br /&gt;
在文本框输入文字时常会出现一个可选项的列表,比如在浏览器输入URL和在搜索引擎输入搜索内容时。但当你快速输入时,就会希望它不要每输入一个字符都给出建议选项。这有两方面的原因,.一是这样太耗资源;二是快速闪动变化的可选项会使人因眼花瞭乱而不舒服。&lt;br /&gt;
&lt;br /&gt;
不要每次击键都更新列表,而是延迟到用户输入有了停顿时才更新。也就是延迟到最后一次击键过了一定时间后再更新。&lt;br /&gt;
&lt;br /&gt;
这里还有两个例子,也可以应用上述同样的解决方案来避免快速闪动及/或性能上的问题:&lt;br /&gt;
* 实际的滚动最好是延迟到滚动事件有暂停时进行。&lt;br /&gt;
* 接收来自外部的数据更新程序,最好等到外部数据有间歇时再对图形用户界面进行更新。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Drawback&amp;#039;&amp;#039;&amp;#039;: 直到超过了某个时间,我们才能知道有了停顿/暂停。所以这种方法引入了一个时延,这个时间并非所有情况都期望: 所以这个延迟时间应在既不要反应太频繁又不要停顿时间太长之间作折衷。&lt;br /&gt;
&lt;br /&gt;
这里 [http://youtu.be/IAonJ6t6eho video] 演示了三个控件。中间一个每次调整窗口大小事件都重画窗口,另外两个则是在事件后停顿一下再重画。&lt;br /&gt;
&lt;br /&gt;
== delayCall ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; 可以用来实现“过一会再响应”的方案。&lt;br /&gt;
&lt;br /&gt;
先来看个简单的例子,控件的代码如下: &lt;br /&gt;
&amp;lt;vip&amp;gt;constants&lt;br /&gt;
    delay = 100. % ms&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    addData(NewData) :-&lt;br /&gt;
        doAddData(NewData),&lt;br /&gt;
        delayCall(delay, invalidate).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    removeData(ToRemove) :-&lt;br /&gt;
        doRemoveData(ToRemove),&lt;br /&gt;
        delayCall(delay, invalidate).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
该控件有对自身添加和删除数据的谓词。在数据更新时,控件会失效以便用新数据进行重画。但为了防止对控件大量重复的更新,我们要使失效推迟直到数据稳定了1OOms。这正是&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;调用做的事:它安排调用&amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt; 在&amp;lt;vp&amp;gt;delay&amp;lt;/vp&amp;gt; (= 100ms)之后进行,同时删除先前别的安排,如果有的话。因此,它的效果就是在数据更新后停顿100ms产生一次失效。&lt;br /&gt;
&lt;br /&gt;
现在,基本动作都到位了。我们再来看看细节、常见问题及解决方案。&lt;br /&gt;
&lt;br /&gt;
在这个&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;简单调用中有两个参数: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    delayCall : (positive Delay, runnable Predicate).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
对 &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; 的调用将会:&lt;br /&gt;
*或是在&amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt;毫秒过后调用 &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt;&lt;br /&gt;
*或是重启一个新的 &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; 调用,使用相同的&amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt; ,而 &amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt; 参数重新开始&lt;br /&gt;
*或是被丢弃,由于&amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt;到达之前窗口被销毁了。&lt;br /&gt;
&lt;br /&gt;
注意,delayCall是一个窗口一个 &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
区分窗口可以避免因窗口已经不存在而执行不相关的延迟谓词。The per window part can be used to avoid executing delayed predicate that is no longer relevant because the window it concerns no longer exist.&lt;br /&gt;
&lt;br /&gt;
各自的&amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt;也是十分重要的,因为可能会对许多不相关的事使用delayCall,这些不相关的事不应该彼此取消对方的延迟调用。The per &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt; part is important because you may want to delayCall for many unrelated things, and such things should not cancel each other. 把应用程序窗口用作全局响应是很正常的事,它不是与某个特定窗口关联。 It is very common to use the application window for &amp;quot;global&amp;quot; actions, which are not related to a specific window.例如,在某个元件变化停顿时重新计算全局状态。For example recalculating a global state when there is a pause in atomic changes.&lt;br /&gt;
&lt;br /&gt;
在我们的例子中，两个调用中使用的谓词都是&amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt;，因而不管我们调用的&amp;lt;vp&amp;gt;addData&amp;lt;/vp&amp;gt;还是&amp;lt;vp&amp;gt;removeData&amp;lt;/vp&amp;gt;都会再次对&amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt;延期。而如果调用是&amp;lt;vp&amp;gt;delayCall(17, somethingElse)&amp;lt;/vp&amp;gt;，则不会对&amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt;产生影响，不会取消它，也不会对它延期。&lt;br /&gt;
&lt;br /&gt;
如同上例中的情形所示，一般情况下谓词的标识表明了期望&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;区分的事情。不过对于匿名谓词来说这里容易出错，看下面的例子：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
       delayCall(10, { :- doSetColor(NewColor) }).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
代码的初衷是打算延迟到颜色不再频繁变化时再设置颜色。它使用了一个匿名谓词来捕获新的颜色。通常情况下这种匿名谓词使用方法很值得推荐，不过在这里会产生一个问题，就是：每调用一次&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;就会创建一个新的匿名谓词来捕获&amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;。因而，每调用一次&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;就会用&amp;#039;&amp;#039;&amp;#039;唯一的&amp;#039;&amp;#039;&amp;#039;谓词调用一次&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;。由于每个谓词都和前一个不是一回事，所以就不会对上一次的延时调用产生影响。如此一来，它就会每延迟10毫秒执行一次。&lt;br /&gt;
&lt;br /&gt;
解决该问题可以这样做：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
facts&lt;br /&gt;
   newColor : color := color_white. % 没有实际使用的哑赋值&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
        newColor := NewColor,&lt;br /&gt;
        delayCall(10, setNewColor).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    setNewColor() :-&lt;br /&gt;
        doSetColor(newColor).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
调用这个版本的&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;时，会更新事实变量&amp;lt;vp&amp;gt;newColor&amp;lt;/vp&amp;gt;，所以这个事实变量中总是包含着&amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;的最新值。现在，对&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;的调用涉及是的同一个谓词（也就是&amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt;）。因此，这段代码每次调用&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;都会取消前一次的&amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt;计划并重新做延期安排。这样，在&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;的调用中&amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt;的请求就延期在稍有停顿时。&lt;br /&gt;
&lt;br /&gt;
与把新颜色放在匿名谓词中不同，我们把新颜色放在了一个事实变量中。因为这样一来我们就可以调用一个命名的谓词而不是一个匿名谓词。&lt;br /&gt;
&lt;br /&gt;
The overall principle here is to maintain a shaddow (sub-)state which is then merged into the real state in a pause.&lt;br /&gt;
&lt;br /&gt;
上面匿名谓词问题的解决方案是相当复杂和麻烦，需要对匿名和命名谓词都有深入的了解。The solution above to the anonymous predicate problem is rather complex/elaborate, we both need to introduce extra fact(s) and an extra named predicate.&lt;br /&gt;
&lt;br /&gt;
最要命的是，那个事实变量中并没有包含对象所呈现的实际信息，它所存储的只不过是一个算法中的一点到另一点所传递的信息，就是说它是算法的局部信息而不是对象的全局信息。&lt;br /&gt;
&lt;br /&gt;
这样的事实令人非常不满意，常常会用&amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt;或匿名谓词取而代之。在这里，&amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt;也无助于问题的解决，因为与&amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;完全相同的原因，它也需要是对象的全局信息。Such facts are highly undesirable and very often you will use &amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt;&amp;#039;s or anonymous predicates to eliminate them.  In this case a &amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt; will not help, because it will also have to be made global in the object for exactly the same reasons as for &amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
因此，有办法在&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;中处理好匿名谓词就太好了。问题在于匿名谓词的身份不太适合确定是否需要拖延时间。解决这个问题（及其它一些问题）我们可以用另一版本的delayCall，它显式提供身份值来决定是拖延已有的延时还是要创建一个新的延时。So it would be very nice to have a way to deal with anonymous predicates in &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;.  The problem was that the identity of anonymous predicates is not very suitable for determine whether to prolong a delay.  To solve this problem (and others) we can use another version of delayCall which uses an explicit provided identity value to determine whether to prolong an existing delay or to create a new one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    delayCall : (positive Delay, runnable Predicate, IdType Id).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这个版本中多了一个&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;参数，其类型可以任意确定。该值如果在两次调用中相同，后面的就会替代前面的（当然是前一个延时还没到期）；如果值不一样，就认为调用间是不相关的。This version takes an extra &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt; argumment of any type you like.  If you provide the same value in two calls the latter will replace the first (if has not already fired of course), if you provide different values the calls are considered unrelated.&lt;br /&gt;
&lt;br /&gt;
编程人员需要确保选择&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;：&lt;br /&gt;
* 取消/替换/拖延所指定的调用，并且&lt;br /&gt;
* 不对其它&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;的使用产生影响&lt;br /&gt;
&lt;br /&gt;
有许多不同的策略可以用于&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;，我们后面要讨论其中的一些。&lt;br /&gt;
&lt;br /&gt;
在前面讨论的第一个例子中，我们有两个调用，都属于同样的延迟。在这其中我们（间接地）使用了谓词本身作为ID。前面已经说过，这样意义很明确，在许多情况下是很好的，不过它需要使用命名了的谓词。&lt;br /&gt;
&lt;br /&gt;
对于匿名谓词，当需要更精确控制时就必须使用其它的ID。关键问题在于用某种东西来“碰撞”相同的调用，而不与其它的调用发生关系。&lt;br /&gt;
&lt;br /&gt;
常常与&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;的例子中一样，期望碰撞的调用来自于代码中的特定位置。Very often like in the &amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt; example the calls you want to collide comes from a specific place in the code.&lt;br /&gt;
&lt;br /&gt;
在这种情况下，将&amp;lt;vp&amp;gt;programPoint&amp;lt;/vp&amp;gt;用作&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;最简单。  The is easily obtained using the builtin predicate &amp;lt;vp&amp;gt;programPoint/0&amp;lt;/vp&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
       delayCall(10, { :- doSetColor(NewColor) }, programPoint()).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这种源程序行确定的ID调用可以相互取消但不会与其它地方的调用产生冲突（不管它们用什么策略）。&lt;br /&gt;
&lt;br /&gt;
如果需要把调用放在若干行中，或是对同一组不同的命名谓词进行调用，则可以考虑定义一个组ID。同样，仍可以使用&amp;lt;vp&amp;gt;programPoint&amp;lt;/vp&amp;gt;：If you need to put calls from several lines, or calls to different named predicates in the same group you should consider defining a group id.  Also here you can use a &amp;lt;vp&amp;gt;programPoint&amp;lt;/vp&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
constants&lt;br /&gt;
    myDelayCallId : programPoint = programPoint().&lt;br /&gt;
 &lt;br /&gt;
   ..., delayCall(10, { :- doSomeThing(...) }, myDelayCallId), ...&lt;br /&gt;
   &lt;br /&gt;
   ..., delayCall(20, doSomeThingElse, myDelayCallId), ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
如果由于某种原因需要确保源代码变化后这种ID不变（比如一个DLL需要知道作为另一个DLL的相同ID），则可以生成和使用GUID。GUID是一个128比特的数，Windows可以生成这样的数并且很少可能产生冲突。在Visual Prolog集成开发环境中可以生成使用这样的数，只需要在菜单中选择&amp;#039;&amp;#039;&amp;#039;Insert -&amp;gt; New GUID&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
constants&lt;br /&gt;
    myDelayCallId : nativeGuid = % {3363A750-0320-4B2A-BE3A-27F66BD7B875}&lt;br /&gt;
        nativeGuid(0x3363A750, 0x0320, 0x4B2A, 0xBE, 0x3A, 0x27, 0xF6, 0x6B, 0xD7, 0xB8, 0x75).&lt;br /&gt;
&lt;br /&gt;
   ..., delayCall(10, { :- doSomeThing(...) }, myDelayCallId), ...&lt;br /&gt;
   &lt;br /&gt;
   ..., delayCall(20, doSomeThingElse, myDelayCallId), ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
最后，我们来看看如何使用“语义”&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;，看下面的代码：Finally, let us consider using &amp;quot;semantic&amp;quot; &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;&amp;#039;s.  Consider the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        doOnFileChanged(Filename).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
一个程序监视着磁盘上文件的变化。如果一个（相关的）文件变化了，程序就会执行某个动作。上面代码中每当文件变化就会调用onFileChanged，而&amp;lt;vp&amp;gt;doOnFileChanged&amp;lt;/vp&amp;gt;表示文件变化了我们想要做的事情。&lt;br /&gt;
&lt;br /&gt;
不过文件变化相继来得会很快，最好是等这些更新都消停了再动作。因此，这个过程中自然要用到delayCall。&lt;br /&gt;
&lt;br /&gt;
但上面的解决方案在这里似乎都不适用。因为delayCall应该是针对每个文件的，而不能是针对每次功能或每个程序点的。简单的办法就是用匿名谓词来跟踪文件并使用文件名作为&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, Filename).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
现在，只有某个文件最后一个onFileChange在记忆中并被延迟处理。&lt;br /&gt;
&lt;br /&gt;
更广泛的关联中，我们担心使用文件名会与其它地方的某种计划安排产生冲突。一种解决办法是混合使用语义和程序点，像这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, tuple(Filename, programPoint())).&amp;lt;/vip&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
另一个办法是创建一个唯一的延迟域：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
domains&lt;br /&gt;
    delayId = fileChangeId(string Filename).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, fileChangeId(Filename)).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
程序点解决方案的好处是不需要额外的定义，是一种“协调”的解决办法。而&amp;lt;vp&amp;gt;delayId&amp;lt;/vp&amp;gt;域的解决方案好处是在代码中它可以使用在若干个地方。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Web_Services_(Web%E6%9C%8D%E5%8A%A1)&amp;diff=4162</id>
		<title>Web Services (Web服务)</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Web_Services_(Web%E6%9C%8D%E5%8A%A1)&amp;diff=4162"/>
		<updated>2015-08-23T09:23:43Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;（以下内容，译自:Category:Tutorials中的Web Services。更多内容可以参看:Category:Chinese。）  本教程描述如何创建Web应用程序，它的前端...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的Web Services。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
本教程描述如何创建Web应用程序，它的前端是运行在用户浏览器中的HTML/JavaScript，而后端是用Visual Prolog编写的。&lt;br /&gt;
&lt;br /&gt;
介绍的重点是Visual Prolog的后端，它实现JSON-RPC Web服务。所包含的HTML/JavaScript代码主要是为了使例子自身完整，说明前端代码如何与Visual Prolog服务互动。&lt;br /&gt;
&lt;br /&gt;
教程描述了同样的服务如何才能：&lt;br /&gt;
&lt;br /&gt;
* 作为独立的HTTP/HTTPS服务器运行&lt;br /&gt;
* 作为IIS（Microsoft Information Services微软信息服务）的ISAPI扩展嵌入式地运行&lt;br /&gt;
&lt;br /&gt;
注意，web服务需要使用Visual Prolog的 &amp;#039;&amp;#039;&amp;#039;商业版&amp;#039;&amp;#039;&amp;#039;。&lt;br /&gt;
还要注意，本教程中完全没有考虑SOAP。&lt;br /&gt;
&lt;br /&gt;
使用的例子是商业版中的 &amp;#039;&amp;#039;&amp;#039;webRPC&amp;#039;&amp;#039;&amp;#039;。 (IDE: &amp;#039;&amp;#039;&amp;#039;Help -&amp;gt;Install Examples...&amp;#039;&amp;#039;&amp;#039;)。&lt;br /&gt;
&lt;br /&gt;
=== 概述 ===&lt;br /&gt;
&lt;br /&gt;
本节描述应用程序的整体概貌及工作方式。实际发生的事情是相当复杂的，不过好在很多复杂的东西是自动处理的。了解过程的概貌及其复杂性，就比较容易理解要做些什么及为什么这样做。&lt;br /&gt;
&lt;br /&gt;
总的来说，作为例子的应用程序其工作流程是这样的：用户（在web浏览器中）浏览网页 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//&amp;lt;server&amp;gt;/file/test.htm&amp;#039;&amp;#039;&amp;#039; （或 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//&amp;lt;server&amp;gt;/file/calendar.htm&amp;#039;&amp;#039;&amp;#039;），服务器返回相应的文件给浏览器。浏览器对文件的上下文进行评估后，可能进一步请求级联的css、js文件和图片等。此外，HTML文件包含的嵌入式JavaScript代码也会对服务器产生远端过程调用 (&amp;#039;&amp;#039;&amp;#039;RPC&amp;#039;&amp;#039;&amp;#039;)，服务器执行相应的过程并将结果返回给浏览器。最后，嵌入式的JavaScript用返回的数据更新浏览器的HTML内容。&lt;br /&gt;
&lt;br /&gt;
客户机与服务器之间的通信由HTTP (或 HTTPS)协议完成。因此在服务器一侧需要一个HTTP服务器，它可以返回文件并实现远端过程调用。在 &amp;#039;&amp;#039;&amp;#039;jsonRpcService_httpApi&amp;#039;&amp;#039;&amp;#039; 中的程序运行该服务作为一个独立的HTT服务器，它可以处理文件请求，实现远端过程调用。&amp;#039;&amp;#039;&amp;#039;jsonRpcService_isApi&amp;#039;&amp;#039;&amp;#039;中的程序是用ISAPI插件来实现远端过程调用的，通过对IIS的配置来处理文件请求并对远端过程调用使用ISAPI。&lt;br /&gt;
&lt;br /&gt;
独立的HTTP服务器通过微软的[http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510(v=vs.85).aspx HTTP Server API]来实现，它可以共享带有IIS的端口及URL，这样一来，&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//&amp;lt;server&amp;gt;/file&amp;#039;&amp;#039;&amp;#039;等可以进入到我们的独立服务器而&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//&amp;lt;server&amp;gt;/&amp;lt;something&amp;gt;&amp;#039;&amp;#039;&amp;#039;也可以由IIS得到处理。&lt;br /&gt;
&lt;br /&gt;
=== JSON 和 JSON-RPC ===&lt;br /&gt;
&lt;br /&gt;
远端过程调用是按照[http://www.jsonrpc.org/specification JSON-RPC 2.0 Specification]规程实现的，这实际上要求数据按[http://www.json.org/ JSON] (&amp;#039;&amp;#039;&amp;#039;J&amp;#039;&amp;#039;&amp;#039;ava&amp;#039;&amp;#039;&amp;#039;S&amp;#039;&amp;#039;&amp;#039;cript &amp;#039;&amp;#039;&amp;#039;O&amp;#039;&amp;#039;&amp;#039;bject &amp;#039;&amp;#039;&amp;#039;N&amp;#039;&amp;#039;&amp;#039;otation) 格式编码。&lt;br /&gt;
&lt;br /&gt;
JSON语法是JavaScript的一个子集：在对有效JSON文本进行求值时它会成为一个相应的JavaScript对象。然而，还是应该总是使用JSON解析程序而不是对该文本求值：因为文本更可能是“代码”而不是“数据”，所以对文本求值有安全上的风险。&lt;br /&gt;
&lt;br /&gt;
不需要对JSON格式有详细的了解，因为在客户端一侧使用的是JavaScript而在服务器一侧使用的是JSON的对象/域表示方式，（对JSON格式的）解析和编写都可以交给标准软件去做。&lt;br /&gt;
&lt;br /&gt;
执行JSON-RPC调用时，客户端发送一个JSON请求对象，服务器用一个JSON响应对象做响应，除非请求是一个&amp;#039;&amp;#039;&amp;#039;notification（通知）&amp;#039;&amp;#039;&amp;#039;，此时不需要响应。&lt;br /&gt;
&lt;br /&gt;
请求对象有四个成员：&lt;br /&gt;
* &amp;lt;vp&amp;gt;jsonrpc&amp;lt;/vp&amp;gt;: 一个说明JSON-RPC协议版本的串，必须就是 &amp;quot;2.0&amp;quot;。&lt;br /&gt;
* &amp;lt;vp&amp;gt;method&amp;lt;/vp&amp;gt;: 一个包含被调用方法名称的串。&lt;br /&gt;
* &amp;lt;vp&amp;gt;params&amp;lt;/vp&amp;gt;: 该方法的参数（如果方法不带参数就省略）。&lt;br /&gt;
**by-position: 一个JSON数组，包含有按服务器要求顺序排列的值。&lt;br /&gt;
**by-name: 一个带有成员名称的JSON对象，成员名称要严格与方法所期望的参数相匹配，包括大小写都要一致。 &lt;br /&gt;
* &amp;lt;vp&amp;gt;id&amp;lt;/vp&amp;gt;: 一个由客户端创建的标识该请求的串或数。如果是通知则这个域就省掉了。&lt;br /&gt;
&lt;br /&gt;
响应对象有三个成员：&lt;br /&gt;
* &amp;lt;vp&amp;gt;jsonrpc&amp;lt;/vp&amp;gt; 一个说明JSON-RPC协议版本的串，必须就是 &amp;quot;2.0&amp;quot;。&lt;br /&gt;
* &amp;lt;vp&amp;gt;id&amp;lt;/vp&amp;gt; 由客户端发布的值。&lt;br /&gt;
第三个成员是下列之一：&lt;br /&gt;
* &amp;lt;vp&amp;gt;result&amp;lt;/vp&amp;gt; （意味着该调用成功了）程序调用的结果&lt;br /&gt;
* &amp;lt;vp&amp;gt;error&amp;lt;/vp&amp;gt; （意味着该调用失败了） 一个描述所发生错误的对象&lt;br /&gt;
&lt;br /&gt;
=== 客户端一侧 ===&lt;br /&gt;
&lt;br /&gt;
{{例|这段代码演示了在客户端一侧进行一次过程调用的方法：&lt;br /&gt;
&amp;lt;source lang=&amp;quot;JavaScript&amp;quot;&amp;gt;&lt;br /&gt;
function httpPOST(URL, method, params) {&lt;br /&gt;
    var ReqObj = { jsonrpc: &amp;quot;2.0&amp;quot;, id: getId(), method: method, params: params };&lt;br /&gt;
    var Req = JSON.stringify(ReqObj);&lt;br /&gt;
    var xmlHttp = new XMLHttpRequest();&lt;br /&gt;
    xmlHttp.open(&amp;quot;POST&amp;quot;, URL, false);&lt;br /&gt;
    xmlHttp.setRequestHeader(&amp;#039;Content-Type&amp;#039;, &amp;#039;application/json-rpc; charset=UTF-8&amp;#039;);&lt;br /&gt;
    xmlHttp.send(Req);&lt;br /&gt;
    var resp = xmlHttp.responseText;&lt;br /&gt;
    var respObj = JSON.parse(resp);&lt;br /&gt;
    var error = respObj.error;&lt;br /&gt;
    if (error) {&lt;br /&gt;
        if (Data = error.data) {&lt;br /&gt;
            log(&amp;quot;&amp;lt;pre&amp;gt;&amp;quot; + Data +&amp;quot;&amp;lt;/pre&amp;gt;\n&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
        log(&amp;quot;&amp;lt;p&amp;gt;error : &amp;quot;+ error.code +&amp;quot;: &amp;quot; + error.message + &amp;quot;&amp;lt;/p&amp;gt;\n&amp;quot;);&lt;br /&gt;
    } else {&lt;br /&gt;
        // respObj.result contains the result&lt;br /&gt;
    }&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
小结一下：&lt;br /&gt;
* 创建一个请求对象Create a request object&lt;br /&gt;
* 由JSON将其 &amp;quot;串化（stringify）&amp;quot;&lt;br /&gt;
* 用一个XMLHttpRequest对象发布它&lt;br /&gt;
* JSON解析响应文本&lt;br /&gt;
* 对 &amp;quot;error&amp;quot; 成员进行检查&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
有很多其它的方法来写客户端一侧的代码，通常会使用一些标准的JavaScript库程序包，这些包可以异步处理错误及执行调用。 &amp;#039;&amp;#039;&amp;#039;calendar.htm&amp;#039;&amp;#039;&amp;#039; 使用了这样的库代码，不过这些内容超出了本教材的范围。&lt;br /&gt;
&lt;br /&gt;
=== 服务代码 ===&lt;br /&gt;
&lt;br /&gt;
在Visual Prolog中JSON是由域&amp;lt;vp&amp;gt;json::jsonValue&amp;lt;/vp&amp;gt;来表示的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;domains&lt;br /&gt;
    jsonValue =&lt;br /&gt;
        n; % null&lt;br /&gt;
        f; % false&lt;br /&gt;
        t; % true&lt;br /&gt;
        r(real Number);&lt;br /&gt;
        s(string String);&lt;br /&gt;
        a(jsonValue* Array);&lt;br /&gt;
        o(jsonObject Object).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
JSON值只有7类：null、false、true、数、串、数组、对象。&lt;br /&gt;
&lt;br /&gt;
数组是一个JSON值的序列，用Visual Prolog的表来表示。&lt;br /&gt;
&lt;br /&gt;
JSON对象由Visual Prolog的 &amp;lt;vp&amp;gt;jsonObject&amp;lt;/vp&amp;gt; 接口表示：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;interface jsonObject supports mapM{string Key, jsonValue Value}&lt;br /&gt;
...&lt;br /&gt;
end interface jsonObject&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
该接口含有一些便利的谓词/属性（这些上面都没有说到），不过从本质上来说&amp;lt;vp&amp;gt;jsonObject&amp;lt;/vp&amp;gt;不过就是从Key（名称）到JSON值之间的映射。&lt;br /&gt;
&lt;br /&gt;
{{例|以下代码：&lt;br /&gt;
&amp;lt;vip&amp;gt;J = jsonObject::new(),&lt;br /&gt;
J:set_boolean(&amp;quot;bbb&amp;quot;, true),&lt;br /&gt;
J:set_string(&amp;quot;sss&amp;quot;, &amp;quot;The String&amp;quot;),&lt;br /&gt;
J:writeTo(stdio::outputStream),&lt;br /&gt;
stdio::nl,&lt;br /&gt;
jsonPrettyPrint::ppObject(stdio::outputStream, J)&amp;lt;/vip&amp;gt;&lt;br /&gt;
会生成如下输出：&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;{&amp;quot;bbb&amp;quot;:true,&amp;quot;sss&amp;quot;:&amp;quot;The String&amp;quot;}&lt;br /&gt;
{&lt;br /&gt;
    &amp;quot;bbb&amp;quot; : true,&lt;br /&gt;
    &amp;quot;sss&amp;quot; : &amp;quot;The String&amp;quot;&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;lt;vp&amp;gt;writeTo&amp;lt;/vp&amp;gt; 压缩生成一种适用于进程间通信等的 JSON 表示。 &amp;lt;vp&amp;gt;jsonPrettyPrint&amp;lt;/vp&amp;gt; 可用良好的格式（比如调试时用）打印该值。&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
创建 RPC 服务时，必须实现一个支持&amp;lt;vp&amp;gt;rpcService&amp;lt;/vp&amp;gt;接口的对象：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;interface testService supports rpcService&lt;br /&gt;
end interface testService&lt;br /&gt;
&lt;br /&gt;
class testService : testService&lt;br /&gt;
end class testService&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
在该实现中，需要继承处理JSON解析、编写等的&amp;lt;vp&amp;gt;jsonRpcServiceSupport&amp;lt;/vp&amp;gt;：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;implement testService&lt;br /&gt;
    inherits jsonRpcServiceSupport&lt;br /&gt;
    open core, json&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
end implement testService&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
还需要在构造器中注册你的过程：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    new() :-&lt;br /&gt;
        setProc_name(&amp;quot;hello&amp;quot;, hello),&lt;br /&gt;
        setProc_name(&amp;quot;calendar&amp;quot;, calendar).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
过程有四类，相应于不同的注册谓词：There are four kinds of procedures, with corresponding registration predicates:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;vp&amp;gt;setProc_array&amp;lt;/vp&amp;gt; 用于注册将参数放在数组中的过程&lt;br /&gt;
* &amp;lt;vp&amp;gt;setProc_name&amp;lt;/vp&amp;gt; 用于注册在对象中使用命名参数的过程&lt;br /&gt;
* &amp;lt;vp&amp;gt;setListener_array&amp;lt;/vp&amp;gt; 用于注册将参数放在数组中的通知监听器&lt;br /&gt;
* &amp;lt;vp&amp;gt;setListener_name &amp;lt;/vp&amp;gt; 用于注册在对象中使用命名参数的通知监听器&lt;br /&gt;
&lt;br /&gt;
在上面的例子中，有两个过程“hello”和“calendar”，都是使用命名参数。 hello 的代码可以是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;constants&lt;br /&gt;
    testService_error : jsonRpcError::serverErrorCode = jsonRpcError::serverError_last+1.&lt;br /&gt;
    % 从last开始增加，last是用于未指明的服务器错误&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    hello : jsonProc_name.&lt;br /&gt;
clauses&lt;br /&gt;
    hello(_Context, ArgMap) = o(Result) :-&lt;br /&gt;
        Result = jsonObject::new(),&lt;br /&gt;
        Username = ArgMap:get_string(&amp;quot;username&amp;quot;),&lt;br /&gt;
        if &amp;quot;error&amp;quot; = Username then&lt;br /&gt;
            raise_serviceError(testService_error, &amp;quot;&amp;#039;error&amp;#039; is an invalid user name&amp;quot;)&lt;br /&gt;
        elseif &amp;quot;userError&amp;quot; = Username then&lt;br /&gt;
            exception::raise_user(&amp;quot;&amp;#039;%Name%&amp;#039; is an invalid user name&amp;quot;, [namedValue(&amp;quot;Name&amp;quot;, string(Username))])&lt;br /&gt;
        elseif &amp;quot;definiteUserError&amp;quot; = Username then&lt;br /&gt;
            exception::raise_definiteUser(&amp;quot;&amp;#039;%Name%&amp;#039; is an invalid user name&amp;quot;, [namedValue(&amp;quot;Name&amp;quot;, string(Username))])&lt;br /&gt;
        else&lt;br /&gt;
            Result:set_string(&amp;quot;token&amp;quot;, string::concat(&amp;quot;Hello &amp;quot;, Username))&lt;br /&gt;
        end if.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;hello&amp;lt;/vp&amp;gt;过程首先创建一个&amp;lt;vp&amp;gt;jsonObject&amp;lt;/vp&amp;gt;用于&amp;lt;vp&amp;gt;Result&amp;lt;/vp&amp;gt;。任意 JSON 值都可以当成结果，不过 &amp;lt;vp&amp;gt;hello&amp;lt;/vp&amp;gt; 设计为返回带一个&amp;lt;vp&amp;gt;&amp;quot;token&amp;quot;&amp;lt;/vp&amp;gt;成员的JSON对象。&lt;br /&gt;
&lt;br /&gt;
接下来它由&amp;lt;vp&amp;gt;ArgMap&amp;lt;/vp&amp;gt;获取参数，要的是一个名为&amp;lt;vp&amp;gt;&amp;quot;username&amp;quot;&amp;lt;/vp&amp;gt;的串参数。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;hello&amp;lt;/vp&amp;gt; 使用 &amp;lt;vp&amp;gt;Username&amp;lt;/vp&amp;gt; 的值来说明异常处理与成功。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;&amp;quot;error&amp;quot;&amp;lt;/vp&amp;gt;一段演示如何引发“服务器错误”，它是用于RPC服务的特别设计的错误消息。“userError”和“definiteUserError”情况说明的是该过程调用时除其它异常外可能发生的异常。&lt;br /&gt;
&lt;br /&gt;
要注意，客户端代码要编程处理相应的错误。当然服务器一侧也可以在任意其它程序中处理指定的异常，也可以在适当地方记录这些错误。&lt;br /&gt;
&lt;br /&gt;
错误不是 &amp;lt;vp&amp;gt;Username&amp;lt;/vp&amp;gt; 的情况，映射 &amp;quot;token&amp;quot; 串给 &amp;lt;vp&amp;gt;Result&amp;lt;/vp&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
{{例|建议 &amp;lt;vp&amp;gt;jsonProc&amp;lt;/vp&amp;gt; 只做参数提取与结果构造，而使实际功能在分开的事务层中，如下面这段代码演示的：&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    hello : jsonProc_name.&lt;br /&gt;
clauses&lt;br /&gt;
    hello(_Context, ArgMap) = o(Result) :-&lt;br /&gt;
        Result = jsonObject::new(),&lt;br /&gt;
        Username = ArgMap:get_string(&amp;quot;username&amp;quot;),&lt;br /&gt;
        Token = businessLayer::hello(Username),  % 实际功能在分开的事务层中&lt;br /&gt;
        Result:set_string(&amp;quot;token&amp;quot;, Token).&amp;lt;/vip&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
=== 独立的 HTTP 服务器 ===&lt;br /&gt;
&lt;br /&gt;
如同上面 &amp;lt;vp&amp;gt;jsonRpcService_httpApi&amp;lt;/vp&amp;gt; 程序演示的，&amp;lt;vp&amp;gt;testService&amp;lt;/vp&amp;gt;（支持 &amp;lt;vp&amp;gt;rpcService&amp;lt;/vp&amp;gt;）可以用于独立的 HTTP 服务器，不需要多少代码，而且为适合特殊需要而做的代码调整也很简单。但不管怎么样，也还是相当复杂的。&lt;br /&gt;
&lt;br /&gt;
该服务器基于微软能与IIS很好配合的[http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510(v=vs.85).aspx HTTP Server API]。其实，IIS好像是构建在该 HTTP Server API 的顶层（或起码它们两者都是构建在共同基础之上的）。&lt;br /&gt;
&lt;br /&gt;
HTTP Server api 与以下实体打交道：&lt;br /&gt;
* 创建器/构造器的进程&lt;br /&gt;
* Server会话&lt;br /&gt;
* URL组&lt;br /&gt;
* Request队列&lt;br /&gt;
* Worker进程&lt;br /&gt;
&lt;br /&gt;
创建器/构造器进程创建服务器会话，由其定义URL组及请求队列。worker进程与请求队列相关联，处理队列接收到的请求。&lt;br /&gt;
&lt;br /&gt;
在示例程序中创建器/构造器是仅有的worker进程，更复杂的情形超出了本教材的范围。&lt;br /&gt;
&lt;br /&gt;
服务器会话是URL组及请求队列的所有者/持有者，它们才是我们需要实际考虑的。 &lt;br /&gt;
&lt;br /&gt;
URL组是由URL模型集定义的，其形式为：&lt;br /&gt;
:&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//&amp;lt;host&amp;gt;:&amp;lt;port&amp;gt;/&amp;lt;relativeURI&amp;gt;&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
:&amp;#039;&amp;#039;&amp;#039;https:&amp;lt;nowiki /&amp;gt;//&amp;lt;host&amp;gt;:&amp;lt;port&amp;gt;/&amp;lt;relativeURI&amp;gt;&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
&lt;br /&gt;
一个请求到达计算机时，一种匹配机制会按照活动的URL组中URL模型集对其进行匹配，结果要不是匹配不上就是覆盖该URL。&lt;br /&gt;
&lt;br /&gt;
每个URL组分配一个请求队列，与某个URL组匹配的请求就放入其相应的队列。那些没有分配请求队列的URL组是非活动的，不参加匹配。&lt;br /&gt;
&lt;br /&gt;
Worker进程从请求队列中取出请求并进行处理。一个请求队列可以与若干个URL组相联系，而每个URL组又可以有若干URL模型。因此，worker进程由某个请求队列摘出的请求可以会匹配与该队列联系的某个URL组的某个URL模型。&lt;br /&gt;
&lt;br /&gt;
在示例服务器中，我们创建了两个请求队列，每个都只有一个URL组与之联系，并且每个URL组也只有一个URL模型。事实上：&lt;br /&gt;
* &amp;lt;vp&amp;gt;reqQueue_rpc&amp;lt;/vp&amp;gt; 接收来自 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki/&amp;gt;//localhost:5555/jsonrpc/&amp;lt;something&amp;gt;&amp;#039;&amp;#039;&amp;#039;的请求，而&lt;br /&gt;
* &amp;lt;vp&amp;gt;reqQueue_file&amp;lt;/vp&amp;gt; 接收来自&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki/&amp;gt;//localhost:5555/file/&amp;lt;something&amp;gt;&amp;#039;&amp;#039;&amp;#039;&amp;#039;的请求。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;reqQueue_rpc&amp;lt;/vp&amp;gt; is a &amp;lt;vp&amp;gt;requestQueue_rpc&amp;lt;/vp&amp;gt; handles RPC requests by feeding them to our &amp;lt;vp&amp;gt;testService&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;reqQueue_file&amp;lt;/vp&amp;gt;是一个&amp;lt;vp&amp;gt;requestQueue_file&amp;lt;/vp&amp;gt;，它用 &amp;lt;vp&amp;gt;fileMapper&amp;lt;/vp&amp;gt;映射该请求到一个文件名及相应的&amp;lt;vp&amp;gt;ContentType&amp;lt;/vp&amp;gt;，&amp;lt;vp&amp;gt;requestQueue_file&amp;lt;/vp&amp;gt;接下来就会处理该文件的传输。&lt;br /&gt;
&lt;br /&gt;
==== 服务器配置：netsh ====&lt;br /&gt;
&lt;br /&gt;
上面的示例运行很平稳，因为它使用的是宿主机5555端口。然而一般情况下运行应用程序会要求http的使用80端口、https的使用443端口，这时就会出现一些安全性问题。&lt;br /&gt;
&lt;br /&gt;
如果仅只是更换端口号并使用公共宿主机名，在给URL组添加URL模型时服务器程序将会得到一个 &amp;#039;Access is denied&amp;#039;（拒绝访问）的异常。&lt;br /&gt;
&lt;br /&gt;
如果&amp;quot;as Administrator&amp;quot;来运行服务器则不会产生访问违例。但这绝对是不值得提倡的，因为这样一来它也有了做某些有害事情的权力。&lt;br /&gt;
&lt;br /&gt;
要使程序运行于普通模式，必须要对运行服务器程序的用户保留上面讨论的URL模型。&lt;br /&gt;
&lt;br /&gt;
使用netsh.exe (net shell)程序可以完成这个工作。 program.这将会需要处理http URL acl。详细内容参见 [http://msdn.microsoft.com/en-us/library/windows/desktop/cc307236(v=vs.85).aspx Netsh commands for HTTP].&lt;br /&gt;
&lt;br /&gt;
{{例|演示在命令行提示符下的工作：&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&amp;gt;netsh http add urlacl http://www.something.com:80/file/ user=SMT\serviceuser&lt;br /&gt;
&amp;gt;netsh http add urlacl http://www.something.com:80/jsonrpc/ user=SMT\serviceuser&amp;lt;/source&amp;gt;&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;注意&amp;#039;&amp;#039;&amp;#039; 要做修改需使命令行提示符运行于&amp;quot;as Administrator&amp;quot;。&lt;br /&gt;
如果仅写 &amp;quot;netsh&amp;quot; 就会进入http模式的提示符模式，只是写http命令。&lt;br /&gt;
&amp;lt;source lang=&amp;quot;text&amp;quot;&amp;gt;&amp;gt;netsh&lt;br /&gt;
netsh&amp;gt;http&lt;br /&gt;
netsh http&amp;gt;add urlacl http://www.something.com:80/file/ user=SMT\serviceuser&lt;br /&gt;
netsh http&amp;gt;add urlacl http://www.something.com:80/jsonrpc/ user=SMT\serviceuser&lt;br /&gt;
netsh http&amp;gt;exit&amp;lt;/source&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
通过Visual Prolog来进行URL保留也是可以的，但为了防止冲突及其它一些问题，最好还是使用标准netsh程序。&lt;br /&gt;
&lt;br /&gt;
注意示例是个控制台程序。在大规模应用情况下设计一个通用窗口服务会更好些。Visual Prolog有工程模板用于创建服务，但这超出了本教材的范围。&lt;br /&gt;
&lt;br /&gt;
=== IISAPI插件 ===&lt;br /&gt;
&lt;br /&gt;
如同 &amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 程序所演示的那样，&amp;lt;vp&amp;gt;testService&amp;lt;/vp&amp;gt;（支持&amp;lt;vp&amp;gt;rpcService&amp;lt;/vp&amp;gt;)也可以ISAPI dll的形式来应用。该DLL可以由IIS使用来运行RPC请求。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 不处理文件请求，更自然的办法是把它留给IIS本身。该DLL代码其实不复杂，关键是IIS的配置。&lt;br /&gt;
&lt;br /&gt;
IIS配置有专门教材，参见[[IIS configuration]]。&lt;br /&gt;
&lt;br /&gt;
=== GUI程序 ===&lt;br /&gt;
&lt;br /&gt;
在&amp;#039;&amp;#039;&amp;#039;webRPC&amp;#039;&amp;#039;&amp;#039;示例目录中还有一个&amp;lt;vp&amp;gt;jsonRpcService_httpGUI&amp;lt;/vp&amp;gt;程序，它演示了图形用户界面程序如何用作独立Web RPC服务。要注意，并不提倡实际的服务程序具有GUI。这只是说明，当需要其它程序与GUI程序交互时GUI很有用，比如当用户登录时。它也可以用作一种快速借用已有GUI代码用于Web服务的方法（比如在创建实际服务前的试验）。&lt;br /&gt;
&lt;br /&gt;
=== 多线程 ===&lt;br /&gt;
&lt;br /&gt;
不管web服务如何运行（独立的带或不带GUI、或是ISAPI），需要注意的是HTTP请求运行于并发的若干线程。所以，保证请求处理的线程安全是很重要的。&amp;lt;vp&amp;gt;requestQueue_rpc::synchronizer&amp;lt;/vp&amp;gt;属性可用于同步RPC的执行，这个属性可以由一个谓词进行设置接收&amp;lt;vp&amp;gt;runnable&amp;lt;/vp&amp;gt;，而&amp;lt;vp&amp;gt;runnable&amp;lt;/vp&amp;gt;时才实际执行RPC调用。可运行的程序可以是，比如在一个监控程序（monitor）下运行，以此来保证这个时间只有一个程序在运行；或者是放入一个队列并从中顺序执行；再或者是如 &amp;lt;vp&amp;gt;jsonRpcService_httpGUI&amp;lt;/vp&amp;gt; 那样发布给GUI队列交由GUI线程执行。&lt;br /&gt;
&lt;br /&gt;
=== Visual Prolog 客户端 ===&lt;br /&gt;
&lt;br /&gt;
本教材及所提及的示例主要讨论Visual Prolog服务器一侧内容，而使用web浏览器作为客户端。不过，有时Visual Prolog程序也需要作为客户端访问WEB服务。示例 &amp;lt;vp&amp;gt;jsonRpcClient&amp;lt;/vp&amp;gt; 演示了如何做这样的事情。其方法与使用JavaScript时很相似：&lt;br /&gt;
&lt;br /&gt;
* 创建一个带有方法名及参数的 &amp;lt;vp&amp;gt;jsonRpcRequest&amp;lt;/vp&amp;gt; 对象&lt;br /&gt;
* 用 &amp;lt;vp&amp;gt;asString&amp;lt;/vp&amp;gt; 对其进行JSON串化&lt;br /&gt;
* 用 XMLHttpRequest 对象（&amp;lt;vp&amp;gt;xmlHTTP&amp;lt;/vp&amp;gt; 或 &amp;lt;vp&amp;gt;xmlHTTP60&amp;lt;/vp&amp;gt;的一个实例）进行投递&lt;br /&gt;
* JSON 解析响应文本（用&amp;lt;vp&amp;gt;jsonObject::fromString&amp;lt;/vp&amp;gt;）&lt;br /&gt;
* 检查 &amp;quot;error&amp;quot; 成员&lt;br /&gt;
&lt;br /&gt;
在示例程序中，有一个 &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;，,它是为了使GUI得到更新。如果没有它，GUI的更新就要一直等到RPC完成。不过这个调用与RPC本身是无关的。&lt;br /&gt;
&lt;br /&gt;
=== Skype HTTP/HTTPS 冲突 ===&lt;br /&gt;
{{:Include/SkypeHTTPConflict}}&lt;br /&gt;
&lt;br /&gt;
=== HTTPS ===&lt;br /&gt;
&lt;br /&gt;
HTTPS是使用安全套接的HTTP协议。IIS和独立HTTP服务器都可以使用HTTPS。要使用HTTPS，服务器计算机必须要有一个证书。可以自己创制证书，但自制的证书是不可信的，因而会引发安全提示。自制证书用于试验与开发是够用了，但在实际使用时需要获取一个可信的证书。&lt;br /&gt;
&lt;br /&gt;
简单说，可信证书必须从可信提供者那里获取。这种信任基于以下三件事：&lt;br /&gt;
* 提供者是自身可信的&lt;br /&gt;
* 提供者（通过正常购买链）对证书持有者真实身份具有认知&lt;br /&gt;
* 提供者可以在证书被滥用时撤消证书&lt;br /&gt;
&lt;br /&gt;
注意，HTTPS缺省时使用443端口（而HTTP使用80端口），需要时必须在使用&amp;quot;http&amp;quot;的地方写明 &amp;quot;https&amp;quot;（例如&amp;#039;&amp;#039;&amp;#039;https:&amp;lt;nowiki /&amp;gt;//+:443/mypath&amp;#039;&amp;#039;&amp;#039;）&lt;br /&gt;
&lt;br /&gt;
对证书的进一步讨论超出了本教材的范围。&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
* [http://www.json.org/ JSON]&lt;br /&gt;
* [http://www.jsonrpc.org/specification JSON-RPC 2.0 Specification]&lt;br /&gt;
* Microsoft&amp;#039;s [http://msdn.microsoft.com/en-us/library/windows/desktop/aa364510(v=vs.85).aspx HTTP Server API]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Text_Representation_(%E6%96%87%E6%9C%AC%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95%EF%BC%89&amp;diff=4161</id>
		<title>Text Representation (文本表示方法）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Text_Representation_(%E6%96%87%E6%9C%AC%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95%EF%BC%89&amp;diff=4161"/>
		<updated>2015-08-23T09:21:24Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Unicode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的Text Representation。更多内容可以参看[[:Category:Chinese]]。）&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中(与在C/C++中一样)串通常是由零结尾的,这意味着零是保留字符,它不会在正常文本中出现。所以,零可以用来表示一个串的结尾,也就是在串的最后一个实际字符后放一个零,表示这个串到此为止。这个表示法在8比特串和16比特串中都适用。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
COM(以及Visual Basic)使用的串表示法,是在串的起始放置一个32比特的数,这个数就是后跟的串字符个数。这种方法只用于16比特串。由于串的长度是已知的,这样的串可以包含“零”这个字符,甚至可以包含无意义的字符。这有时还是有用的(只要可能,总会有人加以利用)。&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;
* &amp;#039;&amp;#039;&amp;#039;单字节字符集 &amp;#039;&amp;#039;&amp;#039;: 每个字符用一个字节(8比特的数)表示 &lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;多字节字符集&amp;#039;&amp;#039;&amp;#039;: 每个字符用一个或多个字节表示&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;宽字符集&amp;#039;&amp;#039;&amp;#039;: 每个字符用一个或多个字(16比特的数)表示&lt;br /&gt;
&lt;br /&gt;
单字节字符集是多字节字符集的一种特殊情况,就是不用“或多字节”的情况。之所以还要说一下单字节字符集,是要用它来介绍一下“代码页”的概念。 代码页是微软在处理字符集时用的一个概念。代码页是数字与其所表示的字符间的映射,比如说,数字87表示字符&amp;#039;W&amp;#039;。代码页有很多种,主要用来处理不同的语言文字,但也还有其它一些原因(不同的群体使用各自的表示法)。&lt;br /&gt;
&lt;br /&gt;
因此,代码页描述的是各字符编码的字节。各个代码页都有一个名字和一个标识(一个数字)以便使用时指明用的是哪个字符集。&lt;br /&gt;
&lt;br /&gt;
微软还把代码页名称与代码页标识通用化地用于并非基于代码页的字符集。比如,多字节字符集也有相应的代码页名和代码页标识,尽管其编码不是用一个代码页描述的。&lt;br /&gt;
&lt;br /&gt;
而 &amp;#039;&amp;#039;宽字符集&amp;#039;&amp;#039; 的概念只用于Unicode,我们在下一节中讨论。&lt;br /&gt;
&lt;br /&gt;
== Unicode ==&lt;br /&gt;
&lt;br /&gt;
Unicode定义了一种字符集,不过这个字符集有好几种表示方法。人们常会被 Unicode的这些表示方法搞糊涂,不过只要搞清楚它们的意思就能很好地理解它们了。我要为这种理解做点儿贡献,仔细听我说:&lt;br /&gt;
&lt;br /&gt;
:;Unicode是一个“抽象”的字符集,它有若干种可选的表示方法。&lt;br /&gt;
&lt;br /&gt;
Unicode字符集分成17个版面，每个版面包含有65536个 &amp;#039;&amp;#039;&amp;#039;代码点&amp;#039;&amp;#039;&amp;#039;。这样，一共就有17*2^16 = 1114112个代码点。每个代码点可以表示一个字符，不过有些代码点被保留用作特殊用途。到目前为止很多这样的代码点并没有被规定（也就是可以留作以后使用）。&lt;br /&gt;
&lt;br /&gt;
如果我们把代码点写成十六进制数的话，它的分布是0x000000到0x10FFFF。也就是说，头两位十六进制数表示了版面而剩下的四位表示该版面中的代码点。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;utf32&amp;#039;&amp;#039;&amp;#039; 表示用32比特的数来代表那个范围(0-0x10FFFF)的各个代码点。这很少用于多个字符，因为每个字符要占32比特而其中有11比特总是0。但它用于处理单个字符时还是比较方便的,一个字符可以当作单独一个数。&lt;br /&gt;
&lt;br /&gt;
可能有人会奇怪(至少我是有过疑问):为什么是17个版面?13个或16个就不行了吗?现在还有10个版面没用。其原因(同时也可以解释其它许多情况)是:尽管“ Unicode是一种具有多个表示方法的抽象字符集”,但它实际上是设计用于一种特定的宽字符表示方法。其实,它是设计用于一种特定的16比特字符表示法的。在这个设计中,最常用的字符占用16比特数而所有其它的则使用两个16比特数,叫替身对(或就叫替身)。&lt;br /&gt;
&lt;br /&gt;
对于这种表示法,会是这样:当在一个串中看一个单个16比特数时,你可以确定:&lt;br /&gt;
* 它是否是常用的单字(one-wor)字符&lt;br /&gt;
* 它是否是替身对的第一个数&lt;br /&gt;
* 它是否是替身对的第二个数&lt;br /&gt;
&lt;br /&gt;
它还可以是字节顺序标记,这也可以区分不同的东西(或是把其它的归入特定的类)。&lt;br /&gt;
&lt;br /&gt;
所以Unicode的设计是用16比特做大多数事而其余的则用32比特办,同时它又可以立即区分出这些不同的情况。&lt;br /&gt;
&lt;br /&gt;
这种特殊的宽字符表示法就是Windows使用的方法,也是Visual Prolog使用的。&lt;br /&gt;
&lt;br /&gt;
=== utf16和utf16BE ===&lt;br /&gt;
&lt;br /&gt;
计算机之间的通信总是基于字节序列的。因此,如果一台计算机写出字节序列 0x01 0x02 0x03 0x04 那任一台别的计算机也应该读如 0x01 0x02 0x03 0x04。&lt;br /&gt;
&lt;br /&gt;
多字节字符集用一个或多个字节来表示字符。由于它定义了计算机传输时字节的顺序,所以可以得到期望的结果。&lt;br /&gt;
&lt;br /&gt;
但当传输宽字符集时,每个宽字符需要传两个字节,此时就需要确定先传哪个。如果有一个Unicode串,它是用宽字符格式编码的,还带有前面说的替身对,对每一个16比特数,先传最低位字节再传最高位字节,这就是&amp;#039;&amp;#039;&amp;#039;utf16&amp;#039;&amp;#039;&amp;#039;格式(它在HTML应用时就被称为Unicode)。反过来,要是先传最高位字节再传最低位字节, 就是 &amp;#039;&amp;#039;&amp;#039;utf16BE&amp;#039;&amp;#039;&amp;#039; 格式(utf16 big-endian的缩写)。&lt;br /&gt;
&lt;br /&gt;
所以,在Windows计算机中宽字符串的utf16格式与多字节串是一样的。不过要注意,8比特串程序对这样的串是不能正常处理的,因为utf16串中可以包含“零”这个字节, 而这在8比特串中表示串的结尾。&lt;br /&gt;
&lt;br /&gt;
用utf16或utf16BE格式写文件时,可以在文件起始处写一个字节顺序标识,这个标识是数0xFEFF。如果是小结尾模式,第一个字节应该是0xFF而第二个字节应该是 0xFE;如果是大结尾模式则顺序与小结尾模式刚好相反。如此一来,这两个字节就可以反映出文件是小结尾还是大结尾格式。不过,需要注意无论是0xFEFF还是 0xFFFE都不能在utf16和utf16BE中有别的用途。它只能是用户定义的文件中的字节顺序标识,而不能是其它的,比如说不能用于WEB服务器传送HTML页时的字节顺序。大多数浏览器使用的是两个奇怪的字符来表示字节顺序标识。&lt;br /&gt;
&lt;br /&gt;
=== utf8 ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;utf8&amp;#039;&amp;#039;&amp;#039; 是另一种有意思的Unicode多字节编码。utf8每个代码点使用1-6个字节,因此宽字符(utf16)中的替身对先转换成(utf32)代码点然后再转换成utf8。当从任一字节开始处理一个utf8串时,在下一个代码点一定可以得到解释,也就是说最多只需要略过当前多字节字符的其余部分就可以得到同步。&lt;br /&gt;
&lt;br /&gt;
utf8编码不包含“零”字节,所以可以用零做结尾。&lt;br /&gt;
&lt;br /&gt;
在7比特ASCII码中的那些字符可以用单字节utf8表示,而其它字符就需要使用较多字节。因此,西方语言的文本表示起来就相当紧凑,而其它语言用utf16表示时比较紧凑。&lt;br /&gt;
&lt;br /&gt;
utf8文件也可以包含一个字节顺序标识 0xEF 0xBB 0xBF (它不是用来表示字节顺序的,而是用来表 utf8 格式)。&lt;br /&gt;
&lt;br /&gt;
[[:Category:Basic Information]]&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Text_Representation_(%E6%96%87%E6%9C%AC%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95%EF%BC%89&amp;diff=4160</id>
		<title>Text Representation (文本表示方法）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Text_Representation_(%E6%96%87%E6%9C%AC%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95%EF%BC%89&amp;diff=4160"/>
		<updated>2015-08-23T09:20:44Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Unicode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的Text Representation。更多内容可以参看[[:Category:Chinese]]。）&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中(与在C/C++中一样)串通常是由零结尾的,这意味着零是保留字符,它不会在正常文本中出现。所以,零可以用来表示一个串的结尾,也就是在串的最后一个实际字符后放一个零,表示这个串到此为止。这个表示法在8比特串和16比特串中都适用。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
COM(以及Visual Basic)使用的串表示法,是在串的起始放置一个32比特的数,这个数就是后跟的串字符个数。这种方法只用于16比特串。由于串的长度是已知的,这样的串可以包含“零”这个字符,甚至可以包含无意义的字符。这有时还是有用的(只要可能,总会有人加以利用)。&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;
* &amp;#039;&amp;#039;&amp;#039;单字节字符集 &amp;#039;&amp;#039;&amp;#039;: 每个字符用一个字节(8比特的数)表示 &lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;多字节字符集&amp;#039;&amp;#039;&amp;#039;: 每个字符用一个或多个字节表示&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;宽字符集&amp;#039;&amp;#039;&amp;#039;: 每个字符用一个或多个字(16比特的数)表示&lt;br /&gt;
&lt;br /&gt;
单字节字符集是多字节字符集的一种特殊情况,就是不用“或多字节”的情况。之所以还要说一下单字节字符集,是要用它来介绍一下“代码页”的概念。 代码页是微软在处理字符集时用的一个概念。代码页是数字与其所表示的字符间的映射,比如说,数字87表示字符&amp;#039;W&amp;#039;。代码页有很多种,主要用来处理不同的语言文字,但也还有其它一些原因(不同的群体使用各自的表示法)。&lt;br /&gt;
&lt;br /&gt;
因此,代码页描述的是各字符编码的字节。各个代码页都有一个名字和一个标识(一个数字)以便使用时指明用的是哪个字符集。&lt;br /&gt;
&lt;br /&gt;
微软还把代码页名称与代码页标识通用化地用于并非基于代码页的字符集。比如,多字节字符集也有相应的代码页名和代码页标识,尽管其编码不是用一个代码页描述的。&lt;br /&gt;
&lt;br /&gt;
而 &amp;#039;&amp;#039;宽字符集&amp;#039;&amp;#039; 的概念只用于Unicode,我们在下一节中讨论。&lt;br /&gt;
&lt;br /&gt;
== Unicode ==&lt;br /&gt;
&lt;br /&gt;
Unicode定义了一种字符集,不过这个字符集有好几种表示方法。人们常会被 Unicode的这些表示方法搞糊涂,不过只要搞清楚它们的意思就能很好地理解它们了。我要为这种理解做点儿贡献,仔细听我说:&lt;br /&gt;
&lt;br /&gt;
:;Unicode是一个“抽象”的字符集,它有若干种可选的表示方法。&lt;br /&gt;
&lt;br /&gt;
Unicode字符集分成17个版面，每个版面包含有65536个 &amp;#039;&amp;#039;&amp;#039;代码点&amp;#039;&amp;#039;&amp;#039;。这样，一共就有17*2^16 = 1114112个代码点。每个代码点可以表示一个字符，不过有些代码点被保留用作特殊用途。到目前为止很多这样的代码点并没有被规定（也就是可以留作以后使用）。&lt;br /&gt;
&lt;br /&gt;
如果我们把代码点写成十六进制数的话，它的分布是0x000000到0x10FFFF。也就是说，头两位十六进制数表示了版面而剩下的四位表示该版面中的代码点。&lt;br /&gt;
&lt;br /&gt;
 &amp;#039;&amp;#039;&amp;#039;utf32&amp;#039;&amp;#039;&amp;#039; 表示用32比特的数来代表那个范围(0-0x10FFFF)的各个代码点。这很少用于多个字符，因为每个字符要占32比特而其中有11比特总是0。&lt;br /&gt;
但它用于处理单个字符时还是比较方便的,一个字符可以当作单独一个数。&lt;br /&gt;
&lt;br /&gt;
可能有人会奇怪(至少我是有过疑问):为什么是17个版面?13个或16个就不行了吗?现在还有10个版面没用。其原因(同时也可以解释其它许多情况)是:尽管“ Unicode是一种具有多个表示方法的抽象字符集”,但它实际上是设计用于一种特定的宽字符表示方法。其实,它是设计用于一种特定的16比特字符表示法的。在这个设计中,最常用的字符占用16比特数而所有其它的则使用两个16比特数,叫替身对(或就叫替身)。&lt;br /&gt;
&lt;br /&gt;
对于这种表示法,会是这样:当在一个串中看一个单个16比特数时,你可以确定:&lt;br /&gt;
* 它是否是常用的单字(one-wor)字符&lt;br /&gt;
* 它是否是替身对的第一个数&lt;br /&gt;
* 它是否是替身对的第二个数&lt;br /&gt;
&lt;br /&gt;
它还可以是字节顺序标记,这也可以区分不同的东西(或是把其它的归入特定的类)。&lt;br /&gt;
&lt;br /&gt;
所以Unicode的设计是用16比特做大多数事而其余的则用32比特办,同时它又可以立即区分出这些不同的情况。&lt;br /&gt;
&lt;br /&gt;
这种特殊的宽字符表示法就是Windows使用的方法,也是Visual Prolog使用的。&lt;br /&gt;
&lt;br /&gt;
=== utf16和utf16BE ===&lt;br /&gt;
&lt;br /&gt;
计算机之间的通信总是基于字节序列的。因此,如果一台计算机写出字节序列 0x01 0x02 0x03 0x04 那任一台别的计算机也应该读如 0x01 0x02 0x03 0x04。&lt;br /&gt;
&lt;br /&gt;
多字节字符集用一个或多个字节来表示字符。由于它定义了计算机传输时字节的顺序,所以可以得到期望的结果。&lt;br /&gt;
&lt;br /&gt;
但当传输宽字符集时,每个宽字符需要传两个字节,此时就需要确定先传哪个。如果有一个Unicode串,它是用宽字符格式编码的,还带有前面说的替身对,对每一个16比特数,先传最低位字节再传最高位字节,这就是&amp;#039;&amp;#039;&amp;#039;utf16&amp;#039;&amp;#039;&amp;#039;格式(它在HTML应用时就被称为Unicode)。反过来,要是先传最高位字节再传最低位字节, 就是 &amp;#039;&amp;#039;&amp;#039;utf16BE&amp;#039;&amp;#039;&amp;#039; 格式(utf16 big-endian的缩写)。&lt;br /&gt;
&lt;br /&gt;
所以,在Windows计算机中宽字符串的utf16格式与多字节串是一样的。不过要注意,8比特串程序对这样的串是不能正常处理的,因为utf16串中可以包含“零”这个字节, 而这在8比特串中表示串的结尾。&lt;br /&gt;
&lt;br /&gt;
用utf16或utf16BE格式写文件时,可以在文件起始处写一个字节顺序标识,这个标识是数0xFEFF。如果是小结尾模式,第一个字节应该是0xFF而第二个字节应该是 0xFE;如果是大结尾模式则顺序与小结尾模式刚好相反。如此一来,这两个字节就可以反映出文件是小结尾还是大结尾格式。不过,需要注意无论是0xFEFF还是 0xFFFE都不能在utf16和utf16BE中有别的用途。它只能是用户定义的文件中的字节顺序标识,而不能是其它的,比如说不能用于WEB服务器传送HTML页时的字节顺序。大多数浏览器使用的是两个奇怪的字符来表示字节顺序标识。&lt;br /&gt;
&lt;br /&gt;
=== utf8 ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;utf8&amp;#039;&amp;#039;&amp;#039; 是另一种有意思的Unicode多字节编码。utf8每个代码点使用1-6个字节,因此宽字符(utf16)中的替身对先转换成(utf32)代码点然后再转换成utf8。当从任一字节开始处理一个utf8串时,在下一个代码点一定可以得到解释,也就是说最多只需要略过当前多字节字符的其余部分就可以得到同步。&lt;br /&gt;
&lt;br /&gt;
utf8编码不包含“零”字节,所以可以用零做结尾。&lt;br /&gt;
&lt;br /&gt;
在7比特ASCII码中的那些字符可以用单字节utf8表示,而其它字符就需要使用较多字节。因此,西方语言的文本表示起来就相当紧凑,而其它语言用utf16表示时比较紧凑。&lt;br /&gt;
&lt;br /&gt;
utf8文件也可以包含一个字节顺序标识 0xEF 0xBB 0xBF (它不是用来表示字节顺序的,而是用来表 utf8 格式)。&lt;br /&gt;
&lt;br /&gt;
[[:Category:Basic Information]]&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Text_Representation_(%E6%96%87%E6%9C%AC%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95%EF%BC%89&amp;diff=4159</id>
		<title>Text Representation (文本表示方法）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Text_Representation_(%E6%96%87%E6%9C%AC%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95%EF%BC%89&amp;diff=4159"/>
		<updated>2015-08-23T09:20:11Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Unicode */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的Text Representation。更多内容可以参看[[:Category:Chinese]]。）&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中(与在C/C++中一样)串通常是由零结尾的,这意味着零是保留字符,它不会在正常文本中出现。所以,零可以用来表示一个串的结尾,也就是在串的最后一个实际字符后放一个零,表示这个串到此为止。这个表示法在8比特串和16比特串中都适用。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
COM(以及Visual Basic)使用的串表示法,是在串的起始放置一个32比特的数,这个数就是后跟的串字符个数。这种方法只用于16比特串。由于串的长度是已知的,这样的串可以包含“零”这个字符,甚至可以包含无意义的字符。这有时还是有用的(只要可能,总会有人加以利用)。&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;
* &amp;#039;&amp;#039;&amp;#039;单字节字符集 &amp;#039;&amp;#039;&amp;#039;: 每个字符用一个字节(8比特的数)表示 &lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;多字节字符集&amp;#039;&amp;#039;&amp;#039;: 每个字符用一个或多个字节表示&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;宽字符集&amp;#039;&amp;#039;&amp;#039;: 每个字符用一个或多个字(16比特的数)表示&lt;br /&gt;
&lt;br /&gt;
单字节字符集是多字节字符集的一种特殊情况,就是不用“或多字节”的情况。之所以还要说一下单字节字符集,是要用它来介绍一下“代码页”的概念。 代码页是微软在处理字符集时用的一个概念。代码页是数字与其所表示的字符间的映射,比如说,数字87表示字符&amp;#039;W&amp;#039;。代码页有很多种,主要用来处理不同的语言文字,但也还有其它一些原因(不同的群体使用各自的表示法)。&lt;br /&gt;
&lt;br /&gt;
因此,代码页描述的是各字符编码的字节。各个代码页都有一个名字和一个标识(一个数字)以便使用时指明用的是哪个字符集。&lt;br /&gt;
&lt;br /&gt;
微软还把代码页名称与代码页标识通用化地用于并非基于代码页的字符集。比如,多字节字符集也有相应的代码页名和代码页标识,尽管其编码不是用一个代码页描述的。&lt;br /&gt;
&lt;br /&gt;
而 &amp;#039;&amp;#039;宽字符集&amp;#039;&amp;#039; 的概念只用于Unicode,我们在下一节中讨论。&lt;br /&gt;
&lt;br /&gt;
== Unicode ==&lt;br /&gt;
&lt;br /&gt;
Unicode定义了一种字符集,不过这个字符集有好几种表示方法。人们常会被 Unicode的这些表示方法搞糊涂,不过只要搞清楚它们的意思就能很好地理解它们了。我要为这种理解做点儿贡献,仔细听我说:&lt;br /&gt;
&lt;br /&gt;
:;Unicode是一个“抽象”的字符集,它有若干种可选的表示方法。&lt;br /&gt;
&lt;br /&gt;
Unicode字符集分成17个版面，每个版面包含有65536个 &amp;#039;&amp;#039;&amp;#039;代码点&amp;#039;&amp;#039;&amp;#039;。这样，一共就有17*2^16 = 1114112个代码点。每个代码点可以表示一个字符，不过有些代码点被保留用作特殊用途。到目前为止很多这样的代码点并没有被规定（也就是可以留作以后使用）。&lt;br /&gt;
&lt;br /&gt;
如果我们把代码点写成十六进制数的话，它的分布是0x000000到0x10FFFF。也就是说，头两位十六进制数表示了版面而剩下的四位表示该版面中的代码点。&lt;br /&gt;
&lt;br /&gt;
 &amp;#039;&amp;#039;&amp;#039;utf32&amp;#039;&amp;#039;&amp;#039; 表示用32比特的数来代表那个范围(0-0x10FFFF)的各个代码点。这很少用于多个字符，因为每个字符要占32比特而其中有11比特总是0。但它用于处理单个字符时还是比较方便的,一个字符可以当作单独一个数。&lt;br /&gt;
&lt;br /&gt;
可能有人会奇怪(至少我是有过疑问):为什么是17个版面?13个或16个就不行了吗?现在还有10个版面没用。其原因(同时也可以解释其它许多情况)是:尽管“ Unicode是一种具有多个表示方法的抽象字符集”,但它实际上是设计用于一种特定的宽字符表示方法。其实,它是设计用于一种特定的16比特字符表示法的。在这个设计中,最常用的字符占用16比特数而所有其它的则使用两个16比特数,叫替身对(或就叫替身)。&lt;br /&gt;
&lt;br /&gt;
对于这种表示法,会是这样:当在一个串中看一个单个16比特数时,你可以确定:&lt;br /&gt;
* 它是否是常用的单字(one-wor)字符&lt;br /&gt;
* 它是否是替身对的第一个数&lt;br /&gt;
* 它是否是替身对的第二个数&lt;br /&gt;
&lt;br /&gt;
它还可以是字节顺序标记,这也可以区分不同的东西(或是把其它的归入特定的类)。&lt;br /&gt;
&lt;br /&gt;
所以Unicode的设计是用16比特做大多数事而其余的则用32比特办,同时它又可以立即区分出这些不同的情况。&lt;br /&gt;
&lt;br /&gt;
这种特殊的宽字符表示法就是Windows使用的方法,也是Visual Prolog使用的。&lt;br /&gt;
&lt;br /&gt;
=== utf16和utf16BE ===&lt;br /&gt;
&lt;br /&gt;
计算机之间的通信总是基于字节序列的。因此,如果一台计算机写出字节序列 0x01 0x02 0x03 0x04 那任一台别的计算机也应该读如 0x01 0x02 0x03 0x04。&lt;br /&gt;
&lt;br /&gt;
多字节字符集用一个或多个字节来表示字符。由于它定义了计算机传输时字节的顺序,所以可以得到期望的结果。&lt;br /&gt;
&lt;br /&gt;
但当传输宽字符集时,每个宽字符需要传两个字节,此时就需要确定先传哪个。如果有一个Unicode串,它是用宽字符格式编码的,还带有前面说的替身对,对每一个16比特数,先传最低位字节再传最高位字节,这就是&amp;#039;&amp;#039;&amp;#039;utf16&amp;#039;&amp;#039;&amp;#039;格式(它在HTML应用时就被称为Unicode)。反过来,要是先传最高位字节再传最低位字节, 就是 &amp;#039;&amp;#039;&amp;#039;utf16BE&amp;#039;&amp;#039;&amp;#039; 格式(utf16 big-endian的缩写)。&lt;br /&gt;
&lt;br /&gt;
所以,在Windows计算机中宽字符串的utf16格式与多字节串是一样的。不过要注意,8比特串程序对这样的串是不能正常处理的,因为utf16串中可以包含“零”这个字节, 而这在8比特串中表示串的结尾。&lt;br /&gt;
&lt;br /&gt;
用utf16或utf16BE格式写文件时,可以在文件起始处写一个字节顺序标识,这个标识是数0xFEFF。如果是小结尾模式,第一个字节应该是0xFF而第二个字节应该是 0xFE;如果是大结尾模式则顺序与小结尾模式刚好相反。如此一来,这两个字节就可以反映出文件是小结尾还是大结尾格式。不过,需要注意无论是0xFEFF还是 0xFFFE都不能在utf16和utf16BE中有别的用途。它只能是用户定义的文件中的字节顺序标识,而不能是其它的,比如说不能用于WEB服务器传送HTML页时的字节顺序。大多数浏览器使用的是两个奇怪的字符来表示字节顺序标识。&lt;br /&gt;
&lt;br /&gt;
=== utf8 ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;utf8&amp;#039;&amp;#039;&amp;#039; 是另一种有意思的Unicode多字节编码。utf8每个代码点使用1-6个字节,因此宽字符(utf16)中的替身对先转换成(utf32)代码点然后再转换成utf8。当从任一字节开始处理一个utf8串时,在下一个代码点一定可以得到解释,也就是说最多只需要略过当前多字节字符的其余部分就可以得到同步。&lt;br /&gt;
&lt;br /&gt;
utf8编码不包含“零”字节,所以可以用零做结尾。&lt;br /&gt;
&lt;br /&gt;
在7比特ASCII码中的那些字符可以用单字节utf8表示,而其它字符就需要使用较多字节。因此,西方语言的文本表示起来就相当紧凑,而其它语言用utf16表示时比较紧凑。&lt;br /&gt;
&lt;br /&gt;
utf8文件也可以包含一个字节顺序标识 0xEF 0xBB 0xBF (它不是用来表示字节顺序的,而是用来表 utf8 格式)。&lt;br /&gt;
&lt;br /&gt;
[[:Category:Basic Information]]&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Text_Representation_(%E6%96%87%E6%9C%AC%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95%EF%BC%89&amp;diff=4158</id>
		<title>Text Representation (文本表示方法）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Text_Representation_(%E6%96%87%E6%9C%AC%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95%EF%BC%89&amp;diff=4158"/>
		<updated>2015-08-23T09:17:05Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;（以下内容，译自:Category:Tutorials中的Text Representation。更多内容可以参看:Category:Chinese。）  一般情况下文本处理是相当简单的,至...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的Text Representation。更多内容可以参看[[:Category:Chinese]]。）&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中(与在C/C++中一样)串通常是由零结尾的,这意味着零是保留字符,它不会在正常文本中出现。所以,零可以用来表示一个串的结尾,也就是在串的最后一个实际字符后放一个零,表示这个串到此为止。这个表示法在8比特串和16比特串中都适用。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
COM(以及Visual Basic)使用的串表示法,是在串的起始放置一个32比特的数,这个数就是后跟的串字符个数。这种方法只用于16比特串。由于串的长度是已知的,这样的串可以包含“零”这个字符,甚至可以包含无意义的字符。这有时还是有用的(只要可能,总会有人加以利用)。&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;
* &amp;#039;&amp;#039;&amp;#039;单字节字符集 &amp;#039;&amp;#039;&amp;#039;: 每个字符用一个字节(8比特的数)表示 &lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;多字节字符集&amp;#039;&amp;#039;&amp;#039;: 每个字符用一个或多个字节表示&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;宽字符集&amp;#039;&amp;#039;&amp;#039;: 每个字符用一个或多个字(16比特的数)表示&lt;br /&gt;
&lt;br /&gt;
单字节字符集是多字节字符集的一种特殊情况,就是不用“或多字节”的情况。之所以还要说一下单字节字符集,是要用它来介绍一下“代码页”的概念。 代码页是微软在处理字符集时用的一个概念。代码页是数字与其所表示的字符间的映射,比如说,数字87表示字符&amp;#039;W&amp;#039;。代码页有很多种,主要用来处理不同的语言文字,但也还有其它一些原因(不同的群体使用各自的表示法)。&lt;br /&gt;
&lt;br /&gt;
因此,代码页描述的是各字符编码的字节。各个代码页都有一个名字和一个标识(一个数字)以便使用时指明用的是哪个字符集。&lt;br /&gt;
&lt;br /&gt;
微软还把代码页名称与代码页标识通用化地用于并非基于代码页的字符集。比如,多字节字符集也有相应的代码页名和代码页标识,尽管其编码不是用一个代码页描述的。&lt;br /&gt;
&lt;br /&gt;
而 &amp;#039;&amp;#039;宽字符集&amp;#039;&amp;#039; 的概念只用于Unicode,我们在下一节中讨论。&lt;br /&gt;
&lt;br /&gt;
== Unicode ==&lt;br /&gt;
&lt;br /&gt;
Unicode定义了一种字符集,不过这个字符集有好几种表示方法。人们常会被 Unicode的这些表示方法搞糊涂,不过只要搞清楚它们的意思就能很好地理解它们了。我要为这种理解做点儿贡献,仔细听我说:&lt;br /&gt;
&lt;br /&gt;
:;Unicode是一个“抽象”的字符集,它有若干种可选的表示方法。&lt;br /&gt;
&lt;br /&gt;
Unicode字符集分成17个版面，每个版面包含有65536个 &amp;#039;&amp;#039;&amp;#039;代码点&amp;#039;&amp;#039;&amp;#039;。这样，一共就有17*2^16 = 1114112个代码点。每个代码点可以表示一个字符，不过有些代码点被保留用作特殊用途。到目前为止很多这样的代码点并没有被规定（也就是可以留作以后使用）。&lt;br /&gt;
&lt;br /&gt;
如果我们把代码点写成十六进制数的话，它的分布是0x000000到0x10FFFF。也就是说，头两位十六进制数表示了版面而剩下的四位表示该版面中的代码点。&lt;br /&gt;
&lt;br /&gt;
 &amp;#039;&amp;#039;&amp;#039;utf32&amp;#039;&amp;#039;&amp;#039; 表示用32比特的数来代表那个范围(0-0x10FFFF)的各个代码点。这很少用于多个字符,因为每个字符要占32比特而其中有11比特总是0。但它用于处理单个字符时还是比较方便的,一个字符可以当作单独一个数。&lt;br /&gt;
&lt;br /&gt;
可能有人会奇怪(至少我是有过疑问):为什么是17个版面?13个或16个就不行了吗?现在还有10个版面没用。其原因(同时也可以解释其它许多情况)是:尽管“ Unicode是一种具有多个表示方法的抽象字符集”,但它实际上是设计用于一种特定的宽字符表示方法。其实,它是设计用于一种特定的16比特字符表示法的。在这个设计中,最常用的字符占用16比特数而所有其它的则使用两个16比特数,叫替身对(或就叫替身)。&lt;br /&gt;
&lt;br /&gt;
对于这种表示法,会是这样:当在一个串中看一个单个16比特数时,你可以确定:&lt;br /&gt;
* 它是否是常用的单字(one-wor)字符&lt;br /&gt;
* 它是否是替身对的第一个数&lt;br /&gt;
* 它是否是替身对的第二个数&lt;br /&gt;
&lt;br /&gt;
它还可以是字节顺序标记,这也可以区分不同的东西(或是把其它的归入特定的类)。&lt;br /&gt;
&lt;br /&gt;
所以Unicode的设计是用16比特做大多数事而其余的则用32比特办,同时它又可以立即区分出这些不同的情况。&lt;br /&gt;
&lt;br /&gt;
这种特殊的宽字符表示法就是Windows使用的方法,也是Visual Prolog使用的。&lt;br /&gt;
&lt;br /&gt;
=== utf16和utf16BE ===&lt;br /&gt;
&lt;br /&gt;
计算机之间的通信总是基于字节序列的。因此,如果一台计算机写出字节序列 0x01 0x02 0x03 0x04 那任一台别的计算机也应该读如 0x01 0x02 0x03 0x04。&lt;br /&gt;
&lt;br /&gt;
多字节字符集用一个或多个字节来表示字符。由于它定义了计算机传输时字节的顺序,所以可以得到期望的结果。&lt;br /&gt;
&lt;br /&gt;
但当传输宽字符集时,每个宽字符需要传两个字节,此时就需要确定先传哪个。如果有一个Unicode串,它是用宽字符格式编码的,还带有前面说的替身对,对每一个16比特数,先传最低位字节再传最高位字节,这就是&amp;#039;&amp;#039;&amp;#039;utf16&amp;#039;&amp;#039;&amp;#039;格式(它在HTML应用时就被称为Unicode)。反过来,要是先传最高位字节再传最低位字节, 就是 &amp;#039;&amp;#039;&amp;#039;utf16BE&amp;#039;&amp;#039;&amp;#039; 格式(utf16 big-endian的缩写)。&lt;br /&gt;
&lt;br /&gt;
所以,在Windows计算机中宽字符串的utf16格式与多字节串是一样的。不过要注意,8比特串程序对这样的串是不能正常处理的,因为utf16串中可以包含“零”这个字节, 而这在8比特串中表示串的结尾。&lt;br /&gt;
&lt;br /&gt;
用utf16或utf16BE格式写文件时,可以在文件起始处写一个字节顺序标识,这个标识是数0xFEFF。如果是小结尾模式,第一个字节应该是0xFF而第二个字节应该是 0xFE;如果是大结尾模式则顺序与小结尾模式刚好相反。如此一来,这两个字节就可以反映出文件是小结尾还是大结尾格式。不过,需要注意无论是0xFEFF还是 0xFFFE都不能在utf16和utf16BE中有别的用途。它只能是用户定义的文件中的字节顺序标识,而不能是其它的,比如说不能用于WEB服务器传送HTML页时的字节顺序。大多数浏览器使用的是两个奇怪的字符来表示字节顺序标识。&lt;br /&gt;
&lt;br /&gt;
=== utf8 ===&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;utf8&amp;#039;&amp;#039;&amp;#039; 是另一种有意思的Unicode多字节编码。utf8每个代码点使用1-6个字节,因此宽字符(utf16)中的替身对先转换成(utf32)代码点然后再转换成utf8。当从任一字节开始处理一个utf8串时,在下一个代码点一定可以得到解释,也就是说最多只需要略过当前多字节字符的其余部分就可以得到同步。&lt;br /&gt;
&lt;br /&gt;
utf8编码不包含“零”字节,所以可以用零做结尾。&lt;br /&gt;
&lt;br /&gt;
在7比特ASCII码中的那些字符可以用单字节utf8表示,而其它字符就需要使用较多字节。因此,西方语言的文本表示起来就相当紧凑,而其它语言用utf16表示时比较紧凑。&lt;br /&gt;
&lt;br /&gt;
utf8文件也可以包含一个字节顺序标识 0xEF 0xBB 0xBF (它不是用来表示字节顺序的,而是用来表 utf8 格式)。&lt;br /&gt;
&lt;br /&gt;
[[:Category:Basic Information]]&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=DelayCall(window)_(%E5%BB%B6%E6%97%B6%E8%B0%83%E7%94%A8%EF%BC%89&amp;diff=4157</id>
		<title>DelayCall(window) (延时调用）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=DelayCall(window)_(%E5%BB%B6%E6%97%B6%E8%B0%83%E7%94%A8%EF%BC%89&amp;diff=4157"/>
		<updated>2015-08-23T09:13:17Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;（以下内容，译自:Category:Tutorials中的DelayCall (window)。更多内容可以参看:Category:Chinese。）  事件可能发生得过于频繁而超过处理的...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的DelayCall (window)。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
事件可能发生得过于频繁而超过处理的需要,&amp;lt;vp&amp;gt;window::delayCall&amp;lt;/vp&amp;gt; 提供了一种解决这个问题的方法。&lt;br /&gt;
&lt;br /&gt;
== Motivating examples ==&lt;br /&gt;
&lt;br /&gt;
在文本框输入文字时常会出现一个可选项的列表,比如在浏览器输入URL和在搜索引擎输入搜索内容时。但当你快速输入时,就会希望它不要每输入一个字符都给出建议选项。这有两方面的原因,.一是这样太耗资源;二是快速闪动变化的可选项会使人因眼花撩乱而不舒服。&lt;br /&gt;
&lt;br /&gt;
不要每次击键都更新列表,而是延迟到用户输入有了停顿时才更新。也就是延迟到最后一次击键过了一定时间后再更新。&lt;br /&gt;
&lt;br /&gt;
这里还有两个例子,也可以应用上述同样的解决方案来避免快速闪动及/或性能上的问题:&lt;br /&gt;
* 实际的滚动最好是延迟到滚动事件有暂停时进行。&lt;br /&gt;
* 接收来自外部的数据更新程序,最好等到外部数据有间歇时再对图形用户界面进行更新。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Drawback&amp;#039;&amp;#039;&amp;#039;: 直到超过了某个时间,我们才能知道有了停顿/暂停。所以这种方法引入了一个时延,这个时间并非所有情况都期望: 所以这个延迟时间应在既不要反应太频繁又不要停顿时间太长之间作折衷。&lt;br /&gt;
&lt;br /&gt;
这里 [http://youtu.be/IAonJ6t6eho video] 演示了三个控件。中间一个每次调整窗口大小事件都重画窗口,另外两个则是在事件后停顿一下再重画。 &lt;br /&gt;
&lt;br /&gt;
== delayCall ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; 可以用来实现“过一会再响应”的方案。&lt;br /&gt;
&lt;br /&gt;
先来看个简单的例子,控件的代码如下: &lt;br /&gt;
&amp;lt;vip&amp;gt;constants&lt;br /&gt;
    delay = 100. % ms&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    addData(NewData) :-&lt;br /&gt;
        doAddData(NewData),&lt;br /&gt;
        delayCall(delay, invalidate).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    removeData(ToRemove) :-&lt;br /&gt;
        doRemoveData(ToRemove),&lt;br /&gt;
        delayCall(delay, invalidate).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
该控件有对自身添加和删除数据的谓词。在数据更新时,控件会失效以便用新数据进行重画。但为了防止对控件大量重复的更新,我们要使失效推迟直到数据稳定了1OOms。这正是&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;调用做的事:它安排调用&amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt; 在&amp;lt;vp&amp;gt;delay&amp;lt;/vp&amp;gt; (= 100ms)之后进行,同时删除先前别的安排,如果有的话。因此,它的效果就是在数据更新后停顿100ms产生一次失效。&lt;br /&gt;
&lt;br /&gt;
现在,基本动作都到位了。我们再来看看细节、常见问题及解决方案。&lt;br /&gt;
&lt;br /&gt;
在这个&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;简单调用中有两个参数: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    delayCall : (positive Delay, runnable Predicate).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
对 &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; 的调用将会:&lt;br /&gt;
*或是在&amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt;毫秒过后调用 &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt;&lt;br /&gt;
*或是重启一个新的 &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; 调用,使用相同的&amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt; ,而 &amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt; 参数重新开始&lt;br /&gt;
*或是被丢弃,由于&amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt;到达之前窗口被销毁了。&lt;br /&gt;
&lt;br /&gt;
注意,delayCall是一个窗口一个 &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
区分窗口可以避免因窗口已经不存在而执行不相关的延迟谓词。The per window part can be used to avoid executing delayed predicate that is no longer relevant because the window it concerns no longer exist.&lt;br /&gt;
&lt;br /&gt;
各自的&amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt;也是十分重要的,因为可能会对许多不相关的事使用delayCall,这些不相关的事不应该彼此取消对方的延迟调用。The per &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt; part is important because you may want to delayCall for many unrelated things, and such things should not cancel each other. 把应用程序窗口用作全局响应是很正常的事,它不是与某个特定窗口关联。 It is very common to use the application window for &amp;quot;global&amp;quot; actions, which are not related to a specific window.例如,在某个元件变化停顿时重新计算全局状态。For example recalculating a global state when there is a pause in atomic changes.&lt;br /&gt;
&lt;br /&gt;
在我们的例子中，两个调用中使用的谓词都是&amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt;，因而不管我们调用的&amp;lt;vp&amp;gt;addData&amp;lt;/vp&amp;gt;还是&amp;lt;vp&amp;gt;removeData&amp;lt;/vp&amp;gt;都会再次对&amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt;延期。而如果调用是&amp;lt;vp&amp;gt;delayCall(17, somethingElse)&amp;lt;/vp&amp;gt;，则不会对&amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt;产生影响，不会取消它，也不会对它延期。&lt;br /&gt;
&lt;br /&gt;
如同上例中的情形所示，一般情况下谓词的标识表明了期望&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;区分的事情。不过对于匿名谓词来说这里容易出错，看下面的例子：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
       delayCall(10, { :- doSetColor(NewColor) }).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
代码的初衷是打算延迟到颜色不再频繁变化时再设置颜色。它使用了一个匿名谓词来捕获新的颜色。通常情况下这种匿名谓词使用方法很值得推荐，不过在这里会产生一个问题，就是：每调用一次&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;就会创建一个新的匿名谓词来捕获&amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;。因而，每调用一次&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;就会用&amp;#039;&amp;#039;&amp;#039;唯一的&amp;#039;&amp;#039;&amp;#039;谓词调用一次&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;。由于每个谓词都和前一个不是一回事，所以就不会对上一次的延时调用产生影响。如此一来，它就会每延迟10毫秒执行一次。&lt;br /&gt;
&lt;br /&gt;
解决该问题可以这样做：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
facts&lt;br /&gt;
   newColor : color := color_white. % 没有实际使用的哑赋值&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
        newColor := NewColor,&lt;br /&gt;
        delayCall(10, setNewColor).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    setNewColor() :-&lt;br /&gt;
        doSetColor(newColor).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
调用这个版本的&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;时，会更新事实变量&amp;lt;vp&amp;gt;newColor&amp;lt;/vp&amp;gt;，所以这个事实变量中总是包含着&amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;的最新值。现在，对&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;的调用涉及是的同一个谓词（也就是&amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt;）。因此，这段代码每次调用&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;都会取消前一次的&amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt;计划并重新做延期安排。这样，在&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;的调用中&amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt;的请求就延期在稍有停顿时。&lt;br /&gt;
&lt;br /&gt;
与把新颜色放在匿名谓词中不同，我们把新颜色放在了一个事实变量中。因为这样一来我们就可以调用一个命名的谓词而不是一个匿名谓词。&lt;br /&gt;
&lt;br /&gt;
The overall principle here is to maintain a shaddow (sub-)state which is then merged into the real state in a pause.&lt;br /&gt;
&lt;br /&gt;
上面匿名谓词问题的解决方案是相当复杂和麻烦，需要对匿名和命名谓词都有深入的了解。The solution above to the anonymous predicate problem is rather complex/elaborate, we both need to introduce extra fact(s) and an extra named predicate.&lt;br /&gt;
&lt;br /&gt;
最要命的是，那个事实变量中并没有包含对象所呈现的实际信息，它所存储的只不过是一个算法中的一点到另一点所传递的信息，就是说它是算法的局部信息而不是对象的全局信息。&lt;br /&gt;
&lt;br /&gt;
这样的事实令人非常不满意，常常会用&amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt;或匿名谓词取而代之。在这里，&amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt;也无助于问题的解决，因为与&amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;完全相同的原因，它也需要是对象的全局信息。Such facts are highly undesirable and very often you will use &amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt;&amp;#039;s or anonymous predicates to eliminate them.  In this case a &amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt; will not help, because it will also have to be made global in the object for exactly the same reasons as for &amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
因此，有办法在&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;中处理好匿名谓词就太好了。问题在于匿名谓词的身份不太适合确定是否需要拖延时间。解决这个问题（及其它一些问题）我们可以用另一版本的delayCall，它显式提供身份值来决定是拖延已有的延时还是要创建一个新的延时。So it would be very nice to have a way to deal with anonymous predicates in &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;.  The problem was that the identity of anonymous predicates is not very suitable for determine whether to prolong a delay.  To solve this problem (and others) we can use another version of delayCall which uses an explicit provided identity value to determine whether to prolong an existing delay or to create a new one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    delayCall : (positive Delay, runnable Predicate, IdType Id).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这个版本中多了一个&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;参数，其类型可以任意确定。该值如果在两次调用中相同，后面的就会替代前面的（当然是前一个延时还没到期）；如果值不一样，就认为调用间是不相关的。This version takes an extra &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt; argumment of any type you like.  If you provide the same value in two calls the latter will replace the first (if has not already fired of course), if you provide different values the calls are considered unrelated.&lt;br /&gt;
&lt;br /&gt;
编程人员需要确保选择&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;：&lt;br /&gt;
* 取消/替换/拖延所指定的调用，并且&lt;br /&gt;
* 不对其它&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;的使用产生影响&lt;br /&gt;
&lt;br /&gt;
有许多不同的策略可以用于&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;，我们后面要讨论其中的一些。&lt;br /&gt;
&lt;br /&gt;
在前面讨论的第一个例子中，我们有两个调用，都属于同样的延迟。在这其中我们（间接地）使用了谓词本身作为ID。前面已经说过，这样意义很明确，在许多情况下是很好的，不过它需要使用命名了的谓词。&lt;br /&gt;
&lt;br /&gt;
对于匿名谓词，当需要更精确控制时就必须使用其它的ID。关键问题在于用某种东西来“碰撞”相同的调用，而不与其它的调用发生关系。&lt;br /&gt;
&lt;br /&gt;
常常与&amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt;的例子中一样，期望碰撞的调用来自于代码中的特定位置。Very often like in the &amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt; example the calls you want to collide comes from a specific place in the code.&lt;br /&gt;
&lt;br /&gt;
在这种情况下，将&amp;lt;vp&amp;gt;programPoint&amp;lt;/vp&amp;gt;用作&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;最简单。  The is easily obtained using the builtin predicate &amp;lt;vp&amp;gt;programPoint/0&amp;lt;/vp&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
       delayCall(10, { :- doSetColor(NewColor) }, programPoint()).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
这种源程序行确定的ID调用可以相互取消但不会与其它地方的调用产生冲突（不管它们用什么策略）。&lt;br /&gt;
&lt;br /&gt;
如果需要把调用放在若干行中，或是对同一组不同的命名谓词进行调用，则可以考虑定义一个组ID。同样，仍可以使用&amp;lt;vp&amp;gt;programPoint&amp;lt;/vp&amp;gt;：If you need to put calls from several lines, or calls to different named predicates in the same group you should consider defining a group id.  Also here you can use a &amp;lt;vp&amp;gt;programPoint&amp;lt;/vp&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
constants&lt;br /&gt;
    myDelayCallId : programPoint = programPoint().&lt;br /&gt;
 &lt;br /&gt;
   ..., delayCall(10, { :- doSomeThing(...) }, myDelayCallId), ...&lt;br /&gt;
   &lt;br /&gt;
   ..., delayCall(20, doSomeThingElse, myDelayCallId), ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
如果由于某种原因需要确保源代码变化后这种ID不变（比如一个DLL需要知道作为另一个DLL的相同ID），则可以生成和使用GUID。GUID是一个128比特的数，Windows可以生成这样的数并且很少可能产生冲突。在Visual Prolog集成开发环境中可以生成使用这样的数，只需要在菜单中选择&amp;#039;&amp;#039;&amp;#039;Insert -&amp;gt; New GUID&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
constants&lt;br /&gt;
    myDelayCallId : nativeGuid = % {3363A750-0320-4B2A-BE3A-27F66BD7B875}&lt;br /&gt;
        nativeGuid(0x3363A750, 0x0320, 0x4B2A, 0xBE, 0x3A, 0x27, 0xF6, 0x6B, 0xD7, 0xB8, 0x75).&lt;br /&gt;
&lt;br /&gt;
   ..., delayCall(10, { :- doSomeThing(...) }, myDelayCallId), ...&lt;br /&gt;
   &lt;br /&gt;
   ..., delayCall(20, doSomeThingElse, myDelayCallId), ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
最后，我们来看看如何使用“语义”&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;，看下面的代码：Finally, let us consider using &amp;quot;semantic&amp;quot; &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;&amp;#039;s.  Consider the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        doOnFileChanged(Filename).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
一个程序监视着磁盘上文件的变化。如果一个（相关的）文件变化了，程序就会执行某个动作。上面代码中每当文件变化就会调用onFileChanged，而&amp;lt;vp&amp;gt;doOnFileChanged&amp;lt;/vp&amp;gt;表示文件变化了我们想要做的事情。&lt;br /&gt;
&lt;br /&gt;
不过文件变化相继来得会很快，最好是等这些更新都消停了再动作。因此，这个过程中自然要用到delayCall。&lt;br /&gt;
&lt;br /&gt;
但上面的解决方案在这里似乎都不适用。因为delayCall应该是针对每个文件的，而不能是针对每次功能或每个程序点的。简单的办法就是用匿名谓词来跟踪文件并使用文件名作为&amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, Filename).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
现在，只有某个文件最后一个onFileChange在记忆中并被延迟处理。&lt;br /&gt;
&lt;br /&gt;
更广泛的关联中，我们担心使用文件名会与其它地方的某种计划安排产生冲突。一种解决办法是混合使用语义和程序点，像这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, tuple(Filename, programPoint())).&amp;lt;/vip&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
另一个办法是创建一个唯一的延迟域：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
domains&lt;br /&gt;
    delayId = fileChangeId(string Filename).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, fileChangeId(Filename)).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
程序点解决方案的好处是不需要额外的定义，是一种“协调”的解决办法。而&amp;lt;vp&amp;gt;delayId&amp;lt;/vp&amp;gt;域的解决方案好处是在代码中它可以使用在若干个地方。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=DelayCall_(window::)&amp;diff=4156</id>
		<title>DelayCall (window::)</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=DelayCall_(window::)&amp;diff=4156"/>
		<updated>2015-07-20T09:58:45Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Motivating examples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Events may occur more frequent than it is desirable to handle them. &amp;lt;vp&amp;gt;window::delayCall&amp;lt;/vp&amp;gt; provides a way to deal with this problem.&lt;br /&gt;
&lt;br /&gt;
== Motivating examples ==&lt;br /&gt;
&lt;br /&gt;
When typing text into text fields you often get a list of suggestions which you can pick from.  Typical examples is the browsers URL field and search engines search fields.  But if you type fast then it may not be desirable to provide suggestions at each typed character.  Both because it may be too resource demanding to calculate the suggestions, but also because the list may change/flicker in a distracting way.&lt;br /&gt;
&lt;br /&gt;
Instead of updating the list at each keystroke, we will delay the update until the user hesitates the typing a little.  I.e. we delay the update until a certain amount of time has elapsed since the last keystroke.&lt;br /&gt;
&lt;br /&gt;
Here are two other examples that can utilize the same solution to avoid flickering and/or performance problems:&lt;br /&gt;
* It may be better to delay the actual scroll until there is a pause/hesitation in scroll events.&lt;br /&gt;
* A program that receives data updates from an external source, may delay the update of the GUI until there is a pause/hesitation in the external updates.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Drawback&amp;#039;&amp;#039;&amp;#039;: We cannot detect a hesitation/pause until it has exceeded the desired time.  So the solution also introduces a delay, which may not always be desirable: The delay time may have to be a compromise between not reacting too often and not pausing too long.&lt;br /&gt;
&lt;br /&gt;
This [http://youtu.be/IAonJ6t6eho video] shows three control.  The middle one invalidate and repaint on each size event, the other two postpones the repainting to a pause in the size events.&lt;br /&gt;
&lt;br /&gt;
== delayCall ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; can be used to implement such &amp;quot;react on hesitation&amp;quot; solutions.&lt;br /&gt;
&lt;br /&gt;
At first consider this simple example code assumed to be in a control:&lt;br /&gt;
&amp;lt;vip&amp;gt;constants&lt;br /&gt;
    delay = 100. % milliseconds&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    addData(NewData) :-&lt;br /&gt;
        doAddData(NewData),&lt;br /&gt;
        delayCall(delay, invalidate).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    removeData(ToRemove) :-&lt;br /&gt;
        doRemoveData(ToRemove),&lt;br /&gt;
        delayCall(delay, invalidate).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The control has predicates for adding and removing data from it.  When the data has been updated the control is invalidated so that it will be redrawn using the new data.  But to prevent a lot of rapid updates of the control we will postpone the invalidation until the data has been stable for 100ms.  This is exactly what the calls to &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; does: They schedule a call to &amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt; to occur after &amp;lt;vp&amp;gt;delay&amp;lt;/vp&amp;gt; (= 100ms) has passed, but they will also cancel a previous scheduling, if such one exists.  So in effect the an invalidate will happen when there has been a 100ms hesitation in the data updates.&lt;br /&gt;
&lt;br /&gt;
Now that the basic behavior is in place let us look at the details and some frequent pitfalls and some possible solutions to these.&lt;br /&gt;
&lt;br /&gt;
In its simple form &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; takes two arguments:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    delayCall : (positive Delay, runnable Predicate).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A call to &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; will:&lt;br /&gt;
*Either invoke &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt; when &amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt; milliseconds has passed.&lt;br /&gt;
*Or be overidden by a new invocation of &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; with the same &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt; in which case the &amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt; is restarted with the new value.&lt;br /&gt;
*Or be lost because the window is destroyed before the &amp;lt;vp&amp;gt;Delay&amp;lt;/vp&amp;gt; has expired.&lt;br /&gt;
&lt;br /&gt;
Notice that that delayCall is per window and per &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The per window part can be used to avoid executing delayed predicate that is no longer relevant because the window it concerns no longer exist.&lt;br /&gt;
&lt;br /&gt;
The per &amp;lt;vp&amp;gt;Predicate&amp;lt;/vp&amp;gt; part is important because you may want to delayCall for many unrelated things, and such things should not cancel each other.  It is very common to use the application window for &amp;quot;global&amp;quot; actions, which are not related to a specific window. For example recalculating a global state when there is a pause in atomic changes.&lt;br /&gt;
&lt;br /&gt;
In the example the predicate is &amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt; in both calls, so no matter if we call &amp;lt;vp&amp;gt;addData&amp;lt;/vp&amp;gt; or &amp;lt;vp&amp;gt;removeData&amp;lt;/vp&amp;gt; the &amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt; will be postponed further. But calling &amp;lt;vp&amp;gt;delayCall(17, somethingElse)&amp;lt;/vp&amp;gt; will not cancel or postpone the invocation of &amp;lt;vp&amp;gt;invalidate&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Just like in this example, the idenitity of predicates is very often exactly what you want &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; to distinguish on.  There is however a pitfall with anonymous predicates, illustrated by this example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
       delayCall(10, { :- doSetColor(NewColor) }).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The intension of the code is to postpone setting the color until it doesn&amp;#039;t change frequently.  The code uses an anonymous predicate to capture the new color.  Such a use of an anonymous predicate is normally highly recommendable, but here it cause a problem.  The problem is that each time you call &amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt; a &amp;#039;&amp;#039;&amp;#039;new&amp;#039;&amp;#039;&amp;#039; anonymous predicate is created, to capture the &amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;.  So each time we call &amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt; we call &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; with a &amp;#039;&amp;#039;&amp;#039;new unique&amp;#039;&amp;#039;&amp;#039; predicate.  And since it is different from the privous one it will not influence on the previously delay calls.  All of them will be executed each with 10 milisecond dealy.&lt;br /&gt;
&lt;br /&gt;
We can solve the problem like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
facts&lt;br /&gt;
   newColor : color := color_white. % dummy assignment that is not used&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
        newColor := NewColor,&lt;br /&gt;
        delayCall(10, setNewColor).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    setNewColor() :-&lt;br /&gt;
        doSetColor(newColor).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Calling this version of &amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt; will update the fact variable &amp;lt;vp&amp;gt;newColor&amp;lt;/vp&amp;gt;, so this fact variable will always contain the most recent value of &amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;.  The call to &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt; now refernce the same predicate (i.e. &amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt;) each time. So in this code each call to &amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt; will cancel the previous scheduled invocation of &amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt; and schedule another instead.  So the invocation of &amp;lt;vp&amp;gt;setNewColor&amp;lt;/vp&amp;gt; will be postponed to a pause in the &amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt; calls.&lt;br /&gt;
&lt;br /&gt;
Instead of storing the new color in an anomyous predicate we store it in a fact, because then we can delay a call to a named predicate instead of an anonymous predicate.&lt;br /&gt;
&lt;br /&gt;
The overall principle here is to maintain a shaddow (sub-)state which is then merged into the real state in a pause.&lt;br /&gt;
&lt;br /&gt;
The solution above to the anonymous predicate problem is rather complex/elaborate, we both need to introduce extra fact(s) and an extra named predicate.&lt;br /&gt;
&lt;br /&gt;
Worst of all is that the extra facts does not carry real information about the object in which they appear, they only store information that has to be transferred from one point in an algorithm to another point in that algorithm.  I.e. they are local to the algorithm rather than global in the object.&lt;br /&gt;
&lt;br /&gt;
Such facts are highly undesirable and very often you will use &amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt;&amp;#039;s or anonymous predicates to eliminate them.  In this case a &amp;lt;vp&amp;gt;varM&amp;lt;/vp&amp;gt; will not help, because it will also have to be made global in the object for exactly the same reasons as for &amp;lt;vp&amp;gt;NewColor&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
So it would be very nice to have a way to deal with anonymous predicates in &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;.  The problem was that the identity of anonymous predicates is not very suitable for determine whether to prolong a delay.  To solve this problem (and others) we can use another version of delayCall which uses an explicit provided identity value to determine whether to prolong an existing delay or to create a new one.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    delayCall : (positive Delay, runnable Predicate, IdType Id).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This version takes an extra &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt; argumment of any type you like.  If you provide the same value in two calls the latter will replace the first (if has not already fired of course), if you provide different values the calls are considered unrelated.&lt;br /&gt;
&lt;br /&gt;
It is the programmers responsibility to ensure that the chosen &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;:&lt;br /&gt;
* cancel/replace/prolong the intended calls, and&lt;br /&gt;
* does not interfere with other usages of &amp;lt;vp&amp;gt;delayCall&amp;lt;/vp&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Many different strategies may be used for the &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;&amp;#039;s in the sequel we will discuss some.&lt;br /&gt;
&lt;br /&gt;
In the first example above we had two calls belonging to the same delay group. In this example we (indirectly) used the predicates themselves as id. As described this makes good sense in many situations, but requires named predicate.&lt;br /&gt;
&lt;br /&gt;
For anonymous predicates and in cases where you need more detailed control you will have to use another id.  The key issue is to use something that &amp;quot;collides&amp;quot; the intendet calls, but don&amp;#039;t collide with other calls.&lt;br /&gt;
&lt;br /&gt;
Very often like in the &amp;lt;vp&amp;gt;setColor&amp;lt;/vp&amp;gt; example the calls you want to collide comes from a specific place in the code.&lt;br /&gt;
&lt;br /&gt;
In such cases it is very simple to use the &amp;lt;vp&amp;gt;programPoint&amp;lt;/vp&amp;gt; as &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;.  The is easily obtained using the builtin predicate &amp;lt;vp&amp;gt;programPoint/0&amp;lt;/vp&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    setColor(NewColor) :-&lt;br /&gt;
       delayCall(10, { :- doSetColor(NewColor) }, programPoint()).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With this Id calls made from that source line will cancel each but it will not have any impact on calls made elsewhere (regardless of their strategy).&lt;br /&gt;
&lt;br /&gt;
If you need to put calls from several lines, or calls to different named predicates in the same group you should consider defining a group id.  Also here you can use a &amp;lt;vp&amp;gt;programPoint&amp;lt;/vp&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
constants&lt;br /&gt;
    myDelayCallId : programPoint = programPoint().&lt;br /&gt;
 &lt;br /&gt;
   ..., delayCall(10, { :- doSomeThing(...) }, myDelayCallId), ...&lt;br /&gt;
   &lt;br /&gt;
   ..., delayCall(20, doSomeThingElse, myDelayCallId), ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you for some reason need to be certain that the Id doesn&amp;#039;t change when the source code change (for example if a DLL needs to know the same Id as another DLL).  You could generate and use a GUID instead.  GUID&amp;#039;s are 128bit numbers that Windows can generate such that there is an extremely little probability for (world wide) collision.  The Visual Prolog IDE can generate GUID&amp;#039;s for you, just select &amp;#039;&amp;#039;&amp;#039;Insert -&amp;gt; New GUID&amp;#039;&amp;#039;&amp;#039; in the menu:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
constants&lt;br /&gt;
    myDelayCallId : nativeGuid = % {3363A750-0320-4B2A-BE3A-27F66BD7B875}&lt;br /&gt;
        nativeGuid(0x3363A750, 0x0320, 0x4B2A, 0xBE, 0x3A, 0x27, 0xF6, 0x6B, 0xD7, 0xB8, 0x75).&lt;br /&gt;
&lt;br /&gt;
   ..., delayCall(10, { :- doSomeThing(...) }, myDelayCallId), ...&lt;br /&gt;
   &lt;br /&gt;
   ..., delayCall(20, doSomeThingElse, myDelayCallId), ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, let us consider using &amp;quot;semantic&amp;quot; &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;&amp;#039;s.  Consider the following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        doOnFileChanged(Filename).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A program is monitoring file changes on a disk, when a (relevant) file changes the program has to do something.  In the code above onFileChanged is called each time a file changes, and &amp;lt;vp&amp;gt;doOnFileChanged&amp;lt;/vp&amp;gt; represents the work we want to do when the file changes.&lt;br /&gt;
&lt;br /&gt;
However file changes often comes rapidly after each other and it is better to wait with the update when the updates have settled down.  So it would be natural to apply a delayCall in the process.&lt;br /&gt;
&lt;br /&gt;
But none of the solutions above seem to cover this example.  Because the delayCall has to be per file, rather than per function or per programPoint.  A simple solution is keep track of the file in an anonymous predicate and use the filname as &amp;lt;vp&amp;gt;Id&amp;lt;/vp&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, Filename).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now only the last onFileChange for a certain file is remembered and handled delayed.&lt;br /&gt;
&lt;br /&gt;
In a large context we may fear that filenames collide with some other scheme use in some other place.  One solution to this is to combine the semantic is with a programPoint, for example like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, tuple(Filename, programPoint())).&amp;lt;/vip&amp;gt;&lt;br /&gt;
        &lt;br /&gt;
Another solution is to create a unique delay id domain:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;&lt;br /&gt;
domains&lt;br /&gt;
    delayId = fileChangeId(string Filename).&lt;br /&gt;
&lt;br /&gt;
clauses&lt;br /&gt;
    onFileChanged(Filename) :-&lt;br /&gt;
        delayCall(100, { :- doOnFileChanged(Filename) }, fileChangeId(Filename)).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The advandtage of the programPoint solution is that it does not require any extra definitions, it is simply an &amp;quot;in line&amp;quot; solution.  The advantage of the solution with the &amp;lt;vp&amp;gt;delayId&amp;lt;/vp&amp;gt; domain is that it can be used in several places in the code.&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;br /&gt;
[[Category:GUI]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4155</id>
		<title>IIS configuration（IIS配置）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4155"/>
		<updated>2015-07-11T09:59:43Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Skype HTTP/HTTPS 冲突 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的IIS configuration。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
本专题介绍如何进行各类微软因特网信息服务（the Microsoft Internet Information Services）配置。&lt;br /&gt;
&lt;br /&gt;
文中所用的设置是根据示例目录&amp;lt;vp&amp;gt;webRPC&amp;lt;/vp&amp;gt;中&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt;（参见[[Web Services]]）的需要而做的，这类东西随着情况的不同而不同，不过还是很容易调整的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 工程创建的是一个 ISAPI 插件 (dll)，实现了一个JSON-RPC服务。例子中还包括了一些客户端浏览器能够访问的HTML、CSS以及 JavaScript等相关文件。因此，IIS的配置要满足两个方面的需求：&lt;br /&gt;
* 一是文件提供者&lt;br /&gt;
* 一是ISAPI服务&lt;br /&gt;
&lt;br /&gt;
所有的IIS配置都在 &amp;#039;&amp;#039;&amp;#039;Internet Information Services Manager&amp;#039;&amp;#039;&amp;#039; 中。&lt;br /&gt;
&lt;br /&gt;
=== 配置窗口部件 ===&lt;br /&gt;
&lt;br /&gt;
要使用因特网信息服务、管理器,运行ISAPA和/或CGI的插件，先要确保计算机上它们都是可用的。&lt;br /&gt;
&lt;br /&gt;
在 &amp;#039;&amp;#039;&amp;#039;Programs and Features -&amp;gt; Turn Windows Features on or off&amp;#039;&amp;#039;&amp;#039; 中，应该确保下面这些内容是可用的：&lt;br /&gt;
* 因特网信息服务（Internet Information Services）&lt;br /&gt;
* IIS 管理控制台&lt;br /&gt;
* CGI（其实这一点并非示例 &amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 所要求的）&lt;br /&gt;
* ISAPI 扩展&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_prerequisites_1.png]]&lt;br /&gt;
[[Image:IISM_prerequisites_2.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 应用池 ===&lt;br /&gt;
&lt;br /&gt;
ISAPI服务必须运行在一个可用的应用池中，它提供了各种IIS部件的过程隔离。这有以下优点：&lt;br /&gt;
* 它使得整个IIS的鲁棒性得到加强，因为一个应用池的问题不大可能影响到其它应用池（当然了，耗尽系统资源会产生交叉影响）；&lt;br /&gt;
* 各个池可以有不同的设置（比如用于.net平台的，等等）；&lt;br /&gt;
* 各个池可以独立重启。&lt;br /&gt;
&lt;br /&gt;
要创建应用池，在&amp;#039;&amp;#039;&amp;#039;Application Pools&amp;#039;&amp;#039;&amp;#039;关联菜单中选择 &amp;quot;Add Application Pool...&amp;quot;。用于Visual Prolog程序的池，并不需要任何.net平台，建议选择不需要.net支持以降低出现干扰的风险。给池取个名字，选择&amp;quot;No Managed Code&amp;quot;（除非有其它理由需要它）。别的就不用管了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_addAppPool_1.png]]&lt;br /&gt;
[[Image:IISM_addAppPool_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 32比特支持 ===&lt;br /&gt;
&lt;br /&gt;
注意，缺省时应用池在64位平台上是不能运行32位程序的。必要时可以在&amp;quot;Advanced Setting&amp;quot;中做设置：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_32bit_1.png]]&lt;br /&gt;
[[Image:IISM_32bit_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 文件服务 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 示例需要向运行Web应用程序的Web浏览器分发文件。这可以设置一个“虚拟目录”或是一个“应用程序”（后者仅当程序必须运行于该Web部分时才是必须的）。&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_webRPC_1.png]]&lt;br /&gt;
[[Image:IISM_add_webRPC_2.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Alias&amp;#039;&amp;#039;&amp;#039; 定义了在WEB中与&amp;#039;&amp;#039;&amp;#039;Path&amp;#039;&amp;#039;&amp;#039;相关的名称，而&amp;#039;&amp;#039;&amp;#039;Physical path&amp;#039;&amp;#039;&amp;#039;则定义了这个路径对应的磁盘位置。在这里，&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC&amp;#039;&amp;#039;&amp;#039;对应于&amp;#039;&amp;#039;&amp;#039;C:\webRPC\web&amp;#039;&amp;#039;&amp;#039;，这里就是示例中html、css以及JavaScript等文件的位置（我的情况下）。&lt;br /&gt;
&lt;br /&gt;
=== ISAPI设置 ===&lt;br /&gt;
&lt;br /&gt;
在示例中，路径 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC/jsonrpc&amp;#039;&amp;#039;&amp;#039; 需要运行&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; ISAPI dll。为此，我们来在虚拟目录webRPC中添加一个应用程序：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_1.png]]&lt;br /&gt;
&lt;br /&gt;
要确保该应用程序使用了所期望的应用池。我们一般不使用物理路径，但IIS会将web.config文件放在该目录下。创建一个新目录来放这个文件也是个不错的选择，不过这里我们就直接使用示例根目录：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_2.png]]&lt;br /&gt;
&lt;br /&gt;
放好应用程序之后就可以配置ISAPI了。选择&amp;#039;&amp;#039;&amp;#039;Handler Mappings&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_1.png]]&lt;br /&gt;
&lt;br /&gt;
接着，再添加一个 &amp;quot;Script Map&amp;quot;，与所有东西相匹配并指向ISAPI dll：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_2.png]]&lt;br /&gt;
[[Image:IISM_handlerMapping_3.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在我们的磁盘上JSON-RPC请求不属于&amp;quot;script&amp;quot;文件，所以我们得在&amp;#039;&amp;#039;&amp;#039;Request Restrictions...&amp;#039;&amp;#039;&amp;#039;设置中去掉 &amp;quot;Invoke handler only if request is mapped to&amp;quot; 的勾选：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_4.png]]&lt;br /&gt;
&lt;br /&gt;
如果所有设置OK，出现下图所示的情况，也就可以说OK了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_5.png]]&lt;br /&gt;
&lt;br /&gt;
=== 用户权限 ===&lt;br /&gt;
&lt;br /&gt;
IIS运行时，会作为Windows某类特定用户访问各类文件，因此它对这些文件必须有足够的权限。&lt;br /&gt;
&lt;br /&gt;
究竟是哪一类用户，确定方法如下：&lt;br /&gt;
&lt;br /&gt;
* 若对虚拟目录或应用程序设置的是&amp;#039;&amp;#039;&amp;#039;&amp;quot;Connect as...&amp;quot;&amp;#039;&amp;#039;&amp;#039;用户，则这类用户可以访问文件；&lt;br /&gt;
* 否则（如果使用了&amp;#039;&amp;#039;&amp;#039;Pass-through&amp;#039;&amp;#039;&amp;#039;授权）：&lt;br /&gt;
** 如果允许匿名访问虚拟目录/应用程序，则某些IUSER_...可以访问文件；&lt;br /&gt;
** 如果使用了某些Windows授权，则来自连接的windows用户可以访问文件。&lt;br /&gt;
&lt;br /&gt;
Anonymous/Windows访问由&amp;#039;&amp;#039;&amp;#039;Authentification&amp;#039;&amp;#039;&amp;#039;图标控制。&lt;br /&gt;
&lt;br /&gt;
=== Skype HTTP/HTTPS 冲突 ===&lt;br /&gt;
Skype与HTTP/HTTPS服务是有潜在冲突的，因为缺省时Skype保留了端口80(HTTP)和443(HTTPS)。&lt;br /&gt;
&lt;br /&gt;
这就导致了在缺省模式下运行Skype时就不能运行HTTP/HTTPS服务器。 &lt;br /&gt;
&lt;br /&gt;
解决的办法是在Skype中关闭80/443端口。&lt;br /&gt;
&lt;br /&gt;
还可以参看这个视频： [http://www.lynda.com/Apache-HTTP-Server-tutorials/Handling-port-conflicts-Skype-Windows/77958/90546-4.html Handling port conflicts with Skype on Windows]&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Web Services]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4154</id>
		<title>IIS configuration（IIS配置）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4154"/>
		<updated>2015-07-11T09:56:25Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Skype HTTP/HTTPS 冲突 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的IIS configuration。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
本专题介绍如何进行各类微软因特网信息服务（the Microsoft Internet Information Services）配置。&lt;br /&gt;
&lt;br /&gt;
文中所用的设置是根据示例目录&amp;lt;vp&amp;gt;webRPC&amp;lt;/vp&amp;gt;中&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt;（参见[[Web Services]]）的需要而做的，这类东西随着情况的不同而不同，不过还是很容易调整的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 工程创建的是一个 ISAPI 插件 (dll)，实现了一个JSON-RPC服务。例子中还包括了一些客户端浏览器能够访问的HTML、CSS以及 JavaScript等相关文件。因此，IIS的配置要满足两个方面的需求：&lt;br /&gt;
* 一是文件提供者&lt;br /&gt;
* 一是ISAPI服务&lt;br /&gt;
&lt;br /&gt;
所有的IIS配置都在 &amp;#039;&amp;#039;&amp;#039;Internet Information Services Manager&amp;#039;&amp;#039;&amp;#039; 中。&lt;br /&gt;
&lt;br /&gt;
=== 配置窗口部件 ===&lt;br /&gt;
&lt;br /&gt;
要使用因特网信息服务、管理器,运行ISAPA和/或CGI的插件，先要确保计算机上它们都是可用的。&lt;br /&gt;
&lt;br /&gt;
在 &amp;#039;&amp;#039;&amp;#039;Programs and Features -&amp;gt; Turn Windows Features on or off&amp;#039;&amp;#039;&amp;#039; 中，应该确保下面这些内容是可用的：&lt;br /&gt;
* 因特网信息服务（Internet Information Services）&lt;br /&gt;
* IIS 管理控制台&lt;br /&gt;
* CGI（其实这一点并非示例 &amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 所要求的）&lt;br /&gt;
* ISAPI 扩展&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_prerequisites_1.png]]&lt;br /&gt;
[[Image:IISM_prerequisites_2.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 应用池 ===&lt;br /&gt;
&lt;br /&gt;
ISAPI服务必须运行在一个可用的应用池中，它提供了各种IIS部件的过程隔离。这有以下优点：&lt;br /&gt;
* 它使得整个IIS的鲁棒性得到加强，因为一个应用池的问题不大可能影响到其它应用池（当然了，耗尽系统资源会产生交叉影响）；&lt;br /&gt;
* 各个池可以有不同的设置（比如用于.net平台的，等等）；&lt;br /&gt;
* 各个池可以独立重启。&lt;br /&gt;
&lt;br /&gt;
要创建应用池，在&amp;#039;&amp;#039;&amp;#039;Application Pools&amp;#039;&amp;#039;&amp;#039;关联菜单中选择 &amp;quot;Add Application Pool...&amp;quot;。用于Visual Prolog程序的池，并不需要任何.net平台，建议选择不需要.net支持以降低出现干扰的风险。给池取个名字，选择&amp;quot;No Managed Code&amp;quot;（除非有其它理由需要它）。别的就不用管了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_addAppPool_1.png]]&lt;br /&gt;
[[Image:IISM_addAppPool_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 32比特支持 ===&lt;br /&gt;
&lt;br /&gt;
注意，缺省时应用池在64位平台上是不能运行32位程序的。必要时可以在&amp;quot;Advanced Setting&amp;quot;中做设置：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_32bit_1.png]]&lt;br /&gt;
[[Image:IISM_32bit_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 文件服务 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 示例需要向运行Web应用程序的Web浏览器分发文件。这可以设置一个“虚拟目录”或是一个“应用程序”（后者仅当程序必须运行于该Web部分时才是必须的）。&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_webRPC_1.png]]&lt;br /&gt;
[[Image:IISM_add_webRPC_2.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Alias&amp;#039;&amp;#039;&amp;#039; 定义了在WEB中与&amp;#039;&amp;#039;&amp;#039;Path&amp;#039;&amp;#039;&amp;#039;相关的名称，而&amp;#039;&amp;#039;&amp;#039;Physical path&amp;#039;&amp;#039;&amp;#039;则定义了这个路径对应的磁盘位置。在这里，&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC&amp;#039;&amp;#039;&amp;#039;对应于&amp;#039;&amp;#039;&amp;#039;C:\webRPC\web&amp;#039;&amp;#039;&amp;#039;，这里就是示例中html、css以及JavaScript等文件的位置（我的情况下）。&lt;br /&gt;
&lt;br /&gt;
=== ISAPI设置 ===&lt;br /&gt;
&lt;br /&gt;
在示例中，路径 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC/jsonrpc&amp;#039;&amp;#039;&amp;#039; 需要运行&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; ISAPI dll。为此，我们来在虚拟目录webRPC中添加一个应用程序：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_1.png]]&lt;br /&gt;
&lt;br /&gt;
要确保该应用程序使用了所期望的应用池。我们一般不使用物理路径，但IIS会将web.config文件放在该目录下。创建一个新目录来放这个文件也是个不错的选择，不过这里我们就直接使用示例根目录：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_2.png]]&lt;br /&gt;
&lt;br /&gt;
放好应用程序之后就可以配置ISAPI了。选择&amp;#039;&amp;#039;&amp;#039;Handler Mappings&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_1.png]]&lt;br /&gt;
&lt;br /&gt;
接着，再添加一个 &amp;quot;Script Map&amp;quot;，与所有东西相匹配并指向ISAPI dll：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_2.png]]&lt;br /&gt;
[[Image:IISM_handlerMapping_3.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在我们的磁盘上JSON-RPC请求不属于&amp;quot;script&amp;quot;文件，所以我们得在&amp;#039;&amp;#039;&amp;#039;Request Restrictions...&amp;#039;&amp;#039;&amp;#039;设置中去掉 &amp;quot;Invoke handler only if request is mapped to&amp;quot; 的勾选：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_4.png]]&lt;br /&gt;
&lt;br /&gt;
如果所有设置OK，出现下图所示的情况，也就可以说OK了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_5.png]]&lt;br /&gt;
&lt;br /&gt;
=== 用户权限 ===&lt;br /&gt;
&lt;br /&gt;
IIS运行时，会作为Windows某类特定用户访问各类文件，因此它对这些文件必须有足够的权限。&lt;br /&gt;
&lt;br /&gt;
究竟是哪一类用户，确定方法如下：&lt;br /&gt;
&lt;br /&gt;
* 若对虚拟目录或应用程序设置的是&amp;#039;&amp;#039;&amp;#039;&amp;quot;Connect as...&amp;quot;&amp;#039;&amp;#039;&amp;#039;用户，则这类用户可以访问文件；&lt;br /&gt;
* 否则（如果使用了&amp;#039;&amp;#039;&amp;#039;Pass-through&amp;#039;&amp;#039;&amp;#039;授权）：&lt;br /&gt;
** 如果允许匿名访问虚拟目录/应用程序，则某些IUSER_...可以访问文件；&lt;br /&gt;
** 如果使用了某些Windows授权，则来自连接的windows用户可以访问文件。&lt;br /&gt;
&lt;br /&gt;
Anonymous/Windows访问由&amp;#039;&amp;#039;&amp;#039;Authentification&amp;#039;&amp;#039;&amp;#039;图标控制。&lt;br /&gt;
&lt;br /&gt;
=== Skype HTTP/HTTPS 冲突 ===&lt;br /&gt;
Skype与HTTP/HTTPS服务是有潜在冲突的，因为缺省时Skype保留了端口80(HTTP)和443(HTTPS)。&lt;br /&gt;
&lt;br /&gt;
这就导致了在缺省模式下运行Skype时就不能运行HTTP/HTTPS服务器。 &lt;br /&gt;
&lt;br /&gt;
解决的办法是在Skype中关闭80/443端口。&lt;br /&gt;
&lt;br /&gt;
还可以参看这个视频： [http://www.lynda.com/Apache-HTTP-Server-tutorials/Handling-port-conflicts-Skype-&lt;br /&gt;
&lt;br /&gt;
Windows/77958/90546-4.html Handling port conflicts with Skype on Windows]&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Web Services]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4153</id>
		<title>IIS configuration（IIS配置）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4153"/>
		<updated>2015-07-11T09:52:14Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* 用户权限 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的IIS configuration。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
本专题介绍如何进行各类微软因特网信息服务（the Microsoft Internet Information Services）配置。&lt;br /&gt;
&lt;br /&gt;
文中所用的设置是根据示例目录&amp;lt;vp&amp;gt;webRPC&amp;lt;/vp&amp;gt;中&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt;（参见[[Web Services]]）的需要而做的，这类东西随着情况的不同而不同，不过还是很容易调整的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 工程创建的是一个 ISAPI 插件 (dll)，实现了一个JSON-RPC服务。例子中还包括了一些客户端浏览器能够访问的HTML、CSS以及 JavaScript等相关文件。因此，IIS的配置要满足两个方面的需求：&lt;br /&gt;
* 一是文件提供者&lt;br /&gt;
* 一是ISAPI服务&lt;br /&gt;
&lt;br /&gt;
所有的IIS配置都在 &amp;#039;&amp;#039;&amp;#039;Internet Information Services Manager&amp;#039;&amp;#039;&amp;#039; 中。&lt;br /&gt;
&lt;br /&gt;
=== 配置窗口部件 ===&lt;br /&gt;
&lt;br /&gt;
要使用因特网信息服务、管理器,运行ISAPA和/或CGI的插件，先要确保计算机上它们都是可用的。&lt;br /&gt;
&lt;br /&gt;
在 &amp;#039;&amp;#039;&amp;#039;Programs and Features -&amp;gt; Turn Windows Features on or off&amp;#039;&amp;#039;&amp;#039; 中，应该确保下面这些内容是可用的：&lt;br /&gt;
* 因特网信息服务（Internet Information Services）&lt;br /&gt;
* IIS 管理控制台&lt;br /&gt;
* CGI（其实这一点并非示例 &amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 所要求的）&lt;br /&gt;
* ISAPI 扩展&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_prerequisites_1.png]]&lt;br /&gt;
[[Image:IISM_prerequisites_2.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 应用池 ===&lt;br /&gt;
&lt;br /&gt;
ISAPI服务必须运行在一个可用的应用池中，它提供了各种IIS部件的过程隔离。这有以下优点：&lt;br /&gt;
* 它使得整个IIS的鲁棒性得到加强，因为一个应用池的问题不大可能影响到其它应用池（当然了，耗尽系统资源会产生交叉影响）；&lt;br /&gt;
* 各个池可以有不同的设置（比如用于.net平台的，等等）；&lt;br /&gt;
* 各个池可以独立重启。&lt;br /&gt;
&lt;br /&gt;
要创建应用池，在&amp;#039;&amp;#039;&amp;#039;Application Pools&amp;#039;&amp;#039;&amp;#039;关联菜单中选择 &amp;quot;Add Application Pool...&amp;quot;。用于Visual Prolog程序的池，并不需要任何.net平台，建议选择不需要.net支持以降低出现干扰的风险。给池取个名字，选择&amp;quot;No Managed Code&amp;quot;（除非有其它理由需要它）。别的就不用管了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_addAppPool_1.png]]&lt;br /&gt;
[[Image:IISM_addAppPool_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 32比特支持 ===&lt;br /&gt;
&lt;br /&gt;
注意，缺省时应用池在64位平台上是不能运行32位程序的。必要时可以在&amp;quot;Advanced Setting&amp;quot;中做设置：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_32bit_1.png]]&lt;br /&gt;
[[Image:IISM_32bit_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 文件服务 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 示例需要向运行Web应用程序的Web浏览器分发文件。这可以设置一个“虚拟目录”或是一个“应用程序”（后者仅当程序必须运行于该Web部分时才是必须的）。&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_webRPC_1.png]]&lt;br /&gt;
[[Image:IISM_add_webRPC_2.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Alias&amp;#039;&amp;#039;&amp;#039; 定义了在WEB中与&amp;#039;&amp;#039;&amp;#039;Path&amp;#039;&amp;#039;&amp;#039;相关的名称，而&amp;#039;&amp;#039;&amp;#039;Physical path&amp;#039;&amp;#039;&amp;#039;则定义了这个路径对应的磁盘位置。在这里，&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC&amp;#039;&amp;#039;&amp;#039;对应于&amp;#039;&amp;#039;&amp;#039;C:\webRPC\web&amp;#039;&amp;#039;&amp;#039;，这里就是示例中html、css以及JavaScript等文件的位置（我的情况下）。&lt;br /&gt;
&lt;br /&gt;
=== ISAPI设置 ===&lt;br /&gt;
&lt;br /&gt;
在示例中，路径 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC/jsonrpc&amp;#039;&amp;#039;&amp;#039; 需要运行&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; ISAPI dll。为此，我们来在虚拟目录webRPC中添加一个应用程序：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_1.png]]&lt;br /&gt;
&lt;br /&gt;
要确保该应用程序使用了所期望的应用池。我们一般不使用物理路径，但IIS会将web.config文件放在该目录下。创建一个新目录来放这个文件也是个不错的选择，不过这里我们就直接使用示例根目录：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_2.png]]&lt;br /&gt;
&lt;br /&gt;
放好应用程序之后就可以配置ISAPI了。选择&amp;#039;&amp;#039;&amp;#039;Handler Mappings&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_1.png]]&lt;br /&gt;
&lt;br /&gt;
接着，再添加一个 &amp;quot;Script Map&amp;quot;，与所有东西相匹配并指向ISAPI dll：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_2.png]]&lt;br /&gt;
[[Image:IISM_handlerMapping_3.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在我们的磁盘上JSON-RPC请求不属于&amp;quot;script&amp;quot;文件，所以我们得在&amp;#039;&amp;#039;&amp;#039;Request Restrictions...&amp;#039;&amp;#039;&amp;#039;设置中去掉 &amp;quot;Invoke handler only if request is mapped to&amp;quot; 的勾选：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_4.png]]&lt;br /&gt;
&lt;br /&gt;
如果所有设置OK，出现下图所示的情况，也就可以说OK了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_5.png]]&lt;br /&gt;
&lt;br /&gt;
=== 用户权限 ===&lt;br /&gt;
&lt;br /&gt;
IIS运行时，会作为Windows某类特定用户访问各类文件，因此它对这些文件必须有足够的权限。&lt;br /&gt;
&lt;br /&gt;
究竟是哪一类用户，确定方法如下：&lt;br /&gt;
&lt;br /&gt;
* 若对虚拟目录或应用程序设置的是&amp;#039;&amp;#039;&amp;#039;&amp;quot;Connect as...&amp;quot;&amp;#039;&amp;#039;&amp;#039;用户，则这类用户可以访问文件；&lt;br /&gt;
* 否则（如果使用了&amp;#039;&amp;#039;&amp;#039;Pass-through&amp;#039;&amp;#039;&amp;#039;授权）：&lt;br /&gt;
** 如果允许匿名访问虚拟目录/应用程序，则某些IUSER_...可以访问文件；&lt;br /&gt;
** 如果使用了某些Windows授权，则来自连接的windows用户可以访问文件。&lt;br /&gt;
&lt;br /&gt;
Anonymous/Windows访问由&amp;#039;&amp;#039;&amp;#039;Authentification&amp;#039;&amp;#039;&amp;#039;图标控制。&lt;br /&gt;
&lt;br /&gt;
=== Skype HTTP/HTTPS 冲突 ===&lt;br /&gt;
Skype与HTTP/HTTPS服务是有潜在冲突的，因为缺省时Skype保留了端口80(HTTP)和443(HTTPS)。&lt;br /&gt;
&lt;br /&gt;
这就导致了在缺省模式下运行Skype时就不能运行HTTP/HTTPS服务器。 &lt;br /&gt;
&lt;br /&gt;
解决的办法是在Skype中关闭80/443端口。&lt;br /&gt;
&lt;br /&gt;
还可以参看这个视频：&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Web Services]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4152</id>
		<title>IIS configuration（IIS配置）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4152"/>
		<updated>2015-07-11T09:44:45Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* 参考 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的IIS configuration。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
本专题介绍如何进行各类微软因特网信息服务（the Microsoft Internet Information Services）配置。&lt;br /&gt;
&lt;br /&gt;
文中所用的设置是根据示例目录&amp;lt;vp&amp;gt;webRPC&amp;lt;/vp&amp;gt;中&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt;（参见[[Web Services]]）的需要而做的，这类东西随着情况的不同而不同，不过还是很容易调整的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 工程创建的是一个 ISAPI 插件 (dll)，实现了一个JSON-RPC服务。例子中还包括了一些客户端浏览器能够访问的HTML、CSS以及 JavaScript等相关文件。因此，IIS的配置要满足两个方面的需求：&lt;br /&gt;
* 一是文件提供者&lt;br /&gt;
* 一是ISAPI服务&lt;br /&gt;
&lt;br /&gt;
所有的IIS配置都在 &amp;#039;&amp;#039;&amp;#039;Internet Information Services Manager&amp;#039;&amp;#039;&amp;#039; 中。&lt;br /&gt;
&lt;br /&gt;
=== 配置窗口部件 ===&lt;br /&gt;
&lt;br /&gt;
要使用因特网信息服务、管理器,运行ISAPA和/或CGI的插件，先要确保计算机上它们都是可用的。&lt;br /&gt;
&lt;br /&gt;
在 &amp;#039;&amp;#039;&amp;#039;Programs and Features -&amp;gt; Turn Windows Features on or off&amp;#039;&amp;#039;&amp;#039; 中，应该确保下面这些内容是可用的：&lt;br /&gt;
* 因特网信息服务（Internet Information Services）&lt;br /&gt;
* IIS 管理控制台&lt;br /&gt;
* CGI（其实这一点并非示例 &amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 所要求的）&lt;br /&gt;
* ISAPI 扩展&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_prerequisites_1.png]]&lt;br /&gt;
[[Image:IISM_prerequisites_2.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 应用池 ===&lt;br /&gt;
&lt;br /&gt;
ISAPI服务必须运行在一个可用的应用池中，它提供了各种IIS部件的过程隔离。这有以下优点：&lt;br /&gt;
* 它使得整个IIS的鲁棒性得到加强，因为一个应用池的问题不大可能影响到其它应用池（当然了，耗尽系统资源会产生交叉影响）；&lt;br /&gt;
* 各个池可以有不同的设置（比如用于.net平台的，等等）；&lt;br /&gt;
* 各个池可以独立重启。&lt;br /&gt;
&lt;br /&gt;
要创建应用池，在&amp;#039;&amp;#039;&amp;#039;Application Pools&amp;#039;&amp;#039;&amp;#039;关联菜单中选择 &amp;quot;Add Application Pool...&amp;quot;。用于Visual Prolog程序的池，并不需要任何.net平台，建议选择不需要.net支持以降低出现干扰的风险。给池取个名字，选择&amp;quot;No Managed Code&amp;quot;（除非有其它理由需要它）。别的就不用管了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_addAppPool_1.png]]&lt;br /&gt;
[[Image:IISM_addAppPool_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 32比特支持 ===&lt;br /&gt;
&lt;br /&gt;
注意，缺省时应用池在64位平台上是不能运行32位程序的。必要时可以在&amp;quot;Advanced Setting&amp;quot;中做设置：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_32bit_1.png]]&lt;br /&gt;
[[Image:IISM_32bit_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 文件服务 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 示例需要向运行Web应用程序的Web浏览器分发文件。这可以设置一个“虚拟目录”或是一个“应用程序”（后者仅当程序必须运行于该Web部分时才是必须的）。&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_webRPC_1.png]]&lt;br /&gt;
[[Image:IISM_add_webRPC_2.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Alias&amp;#039;&amp;#039;&amp;#039; 定义了在WEB中与&amp;#039;&amp;#039;&amp;#039;Path&amp;#039;&amp;#039;&amp;#039;相关的名称，而&amp;#039;&amp;#039;&amp;#039;Physical path&amp;#039;&amp;#039;&amp;#039;则定义了这个路径对应的磁盘位置。在这里，&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC&amp;#039;&amp;#039;&amp;#039;对应于&amp;#039;&amp;#039;&amp;#039;C:\webRPC\web&amp;#039;&amp;#039;&amp;#039;，这里就是示例中html、css以及JavaScript等文件的位置（我的情况下）。&lt;br /&gt;
&lt;br /&gt;
=== ISAPI设置 ===&lt;br /&gt;
&lt;br /&gt;
在示例中，路径 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC/jsonrpc&amp;#039;&amp;#039;&amp;#039; 需要运行&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; ISAPI dll。为此，我们来在虚拟目录webRPC中添加一个应用程序：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_1.png]]&lt;br /&gt;
&lt;br /&gt;
要确保该应用程序使用了所期望的应用池。我们一般不使用物理路径，但IIS会将web.config文件放在该目录下。创建一个新目录来放这个文件也是个不错的选择，不过这里我们就直接使用示例根目录：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_2.png]]&lt;br /&gt;
&lt;br /&gt;
放好应用程序之后就可以配置ISAPI了。选择&amp;#039;&amp;#039;&amp;#039;Handler Mappings&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_1.png]]&lt;br /&gt;
&lt;br /&gt;
接着，再添加一个 &amp;quot;Script Map&amp;quot;，与所有东西相匹配并指向ISAPI dll：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_2.png]]&lt;br /&gt;
[[Image:IISM_handlerMapping_3.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在我们的磁盘上JSON-RPC请求不属于&amp;quot;script&amp;quot;文件，所以我们得在&amp;#039;&amp;#039;&amp;#039;Request Restrictions...&amp;#039;&amp;#039;&amp;#039;设置中去掉 &amp;quot;Invoke handler only if request is mapped to&amp;quot; 的勾选：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_4.png]]&lt;br /&gt;
&lt;br /&gt;
如果所有设置OK，出现下图所示的情况，也就可以说OK了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_5.png]]&lt;br /&gt;
&lt;br /&gt;
=== 用户权限 ===&lt;br /&gt;
&lt;br /&gt;
IIS运行时，会作为Windows某类特定用户访问各类文件，因此它对这些文件必须有足够的权限。 run it will access various files as a certain Windows user, this user must have sufficient rights to the files.&lt;br /&gt;
&lt;br /&gt;
Which user is determined like this:&lt;br /&gt;
&lt;br /&gt;
* 若对虚拟目录或应用程序设置的是&amp;#039;&amp;#039;&amp;#039;Connect as...&amp;quot;用户，则这类用户可以访问文件； user is set for the virtual directory or application then that user will access the files&lt;br /&gt;
* 否则（如果使用了&amp;#039;&amp;#039;&amp;#039;Pass-through授权）Otherwise (if &amp;#039;&amp;#039;&amp;#039;Pass-through authentication is used):&lt;br /&gt;
** 如果允许匿名访问虚拟目录/应用程序，则某些IUSER_...可以访问文件；If annoymous access is allowed to the virtual directory/application then some IUSER_... is will access the files&lt;br /&gt;
** 如果使用了某些Windows授权，则来自连接的windows用户可以访问文件。If some Windows authentification is used the windows user from the connection will will access the files&lt;br /&gt;
&lt;br /&gt;
Anonymous/Windows访问由&amp;#039;&amp;#039;&amp;#039;Authentification&amp;#039;&amp;#039;&amp;#039;图标控制。&lt;br /&gt;
&lt;br /&gt;
=== Skype HTTP/HTTPS 冲突 ===&lt;br /&gt;
Skype与HTTP/HTTPS服务是有潜在冲突的，因为缺省时Skype保留了端口80(HTTP)和443(HTTPS)。&lt;br /&gt;
&lt;br /&gt;
这就导致了在缺省模式下运行Skype时就不能运行HTTP/HTTPS服务器。 &lt;br /&gt;
&lt;br /&gt;
解决的办法是在Skype中关闭80/443端口。&lt;br /&gt;
&lt;br /&gt;
还可以参看这个视频：&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Web Services]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4151</id>
		<title>IIS configuration（IIS配置）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4151"/>
		<updated>2015-07-11T09:44:19Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Skype HTTP/HTTPS 冲突 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的IIS configuration。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
本专题介绍如何进行各类微软因特网信息服务（the Microsoft Internet Information Services）配置。&lt;br /&gt;
&lt;br /&gt;
文中所用的设置是根据示例目录&amp;lt;vp&amp;gt;webRPC&amp;lt;/vp&amp;gt;中&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt;（参见[[Web Services]]）的需要而做的，这类东西随着情况的不同而不同，不过还是很容易调整的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 工程创建的是一个 ISAPI 插件 (dll)，实现了一个JSON-RPC服务。例子中还包括了一些客户端浏览器能够访问的HTML、CSS以及 JavaScript等相关文件。因此，IIS的配置要满足两个方面的需求：&lt;br /&gt;
* 一是文件提供者&lt;br /&gt;
* 一是ISAPI服务&lt;br /&gt;
&lt;br /&gt;
所有的IIS配置都在 &amp;#039;&amp;#039;&amp;#039;Internet Information Services Manager&amp;#039;&amp;#039;&amp;#039; 中。&lt;br /&gt;
&lt;br /&gt;
=== 配置窗口部件 ===&lt;br /&gt;
&lt;br /&gt;
要使用因特网信息服务、管理器,运行ISAPA和/或CGI的插件，先要确保计算机上它们都是可用的。&lt;br /&gt;
&lt;br /&gt;
在 &amp;#039;&amp;#039;&amp;#039;Programs and Features -&amp;gt; Turn Windows Features on or off&amp;#039;&amp;#039;&amp;#039; 中，应该确保下面这些内容是可用的：&lt;br /&gt;
* 因特网信息服务（Internet Information Services）&lt;br /&gt;
* IIS 管理控制台&lt;br /&gt;
* CGI（其实这一点并非示例 &amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 所要求的）&lt;br /&gt;
* ISAPI 扩展&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_prerequisites_1.png]]&lt;br /&gt;
[[Image:IISM_prerequisites_2.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 应用池 ===&lt;br /&gt;
&lt;br /&gt;
ISAPI服务必须运行在一个可用的应用池中，它提供了各种IIS部件的过程隔离。这有以下优点：&lt;br /&gt;
* 它使得整个IIS的鲁棒性得到加强，因为一个应用池的问题不大可能影响到其它应用池（当然了，耗尽系统资源会产生交叉影响）；&lt;br /&gt;
* 各个池可以有不同的设置（比如用于.net平台的，等等）；&lt;br /&gt;
* 各个池可以独立重启。&lt;br /&gt;
&lt;br /&gt;
要创建应用池，在&amp;#039;&amp;#039;&amp;#039;Application Pools&amp;#039;&amp;#039;&amp;#039;关联菜单中选择 &amp;quot;Add Application Pool...&amp;quot;。用于Visual Prolog程序的池，并不需要任何.net平台，建议选择不需要.net支持以降低出现干扰的风险。给池取个名字，选择&amp;quot;No Managed Code&amp;quot;（除非有其它理由需要它）。别的就不用管了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_addAppPool_1.png]]&lt;br /&gt;
[[Image:IISM_addAppPool_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 32比特支持 ===&lt;br /&gt;
&lt;br /&gt;
注意，缺省时应用池在64位平台上是不能运行32位程序的。必要时可以在&amp;quot;Advanced Setting&amp;quot;中做设置：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_32bit_1.png]]&lt;br /&gt;
[[Image:IISM_32bit_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 文件服务 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 示例需要向运行Web应用程序的Web浏览器分发文件。这可以设置一个“虚拟目录”或是一个“应用程序”（后者仅当程序必须运行于该Web部分时才是必须的）。&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_webRPC_1.png]]&lt;br /&gt;
[[Image:IISM_add_webRPC_2.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Alias&amp;#039;&amp;#039;&amp;#039; 定义了在WEB中与&amp;#039;&amp;#039;&amp;#039;Path&amp;#039;&amp;#039;&amp;#039;相关的名称，而&amp;#039;&amp;#039;&amp;#039;Physical path&amp;#039;&amp;#039;&amp;#039;则定义了这个路径对应的磁盘位置。在这里，&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC&amp;#039;&amp;#039;&amp;#039;对应于&amp;#039;&amp;#039;&amp;#039;C:\webRPC\web&amp;#039;&amp;#039;&amp;#039;，这里就是示例中html、css以及JavaScript等文件的位置（我的情况下）。&lt;br /&gt;
&lt;br /&gt;
=== ISAPI设置 ===&lt;br /&gt;
&lt;br /&gt;
在示例中，路径 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC/jsonrpc&amp;#039;&amp;#039;&amp;#039; 需要运行&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; ISAPI dll。为此，我们来在虚拟目录webRPC中添加一个应用程序：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_1.png]]&lt;br /&gt;
&lt;br /&gt;
要确保该应用程序使用了所期望的应用池。我们一般不使用物理路径，但IIS会将web.config文件放在该目录下。创建一个新目录来放这个文件也是个不错的选择，不过这里我们就直接使用示例根目录：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_2.png]]&lt;br /&gt;
&lt;br /&gt;
放好应用程序之后就可以配置ISAPI了。选择&amp;#039;&amp;#039;&amp;#039;Handler Mappings&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_1.png]]&lt;br /&gt;
&lt;br /&gt;
接着，再添加一个 &amp;quot;Script Map&amp;quot;，与所有东西相匹配并指向ISAPI dll：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_2.png]]&lt;br /&gt;
[[Image:IISM_handlerMapping_3.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在我们的磁盘上JSON-RPC请求不属于&amp;quot;script&amp;quot;文件，所以我们得在&amp;#039;&amp;#039;&amp;#039;Request Restrictions...&amp;#039;&amp;#039;&amp;#039;设置中去掉 &amp;quot;Invoke handler only if request is mapped to&amp;quot; 的勾选：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_4.png]]&lt;br /&gt;
&lt;br /&gt;
如果所有设置OK，出现下图所示的情况，也就可以说OK了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_5.png]]&lt;br /&gt;
&lt;br /&gt;
=== 用户权限 ===&lt;br /&gt;
&lt;br /&gt;
IIS运行时，会作为Windows某类特定用户访问各类文件，因此它对这些文件必须有足够的权限。 run it will access various files as a certain Windows user, this user must have sufficient rights to the files.&lt;br /&gt;
&lt;br /&gt;
Which user is determined like this:&lt;br /&gt;
&lt;br /&gt;
* 若对虚拟目录或应用程序设置的是&amp;#039;&amp;#039;&amp;#039;Connect as...&amp;quot;用户，则这类用户可以访问文件； user is set for the virtual directory or application then that user will access the files&lt;br /&gt;
* 否则（如果使用了&amp;#039;&amp;#039;&amp;#039;Pass-through授权）Otherwise (if &amp;#039;&amp;#039;&amp;#039;Pass-through authentication is used):&lt;br /&gt;
** 如果允许匿名访问虚拟目录/应用程序，则某些IUSER_...可以访问文件；If annoymous access is allowed to the virtual directory/application then some IUSER_... is will access the files&lt;br /&gt;
** 如果使用了某些Windows授权，则来自连接的windows用户可以访问文件。If some Windows authentification is used the windows user from the connection will will access the files&lt;br /&gt;
&lt;br /&gt;
Anonymous/Windows访问由&amp;#039;&amp;#039;&amp;#039;Authentification&amp;#039;&amp;#039;&amp;#039;图标控制。&lt;br /&gt;
&lt;br /&gt;
=== Skype HTTP/HTTPS 冲突 ===&lt;br /&gt;
Skype与HTTP/HTTPS服务是有潜在冲突的，因为缺省时Skype保留了端口80(HTTP)和443(HTTPS)。&lt;br /&gt;
&lt;br /&gt;
这就导致了在缺省模式下运行Skype时就不能运行HTTP/HTTPS服务器。 &lt;br /&gt;
&lt;br /&gt;
解决的办法是在Skype中关闭80/443端口。&lt;br /&gt;
&lt;br /&gt;
还可以参看这个视频：&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Web Services]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4150</id>
		<title>IIS configuration（IIS配置）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4150"/>
		<updated>2015-07-11T09:32:28Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* ISAPI设置 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的IIS configuration。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
本专题介绍如何进行各类微软因特网信息服务（the Microsoft Internet Information Services）配置。&lt;br /&gt;
&lt;br /&gt;
文中所用的设置是根据示例目录&amp;lt;vp&amp;gt;webRPC&amp;lt;/vp&amp;gt;中&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt;（参见[[Web Services]]）的需要而做的，这类东西随着情况的不同而不同，不过还是很容易调整的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 工程创建的是一个 ISAPI 插件 (dll)，实现了一个JSON-RPC服务。例子中还包括了一些客户端浏览器能够访问的HTML、CSS以及 JavaScript等相关文件。因此，IIS的配置要满足两个方面的需求：&lt;br /&gt;
* 一是文件提供者&lt;br /&gt;
* 一是ISAPI服务&lt;br /&gt;
&lt;br /&gt;
所有的IIS配置都在 &amp;#039;&amp;#039;&amp;#039;Internet Information Services Manager&amp;#039;&amp;#039;&amp;#039; 中。&lt;br /&gt;
&lt;br /&gt;
=== 配置窗口部件 ===&lt;br /&gt;
&lt;br /&gt;
要使用因特网信息服务、管理器,运行ISAPA和/或CGI的插件，先要确保计算机上它们都是可用的。&lt;br /&gt;
&lt;br /&gt;
在 &amp;#039;&amp;#039;&amp;#039;Programs and Features -&amp;gt; Turn Windows Features on or off&amp;#039;&amp;#039;&amp;#039; 中，应该确保下面这些内容是可用的：&lt;br /&gt;
* 因特网信息服务（Internet Information Services）&lt;br /&gt;
* IIS 管理控制台&lt;br /&gt;
* CGI（其实这一点并非示例 &amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 所要求的）&lt;br /&gt;
* ISAPI 扩展&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_prerequisites_1.png]]&lt;br /&gt;
[[Image:IISM_prerequisites_2.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 应用池 ===&lt;br /&gt;
&lt;br /&gt;
ISAPI服务必须运行在一个可用的应用池中，它提供了各种IIS部件的过程隔离。这有以下优点：&lt;br /&gt;
* 它使得整个IIS的鲁棒性得到加强，因为一个应用池的问题不大可能影响到其它应用池（当然了，耗尽系统资源会产生交叉影响）；&lt;br /&gt;
* 各个池可以有不同的设置（比如用于.net平台的，等等）；&lt;br /&gt;
* 各个池可以独立重启。&lt;br /&gt;
&lt;br /&gt;
要创建应用池，在&amp;#039;&amp;#039;&amp;#039;Application Pools&amp;#039;&amp;#039;&amp;#039;关联菜单中选择 &amp;quot;Add Application Pool...&amp;quot;。用于Visual Prolog程序的池，并不需要任何.net平台，建议选择不需要.net支持以降低出现干扰的风险。给池取个名字，选择&amp;quot;No Managed Code&amp;quot;（除非有其它理由需要它）。别的就不用管了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_addAppPool_1.png]]&lt;br /&gt;
[[Image:IISM_addAppPool_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 32比特支持 ===&lt;br /&gt;
&lt;br /&gt;
注意，缺省时应用池在64位平台上是不能运行32位程序的。必要时可以在&amp;quot;Advanced Setting&amp;quot;中做设置：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_32bit_1.png]]&lt;br /&gt;
[[Image:IISM_32bit_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 文件服务 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 示例需要向运行Web应用程序的Web浏览器分发文件。这可以设置一个“虚拟目录”或是一个“应用程序”（后者仅当程序必须运行于该Web部分时才是必须的）。&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_webRPC_1.png]]&lt;br /&gt;
[[Image:IISM_add_webRPC_2.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Alias&amp;#039;&amp;#039;&amp;#039; 定义了在WEB中与&amp;#039;&amp;#039;&amp;#039;Path&amp;#039;&amp;#039;&amp;#039;相关的名称，而&amp;#039;&amp;#039;&amp;#039;Physical path&amp;#039;&amp;#039;&amp;#039;则定义了这个路径对应的磁盘位置。在这里，&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC&amp;#039;&amp;#039;&amp;#039;对应于&amp;#039;&amp;#039;&amp;#039;C:\webRPC\web&amp;#039;&amp;#039;&amp;#039;，这里就是示例中html、css以及JavaScript等文件的位置（我的情况下）。&lt;br /&gt;
&lt;br /&gt;
=== ISAPI设置 ===&lt;br /&gt;
&lt;br /&gt;
在示例中，路径 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC/jsonrpc&amp;#039;&amp;#039;&amp;#039; 需要运行&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; ISAPI dll。为此，我们来在虚拟目录webRPC中添加一个应用程序：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_1.png]]&lt;br /&gt;
&lt;br /&gt;
要确保该应用程序使用了所期望的应用池。我们一般不使用物理路径，但IIS会将web.config文件放在该目录下。创建一个新目录来放这个文件也是个不错的选择，不过这里我们就直接使用示例根目录：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_2.png]]&lt;br /&gt;
&lt;br /&gt;
放好应用程序之后就可以配置ISAPI了。选择&amp;#039;&amp;#039;&amp;#039;Handler Mappings&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_1.png]]&lt;br /&gt;
&lt;br /&gt;
接着，再添加一个 &amp;quot;Script Map&amp;quot;，与所有东西相匹配并指向ISAPI dll：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_2.png]]&lt;br /&gt;
[[Image:IISM_handlerMapping_3.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在我们的磁盘上JSON-RPC请求不属于&amp;quot;script&amp;quot;文件，所以我们得在&amp;#039;&amp;#039;&amp;#039;Request Restrictions...&amp;#039;&amp;#039;&amp;#039;设置中去掉 &amp;quot;Invoke handler only if request is mapped to&amp;quot; 的勾选：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_4.png]]&lt;br /&gt;
&lt;br /&gt;
如果所有设置OK，出现下图所示的情况，也就可以说OK了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_5.png]]&lt;br /&gt;
&lt;br /&gt;
=== 用户权限 ===&lt;br /&gt;
&lt;br /&gt;
IIS运行时，会作为Windows某类特定用户访问各类文件，因此它对这些文件必须有足够的权限。 run it will access various files as a certain Windows user, this user must have sufficient rights to the files.&lt;br /&gt;
&lt;br /&gt;
Which user is determined like this:&lt;br /&gt;
&lt;br /&gt;
* 若对虚拟目录或应用程序设置的是&amp;#039;&amp;#039;&amp;#039;Connect as...&amp;quot;用户，则这类用户可以访问文件； user is set for the virtual directory or application then that user will access the files&lt;br /&gt;
* 否则（如果使用了&amp;#039;&amp;#039;&amp;#039;Pass-through授权）Otherwise (if &amp;#039;&amp;#039;&amp;#039;Pass-through authentication is used):&lt;br /&gt;
** 如果允许匿名访问虚拟目录/应用程序，则某些IUSER_...可以访问文件；If annoymous access is allowed to the virtual directory/application then some IUSER_... is will access the files&lt;br /&gt;
** 如果使用了某些Windows授权，则来自连接的windows用户可以访问文件。If some Windows authentification is used the windows user from the connection will will access the files&lt;br /&gt;
&lt;br /&gt;
Anonymous/Windows访问由&amp;#039;&amp;#039;&amp;#039;Authentification&amp;#039;&amp;#039;&amp;#039;图标控制。&lt;br /&gt;
&lt;br /&gt;
=== Skype HTTP/HTTPS 冲突 ===&lt;br /&gt;
{{:Include/SkypeHTTPConflict}}&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Web Services]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4149</id>
		<title>IIS configuration（IIS配置）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=IIS_configuration%EF%BC%88IIS%E9%85%8D%E7%BD%AE%EF%BC%89&amp;diff=4149"/>
		<updated>2015-07-11T09:17:33Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;（以下内容，译自:Category:Tutorials中的IIS configuration。更多内容可以参看:Category:Chinese。）   本专题介绍如何进行各类微软因特网信...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的IIS configuration。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
本专题介绍如何进行各类微软因特网信息服务（the Microsoft Internet Information Services）配置。&lt;br /&gt;
&lt;br /&gt;
文中所用的设置是根据示例目录&amp;lt;vp&amp;gt;webRPC&amp;lt;/vp&amp;gt;中&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt;（参见[[Web Services]]）的需要而做的，这类东西随着情况的不同而不同，不过还是很容易调整的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 工程创建的是一个 ISAPI 插件 (dll)，实现了一个JSON-RPC服务。例子中还包括了一些客户端浏览器能够访问的HTML、CSS以及 JavaScript等相关文件。因此，IIS的配置要满足两个方面的需求：&lt;br /&gt;
* 一是文件提供者&lt;br /&gt;
* 一是ISAPI服务&lt;br /&gt;
&lt;br /&gt;
所有的IIS配置都在 &amp;#039;&amp;#039;&amp;#039;Internet Information Services Manager&amp;#039;&amp;#039;&amp;#039; 中。&lt;br /&gt;
&lt;br /&gt;
=== 配置窗口部件 ===&lt;br /&gt;
&lt;br /&gt;
要使用因特网信息服务、管理器,运行ISAPA和/或CGI的插件，先要确保计算机上它们都是可用的。&lt;br /&gt;
&lt;br /&gt;
在 &amp;#039;&amp;#039;&amp;#039;Programs and Features -&amp;gt; Turn Windows Features on or off&amp;#039;&amp;#039;&amp;#039; 中，应该确保下面这些内容是可用的：&lt;br /&gt;
* 因特网信息服务（Internet Information Services）&lt;br /&gt;
* IIS 管理控制台&lt;br /&gt;
* CGI（其实这一点并非示例 &amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 所要求的）&lt;br /&gt;
* ISAPI 扩展&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_prerequisites_1.png]]&lt;br /&gt;
[[Image:IISM_prerequisites_2.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 应用池 ===&lt;br /&gt;
&lt;br /&gt;
ISAPI服务必须运行在一个可用的应用池中，它提供了各种IIS部件的过程隔离。这有以下优点：&lt;br /&gt;
* 它使得整个IIS的鲁棒性得到加强，因为一个应用池的问题不大可能影响到其它应用池（当然了，耗尽系统资源会产生交叉影响）；&lt;br /&gt;
* 各个池可以有不同的设置（比如用于.net平台的，等等）；&lt;br /&gt;
* 各个池可以独立重启。&lt;br /&gt;
&lt;br /&gt;
要创建应用池，在&amp;#039;&amp;#039;&amp;#039;Application Pools&amp;#039;&amp;#039;&amp;#039;关联菜单中选择 &amp;quot;Add Application Pool...&amp;quot;。用于Visual Prolog程序的池，并不需要任何.net平台，建议选择不需要.net支持以降低出现干扰的风险。给池取个名字，选择&amp;quot;No Managed Code&amp;quot;（除非有其它理由需要它）。别的就不用管了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_addAppPool_1.png]]&lt;br /&gt;
[[Image:IISM_addAppPool_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 32比特支持 ===&lt;br /&gt;
&lt;br /&gt;
注意，缺省时应用池在64位平台上是不能运行32位程序的。必要时可以在&amp;quot;Advanced Setting&amp;quot;中做设置：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_32bit_1.png]]&lt;br /&gt;
[[Image:IISM_32bit_2.png]]&lt;br /&gt;
&lt;br /&gt;
=== 文件服务 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; 示例需要向运行Web应用程序的Web浏览器分发文件。这可以设置一个“虚拟目录”或是一个“应用程序”（后者仅当程序必须运行于该Web部分时才是必须的）。&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_webRPC_1.png]]&lt;br /&gt;
[[Image:IISM_add_webRPC_2.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Alias&amp;#039;&amp;#039;&amp;#039; 定义了在WEB中与&amp;#039;&amp;#039;&amp;#039;Path&amp;#039;&amp;#039;&amp;#039;相关的名称，而&amp;#039;&amp;#039;&amp;#039;Physical path&amp;#039;&amp;#039;&amp;#039;则定义了这个路径对应的磁盘位置。在这里，&amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC&amp;#039;&amp;#039;&amp;#039;对应于&amp;#039;&amp;#039;&amp;#039;C:\webRPC\web&amp;#039;&amp;#039;&amp;#039;，这里就是示例中html、css以及JavaScript等文件的位置（我的情况下）。&lt;br /&gt;
&lt;br /&gt;
=== ISAPI设置 ===&lt;br /&gt;
&lt;br /&gt;
在示例中，路径 &amp;#039;&amp;#039;&amp;#039;http:&amp;lt;nowiki /&amp;gt;//localhost/webRPC/jsonrpc&amp;#039;&amp;#039;&amp;#039; 需要运行&amp;lt;vp&amp;gt;jsonRpcService_isApi&amp;lt;/vp&amp;gt; ISAPI dll。为此，我们来在虚拟目录webRPC中添加一个应用程序：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_1.png]]&lt;br /&gt;
&lt;br /&gt;
要确保该应用程序使用了所期望的应用池。我们一般不使用物理路径，但IIS会将web.config文件放在该目录下。创建一个新目录来放这个文件也是个不错的选择，不过这里我们就直接使用示例根目录：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_add_jsonrpc_2.png]]&lt;br /&gt;
&lt;br /&gt;
放好应用程序之后就可以配置ISAPI了。选择&amp;#039;&amp;#039;&amp;#039;Handler Mappings&amp;#039;&amp;#039;&amp;#039;：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_1.png]]&lt;br /&gt;
&lt;br /&gt;
接着，再添加一个 &amp;quot;Script Map&amp;quot;，与所有东西相匹配并指向ISAPI dll：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_2.png]]&lt;br /&gt;
[[Image:IISM_handlerMapping_3.png]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在我们的磁盘上JSON-RPC需要不属于&amp;quot;script&amp;quot;文件，所以我们得在&amp;#039;&amp;#039;&amp;#039;Request Restrictions...&amp;#039;&amp;#039;&amp;#039;设置中关掉 files on our disk so we have to switch off the switch off &amp;quot;Invoke handler only if request is mapped to&amp;#039;&amp;#039;&amp;#039; setting in the &amp;#039;&amp;#039;&amp;#039;Request Restrictions...&amp;#039;&amp;#039;&amp;#039; ：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_4.png]]&lt;br /&gt;
&lt;br /&gt;
如果所有设置OK，出现下图所示的情况，也就可以说OK了：&lt;br /&gt;
&lt;br /&gt;
[[Image:IISM_handlerMapping_5.png]]&lt;br /&gt;
&lt;br /&gt;
=== 用户权限 ===&lt;br /&gt;
&lt;br /&gt;
IIS运行时，会作为Windows某类特定用户访问各类文件，因此它对这些文件必须有足够的权限。 run it will access various files as a certain Windows user, this user must have sufficient rights to the files.&lt;br /&gt;
&lt;br /&gt;
Which user is determined like this:&lt;br /&gt;
&lt;br /&gt;
* 若对虚拟目录或应用程序设置的是&amp;#039;&amp;#039;&amp;#039;Connect as...&amp;quot;用户，则这类用户可以访问文件； user is set for the virtual directory or application then that user will access the files&lt;br /&gt;
* 否则（如果使用了&amp;#039;&amp;#039;&amp;#039;Pass-through授权）Otherwise (if &amp;#039;&amp;#039;&amp;#039;Pass-through authentication is used):&lt;br /&gt;
** 如果允许匿名访问虚拟目录/应用程序，则某些IUSER_...可以访问文件；If annoymous access is allowed to the virtual directory/application then some IUSER_... is will access the files&lt;br /&gt;
** 如果使用了某些Windows授权，则来自连接的windows用户可以访问文件。If some Windows authentification is used the windows user from the connection will will access the files&lt;br /&gt;
&lt;br /&gt;
Anonymous/Windows访问由&amp;#039;&amp;#039;&amp;#039;Authentification&amp;#039;&amp;#039;&amp;#039;图标控制。&lt;br /&gt;
&lt;br /&gt;
=== Skype HTTP/HTTPS 冲突 ===&lt;br /&gt;
{{:Include/SkypeHTTPConflict}}&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Web Services]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=DialogForm_Validation%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E5%8F%8A%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A1%AE%E8%AE%A4%EF%BC%89&amp;diff=4148</id>
		<title>DialogForm Validation（对话框及表单的确认）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=DialogForm_Validation%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E5%8F%8A%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A1%AE%E8%AE%A4%EF%BC%89&amp;diff=4148"/>
		<updated>2015-07-05T01:11:11Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* 参考 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的DialogForm Validation。更多内容可以参看[[:Category:Tutorials部分中文译文]]及[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
用户按了对话框中的OK按钮后，就意味着将要出现某件事情。不过只有对话框或表单中的数据有效时，事情才会出现。对话框和表单必须经过 &amp;#039;&amp;#039;&amp;#039;有效性确认&amp;#039;&amp;#039;&amp;#039;。本专题将描述由PFC GUI支持的有效性确认机制，还要介绍如何使用校验控件&amp;#039;&amp;#039;integerControl&amp;#039;&amp;#039; 和 &amp;#039;&amp;#039;realControl&amp;#039;&amp;#039; 及如何确认对话框表单中其它的控件。&lt;br /&gt;
&lt;br /&gt;
本文将演示如何确认在对话框或表单中的控件。首先，要介绍有效性确认的概念。我们会看到integerControl和realControl是如何使用有效性确认概念的。接着，我们要考虑在容器控件中的有效性确认问题。最后，来看几个常用控件的有效性确认，这些控件有：editControl、listBox、新建用户控件。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;下载&amp;#039;&amp;#039;&amp;#039; 本文使用的示例工程源文件：&lt;br /&gt;
&lt;br /&gt;
* Visual Prolog 7.4 和 7.3 IDE下安装示例：&lt;br /&gt;
*:&amp;#039;&amp;#039;&amp;#039;Help -&amp;gt; Install Examples...&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* [http://download.pdc.dk/vip/72/tutorial_examples/validation.zip Visual Prolog 7.2 version].&lt;br /&gt;
* [http://download.pdc.dk/vip/71/examples/tutorial_examples/validation.zip Visual Prolog 7.1 version].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==有效性确认的概念==&lt;br /&gt;
&lt;br /&gt;
有效性确认的概念基础思想是：“控件值应该是有效的”。有效性的确认发生在对话框或表单中按动OK按钮时，但确认工作也可以由程序安排。&lt;br /&gt;
&lt;br /&gt;
控件负责声明自己的内容是有效的，因此，可以对控件设置有效性确认响应器。&lt;br /&gt;
&lt;br /&gt;
对话框或表单作有效性确认时，是逐个控件依次进行的，但如果结果导致&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;contentsInvalid(Source, FocusControl, ErrorMessage)&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
则确认工作就终止了。&lt;br /&gt;
&lt;br /&gt;
==integerControl和realControl中的有效性确认==&lt;br /&gt;
&lt;br /&gt;
我们来创建一个表单，并用类integerControl和realControl加入两个 &amp;#039;&amp;#039;&amp;#039;custom controls&amp;#039;&amp;#039;&amp;#039; （用户控件）。可以在提供的示例工程&amp;lt;vp&amp;gt;integerControlAndRealControl&amp;lt;/vp&amp;gt;看到相应的表单结果。&lt;br /&gt;
&lt;br /&gt;
示例生成的代码如下：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;integerControl_ctl := integercontrol::new(This),&lt;br /&gt;
integerControl_ctl:setPosition(92, 4),&lt;br /&gt;
integerControl_ctl:setSize(60,12),&lt;br /&gt;
realControl_ctl := realcontrol::new(This),&lt;br /&gt;
realControl_ctl:setPosition(92, 20),&lt;br /&gt;
realControl_ctl:setSize(60,12),&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
还需要加上对 &amp;lt;vp&amp;gt;integerControlAndRealControl&amp;lt;/vp&amp;gt; 表单的调用：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onFileIntegercontrolRealcontrol : window::menuItemListener.&lt;br /&gt;
clauses&lt;br /&gt;
    onFileIntegercontrolRealcontrol(Source, _MenuTag) :-&lt;br /&gt;
        Form = integerControlAndRealControl::new(Source),&lt;br /&gt;
        Form:show().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行这个工程，如果输入了非整数值，将会看到一个说明错误的消息框。&lt;br /&gt;
&lt;br /&gt;
我们也可以加入 &amp;lt;vp&amp;gt;onOK&amp;lt;/vp&amp;gt; 响应器来获取确认后的值：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onOk : button::clickResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onOk(_) = button::defaultAction() :-&lt;br /&gt;
        Integer = integerControl_ctl:getInteger(),&lt;br /&gt;
        Real = realControl_ctl:getReal(),&lt;br /&gt;
        stdIO::write(&amp;quot;Integer value = &amp;quot;,Integer, &amp;quot;\n&amp;quot;),&lt;br /&gt;
        stdIO::write(&amp;quot;Real value = &amp;quot;,Real, &amp;quot;\n&amp;quot;).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
要注意，如果有效性确认不成功，是&amp;#039;&amp;#039;&amp;#039;不会&amp;#039;&amp;#039;&amp;#039;调用 &amp;lt;vp&amp;gt;onOK&amp;lt;/vp&amp;gt; 响应器的，而且该响应器的调用是发生在所有确认谓词之后。&lt;br /&gt;
&lt;br /&gt;
我们来看一下源文件 &amp;#039;&amp;#039;&amp;#039;\pfc\gui\controls\integerControl.pro&amp;#039;&amp;#039;&amp;#039; ，看看有效性确认是如何实现的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    new() :-&lt;br /&gt;
        editControl::new(),&lt;br /&gt;
        addValidateResponder(onIntValidate).&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    onIntValidate : validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onIntValidate(_) = contentsInvalid(This,This, ErrorMessage) :-&lt;br /&gt;
        string(ErrorMessage) = checkContent(getText()),&lt;br /&gt;
        !.&lt;br /&gt;
    onIntValidate(_) = contentsOk.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也就是说，如果局部谓词checkContent认为文本内容是 &amp;lt;vp&amp;gt;integer&amp;lt;/vp&amp;gt; 则确认成功。&lt;br /&gt;
&lt;br /&gt;
上面我们说过，确认工作也可以强制程序安排进行。可以这样加一个按钮 TryValidation 及相应的代码：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onTryValidation : button::clickResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onTryValidation(_Source) = button::defaultAction() :-&lt;br /&gt;
        tryValidateWithErrorDialog(),&lt;br /&gt;
        !,&lt;br /&gt;
        stdIO::write(&amp;quot;All controls are valid\n&amp;quot;).&lt;br /&gt;
    onTryValidation(_Source) = button::defaultAction().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==容器控件中的有效性确认==&lt;br /&gt;
&lt;br /&gt;
考查容器控件中的有效性确认，我们先来创建一个这样的控件：在工程中创建一个新的控件&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;，再在其中加入两个integerControl控件。&lt;br /&gt;
&lt;br /&gt;
考虑到只有当&amp;lt;vp&amp;gt;From&amp;lt;/vp&amp;gt;的值小于&amp;lt;vp&amp;gt;To&amp;lt;/vp&amp;gt;的值控件&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;才是有效的，因此可以这样设置&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;控件的有效性确认响应器：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    validateFromTo : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateFromTo (Source) = control::contentsInvalid(Source,from_ctl,&lt;br /&gt;
      &amp;quot;From value must be less than To value&amp;quot;) :-&lt;br /&gt;
        from_ctl:getInteger() &amp;gt;= to_ctl:getInteger(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateFromTo (_) = control::contentsOk.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
请注意，如果有效性确认不成功，控件 &amp;lt;vp&amp;gt;from_ctl&amp;lt;/vp&amp;gt; 会得到焦点。&lt;br /&gt;
&lt;br /&gt;
==常用控件的有效性确认==&lt;br /&gt;
&lt;br /&gt;
我们在工程中创建一个新控件 &amp;lt;vp&amp;gt;myControl&amp;lt;/vp&amp;gt; ，再创建一个表单并在其中添加三个控件：&lt;br /&gt;
&lt;br /&gt;
*edit 控件；&lt;br /&gt;
&lt;br /&gt;
*listbox 控件；&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;vp&amp;gt;myControl&amp;lt;/vp&amp;gt; 类的用户控件。&lt;br /&gt;
&lt;br /&gt;
可以在提供的示例&amp;#039;&amp;#039;usualControls&amp;#039;&amp;#039;中看到表单的结果。&lt;br /&gt;
&lt;br /&gt;
初始化代码中包含了响应器的设置及对三个控件的初始化内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    new(Parent) :-&lt;br /&gt;
        formWindow::new(Parent),&lt;br /&gt;
        generatedInitialize(),&lt;br /&gt;
        edit_ctl:addValidateResponder(validateEditCtl),&lt;br /&gt;
        listbox_ctl:addList([&amp;quot;First&amp;quot;,&amp;quot;Second&amp;quot;,&amp;quot;Third&amp;quot;]),&lt;br /&gt;
        listbox_ctl:addValidateResponder(validateListBox).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
有效性确认响应器是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    validateEditCtl : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateEditCtl(_) = control::contentsOk :-&lt;br /&gt;
        &amp;quot;OK&amp;quot; = edit_ctl:getText(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateEditCtl(Source) = control::contentsInvalid(&lt;br /&gt;
          Source, Source,&lt;br /&gt;
          &amp;quot;Edit control expects to have the text &amp;#039;OK&amp;#039;&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    validateListBox : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateListBox(Source) = control::contentsInvalid(&lt;br /&gt;
          Source,Source,&lt;br /&gt;
          &amp;quot;Selection of the first row is not allowed&amp;quot;) :-&lt;br /&gt;
        0 = listbox_ctl:tryGetSelectedIndex(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateListBox(_) = control::contentsOk().&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    validateMyControl : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateMyControl(Source) = control::contentsInvalid(&lt;br /&gt;
          Source,Source,&lt;br /&gt;
          string::concat(&amp;quot;A marker in the &amp;quot;,Missed,&lt;br /&gt;
            &amp;quot; part is mandatory&amp;quot;)) :-&lt;br /&gt;
        Missed = toString(isMandatoryMarkNotActive(&lt;br /&gt;
          mandatoryMark, activatedMark)),&lt;br /&gt;
        !.&lt;br /&gt;
    validateMyControl(_) = control::contentsOk().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
可以看到，对控件的有效性确认可以有各种不同的方法。&lt;br /&gt;
&lt;br /&gt;
==参考==&lt;br /&gt;
[[:Category:Tutorials]]&lt;br /&gt;
[[:Category:GUI]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=DialogForm_Validation%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E5%8F%8A%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A1%AE%E8%AE%A4%EF%BC%89&amp;diff=4147</id>
		<title>DialogForm Validation（对话框及表单的确认）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=DialogForm_Validation%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E5%8F%8A%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A1%AE%E8%AE%A4%EF%BC%89&amp;diff=4147"/>
		<updated>2015-07-05T01:05:55Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的DialogForm Validation。更多内容可以参看[[:Category:Tutorials部分中文译文]]及[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
用户按了对话框中的OK按钮后，就意味着将要出现某件事情。不过只有对话框或表单中的数据有效时，事情才会出现。对话框和表单必须经过 &amp;#039;&amp;#039;&amp;#039;有效性确认&amp;#039;&amp;#039;&amp;#039;。本专题将描述由PFC GUI支持的有效性确认机制，还要介绍如何使用校验控件&amp;#039;&amp;#039;integerControl&amp;#039;&amp;#039; 和 &amp;#039;&amp;#039;realControl&amp;#039;&amp;#039; 及如何确认对话框表单中其它的控件。&lt;br /&gt;
&lt;br /&gt;
本文将演示如何确认在对话框或表单中的控件。首先，要介绍有效性确认的概念。我们会看到integerControl和realControl是如何使用有效性确认概念的。接着，我们要考虑在容器控件中的有效性确认问题。最后，来看几个常用控件的有效性确认，这些控件有：editControl、listBox、新建用户控件。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;下载&amp;#039;&amp;#039;&amp;#039; 本文使用的示例工程源文件：&lt;br /&gt;
&lt;br /&gt;
* Visual Prolog 7.4 和 7.3 IDE下安装示例：&lt;br /&gt;
*:&amp;#039;&amp;#039;&amp;#039;Help -&amp;gt; Install Examples...&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* [http://download.pdc.dk/vip/72/tutorial_examples/validation.zip Visual Prolog 7.2 version].&lt;br /&gt;
* [http://download.pdc.dk/vip/71/examples/tutorial_examples/validation.zip Visual Prolog 7.1 version].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==有效性确认的概念==&lt;br /&gt;
&lt;br /&gt;
有效性确认的概念基础思想是：“控件值应该是有效的”。有效性的确认发生在对话框或表单中按动OK按钮时，但确认工作也可以由程序安排。&lt;br /&gt;
&lt;br /&gt;
控件负责声明自己的内容是有效的，因此，可以对控件设置有效性确认响应器。&lt;br /&gt;
&lt;br /&gt;
对话框或表单作有效性确认时，是逐个控件依次进行的，但如果结果导致&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;contentsInvalid(Source, FocusControl, ErrorMessage)&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
则确认工作就终止了。&lt;br /&gt;
&lt;br /&gt;
==integerControl和realControl中的有效性确认==&lt;br /&gt;
&lt;br /&gt;
我们来创建一个表单，并用类integerControl和realControl加入两个 &amp;#039;&amp;#039;&amp;#039;custom controls&amp;#039;&amp;#039;&amp;#039; （用户控件）。可以在提供的示例工程&amp;lt;vp&amp;gt;integerControlAndRealControl&amp;lt;/vp&amp;gt;看到相应的表单结果。&lt;br /&gt;
&lt;br /&gt;
示例生成的代码如下：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;integerControl_ctl := integercontrol::new(This),&lt;br /&gt;
integerControl_ctl:setPosition(92, 4),&lt;br /&gt;
integerControl_ctl:setSize(60,12),&lt;br /&gt;
realControl_ctl := realcontrol::new(This),&lt;br /&gt;
realControl_ctl:setPosition(92, 20),&lt;br /&gt;
realControl_ctl:setSize(60,12),&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
还需要加上对 &amp;lt;vp&amp;gt;integerControlAndRealControl&amp;lt;/vp&amp;gt; 表单的调用：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onFileIntegercontrolRealcontrol : window::menuItemListener.&lt;br /&gt;
clauses&lt;br /&gt;
    onFileIntegercontrolRealcontrol(Source, _MenuTag) :-&lt;br /&gt;
        Form = integerControlAndRealControl::new(Source),&lt;br /&gt;
        Form:show().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行这个工程，如果输入了非整数值，将会看到一个说明错误的消息框。&lt;br /&gt;
&lt;br /&gt;
我们也可以加入 &amp;lt;vp&amp;gt;onOK&amp;lt;/vp&amp;gt; 响应器来获取确认后的值：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onOk : button::clickResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onOk(_) = button::defaultAction() :-&lt;br /&gt;
        Integer = integerControl_ctl:getInteger(),&lt;br /&gt;
        Real = realControl_ctl:getReal(),&lt;br /&gt;
        stdIO::write(&amp;quot;Integer value = &amp;quot;,Integer, &amp;quot;\n&amp;quot;),&lt;br /&gt;
        stdIO::write(&amp;quot;Real value = &amp;quot;,Real, &amp;quot;\n&amp;quot;).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
要注意，如果有效性确认不成功，是&amp;#039;&amp;#039;&amp;#039;不会&amp;#039;&amp;#039;&amp;#039;调用 &amp;lt;vp&amp;gt;onOK&amp;lt;/vp&amp;gt; 响应器的，而且该响应器的调用是发生在所有确认谓词之后。&lt;br /&gt;
&lt;br /&gt;
我们来看一下源文件 &amp;#039;&amp;#039;&amp;#039;\pfc\gui\controls\integerControl.pro&amp;#039;&amp;#039;&amp;#039; ，看看有效性确认是如何实现的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    new() :-&lt;br /&gt;
        editControl::new(),&lt;br /&gt;
        addValidateResponder(onIntValidate).&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    onIntValidate : validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onIntValidate(_) = contentsInvalid(This,This, ErrorMessage) :-&lt;br /&gt;
        string(ErrorMessage) = checkContent(getText()),&lt;br /&gt;
        !.&lt;br /&gt;
    onIntValidate(_) = contentsOk.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也就是说，如果局部谓词checkContent认为文本内容是 &amp;lt;vp&amp;gt;integer&amp;lt;/vp&amp;gt; 则确认成功。&lt;br /&gt;
&lt;br /&gt;
上面我们说过，确认工作也可以强制程序安排进行。可以这样加一个按钮 TryValidation 及相应的代码：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onTryValidation : button::clickResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onTryValidation(_Source) = button::defaultAction() :-&lt;br /&gt;
        tryValidateWithErrorDialog(),&lt;br /&gt;
        !,&lt;br /&gt;
        stdIO::write(&amp;quot;All controls are valid\n&amp;quot;).&lt;br /&gt;
    onTryValidation(_Source) = button::defaultAction().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==容器控件中的有效性确认==&lt;br /&gt;
&lt;br /&gt;
考查容器控件中的有效性确认，我们先来创建一个这样的控件：在工程中创建一个新的控件&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;，再在其中加入两个integerControl控件。&lt;br /&gt;
&lt;br /&gt;
考虑到只有当&amp;lt;vp&amp;gt;From&amp;lt;/vp&amp;gt;的值小于&amp;lt;vp&amp;gt;To&amp;lt;/vp&amp;gt;的值控件&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;才是有效的，因此可以这样设置&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;控件的有效性确认响应器：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    validateFromTo : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateFromTo (Source) = control::contentsInvalid(Source,from_ctl,&lt;br /&gt;
      &amp;quot;From value must be less than To value&amp;quot;) :-&lt;br /&gt;
        from_ctl:getInteger() &amp;gt;= to_ctl:getInteger(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateFromTo (_) = control::contentsOk.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
请注意，如果有效性确认不成功，控件 &amp;lt;vp&amp;gt;from_ctl&amp;lt;/vp&amp;gt; 会得到焦点。&lt;br /&gt;
&lt;br /&gt;
==常用控件的有效性确认==&lt;br /&gt;
&lt;br /&gt;
我们在工程中创建一个新控件 &amp;lt;vp&amp;gt;myControl&amp;lt;/vp&amp;gt; ，再创建一个表单并在其中添加三个控件：&lt;br /&gt;
&lt;br /&gt;
*edit 控件；&lt;br /&gt;
&lt;br /&gt;
*listbox 控件；&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;vp&amp;gt;myControl&amp;lt;/vp&amp;gt; 类的用户控件。&lt;br /&gt;
&lt;br /&gt;
可以在提供的示例&amp;#039;&amp;#039;usualControls&amp;#039;&amp;#039;中看到表单的结果。&lt;br /&gt;
&lt;br /&gt;
初始化代码中包含了响应器的设置及对三个控件的初始化内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    new(Parent) :-&lt;br /&gt;
        formWindow::new(Parent),&lt;br /&gt;
        generatedInitialize(),&lt;br /&gt;
        edit_ctl:addValidateResponder(validateEditCtl),&lt;br /&gt;
        listbox_ctl:addList([&amp;quot;First&amp;quot;,&amp;quot;Second&amp;quot;,&amp;quot;Third&amp;quot;]),&lt;br /&gt;
        listbox_ctl:addValidateResponder(validateListBox).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
有效性确认响应器是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    validateEditCtl : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateEditCtl(_) = control::contentsOk :-&lt;br /&gt;
        &amp;quot;OK&amp;quot; = edit_ctl:getText(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateEditCtl(Source) = control::contentsInvalid(&lt;br /&gt;
          Source, Source,&lt;br /&gt;
          &amp;quot;Edit control expects to have the text &amp;#039;OK&amp;#039;&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    validateListBox : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateListBox(Source) = control::contentsInvalid(&lt;br /&gt;
          Source,Source,&lt;br /&gt;
          &amp;quot;Selection of the first row is not allowed&amp;quot;) :-&lt;br /&gt;
        0 = listbox_ctl:tryGetSelectedIndex(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateListBox(_) = control::contentsOk().&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    validateMyControl : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateMyControl(Source) = control::contentsInvalid(&lt;br /&gt;
          Source,Source,&lt;br /&gt;
          string::concat(&amp;quot;A marker in the &amp;quot;,Missed,&lt;br /&gt;
            &amp;quot; part is mandatory&amp;quot;)) :-&lt;br /&gt;
        Missed = toString(isMandatoryMarkNotActive(&lt;br /&gt;
          mandatoryMark, activatedMark)),&lt;br /&gt;
        !.&lt;br /&gt;
    validateMyControl(_) = control::contentsOk().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
可以看到，对控件的有效性确认可以有各种不同的方法。&lt;br /&gt;
&lt;br /&gt;
==参考==&lt;br /&gt;
[[Category:Tutorials]]&lt;br /&gt;
[[Category:GUI]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=DialogForm_Validation%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E5%8F%8A%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A1%AE%E8%AE%A4%EF%BC%89&amp;diff=4146</id>
		<title>DialogForm Validation（对话框及表单的确认）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=DialogForm_Validation%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E5%8F%8A%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A1%AE%E8%AE%A4%EF%BC%89&amp;diff=4146"/>
		<updated>2015-07-05T00:54:55Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Blanked the page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=DialogForm_Validation%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E5%8F%8A%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A1%AE%E8%AE%A4%EF%BC%89&amp;diff=4145</id>
		<title>DialogForm Validation（对话框及表单的确认）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=DialogForm_Validation%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E5%8F%8A%E8%A1%A8%E5%8D%95%E7%9A%84%E7%A1%AE%E8%AE%A4%EF%BC%89&amp;diff=4145"/>
		<updated>2015-07-05T00:40:23Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;（以下内容，译自:Category:Tutorials中的DialogForm Validation。更多内容可以参看:Category:Tutorials部分中文译文及:Category:Chinese。）   用...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的DialogForm Validation。更多内容可以参看[[:Category:Tutorials部分中文译文]]及[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
用户按了对话框中的OK按钮后，就意味着将要出现某件事情。不过只有对话框或表单中的数据有效时，事情才会出现。对话框和表单必须经过 &amp;#039;&amp;#039;&amp;#039;有效性确认&amp;#039;&amp;#039;&amp;#039;。本专题将描述由PFC GUI支持的有效性确认机制，还要介绍如何使用校验控件&amp;#039;&amp;#039;integerControl&amp;#039;&amp;#039; 和 &amp;#039;&amp;#039;realControl&amp;#039;&amp;#039; 及如何确认对话框表单中其它的控件。&lt;br /&gt;
&lt;br /&gt;
本文将演示如何确认在对话框或表单中的控件。首先，要介绍有效性确认的概念。我们会看到integerControl和realControl是如何使用有效性确认概念的。接着，我们要考虑在容器控件中的有效性确认问题。最后，来看几个常用控件的有效性确认，这些控件有：editControl、listBox、新建用户控件。&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;下载&amp;#039;&amp;#039;&amp;#039; 本文使用的示例工程源文件：&lt;br /&gt;
&lt;br /&gt;
* Visual Prolog 7.4 和 7.3 IDE下安装示例：&lt;br /&gt;
*:&amp;#039;&amp;#039;&amp;#039;Help -&amp;gt; Install Examples...&amp;#039;&amp;#039;&amp;#039;&lt;br /&gt;
* [http://download.pdc.dk/vip/72/tutorial_examples/validation.zip Visual Prolog 7.2 version].&lt;br /&gt;
* [http://download.pdc.dk/vip/71/examples/tutorial_examples/validation.zip Visual Prolog 7.1 version].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==有效性确认的概念==&lt;br /&gt;
&lt;br /&gt;
有效性确认的概念基础思想是：“控件值应该是有效的”。有效性的确认发生在对话框或表单中按动OK按钮时，但确认工作也可以由程序安排。&lt;br /&gt;
&lt;br /&gt;
控件负责声明自己的内容是有效的，因此，可以对控件设置有效性确认响应器。&lt;br /&gt;
&lt;br /&gt;
对话框或表单作有效性确认时，是逐个控件依次进行的，但如果结果导致&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;contentsInvalid(Source, FocusControl, ErrorMessage)&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
则确认工作就终止了。&lt;br /&gt;
&lt;br /&gt;
==integerControl和realControl中的有效性确认==&lt;br /&gt;
&lt;br /&gt;
我们来创建一个表单，并用类integerControl和realControl加入两个 &amp;#039;&amp;#039;&amp;#039;custom controls&amp;#039;&amp;#039;&amp;#039; （用户控件）。可以在提供的示例工程&amp;lt;vp&amp;gt;integerControlAndRealControl&amp;lt;/vp&amp;gt;看到相应的表单结果。&lt;br /&gt;
&lt;br /&gt;
示例生成的代码如下：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;integerControl_ctl := integercontrol::new(This),&lt;br /&gt;
integerControl_ctl:setPosition(92, 4),&lt;br /&gt;
integerControl_ctl:setSize(60,12),&lt;br /&gt;
realControl_ctl := realcontrol::new(This),&lt;br /&gt;
realControl_ctl:setPosition(92, 20),&lt;br /&gt;
realControl_ctl:setSize(60,12),&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
还需要加上对 &amp;lt;vp&amp;gt;integerControlAndRealControl&amp;lt;/vp&amp;gt; 表单的调用：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onFileIntegercontrolRealcontrol : window::menuItemListener.&lt;br /&gt;
clauses&lt;br /&gt;
    onFileIntegercontrolRealcontrol(Source, _MenuTag) :-&lt;br /&gt;
        Form = integerControlAndRealControl::new(Source),&lt;br /&gt;
        Form:show().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
运行这个工程，如果输入了非整数值，将会看到一个说明错误的消息框。&lt;br /&gt;
&lt;br /&gt;
我们也可以加入 &amp;lt;vp&amp;gt;onOK&amp;lt;/vp&amp;gt; 响应器来获取确认后的值：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onOk : button::clickResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onOk(_) = button::defaultAction() :-&lt;br /&gt;
        Integer = integerControl_ctl:getInteger(),&lt;br /&gt;
        Real = realControl_ctl:getReal(),&lt;br /&gt;
        stdIO::write(&amp;quot;Integer value = &amp;quot;,Integer, &amp;quot;\n&amp;quot;),&lt;br /&gt;
        stdIO::write(&amp;quot;Real value = &amp;quot;,Real, &amp;quot;\n&amp;quot;).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
要注意，如果有效性确认不成功，是&amp;#039;&amp;#039;&amp;#039;不会&amp;#039;&amp;#039;&amp;#039;调用 &amp;lt;vp&amp;gt;onOK&amp;lt;/vp&amp;gt; 响应器的，而且该响应器的调用是发生在所有确认谓词之后。&lt;br /&gt;
&lt;br /&gt;
我们来看一下源文件 &amp;#039;&amp;#039;&amp;#039;\pfc\gui\controls\integerControl.pro&amp;#039;&amp;#039;&amp;#039; ，看看有效性确认是如何实现的。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    new() :-&lt;br /&gt;
        editControl::new(),&lt;br /&gt;
        addValidateResponder(onIntValidate).&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    onIntValidate : validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onIntValidate(_) = contentsInvalid(This,This, ErrorMessage) :-&lt;br /&gt;
        string(ErrorMessage) = checkContent(getText()),&lt;br /&gt;
        !.&lt;br /&gt;
    onIntValidate(_) = contentsOk.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也就是说，如果局部谓词checkContent认为文本内容是 &amp;lt;vp&amp;gt;integer&amp;lt;/vp&amp;gt; 则确认成功。&lt;br /&gt;
&lt;br /&gt;
上面我们说过，确认工作也可以强制程序安排进行。可以这样加一个按钮 TryValidation 及相应的代码：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    onTryValidation : button::clickResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    onTryValidation(_Source) = button::defaultAction() :-&lt;br /&gt;
        tryValidateWithErrorDialog(),&lt;br /&gt;
        !,&lt;br /&gt;
        stdIO::write(&amp;quot;All controls are valid\n&amp;quot;).&lt;br /&gt;
    onTryValidation(_Source) = button::defaultAction().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==容器控件中的有效性确认==&lt;br /&gt;
&lt;br /&gt;
考查容器控件中的有效性确认，我们先来创建一个这样的控件：在工程中创建一个新的控件&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;，再在其中加入两个integerControl控件。&lt;br /&gt;
&lt;br /&gt;
考虑到只有当&amp;lt;vp&amp;gt;From&amp;lt;/vp&amp;gt;的值小于&amp;lt;vp&amp;gt;To&amp;lt;/vp&amp;gt;的值控件&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;才是有效的，因此可以这样设置&amp;lt;vp&amp;gt;fromTo&amp;lt;/vp&amp;gt;控件的有效性确认响应器：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    validateFromTo : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateFromTo (Source) = control::contentsInvalid(Source,from_ctl,&lt;br /&gt;
      &amp;quot;From value must be less than To value&amp;quot;) :-&lt;br /&gt;
        from_ctl:getInteger() &amp;gt;= to_ctl:getInteger(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateFromTo (_) = control::contentsOk.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
请注意，如果有效性确认不成功，控件 &amp;lt;vp&amp;gt;from_ctl&amp;lt;/vp&amp;gt; 会得到焦点。&lt;br /&gt;
&lt;br /&gt;
==常用控件的有效性确认==&lt;br /&gt;
&lt;br /&gt;
我们在工程中创建一个新控件 &amp;lt;vp&amp;gt;myControl&amp;lt;/vp&amp;gt; ，再创建一个表单并在其中添加三个控件：&lt;br /&gt;
&lt;br /&gt;
*edit 控件；&lt;br /&gt;
&lt;br /&gt;
*listbox 控件；&lt;br /&gt;
&lt;br /&gt;
*&amp;lt;vp&amp;gt;myControl&amp;lt;/vp&amp;gt; 类的用户控件。&lt;br /&gt;
&lt;br /&gt;
可以在提供的示例&amp;#039;&amp;#039;usualControls&amp;#039;&amp;#039;中看到表单的结果。&lt;br /&gt;
&lt;br /&gt;
初始化代码中包含了响应器的设置及对三个控件的初始化内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    new(Parent) :-&lt;br /&gt;
        formWindow::new(Parent),&lt;br /&gt;
        generatedInitialize(),&lt;br /&gt;
        edit_ctl:addValidateResponder(validateEditCtl),&lt;br /&gt;
        listbox_ctl:addList([&amp;quot;First&amp;quot;,&amp;quot;Second&amp;quot;,&amp;quot;Third&amp;quot;]),&lt;br /&gt;
        listbox_ctl:addValidateResponder(validateListBox).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
有效性确认响应器是这样的：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    validateEditCtl : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateEditCtl(_) = control::contentsOk :-&lt;br /&gt;
        &amp;quot;OK&amp;quot; = edit_ctl:getText(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateEditCtl(Source) = control::contentsInvalid(&lt;br /&gt;
          Source, Source,&lt;br /&gt;
          &amp;quot;Edit control expects to have the text &amp;#039;OK&amp;#039;&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    validateListBox : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateListBox(Source) = control::contentsInvalid(&lt;br /&gt;
          Source,Source,&lt;br /&gt;
          &amp;quot;Selection of the first row is not allowed&amp;quot;) :-&lt;br /&gt;
        0 = listbox_ctl:tryGetSelectedIndex(),&lt;br /&gt;
        !.&lt;br /&gt;
    validateListBox(_) = control::contentsOk().&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    validateMyControl : control::validateResponder.&lt;br /&gt;
clauses&lt;br /&gt;
    validateMyControl(Source) = control::contentsInvalid(&lt;br /&gt;
          Source,Source,&lt;br /&gt;
          string::concat(&amp;quot;A marker in the &amp;quot;,Missed,&lt;br /&gt;
            &amp;quot; part is mandatory&amp;quot;)) :-&lt;br /&gt;
        Missed = toString(isMandatoryMarkNotActive(&lt;br /&gt;
          mandatoryMark, activatedMark)),&lt;br /&gt;
        !.&lt;br /&gt;
    validateMyControl(_) = control::contentsOk().&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
可以看到，对控件的有效性确认可以有各种不同的方法。&lt;br /&gt;
&lt;br /&gt;
==参考==&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;br /&gt;
[[Category:GUI]]&lt;br /&gt;
&lt;br /&gt;
[[:Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Dialogs_and_Forms%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E4%B8%8E%E8%A1%A8%E5%8D%95%EF%BC%89&amp;diff=4144</id>
		<title>Dialogs and Forms（对话框与表单）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Dialogs_and_Forms%EF%BC%88%E5%AF%B9%E8%AF%9D%E6%A1%86%E4%B8%8E%E8%A1%A8%E5%8D%95%EF%BC%89&amp;diff=4144"/>
		<updated>2015-06-28T11:44:53Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;（以下内容，译自:Category:Tutorials中的Dialogs and Forms。更多内容可以参看:Category:Chinese。）  本专题介绍&amp;#039;&amp;#039;模态对话框（modal dialogs）&amp;#039;...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容，译自[[:Category:Tutorials]]中的Dialogs and Forms。更多内容可以参看[[:Category:Chinese]]。）&lt;br /&gt;
&lt;br /&gt;
本专题介绍&amp;#039;&amp;#039;模态对话框（modal dialogs）&amp;#039;&amp;#039;、&amp;#039;&amp;#039;非模态（modeless）对话框&amp;#039;&amp;#039;及&amp;#039;&amp;#039;表单（forms）&amp;#039;&amp;#039;的特点，还将概要说明何时及如何使用这些类型的东西。&lt;br /&gt;
&lt;br /&gt;
=== 特点 ===&lt;br /&gt;
&lt;br /&gt;
;Forms（表单）&lt;br /&gt;
* 出现在 &amp;#039;&amp;#039;&amp;#039;Window&amp;#039;&amp;#039;&amp;#039; 菜单中&lt;br /&gt;
* 可以隐藏在其它窗口之下&lt;br /&gt;
* 可以用 Ctrl-TAB 和 Ctrl-Shift-TAB 反复动作&lt;br /&gt;
* （通常）可以平铺及层叠&lt;br /&gt;
&lt;br /&gt;
;Dialogs（对话框）&lt;br /&gt;
* 不会出现在 &amp;#039;&amp;#039;&amp;#039;Window&amp;#039;&amp;#039;&amp;#039; 菜单中&lt;br /&gt;
* 总在其它窗口之上&lt;br /&gt;
* 不能用 Ctrl-TAB 和 Ctrl-Shift-TAB 反复动作&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;
* 用所选择的颜色写出“谢谢”&lt;br /&gt;
&lt;br /&gt;
不过这种方法也有不少缺点：&lt;br /&gt;
&lt;br /&gt;
# 它对用户有很大限制：只有一条顺序通路。当然也可以在这个通路中加入“if”以及偏好，不过这个办法很快就会复杂得无法控制，编程的困难比做像样的选择难得多。&lt;br /&gt;
# 一般来说，最好是在对话框仍有效时就能确定对话框将要执行的动作，因为要有什么不对头用户当然愿意已经输入的数据还在对话框中时再做些尝试。&lt;br /&gt;
# 模态对话框激活时会阻断整个应用程序（除了对话框本身）。这对用户体验来讲是很不好的，因而需要保留“高度关注”问题/提示的响应。想想看，模态对话框激活时，连最小化应用程序都不能做！&lt;br /&gt;
&lt;br /&gt;
还有其它一些理由，不过仅就上述这些问题就足以要求寻找新的方法。&lt;br /&gt;
&lt;br /&gt;
这个新方法就是在对话框/表单 &amp;#039;&amp;#039;&amp;#039;&amp;#039;&amp;#039;之中&amp;#039;&amp;#039;&amp;#039;&amp;#039;&amp;#039; 做 &amp;#039;&amp;#039;动作&amp;#039;&amp;#039; 。&lt;br /&gt;
&lt;br /&gt;
例子 [http://discuss.visual-prolog.com/viewtopic.php?t=8218 Dialogs and Forms: Modeless dialogs example] 说明了如何用非模态对话框的最后一个动作从一个对话框跳到另一个对话框（对表单来讲方法也是一样的）。值得注意的是，在做OK/Cancel处理时对Cancel没有做任何检查。 有一个表单有三个不同的“最终动作”，在例子中其实是做了同样的事，不过它们也可以去做不同的事。还有，不需要做很多的测试，因为这些动作是关联在按钮上，而不是作为对话框的“结果”被处理的。&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Dialogs_and_Forms&amp;diff=4143</id>
		<title>Dialogs and Forms</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Dialogs_and_Forms&amp;diff=4143"/>
		<updated>2015-06-28T10:27:06Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Guidelines */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article will describe the characteristics of &amp;#039;&amp;#039;modal dialogs&amp;#039;&amp;#039;, &amp;#039;&amp;#039;modeless dialogs&amp;#039;&amp;#039; and &amp;#039;&amp;#039;forms&amp;#039;&amp;#039;.  And give some guide lines for when and how to use each kind.&lt;br /&gt;
&lt;br /&gt;
=== Characteristics ===&lt;br /&gt;
&lt;br /&gt;
;Forms&lt;br /&gt;
* Appear in the &amp;#039;&amp;#039;&amp;#039;Window&amp;#039;&amp;#039;&amp;#039; menu&lt;br /&gt;
* Can be hidden below other windows&lt;br /&gt;
* React to Ctrl-TAB and Ctrl-Shift-TAB&lt;br /&gt;
* Can (normally) be cascaded and tiled&lt;br /&gt;
&lt;br /&gt;
;Dialogs&lt;br /&gt;
* Don&amp;#039;t appear in the &amp;#039;&amp;#039;&amp;#039;Window&amp;#039;&amp;#039;&amp;#039; menu&lt;br /&gt;
* Stay on top of other windows&lt;br /&gt;
* Don&amp;#039;t react to Ctrl-TAB and Ctrl-Shift-TAB&lt;br /&gt;
* Don&amp;#039;t take part in cascading and tiling&lt;br /&gt;
&lt;br /&gt;
;Modal Dialogs&lt;br /&gt;
* Block the rest of the application.  Subsequently, if a modal dialog is active in an application, then it is the only thing that is active in the application (i.e. for user input)&lt;br /&gt;
* In the program the control is not returned until the dialog is closed.  So the &amp;quot;result&amp;quot; of the modal dialog is available when control returns.&lt;br /&gt;
&lt;br /&gt;
;Modeless Dialog (and Forms)&lt;br /&gt;
* Does not block the application, so several modeless dialogs and forms can be active simultaneously, and simultaneous with the menu, toolbars, minimize, maximize, etc of the application.&lt;br /&gt;
* In the program the control returns as soon as the modeless dialog/form has been created/shown.  So the &amp;quot;result&amp;quot; is not awailable when the control returns from the creation/display call.&lt;br /&gt;
&lt;br /&gt;
=== Guidelines ===&lt;br /&gt;
&lt;br /&gt;
Forms should be used for &amp;quot;documents&amp;quot; and dialogs for &amp;quot;questions&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Modal dialogs may seem appealing from a programming point of view, because the flow of the program can be written as a sequence of actions:&lt;br /&gt;
&lt;br /&gt;
* Ask about the users name&lt;br /&gt;
* Greet the user and ask about a color&lt;br /&gt;
* Say &amp;quot;Thank you&amp;quot; in the chosen color&lt;br /&gt;
&lt;br /&gt;
But this approach has many drawbacks:&lt;br /&gt;
&lt;br /&gt;
# It restricts the users possibilities quite a lot:  There is only one path through the sequece.  It is of course possible to put &amp;quot;if&amp;#039;s&amp;quot; and the like into the sequence.  But that approach will very soon result in an untractable complexity, which is far more difficult to program than &amp;quot;professional&amp;quot; choice.&lt;br /&gt;
# It is a very good idea to make sure that the &amp;quot;action&amp;quot; of the dialog to be carried out while the dialog is still active, because if something is not quite right the user will prefer that the entered data remains in the dialog for a second (or third ...) attempt.&lt;br /&gt;
# While active modal dialogs block the entire application (i.e. except for the dialog itself).  This is very unpleasent for users and should therefore be reserved for &amp;quot;high-attention/critical&amp;quot; questions/notifications.  Notice that you cannot even minimize an application while a modal dialog is active.&lt;br /&gt;
&lt;br /&gt;
There are other reasons, but these are the most important ones and they are certainly sufficient to seek other means.&lt;br /&gt;
&lt;br /&gt;
The other means is to perform the &amp;quot;action(s)&amp;quot; &amp;#039;&amp;#039;&amp;#039;&amp;#039;&amp;#039;inside&amp;#039;&amp;#039;&amp;#039;&amp;#039;&amp;#039; the dialog/form.&lt;br /&gt;
&lt;br /&gt;
The example [http://discuss.visual-prolog.com/viewtopic.php?t=8218 Dialogs and Forms: Modeless dialogs example] illustratres how to jump from one modeless dialog to another as a &amp;quot;final&amp;quot; action.  (The same approach is possible for forms).  It is worth noticing that &amp;quot;Cancel&amp;quot; is handled without any checks for OK/Cancel at all.  One of the forms has three different &amp;quot;final actions&amp;quot;, in the example they do practically the same thing but they could just as well have done something different.  Again there is no need for elaborative testing because the actions are tied to the buttons rather than handled as a &amp;quot;result&amp;quot; of the dialog.&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Dialogs_and_Forms&amp;diff=4142</id>
		<title>Dialogs and Forms</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Dialogs_and_Forms&amp;diff=4142"/>
		<updated>2015-06-28T10:26:06Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Characteristics */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article will describe the characteristics of &amp;#039;&amp;#039;modal dialogs&amp;#039;&amp;#039;, &amp;#039;&amp;#039;modeless dialogs&amp;#039;&amp;#039; and &amp;#039;&amp;#039;forms&amp;#039;&amp;#039;.  And give some guide lines for when and how to use each kind.&lt;br /&gt;
&lt;br /&gt;
=== Characteristics ===&lt;br /&gt;
&lt;br /&gt;
;Forms&lt;br /&gt;
* Appear in the &amp;#039;&amp;#039;&amp;#039;Window&amp;#039;&amp;#039;&amp;#039; menu&lt;br /&gt;
* Can be hidden below other windows&lt;br /&gt;
* React to Ctrl-TAB and Ctrl-Shift-TAB&lt;br /&gt;
* Can (normally) be cascaded and tiled&lt;br /&gt;
&lt;br /&gt;
;Dialogs&lt;br /&gt;
* Don&amp;#039;t appear in the &amp;#039;&amp;#039;&amp;#039;Window&amp;#039;&amp;#039;&amp;#039; menu&lt;br /&gt;
* Stay on top of other windows&lt;br /&gt;
* Don&amp;#039;t react to Ctrl-TAB and Ctrl-Shift-TAB&lt;br /&gt;
* Don&amp;#039;t take part in cascading and tiling&lt;br /&gt;
&lt;br /&gt;
;Modal Dialogs&lt;br /&gt;
* Block the rest of the application.  Subsequently, if a modal dialog is active in an application, then it is the only thing that is active in the application (i.e. for user input)&lt;br /&gt;
* In the program the control is not returned until the dialog is closed.  So the &amp;quot;result&amp;quot; of the modal dialog is available when control returns.&lt;br /&gt;
&lt;br /&gt;
;Modeless Dialog (and Forms)&lt;br /&gt;
* Does not block the application, so several modeless dialogs and forms can be active simultaneously, and simultaneous with the menu, toolbars, minimize, maximize, etc of the application.&lt;br /&gt;
* In the program the control returns as soon as the modeless dialog/form has been created/shown.  So the &amp;quot;result&amp;quot; is not awailable when the control returns from the creation/display call.&lt;br /&gt;
&lt;br /&gt;
=== Guidelines ===&lt;br /&gt;
&lt;br /&gt;
Forms should be used for &amp;quot;documents&amp;quot; and dialogs for &amp;quot;questions&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Modal dialogs may seem appealing from a programming point of view, because the flow of the program can be written as a sequence of actions:&lt;br /&gt;
&lt;br /&gt;
* Ask about the users name&lt;br /&gt;
* Greet the user and ask about a color&lt;br /&gt;
* Say &amp;quot;Thank you&amp;quot; in the chosen color&lt;br /&gt;
&lt;br /&gt;
But this approach has many drawbacks:&lt;br /&gt;
&lt;br /&gt;
# It restricts the users possibilities quite a lot:  There is only one path through the sequece.  It is of course possible to put &amp;quot;if&amp;#039;s&amp;quot; and the like into the sequence.  But that approach will very soon result in an untractable complexity, which is far more difficult to program than &amp;quot;professional&amp;quot; choice.&lt;br /&gt;
# It is a very good idea to make sure that the &amp;quot;action&amp;quot; of the dialog to be carried out while the dialog is still active, because if something is not quite right the user will prefer that the entered data remains in the dialog for a second (or third ...) attempt.&lt;br /&gt;
# While active modal dialogs block the entire application (i.e. except for the dialog itself).  This is very unpleasent for users and should therefore be reserved for &amp;quot;high-attention/critical&amp;quot; questions/notifications.  Notice that you cannot even minimize an application while a modal dialog is active.&lt;br /&gt;
&lt;br /&gt;
There are other reasons, but these are the most important ones and they are certainly sufficient to seek other means.&lt;br /&gt;
&lt;br /&gt;
The other means is to perform the &amp;quot;action(s)&amp;quot; &amp;#039;&amp;#039;&amp;#039;&amp;#039;&amp;#039;inside&amp;#039;&amp;#039;&amp;#039;&amp;#039;&amp;#039; the dialog/form.&lt;br /&gt;
&lt;br /&gt;
The example [http://discuss.visual-prolog.com/viewtopic.php?t=8218 Dialogs and Forms: Modeless dialogs example] illustratres how to jump from one modeless dialog to another as a &amp;quot;final&amp;quot; action.  (The same approach is possible for forms).  It is worth noticing that &amp;quot;Cancel&amp;quot; is handled without any checks for OK/Cancel at all.  One of the forms has three different &amp;quot;final actions&amp;quot;, in the example they do practically the same thing but they could just as well have done something different.  Again there is no need for elaborative testing because the actions are tied to the buttons rather than handled as a &amp;quot;result&amp;quot; of the ddialog.&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4141</id>
		<title>WebBrowserControl（WebBrowser控件）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4141"/>
		<updated>2015-06-23T11:23:16Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的WebBrowserControl。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 商业版中的&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 控件，提供了对 [http://msdn.microsoft.com/en-us/library/aa752040(VS.85).aspx Internet Explorer webBrowser control] 的完全绑定。&lt;br /&gt;
&lt;br /&gt;
在对话框或表单（dialog/form）中使用 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 时：&lt;br /&gt;
&lt;br /&gt;
* 首先要确定 &amp;lt;vp&amp;gt;pfc\gui\webBrowserControl&amp;lt;/vp&amp;gt; 包已经添加到该工程中了，而且工程已经通过了编译&lt;br /&gt;
* 再将 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 作为一个用户控件加入到对话框或表单中&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 可以通过“指向”它来显示一个WEB页面：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    navigate : (string URL).&lt;br /&gt;
&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData, string Headers).&lt;br /&gt;
    % 通过URL标识指向一个资源或是通过全路径标识指向一个文件。&lt;br /&gt;
    % 参见MSCN中的 IWebBrowser2::Navigate。&lt;br /&gt;
 &amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以通过直接设置相关的html属性来设定相应内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;properties&lt;br /&gt;
    html : string (i).&lt;br /&gt;
    % 直接设置浏览器的HTML内容&lt;br /&gt;
 &amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowser&amp;lt;/vp&amp;gt; example (IDE: &amp;#039;&amp;#039;&amp;#039;Help | Install Examples...&amp;#039;&amp;#039;&amp;#039;) 演示了&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt;几种使用方法; 同时也还演示了如何自动化独立的Internet Explorer。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4140</id>
		<title>WebBrowserControl（WebBrowser控件）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4140"/>
		<updated>2015-06-23T11:19:38Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的WebBrowserControl。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 商业版中的&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 控件，提供了对 [http://msdn.microsoft.com/en-us/library/aa752040(VS.85).aspx Internet Explorer webBrowser control] 的完全绑定。&lt;br /&gt;
&lt;br /&gt;
在对话框或表单（dialog/form）中使用 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 时：&lt;br /&gt;
&lt;br /&gt;
* 首先要确定 &amp;lt;vp&amp;gt;pfc\gui\webBrowserControl&amp;lt;/vp&amp;gt; 包已经添加到该工程中了，而且工程已经通过了编译&lt;br /&gt;
* 再将 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 作为一个用户控件加入到对话框或表单中&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 可以通过“指向”它来显示一个WEB页面：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    navigate : (string URL).&lt;br /&gt;
&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData, string Headers).&lt;br /&gt;
    % 通过URL标识指向一个资源或是通过全路径标识指向一个文件。&lt;br /&gt;
    % 参见MSCN中的 IWebBrowser2::Navigate。&lt;br /&gt;
 &amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以通过直接设置相关的html属性来设定相应内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;properties&lt;br /&gt;
    html : string (i).&lt;br /&gt;
    % 直接设置浏览器的HTML内容&lt;br /&gt;
 &amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowser&amp;lt;/vp&amp;gt; example (IDE: &amp;#039;&amp;#039;&amp;#039;Help | Install Examples...&amp;#039;&amp;#039;&amp;#039;) 演示了&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt;几种使用方法; 同时也还演示了如何自动化独立的Internet Explorer。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4139</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4139"/>
		<updated>2015-06-22T01:29:38Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Multi-threading: A brief introduction。）&lt;br /&gt;
&lt;br /&gt;
只有 &amp;#039;&amp;#039;商业版&amp;#039;&amp;#039; 才支持多线程应用。&lt;br /&gt;
&lt;br /&gt;
线程是由 &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; 类/接口表示的。创建一个新线程很简单：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % 启动一个新线程，这个线程执行谓词 myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
使用匿名谓词向新线程传递数据也很简单：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
可以等待线程结束，因为线程对象是同步对象：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % 与T并行着做某些工作&lt;br /&gt;
        T:wait(),  % 等待T结束&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
要使线程安全访问共享的数据，可以使用 [[Language Reference/Monitors|monitors]] 和/或同步对象如： &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;、 &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;、&amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; 及  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;。&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4138</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4138"/>
		<updated>2015-06-22T01:23:37Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Multi-threading: A brief introduction。）&lt;br /&gt;
&lt;br /&gt;
只有 &amp;#039;&amp;#039;商业版&amp;#039;&amp;#039; 才支持多线程应用。&lt;br /&gt;
&lt;br /&gt;
线程是由 &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; 类/接口表示的。创建一个新线程很简单：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % 启动一个新线程，这个线程执行谓词 myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
使用匿名谓词向新线程传递数据也很简单：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
可以等待线程结束，因为线程对象是同步对象：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % 与T并行着做某些工作&lt;br /&gt;
        T:wait(),  % 等待T结束&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thread safe acces to shared data can be done using [[Language Reference/Monitors|monitors]] and/or synchronization objects like &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; and  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4137</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4137"/>
		<updated>2015-06-22T01:15:44Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Multi-threading: A brief introduction。）&lt;br /&gt;
&lt;br /&gt;
只有 &amp;#039;&amp;#039;商业版&amp;#039;&amp;#039; 才支持多线程应用。&lt;br /&gt;
&lt;br /&gt;
线程是由 &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; 类/接口表示的。创建一个新线程很简单：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % 启动一个新线程，这个线程执行谓词 myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using anonymous predicates it is also very simple to transfer data to the new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can wait for thread termination, because a thread object is a synchronization object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % stuff done in parallel with T&lt;br /&gt;
        T:wait(),  % Wait for T to terminate&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thread safe acces to shared data can be done using [[Language Reference/Monitors|monitors]] and/or synchronization objects like &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; and  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4136</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4136"/>
		<updated>2015-06-22T01:12:38Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Multi-threading: A brief introduction。）&lt;br /&gt;
&lt;br /&gt;
只有 &amp;#039;&amp;#039;商业版&amp;#039;&amp;#039; 才支持多线程应用。&lt;br /&gt;
&lt;br /&gt;
Threads are represented by the &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; class/interface.  And it is very simple to create a new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % start a new thread which executes the predicate myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using anonymous predicates it is also very simple to transfer data to the new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can wait for thread termination, because a thread object is a synchronization object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % stuff done in parallel with T&lt;br /&gt;
        T:wait(),  % Wait for T to terminate&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thread safe acces to shared data can be done using [[Language Reference/Monitors|monitors]] and/or synchronization objects like &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; and  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4135</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4135"/>
		<updated>2015-06-22T01:12:07Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Multi-threading: A brief introduction。）&lt;br /&gt;
&lt;br /&gt;
只有 &amp;#039;&amp;#039;&amp;#039;&amp;#039;商业版&amp;#039;&amp;#039;&amp;#039;&amp;#039; 才支持多线程应用。&lt;br /&gt;
&lt;br /&gt;
Threads are represented by the &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; class/interface.  And it is very simple to create a new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % start a new thread which executes the predicate myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using anonymous predicates it is also very simple to transfer data to the new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can wait for thread termination, because a thread object is a synchronization object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % stuff done in parallel with T&lt;br /&gt;
        T:wait(),  % Wait for T to terminate&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thread safe acces to shared data can be done using [[Language Reference/Monitors|monitors]] and/or synchronization objects like &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; and  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4134</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4134"/>
		<updated>2015-06-22T01:11:28Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Multi-threading: A brief introduction。）&lt;br /&gt;
&lt;br /&gt;
只有 &amp;lt;vp&amp;gt;商业版&amp;lt;/vp&amp;gt; 才支持多线程应用。&lt;br /&gt;
&lt;br /&gt;
Threads are represented by the &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; class/interface.  And it is very simple to create a new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % start a new thread which executes the predicate myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using anonymous predicates it is also very simple to transfer data to the new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can wait for thread termination, because a thread object is a synchronization object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % stuff done in parallel with T&lt;br /&gt;
        T:wait(),  % Wait for T to terminate&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thread safe acces to shared data can be done using [[Language Reference/Monitors|monitors]] and/or synchronization objects like &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; and  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4133</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4133"/>
		<updated>2015-06-22T01:09:31Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Multi-threading: A brief introduction。）&lt;br /&gt;
&lt;br /&gt;
只有 &amp;#039;&amp;#039;&amp;#039;商业版&amp;#039;&amp;#039;&amp;#039; 才支持多线程应用。&lt;br /&gt;
&lt;br /&gt;
Threads are represented by the &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; class/interface.  And it is very simple to create a new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % start a new thread which executes the predicate myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using anonymous predicates it is also very simple to transfer data to the new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can wait for thread termination, because a thread object is a synchronization object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % stuff done in parallel with T&lt;br /&gt;
        T:wait(),  % Wait for T to terminate&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thread safe acces to shared data can be done using [[Language Reference/Monitors|monitors]] and/or synchronization objects like &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; and  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4132</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4132"/>
		<updated>2015-06-22T01:08:30Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Multi-threading: A brief introduction。）&lt;br /&gt;
&lt;br /&gt;
只有&amp;#039;&amp;#039;&amp;#039;商业版&amp;#039;&amp;#039;&amp;#039;才支持多线程应用。&lt;br /&gt;
&lt;br /&gt;
Threads are represented by the &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; class/interface.  And it is very simple to create a new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % start a new thread which executes the predicate myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using anonymous predicates it is also very simple to transfer data to the new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can wait for thread termination, because a thread object is a synchronization object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % stuff done in parallel with T&lt;br /&gt;
        T:wait(),  % Wait for T to terminate&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thread safe acces to shared data can be done using [[Language Reference/Monitors|monitors]] and/or synchronization objects like &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; and  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4131</id>
		<title>Multi-threading: A brief introduction（多线程简介）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Multi-threading:_A_brief_introduction%EF%BC%88%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AE%80%E4%BB%8B%EF%BC%89&amp;diff=4131"/>
		<updated>2015-06-22T01:04:02Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;Multi-threading is only possible in the &amp;#039;&amp;#039;&amp;#039;Commercial Edition&amp;#039;&amp;#039;&amp;#039;.  Threads are represented by the &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; class/interface.  And it is very simple to create a new thread: ...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Multi-threading is only possible in the &amp;#039;&amp;#039;&amp;#039;Commercial Edition&amp;#039;&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
Threads are represented by the &amp;lt;vp&amp;gt;thread&amp;lt;/vp&amp;gt; class/interface.  And it is very simple to create a new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        T = thread::start(myThread), % start a new thread which executes the predicate myThread&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThread : ().&lt;br /&gt;
clauses&lt;br /&gt;
   myThread() :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using anonymous predicates it is also very simple to transfer data to the new thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) }),&lt;br /&gt;
        ...&lt;br /&gt;
&lt;br /&gt;
predicates&lt;br /&gt;
    myThreadWithData : (complexData ThreadInputData).&lt;br /&gt;
clauses&lt;br /&gt;
   myThreadWithData(ThreadInputData) :-&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can wait for thread termination, because a thread object is a synchronization object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
        ...&lt;br /&gt;
        SomeData = ...,&lt;br /&gt;
        T = thread::start( { :- myThreadWithData(SomeData) } ),&lt;br /&gt;
        ... % stuff done in parallel with T&lt;br /&gt;
        T:wait(),  % Wait for T to terminate&lt;br /&gt;
        ...&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Thread safe acces to shared data can be done using [[Language Reference/Monitors|monitors]] and/or synchronization objects like &amp;lt;vp&amp;gt;criticalSection&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;event&amp;lt;/vp&amp;gt;, &amp;lt;vp&amp;gt;mutex&amp;lt;/vp&amp;gt; and  &amp;lt;vp&amp;gt;semaphore&amp;lt;/vp&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 参考 ===&lt;br /&gt;
&lt;br /&gt;
[[Thread safety in Visual Prolog]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4130</id>
		<title>WebBrowserControl（WebBrowser控件）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4130"/>
		<updated>2015-06-21T10:07:19Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的WebBrowserControl。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 商业版中的&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 控件，提供了对 [http://msdn.microsoft.com/en-us/library/aa752040(VS.85).aspx Internet Explorer webBrowser control] 的完全绑定。&lt;br /&gt;
&lt;br /&gt;
在对话框或表单（dialog/form）中使用 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 时：&lt;br /&gt;
&lt;br /&gt;
* 首先要确定 &amp;lt;vp&amp;gt;pfc\gui\webBrowserControl&amp;lt;/vp&amp;gt; 包已经添加到该工程中了，而且工程已经通过了编译&lt;br /&gt;
* 再将 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 作为一个用户控件加入到对话框或表单中&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 可以通过“指向”它来显示一个WEB页面：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    navigate : (string URL).&lt;br /&gt;
&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData, string Headers).&lt;br /&gt;
    % 通过URL标识指向一个资源或是通过全路径标识指向一个文件。&lt;br /&gt;
    % 参见MSCN中的 IWebBrowser2::Navigate。&lt;br /&gt;
 &amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以通过直接设置相关的html属性来设定相应内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;properties&lt;br /&gt;
    html : string (i).&lt;br /&gt;
    % 直接设置浏览器的HTML内容&lt;br /&gt;
 &amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowser&amp;lt;/vp&amp;gt; example (IDE: &amp;#039;&amp;#039;&amp;#039;Help | Install Examples...&amp;#039;&amp;#039;&amp;#039;) 演示了&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt;几种使用方法; 同时也还演示了如何自动化独立的Internet Explorer。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4129</id>
		<title>WebBrowserControl（WebBrowser控件）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4129"/>
		<updated>2015-06-21T10:04:14Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的WebBrowserControl。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 商业版中的&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 控件，提供了对 [http://msdn.microsoft.com/en-us/library/aa752040(VS.85).aspx Internet Explorer webBrowser control] 的完全绑定。&lt;br /&gt;
&lt;br /&gt;
在对话框或表单（dialog/form）中使用 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 时：&lt;br /&gt;
&lt;br /&gt;
* 首先要确定 &amp;lt;vp&amp;gt;pfc\gui\webBrowserControl&amp;lt;/vp&amp;gt; 包已经添加到该工程中了，而且工程已经通过了编译&lt;br /&gt;
* 再将 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 作为一个用户控件加入到对话框或表单中&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 可以通过“指向”它来显示一个WEB页面：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    navigate : (string URL).&lt;br /&gt;
&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData, string Headers).&lt;br /&gt;
    % 通过URL标识指向一个资源或是通过全路径标识指向一个文件。&lt;br /&gt;
    % 参见MSCN中的 IWebBrowser2::Navigate。&lt;br /&gt;
 &amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以通过直接设置相关的html属性来设定相应内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;properties&lt;br /&gt;
    html : string (i).&lt;br /&gt;
    % 直接设置浏览器的HTML内容&lt;br /&gt;
 &amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowser&amp;lt;/vp&amp;gt; example (IDE: &amp;#039;&amp;#039;&amp;#039;Help | Install Examples...&amp;#039;&amp;#039;&amp;#039;) 演示了&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt;几种使用方法; 同时也还演示了如何自动化独立的。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4128</id>
		<title>WebBrowserControl（WebBrowser控件）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4128"/>
		<updated>2015-06-21T10:02:39Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的WebBrowserControl。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 商业版中的&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 控件，提供了对 [http://msdn.microsoft.com/en-us/library/aa752040(VS.85).aspx Internet Explorer webBrowser control] 的完全绑定。&lt;br /&gt;
&lt;br /&gt;
在对话框或表单（dialog/form）中使用 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 时：&lt;br /&gt;
&lt;br /&gt;
* 首先要确定 &amp;lt;vp&amp;gt;pfc\gui\webBrowserControl&amp;lt;/vp&amp;gt; 包已经添加到该工程中了，而且工程已经通过了编译&lt;br /&gt;
* 再将 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 作为一个用户控件加入到对话框或表单中&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 可以通过“指向”它来显示一个WEB页面：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    navigate : (string URL).&lt;br /&gt;
&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData, string Headers).&lt;br /&gt;
    % @short 通过URL标识指向一个资源或是通过全路径标识指向一个文件。&lt;br /&gt;
    % 参见MSCN中的 IWebBrowser2::Navigate。&lt;br /&gt;
    % @end&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以通过直接设置相关的html属性来设定相应内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;properties&lt;br /&gt;
    html : string (i).&lt;br /&gt;
    % @short 直接设置浏览器的HTML内容&lt;br /&gt;
    % @end&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowser&amp;lt;/vp&amp;gt; example (IDE: &amp;#039;&amp;#039;&amp;#039;Help | Install Examples...&amp;#039;&amp;#039;&amp;#039;) 演示了&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt;几种使用方法; 同时也还演示了如何自动化独立的。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4127</id>
		<title>WebBrowserControl（WebBrowser控件）</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=WebBrowserControl%EF%BC%88WebBrowser%E6%8E%A7%E4%BB%B6%EF%BC%89&amp;diff=4127"/>
		<updated>2015-06-21T10:01:46Z</updated>

		<summary type="html">&lt;p&gt;Yiding: Created page with &amp;quot;（以下内容译自:Category:Tutorials中的WebBrowserControl。）   Visual Prolog 商业版中的&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 控件，提供了对 [http://msdn.microsoft.c...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的WebBrowserControl。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 商业版中的&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 控件，提供了对 [http://msdn.microsoft.com/en-us/library/aa752040(VS.85).aspx Internet Explorer webBrowser control] 的完全绑定（仅商业版）。&lt;br /&gt;
&lt;br /&gt;
在对话框或表单（dialog/form）中使用 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 时：&lt;br /&gt;
&lt;br /&gt;
* 首先要确定 &amp;lt;vp&amp;gt;pfc\gui\webBrowserControl&amp;lt;/vp&amp;gt; 包已经添加到该工程中了，而且工程已经通过了编译&lt;br /&gt;
* 再将 &amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 作为一个用户控件加入到对话框或表单中&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt; 可以通过“指向”它来显示一个WEB页面：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    navigate : (string URL).&lt;br /&gt;
&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData).&lt;br /&gt;
    navigate : (string URL, browserNavConstants Flags, string TargetFrameName, binary PostData, string Headers).&lt;br /&gt;
    % @short 通过URL标识指向一个资源或是通过全路径标识指向一个文件。&lt;br /&gt;
    % 参见MSCN中的 IWebBrowser2::Navigate。&lt;br /&gt;
    % @end&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以通过直接设置相关的html属性来设定相应内容：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;properties&lt;br /&gt;
    html : string (i).&lt;br /&gt;
    % @short 直接设置浏览器的HTML内容&lt;br /&gt;
    % @end&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vp&amp;gt;webBrowser&amp;lt;/vp&amp;gt; example (IDE: &amp;#039;&amp;#039;&amp;#039;Help | Install Examples...&amp;#039;&amp;#039;&amp;#039;) 演示了&amp;lt;vp&amp;gt;webBrowserControl&amp;lt;/vp&amp;gt;几种使用方法; 同时也还演示了如何自动化独立的。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorials部分中文译文]]&lt;/div&gt;</summary>
		<author><name>Yiding</name></author>
	</entry>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84%E5%A4%96%E9%83%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F&amp;diff=4124</id>
		<title>Visual Prolog的外部数据库系统</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84%E5%A4%96%E9%83%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F&amp;diff=4124"/>
		<updated>2015-06-17T12:46:48Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* 小结 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的External Database System。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
下载 本文所用工程例子的源文件：&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 7.1 版. &lt;br /&gt;
&lt;br /&gt;
Visual Prolog 7.2 版. &lt;br /&gt;
&lt;br /&gt;
本教程讨论Visual Prolog的外部数据库系统。 外部数据库（external database） 是由外部链接项集合构成的，这些链可以让我们直接访问并非Prolog程序一部分的那种数据。 外部数据库既可以保存在文件中也可以保存在内存里，它支持B+树（一种可以快速取得数据及排序的数据结构），还通过内部的串行文件访问机制支持多用户访问。&lt;br /&gt;
&lt;br /&gt;
*Visual Prolog中的外部数据库 &lt;br /&gt;
*B+树&lt;br /&gt;
*外部数据库编程&lt;br /&gt;
*小结 &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Visual Prolog中的外部数据库==&lt;br /&gt;
&lt;br /&gt;
Visual Prolog中使用asserta、assertz、retract及retractall的内部事实数据库，用起来很简单，适用于很多场合。但是，它对内存的需求会很快地超出计算机的能力，外部数据库系统部分地就是设计用来解决面临的这个问题。举例来说，我们可能需要下述一种或多种的实现：&lt;br /&gt;
&lt;br /&gt;
*有大量记录的股票控制系统；&lt;br /&gt;
*有大量关系但仅有少量复杂结构记录的专家系统；&lt;br /&gt;
*在数据库中存储大量文本文件的文件系统；&lt;br /&gt;
*自己的数据库产品——它或许与关系数据库系统毫不相干——其中的数据相互链接而又没什么规则；&lt;br /&gt;
*包含上面若干种可能的一个系统；&lt;br /&gt;
Visual Prolog外部数据库系统支持这些不同类型的应用，并且能够满足数据库系统必须在更新操作时甚至是电源故障时不丢失数据的要求。&lt;br /&gt;
&lt;br /&gt;
Visual Prolog外部数据库谓词可以：&lt;br /&gt;
&lt;br /&gt;
*高效处理磁盘中的海量数据；&lt;br /&gt;
*将数据库放在文件中或内存中；&lt;br /&gt;
*进行多用户访问；&lt;br /&gt;
*提供比Visual Prolog自动回溯机制的顺序处理更好的数据处理灵活性；&lt;br /&gt;
*以二进制形式加载或保存外部数据库。&lt;br /&gt;
&lt;br /&gt;
===概览：外部数据库里有什么？===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog外部数据库是由两部分组成的：存储在链中的数据项——实际上是Prolog项，还有相应的用于快速访问数据项的B+树。&lt;br /&gt;
&lt;br /&gt;
外部数据库把数据项放在链中（而不是单独存放）以使相关项集中在一起。比如，一个链可能存放的是库存部件号而另一个链中含有客户名单。简单的数据库操作，如添加新项或替换删除旧项，是用不着B+树的。当要对数据排序或查找数据库中指定项时，才需要使用它，详细内容后面会介绍。&lt;br /&gt;
&lt;br /&gt;
====命名约定====&lt;br /&gt;
&lt;br /&gt;
所有涉及数据库管理的标准谓词，其命名有明确规则：&lt;br /&gt;
&lt;br /&gt;
*名称的第一部分（db_，chain_，term_，等等）指明了需要的输入,&lt;br /&gt;
*名称的第二部分（flush，btrees，delete，等等）表示动作，或是返回的内容，或是受影响的内容。&lt;br /&gt;
例如，db_delete是删除整个数据库，chain_delete是删除整个链，而term_delete只删除单独的一个项。&lt;br /&gt;
&lt;br /&gt;
[[Image:External_DB_01.gif]] &lt;br /&gt;
&lt;br /&gt;
图1：一个Visual Prolog外部数据库结构示例&lt;br /&gt;
&lt;br /&gt;
====外部数据库的选择====&lt;br /&gt;
&lt;br /&gt;
在磁盘上和内存中，可以同时存在多个外部数据库。利用这种特性，可以把外部数据库放在合适的地方以取得速度与占用内存空间的最佳均衡。&lt;br /&gt;
&lt;br /&gt;
为了区分多个打开的外部数据库，需要使用chainDB对象。打开或创建数据库时，调用chainDB类中适当的构造器，使用返回的对象进行数据库访问。&lt;br /&gt;
&lt;br /&gt;
===Chains（链）===&lt;br /&gt;
&lt;br /&gt;
外部数据库是Prolog terms（项）的集合。项，就是整数、实数、串、符号值以及复合对象，例如：32，-194，3.1417, &amp;quot;Wally&amp;quot;， wages，book(&amp;quot;dickens&amp;quot;, &amp;quot;Wally goes to the zoo&amp;quot;)等等。 &lt;br /&gt;
&lt;br /&gt;
在外部数据库内，项是存储在chains（链）中的。一个链里可以包含任意数量的项，而一个外部数据库又可以包含任意数量的链。每个链是用名称来区分的，而名称就是个串。&lt;br /&gt;
&lt;br /&gt;
下图是链结构示意图。&lt;br /&gt;
&lt;br /&gt;
[[Image:External_DB_02.gif]] &lt;br /&gt;
&lt;br /&gt;
图2：链的结构:&lt;br /&gt;
&lt;br /&gt;
数据库关系及数据库表模型的基础是项链。举例来说，假设有客户、供货商及一个部件数据库，要想把所有数据按三个关系放在一个数据库里。这就可以把客户放在一个链里，叫它customers；把供货商放在一个链里，叫suppliers；再把部件放在一个名为parts的链中。&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;
bt_selector： 由bt_open返回的B+树选择符值&lt;br /&gt;
&lt;br /&gt;
place： 数据库的位置，可以在内存中，也可以在文件中&lt;br /&gt;
&lt;br /&gt;
accessmode： 确定如何使用文件&lt;br /&gt;
&lt;br /&gt;
denymode： 确定别的用户可以怎么用文件 &lt;br /&gt;
&lt;br /&gt;
ref： 项在链中位置的索引号 &lt;br /&gt;
&lt;br /&gt;
====数据库索引号====&lt;br /&gt;
 &lt;br /&gt;
每个插入到外部数据库的项，Visual Prolog都会对其赋予一个database reference number（数据库索引号）。可以使用项的数据库索引号进行取出、移动或替换操作，还可以对链中的前一项或后一项进行类似的操作。可以把数据库索引号插入到一个B+树中（后面会有介绍），再用这个B+对项进行排序或对项做快速搜索。&lt;br /&gt;
&lt;br /&gt;
数据库索引号与数据库放在什么地方以及对数据库做了些什么操作都没有关系。一旦它与项关联之后，不管数据库做了什么，都可以用这个号去访问对应的项，一直到这个项被删除为止。&lt;br /&gt;
&lt;br /&gt;
====ref域====&lt;br /&gt;
&lt;br /&gt;
数据库索引号是很特殊的，可以把它们插入到事实段中，也可以用stdIO::write或stdIO::writef把它们写出来，但却不能从键盘上输入它们。对处理数据库索引号的谓词，它们作为参数时必须声明为预定义的ref域。&lt;br /&gt;
&lt;br /&gt;
用term_delete删除了一个项之后，系统会在一个新项插入到外部数据库时重用刚删除的那个项的数据库索引号，这是自动进行的。但如果索引号已经存入事实段或一个B+树中时，保证索引号与对应项的正确关联就是编程人员的责任了。&lt;br /&gt;
&lt;br /&gt;
有一个错误检查选项对此会有帮助，这个选项是用谓词db_reuserefs激活的。db_reuserefs(1)可以激活对释放项的检查，用db_reuserefs(0)可以关闭这个检查。激活检查的开销不大（每项多四个字节，几乎不增加CPU开销），但增加的四个字节总也不会释放了。如果经常地创建和删除项，则数据库就会持续地变大。db_reuserefs的主要目的还是帮助程序开发过程中跟踪定位错误。&lt;br /&gt;
&lt;br /&gt;
===操控整个外部数据库===&lt;br /&gt;
&lt;br /&gt;
创建新的或是打开已有的外部数据库时，可以把数据库放在文件里，也可以放在内存里，这是由调用db_create或db_openon时的参数Place的值决定的。外部数据库使用完毕后，调用db_close关闭它。chainDB接口谓词的相关信息可以参看Visual Prolog帮助文件中的PFC章节。&lt;br /&gt;
&lt;br /&gt;
当外部数据库放在内存中时，用db_close关闭数据库并不会将其从内存中删除。如果要释放数据库占用的内存，需要明确地调用db_delete才行。这样的外部数据库如果只是关闭了但并没有删除，稍后还可以用db_open谓词再重新打开它。&lt;br /&gt;
&lt;br /&gt;
例如，下面是两个不同的db_create调用：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;ChainDbObj1 = chaindb::db_create(&amp;quot;MYFILE.DBA&amp;quot;, in_file),&lt;br /&gt;
        /* 创建磁盘文件MYFILE.DBA */&lt;br /&gt;
ChainDbObj2 = chaindb::db_create(&amp;quot;SymName2&amp;quot;, in_memory),&lt;br /&gt;
        /* 创建内存数据库SymName2 */&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
而下面的这db_copy调用： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;ChainDbObj:db_copy(&amp;quot;dd.dat&amp;quot;, in_file)&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Visual Prolog会把指定的数据库ChainDbObj拷贝到放在硬盘的新数据库文件 &amp;quot;dd.dat&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
数据库拷贝时，原来的仍在那里，这时就会有两份相同的数据库，直到明确地删除一个为止。&lt;br /&gt;
&lt;br /&gt;
数据库的移动不会影响对它的任何处理，因为所有外部数据库的索引号仍是有效的。因此，如果起先一个数据库是放在内存中的，运行时感到内存紧张了又把它拷贝到文件中，这不会影响对这个数据库的任何操作。对内存数据库建立的索引，即使把数据库拷贝到文件中后，还是一样可用的。&lt;br /&gt;
&lt;br /&gt;
db_copy有多种用途，可以：&lt;br /&gt;
&lt;br /&gt;
*从磁盘加载数据库到内存，以后再把它保存为二进制形式，而不是用 file::save 和 file::consult 保存为文本文件。 &lt;br /&gt;
*把合适规模的数据库从磁盘拷贝到内存中以加快访问速度。&lt;br /&gt;
*压缩数据库：数据库拷贝到另一个文件时，会消去所有末使用的空间。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_openinvalid/3====&lt;br /&gt;
&lt;br /&gt;
db_openinvalid可以打开已经标记为无效的数据库。如果在数据库更新时发生了电源故障，数据库中的数据可能会因为部分缓冲区中的数据还没有写到磁盘而全部丢失。数据库有一个标志指示更新后是否为无效有状态。&lt;br /&gt;
&lt;br /&gt;
调用任何一个要改变数据库内容的谓词之后，数据库都会被标记为无效。这些谓词有chain_inserta、chain_insertz、chain_insertafter、term_replace、term_delete、chain_delete、bt_create、key_insert及key_delete。一旦数据库用db_close关闭，或是调用了db_flush清空缓冲区，则数据库就会被标记为有效。&lt;br /&gt;
&lt;br /&gt;
用db_openinvalid，有时可以对标记为无效的数据库进行操作。如果没有以前的备份数据可用，这或许可以恢复部分数据。但是，对用db_openinvalid打开的数据库进行的所有操作，都有可能产生预想不到的结果。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_flush/0====&lt;br /&gt;
&lt;br /&gt;
db_flush把缓冲区的内容写到相应的数据库中并清空缓冲区。数据库被更新时，会被标记为无效状态，直到db_flush或关闭。&lt;br /&gt;
&lt;br /&gt;
对数据库要采取怎样的安全措施，当然与数据的重要程度有关。最起码的数据安全措施是在磁盘上保留备份。中等一些的，可以在每次重要的数据库更新之后调用db_flush。不过清缓冲区是相当费时的一个操作，如果用得太多数据库系统就会慢慢停下来。如果外部数据库的内容是非常重要的，还可以用一个特殊的登记文件把所有更动记录下来，或是在不同的磁盘上维护两个一样的数据库。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_close/0====&lt;br /&gt;
&lt;br /&gt;
调用db_close会关闭打开的数据库。如果数据库是放在磁盘上的文件，则文件会关闭。数据库关闭时不会被删除，就是放在内存的数据库也不会删除，还可以通过调用db_open重新打开它。可以用db_delete删除在内存中的已经关闭了的数据库。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_delete/2====&lt;br /&gt;
&lt;br /&gt;
数据库放在内存中时，db_delete会释放它所占用的空间。&lt;br /&gt;
如果数据库是放在文件中的，db_delete会删除这个文件。如果在指定的地方没有相应的数据库文件，则db_delete会给出一个错误消息。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_garbagecollect/0====&lt;br /&gt;
&lt;br /&gt;
db_garbagecollect扫视整个数据库垃圾回收列表的自由空间，并尽可能把若干碎片合成一个大的自由空间。数据库放在内存中时，这种扫视与合并是自动进行的。 &lt;br /&gt;
&lt;br /&gt;
正常情况下，不需要调用这个谓词。但当插入新项时如果数据库中有太多的回收空间没有得到重新利用，db_garbagecollect有助于得到可利用的空间。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_btrees/1====&lt;br /&gt;
&lt;br /&gt;
回溯时，db_btrees会连续将BtreeName约束为指定数据库中各个B+树的名称。&lt;br /&gt;
&lt;br /&gt;
各个名称是依排定的顺序返回的。关于B+树后面还要介绍。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_chains/1====&lt;br /&gt;
&lt;br /&gt;
回溯时，db_chains会连续将ChainName约束为指定数据库中各个链的名称。各个名称是依排定的顺序返回的。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_statistics/4====&lt;br /&gt;
&lt;br /&gt;
db_statistics返回数据库的统计信息。 各参数所表示的意义如下：&lt;br /&gt;
&lt;br /&gt;
参数NoOfTerms表示数据库中的总项数。参数MemSizeis表示数据库存放于内存中的内表所占字节数。参数DbaSizeis表示数据库中项与描述符占用的总字节数。如果数据库是在文件中，而这个值比该文件占用的字节数小很多，这个文件就应该可以用db_copy进行压缩。参数FreeSize表示空闲内存的值，依数据库当前位置不同这个值也会不同：&lt;br /&gt;
&lt;br /&gt;
*数据库中内存中时，FreeSize表示全局栈顶与堆顶之间空闲的字节数（注意：可能还有若干空间的字节数没有包含在这个值中）。&lt;br /&gt;
*数据库在文件中时，FreeSize表示硬盘文件中未使用的字节数。&lt;br /&gt;
&lt;br /&gt;
===操控链===&lt;br /&gt;
&lt;br /&gt;
To要把一个项插入到外部数据库的链中，可以使用谓词 chain_inserta、chain_insertz 或 chain_insertafter。可以用 chain_terms 连续地把它的参数约束为链的项及其索引号，还可以用 chain_delete 删除外部数据库中某个链的所有项。&lt;br /&gt;
&lt;br /&gt;
还有四个标准谓词可以返回数据库的索引号，这四个标准谓词是：chain_first、chain_last、chain_next 和 chain_prev。&lt;br /&gt;
&lt;br /&gt;
===操控项===&lt;br /&gt;
&lt;br /&gt;
有三个外部数据库管理的标准谓词都与项有关，它们是：term_replace、term_delete 和 ref_term。无论调用哪个与项有关的外部数据库标准谓词，都需要将项的域作为一个参数给出来。正因为如此，所以把一个数据库中所有项的域声明为单个域中的可选项会是个好办法。例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;domains&lt;br /&gt;
    terms_for_my_stock_control_database =&lt;br /&gt;
        customer(Customer, Name, ZipCode, Address);&lt;br /&gt;
        supplier(SupplierNo, Name, Address);&lt;br /&gt;
        parts(PartNo, Description, Price, SupplierNo).&amp;lt;/vip&amp;gt;&lt;br /&gt;
注意，对外部数据库没有类型（域）上的混杂限制要求。一个链可以含有文本串而另一个链上的可以是整数，第三个链中还可以是某种复合结构，等等。但是，外部数据库的数据项并不是按类型描述符存放的，比如一个整数未必就是刚好占用两个字节。取回数据时恰好就是存放时的那种样子（同样的域），这是编程人员应该保证的。如果弄混了域，会产生运行时错误。&lt;br /&gt;
&lt;br /&gt;
==B+树==&lt;br /&gt;
&lt;br /&gt;
B+树是一种数据结构，用于实现快速有效地对大量数据进行分类排序的方法。B+树还有比较高效的搜索算法。可以认为B+树对数据库提供了一个索引，这也是为什么有时它又被称为“索引”的原因。&lt;br /&gt;
&lt;br /&gt;
在Visual Prolog中，B+树是在外部数据库中的。B+树的每个项有一对值：一个键串和相关联的数据库索引号。创建数据库时，先在数据库中插入记录并为该记录建立一个键。Visual Prolog B+树谓词接着就可以用来把这个键及该记录相应的数据库索引号插入到一个B+树中。&lt;br /&gt;
&lt;br /&gt;
要搜索数据库中的某个记录时，只需要找到该记录的键，而B+树会给出相应的索引号。用这个索引号，就可以从数据库中得到需要的记录。当B+树增长变化时，它会保持键的顺序，这也就意味着我们可以很容易地得到排序好的记录列表。&lt;br /&gt;
&lt;br /&gt;
B+树类似于二进制树，只不过在B+树的各个节点上保存了不止一个键串。B+树也是平衡树，这就是说对树中各个叶上的每个键的搜索路径长度是一样的。由于这个特点，对于在数以百万计的键中搜索某个键是有保证的，就是在最坏的情况下，需要对磁盘进行访问，也只需要不多的几次，这和每个节点上保存有多少键相关。&lt;br /&gt;
&lt;br /&gt;
尽管B+树是放在外部数据库中的，但并非一定要与它所指的项放在同一个数据库中，可以是一个数据库中含有一些链，而另一个数据库放指向这些链中项的B+树。&lt;br /&gt;
&lt;br /&gt;
===Pages(页） Order（阶） Key Length（键长）===&lt;br /&gt;
&lt;br /&gt;
在B+树中，键是聚合在页里的，每页有相同的大小，所有的页都包含有同样数量的键，这意味着所有为B+树保存的键必须大小相同。键的大小取决于KeyLen参数，这是在创建B+树时需要指定的一个参数。如果要在B+树中插入比KeyLen还要长的串，Visual Prolog会把多出来的部分截去。通常应该尽可能选小一些的KeyLen值以节省空间、提高速度。&lt;br /&gt;
&lt;br /&gt;
创建B+树时还需要指定称为阶的一个参数。这个参数决定了树的各个节点可以存储多少个键，而通常它是需要反复试验才能取得最佳值。阶的起始值用4是不错的选择，它允许各节点上保存4～8个键。阶的值必须要通过实验来选取，因为B+树的搜索速度与键长与阶的值、B+中键的数量以及计算机硬件配置都有关系。&lt;br /&gt;
&lt;br /&gt;
===重复键===&lt;br /&gt;
 &lt;br /&gt;
设置B+树时，需要允许键的重复。比如，设置一个客户数据库的B+树，用客户的姓做键，键就会有重复出现的可能。由于这个原因，B+树是可以有重复键的。&lt;br /&gt;
&lt;br /&gt;
要删除数据库中的一个项，需要给出相应的B+树键及数据库索引号（因此键重复也不会有问题）。&lt;br /&gt;
&lt;br /&gt;
===多重操作===&lt;br /&gt;
&lt;br /&gt;
为了能对B+进行多重操作，每个B+树内部都有多个指针，允许对一个树多次打开。不过要注意，如果更新了一个B+树的拷贝，而此时又有已经打开了的副本，则副本的指针会重定位到树的顶端。&lt;br /&gt;
&lt;br /&gt;
===B+树的标准谓词===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog提供了一些标准谓词来处理B+树，这些谓词的工作方式与相应的 db_... 谓词是一致的。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_create/4 and chainDB::bt_create/5====&lt;br /&gt;
&lt;br /&gt;
调用这个谓词可以创建一个新的B+树。&lt;br /&gt;
&lt;br /&gt;
参数BtreeName指定新树的名称，以后可以把这个名称作为参数用于bt_open。B+树的KeyLen参数和Order参数是在树创建时给出的，以后不能再更改。如果调用bt_create/4或bt_create/5时参数Duplicates设为1，则在这个B+树中允许重复；若是设为0，则不能在该树中插入重复的项。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_open/2====&lt;br /&gt;
&lt;br /&gt;
bt_open打开名为BtreeName的已经创建了的B+树。&lt;br /&gt;
&lt;br /&gt;
打开一个B+树时，这个调用会返回一个用于该B+树的选择器Btree_Sel，它就是该B+树的引用，无论系统的搜索或定位操作都要用到它。B+树的名称与选择器之间的关系就如同一个实际的文件名称与相应的符号文件名之间的关系。&lt;br /&gt;
&lt;br /&gt;
可以多次打开同一个B+树以便同时进行多项操作。每打开一次该B+树，就分配一个描述符，各个描述符都对应有自己的B+树内部指针。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_close/1 and chainDB::bt_delete/1====&lt;br /&gt;
&lt;br /&gt;
可以用bt_close关闭一个打开的B+树，也可以用bt_delete删除整个B+树。&lt;br /&gt;
&lt;br /&gt;
调用bt_close会释放打开的名为BtreeName的B+树分配的内部缓冲区。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_copyselector/2====&lt;br /&gt;
&lt;br /&gt;
bt_copyselector对已经打开的B+树复制一个新的选择器。&lt;br /&gt;
&lt;br /&gt;
新选择器开始时其指针定位在与老的选择器相同的位置上。之后，B+树的两个选择器定位就都可以自行确定，互不相干。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_statistics/7====&lt;br /&gt;
&lt;br /&gt;
bt_statistics返回由Btree_Sel指定的B+树的统计信息。各参数意义如下：&lt;br /&gt;
&lt;br /&gt;
参数Btree_Sel是该B+树的bt_selector；NumKeys是该树中键的总数；NumPages是该树的总页数；Depth是该树的深度；KeyLen是该树键长；Order是该树的阶；PgSize是页大小（以字节计）。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_insert/3 and chainDB::key_delete/3====&lt;br /&gt;
&lt;br /&gt;
标准谓词key_insert和key_delete用于更改B+树。&lt;br /&gt;
&lt;br /&gt;
给定Key和Ref时，可以删除有重复项的B+树中的某个确定项。&lt;br /&gt;
&lt;br /&gt;
chainDB::key_first/2, chainDB::key_last/2, and chainDB::key_search/3&lt;br /&gt;
每个B+树都维护有指向自身节点的内部指针。key_first和key_last可以分别把指针置于树的第一个键和最后一个键的位置上，而key_search则可以把指针置于指定键的位置上。&lt;br /&gt;
&lt;br /&gt;
如果找到了指定的键，key_search就成功；反之则失败，并将B+的内部指针置于该指定键应该存放的位置之后。这时可以用key_current返回这个键及其数据库索引号。如果想要在一个有重复项的B+树中准确定位，也可以把索引号做为一个条件输入。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_next/2 and chainDB::key_prev/2====&lt;br /&gt;
&lt;br /&gt;
可以用谓词key_next和key_prev在经过排序树中向前或向后移动B+树指针。&lt;br /&gt;
&lt;br /&gt;
If如果已经到了树的端头，想进一步移动指针会引起失败，但指针本身还是会移出树外。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_current/3====&lt;br /&gt;
&lt;br /&gt;
key_current返回B+树当前指针指向的键及其数据库索引号。&lt;br /&gt;
&lt;br /&gt;
调用谓词bt_open、bt_create、key_insert或key_delete之后立即使用key_current谓词会失败，当指针由于key_prev或key_next已经位于第一个键之前或最后一个键之后使用key_current谓词也会失败。&lt;br /&gt;
&lt;br /&gt;
==外部数据库编程==&lt;br /&gt;
&lt;br /&gt;
本节要介绍使用Visual Prolog外部数据库系统的一些基本方法与原则。主要内容有：&lt;br /&gt;
&lt;br /&gt;
*“遍历数据库” 介绍顺序扫遍外部数据库中的链或B+树的方法。&lt;br /&gt;
*“数据库的防护” 说明如何应对没想到的电源故障及其它可能的问题以保护数据库。&lt;br /&gt;
*“使用B+树内部指针” 说明如何在打开的B+树中使用定位指针的一些谓词。&lt;br /&gt;
&lt;br /&gt;
===遍历数据库===&lt;br /&gt;
在使用数据库系统时，头脑中一定要清楚Visual Prolog的存储机制。每当Visual Prolog用ref_term谓词从外部数据库中取回一个项时，会把这个项放在全局栈中。这个项占用的空间，系统要一直等到程序失败并回溯到ref_term调用之前的点才会释放。这意味着，要想连续地遍历外部数据库中某个链，总应该使用下面这样的结构：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 连续访问一个链所用的结构 */&lt;br /&gt;
scan(ChainObj, Chain, ....) :-&lt;br /&gt;
    ChainObj:chain_first(Chain, Ref),&lt;br /&gt;
    scanloop(ChainObj, Ref).&lt;br /&gt;
scanloop(ChainObj, Ref) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:ref_term(Ref, Term),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scanloop(ChainObj, _) :-&lt;br /&gt;
    ChainObj:chain_next(Ref, NextRef),&lt;br /&gt;
    scanloop(ChainObj, NextRef).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
同样，要遍历索引，应该用如下的结构：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 连续访问索引所用的结构 */&lt;br /&gt;
scan(ChainObj, Bt_selector) :-&lt;br /&gt;
    ChainObj:key_first(Bt_selector, FirstRef),&lt;br /&gt;
    scanloop(ChainObj, Bt_selector, FirstRef).&lt;br /&gt;
scanloop(ChainObj, Bt_selector, Ref) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:ref_term(Ref, Term),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scanloop(ChainObj, Bt_selector, _) :-&lt;br /&gt;
    ChainObj:key_next(Bt_selector, NextRef),&lt;br /&gt;
    scanloop(ChainObj, Bt_selector, NextRef).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以用chain_terms来遍历外部数据库中的链，像这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 遍历链的另一种方法 */&lt;br /&gt;
scan(ChainObj, Chain) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:chain_terms(Chain, Term, Ref),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scan(_, _).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
要遍历B+树，可以通过已经定义和使用过的bt_keys谓词来实现。回溯时，这个谓词就会逐一返回（指定B+和数据库）各个键及相应的数据库索引号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/vip&amp;gt;predicates&lt;br /&gt;
    bt_keys : (chainDB, bt_selector, string, ref).&lt;br /&gt;
    bt_keysloop : (chainDB, bt_selector, string, ref).&lt;br /&gt;
clauses&lt;br /&gt;
    bt_keys(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_first(Bt_selector, _),&lt;br /&gt;
        bt_keysloop(ChainObj, Bt_selector, Key, Ref).&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_current(Bt_selector, Key, Ref).&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_next(Bt_selector, _),&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref).&amp;lt;/vip&amp;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;
===使用B+树的内部指针===&lt;br /&gt;
&lt;br /&gt;
每个打开的B+树都有一个指向其节点的指针，打开或更新B+树时，这个指针指向树的起点之前；当指针指向树的最后一个键时，调用key_next会使指针指向树的末端之后的位置。只要指针超出了树，key_current就都会失败。如果这样一些安排不能满足特定应用程序的要求，就需要自行构造其它的谓词了。&lt;br /&gt;
&lt;br /&gt;
比如，可以构造像下面这样一些mykey_next、mykey_prev、mykey_search的谓词，让B+树指针总是在B+树的内部（如果树中有一些键的话）。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    mykey_next(db_selector, bt_selector, ref).&lt;br /&gt;
    mykey_prev(db_selector, bt_selector, ref).&lt;br /&gt;
    mykey_search(db_selector, bt_selector, string, ref).&lt;br /&gt;
clauses&lt;br /&gt;
    mykey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref), !.&lt;br /&gt;
    mykey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref), fail.&lt;br /&gt;
    mykey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref), !.&lt;br /&gt;
    mykey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref), fail.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, Key, Ref) :-&lt;br /&gt;
        Dba:key_search(Bt_selector, Key, Ref), !.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, _, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, _, Ref), !.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, _, Ref) :-&lt;br /&gt;
        Dba:key_last(Bt_selector, Ref).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
还可以使用下面定义的samekey_next和samekey_prev谓词，将索引指针定位到具有重复键的B+树中同一键值的下一个键上：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    samekey_next(db_selector, bt_selector, ref).&lt;br /&gt;
    try_next(db_selector, bt_selector, ref, string).&lt;br /&gt;
    samekey_prev(db_selector, bt_selector, ref).&lt;br /&gt;
    try_prev(db_selector, bt_selector, ref, string).&lt;br /&gt;
clauses&lt;br /&gt;
    samekey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, OldKey, _),&lt;br /&gt;
        try_next(Dba, Bt_selector, Ref, OldKey).&lt;br /&gt;
    try_next(Dba, Bt_selector, Ref, OldKey) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref),&lt;br /&gt;
        Dba:key_current(Bt_selector, NewKey, _),&lt;br /&gt;
        NewKey = OldKey, !.&lt;br /&gt;
    try_next(Dba, Bt_selector, _, _) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, _),&lt;br /&gt;
        fail.&lt;br /&gt;
    samekey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, OldKey, _),&lt;br /&gt;
        try_prev(Dba, Bt_selector, Ref, OldKey).&lt;br /&gt;
    try_prev(Dba, Bt_selector, Ref, OldKey) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref),&lt;br /&gt;
        Dba:key_current(Bt_selector, NewKey, _),&lt;br /&gt;
        NewKey = OldKey, !.&lt;br /&gt;
    try_prev(Dba, Bt_selector, _, _) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, _),&lt;br /&gt;
        fail.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===外部数据库与文件共享===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog支持文件共享型外部数据库，也就是说一个文件可以被若干用户或是进程同时打开，这对在局域网中或多任务平台上使用外部数据库是很有用处的。&lt;br /&gt;
&lt;br /&gt;
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;
域accessMode = read或readWrite；域denyMode = denyNone或denyWrite或denyAll。 &lt;br /&gt;
&lt;br /&gt;
===共享模式下打开数据库===&lt;br /&gt;
&lt;br /&gt;
为了使打开的外部数据库可以共享，需要使用四元维版本的db_open打开已有的数据库文件，并指定AccessMode及DenyMode。 &lt;br /&gt;
&lt;br /&gt;
如果AccessMode是read，打开的文件就只能读，任何对文件的更新都会导致运行时错误；如果是readwrite，该文件就可读可写。AccessMode在谓词db_begintransaction中也会用到。&lt;br /&gt;
&lt;br /&gt;
如果DenyMode是denyNone，那么所有其他的用户都可以更新和读取该文件；若是denyWrite，则其他用户不能以 AccessMode = readWrite 模式打开该文件，但如果该文件是按 AccessMode = readWrite 模式打开的，还是可以对文件进行更新的。如果db_open是带 DenyMode = denyAll 的，则任何其他用户都不能访问该文件。&lt;br /&gt;
&lt;br /&gt;
打开文件的第一个用户使用的DenyMode，决定了后续所有打开该文件的条件，如果试图用不相容的模式打开该文件，就会产生运行时错误。下表归纳了打开文件的结果及后续再打开同一个文件时的各种情况：&lt;br /&gt;
&lt;br /&gt;
Domain（域）  用途  &lt;br /&gt;
bt_selector  bt_open返回的用于选取B+树的唯一值  &lt;br /&gt;
place  数据库的位置：内存中或是文件中 &lt;br /&gt;
accessmode  确定文件的使用方式 &lt;br /&gt;
denymode  确定其他用户可以打开该文件的方式 &lt;br /&gt;
ref  项在链中位置的索引 &lt;br /&gt;
&lt;br /&gt;
===Transactions（事务处理）与文件共享===&lt;br /&gt;
&lt;br /&gt;
如果一个数据库文件是打开共享的，则所有以任何方式访问数据库文件的数据库谓词必须要集中在事务处理内部，它是通过把对这些谓词的调用包围在db_begintransaction和db_endtransaction之间来实现的。&lt;br /&gt;
&lt;br /&gt;
根据选择的AccessMode和DenyMode不同，共享的文件有可能在事务处理期间会被锁定。再根据锁定的程度，事务处理时其他用户可能还不能读或更新该文件。这对于避免读写的混乱当然是必要的，但文件共享要有意义就一定不要有过多的锁定。这可以通过尽量减小（缩短）事务处理来实现，只把那些访问数据库的谓词包含在事务处理中。&lt;br /&gt;
&lt;br /&gt;
涉及文件共享的事务处理这个概念非常重要，两者之间的需求是矛盾的，即同时要实现数据库的安全和最少的文件锁定。&lt;br /&gt;
&lt;br /&gt;
db_begintransaction是确保数据库一致性的，它实现文件的适当锁定，文件可由若干用户同时读但只允许有一个进程在当下更新数据库。谓词db_setretry可以用来设置db_begintransaction在返回运行时错误之前需要等待多长时间才可以访问该文件。当文件AccessMode是按read打开的，而调用db_begintransaction时AccessMode设成了readWrite也会产生运行时错误。如果已经调用了db_begintransaction，对同一个数据库调用新的db_begintransaction之前必须调用db_endtransaction，否则就会出现运行时错误。&lt;br /&gt;
&lt;br /&gt;
下表归纳了db_begintransaction对不同的AccessMode和DenyMode组合所做的反应：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;AccessMode read readWrite DenyMode denyNone WLock\Reload RWLock\Reload denyWrite None RWLock denyAll None NoneActions:&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
WLock ：不允许写，只允许读。&lt;br /&gt;
RWLock ：读和写都不允许。&lt;br /&gt;
Reload ：重新载入文件描述符。&lt;br /&gt;
由于重新载入及锁定都是需要时间的，所以要小心选用AccessMode和DenyMode。比如，若不会有用户更新数据库，将AccessMode设为read而DenyMode设为denyWrite就可以使开销最小化。&lt;br /&gt;
 &lt;br /&gt;
===文件共享谓词===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog中有一些文件共享谓词，它们是db_open、db_begintransaction、db_endtransaction、db_updated、bt_updated和db_setretry。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_begintransaction/1====&lt;br /&gt;
这个谓词标记事务处理的起点，应该在访问共享的数据库之前调用它，哪怕是以denyAll打开的数据库，也应该调用它。db_begintransaction调用时AccessMode必须为read或readwrite。进一步的了解可以参看PFC的帮助文件。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_endtransaction/0====&lt;br /&gt;
db_endtransaction标记一个事务处理的结束并解锁数据库。必须在每个db_begintransaction调用之后且在下一个db_begintransaction调用之前调用db_endtransaction。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_updated/0====&lt;br /&gt;
如果有其他用户更新了数据库，db_begintransaction的调用会保持数据库的一致性。调用谓词db_updated可以检查变更情况：如果在一个事务处理中调用它，而db_begintransaction之后有其他用户更新过数据库，就会成功；如果没有更新，就会失败。如果在事务处理之外调用这个谓词，就会产生运行时错误。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_updated/1====&lt;br /&gt;
这个谓词很像db_updated，只不过它是在指定的B+树更新了才会成功。 &lt;br /&gt;
&lt;br /&gt;
====chainDB::db_setretry/2====&lt;br /&gt;
如果由于某个进程锁定了一个文件而导致另一个进程访问该文件被拒绝，这另一个进程可以进入一个等待期，期满后再试着访问该文件。谓词db_setretry可以改变缺省的SleepPeriod设置，这是以百分之一秒为单位的等待期；这个谓词还可以设置RetryCount，这是重试访问的最大次数。缺省的设置，RetryCount是100而SleepPeriod是10。&lt;br /&gt;
&lt;br /&gt;
===文件共享时的编程===&lt;br /&gt;
使用文件共享谓词一定要十分小心。尽管使用恰当时它们能够保证共享数据库低层的一致性，但防止某个程序危害高层的一致性则是应用程序编写人员的责任。这中间所用的所谓“事务处理（transaction）”是将文件访问集中在一起的一个方法，但一定要清楚它并没有提供什么恢复机制，不论是软件还是硬件失效引起的程序中断，都有可能导致数据库文件的不完整。&lt;br /&gt;
&lt;br /&gt;
若干进程共享一个数据库时，还需要特别注意涉及到的域，它们必须要严格一致，使用时顺序也要一致。&lt;br /&gt;
&lt;br /&gt;
为避免不要地锁定数据库文件，事务处理一定要尽量短小以使文件锁定时间尽可能短。同时，要把对数据库项的定位与访问的谓词放在一个事务处理中，这也很重要。 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;.....&lt;br /&gt;
ChainObj:db_begintransaction(readWrite),&lt;br /&gt;
  ChainObj:key_current(Btree, Key, Ref),&lt;br /&gt;
  ChainObj:ref_term(Ref, Term),&lt;br /&gt;
ChainObj:db_endtransaction(),&lt;br /&gt;
write(Term),&lt;br /&gt;
.....&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
上面的代码中，谓词key_current和ref_term不应该分开放在不同的事务处理中，因为Ref中存储的项可能会在不同事务处理间就被其他用户删除了。 &lt;br /&gt;
&lt;br /&gt;
当B+树被别的用户更新并且文件缓冲区重新加载后，B+树的指针会重新定位到第一项之前。调用谓词bt_updated可以确定是否需要重新对B+树指针定位。把当前的键暂存在内部数据库中，既可以列表完整的索引号，同时也可以使用事务处理短小。 &lt;br /&gt;
&lt;br /&gt;
===实现高级锁定===&lt;br /&gt;
对于一个共享的数据库文件，用户可以进行所有独自享有该文件时相同的操作。把对文件的访问集中于db_begintransaction和db_endtransaction的内部，可以保证Visual Prolog系统自身描述符表的一致性。但是，在网络环境中多用户条件下应用程序的各种逻辑约束, 需要在较高层级上由编程人员确保得以实现。&lt;br /&gt;
&lt;br /&gt;
W我们把这称之为高层级的锁定或应用程序级的锁定。通过使用简单的db_begintransaction和db_endtransaction，可以有多种方法实现高级锁定的方法。&lt;br /&gt;
&lt;br /&gt;
需要使用高级锁定的一个很普通的例子，就是用户想要改变数据库中记录的时候。确定要改变一个记录时，就需要采取一些步骤使应用程序对该记录锁定，直到更改结束、新的记录写回到磁盘中，然后对该记录解锁。&lt;br /&gt;
&lt;br /&gt;
下面是对应用程序级锁定的一些建议：&lt;br /&gt;
&lt;br /&gt;
*在记录中设置一个专门的字段来表示它是否为锁定状态；&lt;br /&gt;
*设置一个专门的B+树或链，存放所有被用户锁定的记录的索引；&lt;br /&gt;
*用一个REF存放所有锁定记录的索引表。&lt;br /&gt;
或许还需要实现某种超级用户机制，以便特殊的用户可以解除对记录的锁定。&lt;br /&gt;
&lt;br /&gt;
这只是个例子，比如需要实现对表或表组，或知识组等的高级锁定。&lt;br /&gt;
&lt;br /&gt;
注意： 如果要在以共享方式打开的数据库文件中删除一个B+树，必须要使用高级锁定来确保没有其他用户打开这同一个树。 Visual Prolog系统中没有对一个B+树名是否因其被删除了而不可用做检查。&lt;br /&gt;
&lt;br /&gt;
===一个完整的文件共享实例===&lt;br /&gt;
在这个完整的例子中，可以看到如何通过使用自己实现的锁定系统来方便地共享文件。如果自己管理锁，可以避免不必要的文件锁定，其他用户不必因为文件锁定而长时间等待对文件的访问。&lt;br /&gt;
&lt;br /&gt;
该程序允许若干用户对一个共享的文件进行文本的创建、编辑、查看及删除。创建和编辑文本时，需要进行锁定直到编辑完成。文本锁定时，其他用户不能删除或编辑该文本，但可以查看它。用不同的 db_open和db_setretry设置运行这个程序，体验一下吧。&lt;br /&gt;
&lt;br /&gt;
===Visual Prolog文件共享的实现方面===&lt;br /&gt;
Visual Prolog中的文件共享很有效率，很快，因为其它用户对数据库进行了更新之后只是必需的数据库文件描述符部分要重新加载。前面已经介绍过，只是在特定环境下才需要进行文件缓冲区的重载及文件锁定，而数据库文件内部完善的管理保证了更新后只进行最小必要的磁盘操作。 &lt;br /&gt;
&lt;br /&gt;
数据库有一个六字节整数序列号，每次更新它都会增加并被写到磁盘上。db_begintransaction谓词会将当前的序列号副本与磁盘上的进行比较，如果不一样，就重载描述符。 锁使用的是一个可以容纳256个用户的数组。当一个用户需要访问文件时，就会为其在这个数组中分配一个未使用的位置，并在事务处理期间一直占用这个位置。这种方法允许文件同时有若干个读者进行访问。如果db_begintransaction是以AccessMode = readWrite方式调用的，则需要等待当前已有读者都从这个数组中退出并放弃所占用的位置之后，再锁定整个的数组，使其他用户不能再访问该文件。&lt;br /&gt;
&lt;br /&gt;
==小结==&lt;br /&gt;
Visual Prolog外部数据库系统为用户的数据库应用程序添加了强大、快速和高效的功能。在本文中主要介绍了：&lt;br /&gt;
&lt;br /&gt;
*外部数据库的项是存储在链中的，它可以直接用数据库索引号进行访问；索引号属于预定义的ref域。&lt;br /&gt;
*各数据库可以由数据库选择符来指定，它是属于标准域db_selector的。&lt;br /&gt;
*外部数据库可以放在两个地方，这与预定义的place域有关：&lt;br /&gt;
**in_file把数据库放在磁盘文件中，&lt;br /&gt;
**in_memory把数据库放在内在中。&lt;br /&gt;
*如果要在数据库中对项进行排序，会要用到B+树。与数据库一样，各个B+树是通过B+树选择符来指定的，它属于标准域bt_selector。 &lt;br /&gt;
*B+树节点的每个项由一个标识记录的键串（也叫索引）和与记录关联的数据库索引号构成。&lt;br /&gt;
*B+树的键组成页，而存储在一个节点上的键的数量是由树的阶指定的。&lt;br /&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>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84%E5%A4%96%E9%83%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F&amp;diff=4123</id>
		<title>Visual Prolog的外部数据库系统</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84%E5%A4%96%E9%83%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F&amp;diff=4123"/>
		<updated>2015-06-17T12:45:28Z</updated>

		<summary type="html">&lt;p&gt;Yiding: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的External Database System。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
下载 本文所用工程例子的源文件：&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 7.1 版. &lt;br /&gt;
&lt;br /&gt;
Visual Prolog 7.2 版. &lt;br /&gt;
&lt;br /&gt;
本教程讨论Visual Prolog的外部数据库系统。 外部数据库（external database） 是由外部链接项集合构成的，这些链可以让我们直接访问并非Prolog程序一部分的那种数据。 外部数据库既可以保存在文件中也可以保存在内存里，它支持B+树（一种可以快速取得数据及排序的数据结构），还通过内部的串行文件访问机制支持多用户访问。&lt;br /&gt;
&lt;br /&gt;
*Visual Prolog中的外部数据库 &lt;br /&gt;
*B+树&lt;br /&gt;
*外部数据库编程&lt;br /&gt;
*小结 &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Visual Prolog中的外部数据库==&lt;br /&gt;
&lt;br /&gt;
Visual Prolog中使用asserta、assertz、retract及retractall的内部事实数据库，用起来很简单，适用于很多场合。但是，它对内存的需求会很快地超出计算机的能力，外部数据库系统部分地就是设计用来解决面临的这个问题。举例来说，我们可能需要下述一种或多种的实现：&lt;br /&gt;
&lt;br /&gt;
*有大量记录的股票控制系统；&lt;br /&gt;
*有大量关系但仅有少量复杂结构记录的专家系统；&lt;br /&gt;
*在数据库中存储大量文本文件的文件系统；&lt;br /&gt;
*自己的数据库产品——它或许与关系数据库系统毫不相干——其中的数据相互链接而又没什么规则；&lt;br /&gt;
*包含上面若干种可能的一个系统；&lt;br /&gt;
Visual Prolog外部数据库系统支持这些不同类型的应用，并且能够满足数据库系统必须在更新操作时甚至是电源故障时不丢失数据的要求。&lt;br /&gt;
&lt;br /&gt;
Visual Prolog外部数据库谓词可以：&lt;br /&gt;
&lt;br /&gt;
*高效处理磁盘中的海量数据；&lt;br /&gt;
*将数据库放在文件中或内存中；&lt;br /&gt;
*进行多用户访问；&lt;br /&gt;
*提供比Visual Prolog自动回溯机制的顺序处理更好的数据处理灵活性；&lt;br /&gt;
*以二进制形式加载或保存外部数据库。&lt;br /&gt;
&lt;br /&gt;
===概览：外部数据库里有什么？===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog外部数据库是由两部分组成的：存储在链中的数据项——实际上是Prolog项，还有相应的用于快速访问数据项的B+树。&lt;br /&gt;
&lt;br /&gt;
外部数据库把数据项放在链中（而不是单独存放）以使相关项集中在一起。比如，一个链可能存放的是库存部件号而另一个链中含有客户名单。简单的数据库操作，如添加新项或替换删除旧项，是用不着B+树的。当要对数据排序或查找数据库中指定项时，才需要使用它，详细内容后面会介绍。&lt;br /&gt;
&lt;br /&gt;
====命名约定====&lt;br /&gt;
&lt;br /&gt;
所有涉及数据库管理的标准谓词，其命名有明确规则：&lt;br /&gt;
&lt;br /&gt;
*名称的第一部分（db_，chain_，term_，等等）指明了需要的输入,&lt;br /&gt;
*名称的第二部分（flush，btrees，delete，等等）表示动作，或是返回的内容，或是受影响的内容。&lt;br /&gt;
例如，db_delete是删除整个数据库，chain_delete是删除整个链，而term_delete只删除单独的一个项。&lt;br /&gt;
&lt;br /&gt;
[[Image:External_DB_01.gif]] &lt;br /&gt;
&lt;br /&gt;
图1：一个Visual Prolog外部数据库结构示例&lt;br /&gt;
&lt;br /&gt;
====外部数据库的选择====&lt;br /&gt;
&lt;br /&gt;
在磁盘上和内存中，可以同时存在多个外部数据库。利用这种特性，可以把外部数据库放在合适的地方以取得速度与占用内存空间的最佳均衡。&lt;br /&gt;
&lt;br /&gt;
为了区分多个打开的外部数据库，需要使用chainDB对象。打开或创建数据库时，调用chainDB类中适当的构造器，使用返回的对象进行数据库访问。&lt;br /&gt;
&lt;br /&gt;
===Chains（链）===&lt;br /&gt;
&lt;br /&gt;
外部数据库是Prolog terms（项）的集合。项，就是整数、实数、串、符号值以及复合对象，例如：32，-194，3.1417, &amp;quot;Wally&amp;quot;， wages，book(&amp;quot;dickens&amp;quot;, &amp;quot;Wally goes to the zoo&amp;quot;)等等。 &lt;br /&gt;
&lt;br /&gt;
在外部数据库内，项是存储在chains（链）中的。一个链里可以包含任意数量的项，而一个外部数据库又可以包含任意数量的链。每个链是用名称来区分的，而名称就是个串。&lt;br /&gt;
&lt;br /&gt;
下图是链结构示意图。&lt;br /&gt;
&lt;br /&gt;
[[Image:External_DB_02.gif]] &lt;br /&gt;
&lt;br /&gt;
图2：链的结构:&lt;br /&gt;
&lt;br /&gt;
数据库关系及数据库表模型的基础是项链。举例来说，假设有客户、供货商及一个部件数据库，要想把所有数据按三个关系放在一个数据库里。这就可以把客户放在一个链里，叫它customers；把供货商放在一个链里，叫suppliers；再把部件放在一个名为parts的链中。&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;
bt_selector： 由bt_open返回的B+树选择符值&lt;br /&gt;
&lt;br /&gt;
place： 数据库的位置，可以在内存中，也可以在文件中&lt;br /&gt;
&lt;br /&gt;
accessmode： 确定如何使用文件&lt;br /&gt;
&lt;br /&gt;
denymode： 确定别的用户可以怎么用文件 &lt;br /&gt;
&lt;br /&gt;
ref： 项在链中位置的索引号 &lt;br /&gt;
&lt;br /&gt;
====数据库索引号====&lt;br /&gt;
 &lt;br /&gt;
每个插入到外部数据库的项，Visual Prolog都会对其赋予一个database reference number（数据库索引号）。可以使用项的数据库索引号进行取出、移动或替换操作，还可以对链中的前一项或后一项进行类似的操作。可以把数据库索引号插入到一个B+树中（后面会有介绍），再用这个B+对项进行排序或对项做快速搜索。&lt;br /&gt;
&lt;br /&gt;
数据库索引号与数据库放在什么地方以及对数据库做了些什么操作都没有关系。一旦它与项关联之后，不管数据库做了什么，都可以用这个号去访问对应的项，一直到这个项被删除为止。&lt;br /&gt;
&lt;br /&gt;
====ref域====&lt;br /&gt;
&lt;br /&gt;
数据库索引号是很特殊的，可以把它们插入到事实段中，也可以用stdIO::write或stdIO::writef把它们写出来，但却不能从键盘上输入它们。对处理数据库索引号的谓词，它们作为参数时必须声明为预定义的ref域。&lt;br /&gt;
&lt;br /&gt;
用term_delete删除了一个项之后，系统会在一个新项插入到外部数据库时重用刚删除的那个项的数据库索引号，这是自动进行的。但如果索引号已经存入事实段或一个B+树中时，保证索引号与对应项的正确关联就是编程人员的责任了。&lt;br /&gt;
&lt;br /&gt;
有一个错误检查选项对此会有帮助，这个选项是用谓词db_reuserefs激活的。db_reuserefs(1)可以激活对释放项的检查，用db_reuserefs(0)可以关闭这个检查。激活检查的开销不大（每项多四个字节，几乎不增加CPU开销），但增加的四个字节总也不会释放了。如果经常地创建和删除项，则数据库就会持续地变大。db_reuserefs的主要目的还是帮助程序开发过程中跟踪定位错误。&lt;br /&gt;
&lt;br /&gt;
===操控整个外部数据库===&lt;br /&gt;
&lt;br /&gt;
创建新的或是打开已有的外部数据库时，可以把数据库放在文件里，也可以放在内存里，这是由调用db_create或db_openon时的参数Place的值决定的。外部数据库使用完毕后，调用db_close关闭它。chainDB接口谓词的相关信息可以参看Visual Prolog帮助文件中的PFC章节。&lt;br /&gt;
&lt;br /&gt;
当外部数据库放在内存中时，用db_close关闭数据库并不会将其从内存中删除。如果要释放数据库占用的内存，需要明确地调用db_delete才行。这样的外部数据库如果只是关闭了但并没有删除，稍后还可以用db_open谓词再重新打开它。&lt;br /&gt;
&lt;br /&gt;
例如，下面是两个不同的db_create调用：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;ChainDbObj1 = chaindb::db_create(&amp;quot;MYFILE.DBA&amp;quot;, in_file),&lt;br /&gt;
        /* 创建磁盘文件MYFILE.DBA */&lt;br /&gt;
ChainDbObj2 = chaindb::db_create(&amp;quot;SymName2&amp;quot;, in_memory),&lt;br /&gt;
        /* 创建内存数据库SymName2 */&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
而下面的这db_copy调用： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;ChainDbObj:db_copy(&amp;quot;dd.dat&amp;quot;, in_file)&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Visual Prolog会把指定的数据库ChainDbObj拷贝到放在硬盘的新数据库文件 &amp;quot;dd.dat&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
数据库拷贝时，原来的仍在那里，这时就会有两份相同的数据库，直到明确地删除一个为止。&lt;br /&gt;
&lt;br /&gt;
数据库的移动不会影响对它的任何处理，因为所有外部数据库的索引号仍是有效的。因此，如果起先一个数据库是放在内存中的，运行时感到内存紧张了又把它拷贝到文件中，这不会影响对这个数据库的任何操作。对内存数据库建立的索引，即使把数据库拷贝到文件中后，还是一样可用的。&lt;br /&gt;
&lt;br /&gt;
db_copy有多种用途，可以：&lt;br /&gt;
&lt;br /&gt;
*从磁盘加载数据库到内存，以后再把它保存为二进制形式，而不是用 file::save 和 file::consult 保存为文本文件。 &lt;br /&gt;
*把合适规模的数据库从磁盘拷贝到内存中以加快访问速度。&lt;br /&gt;
*压缩数据库：数据库拷贝到另一个文件时，会消去所有末使用的空间。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_openinvalid/3====&lt;br /&gt;
&lt;br /&gt;
db_openinvalid可以打开已经标记为无效的数据库。如果在数据库更新时发生了电源故障，数据库中的数据可能会因为部分缓冲区中的数据还没有写到磁盘而全部丢失。数据库有一个标志指示更新后是否为无效有状态。&lt;br /&gt;
&lt;br /&gt;
调用任何一个要改变数据库内容的谓词之后，数据库都会被标记为无效。这些谓词有chain_inserta、chain_insertz、chain_insertafter、term_replace、term_delete、chain_delete、bt_create、key_insert及key_delete。一旦数据库用db_close关闭，或是调用了db_flush清空缓冲区，则数据库就会被标记为有效。&lt;br /&gt;
&lt;br /&gt;
用db_openinvalid，有时可以对标记为无效的数据库进行操作。如果没有以前的备份数据可用，这或许可以恢复部分数据。但是，对用db_openinvalid打开的数据库进行的所有操作，都有可能产生预想不到的结果。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_flush/0====&lt;br /&gt;
&lt;br /&gt;
db_flush把缓冲区的内容写到相应的数据库中并清空缓冲区。数据库被更新时，会被标记为无效状态，直到db_flush或关闭。&lt;br /&gt;
&lt;br /&gt;
对数据库要采取怎样的安全措施，当然与数据的重要程度有关。最起码的数据安全措施是在磁盘上保留备份。中等一些的，可以在每次重要的数据库更新之后调用db_flush。不过清缓冲区是相当费时的一个操作，如果用得太多数据库系统就会慢慢停下来。如果外部数据库的内容是非常重要的，还可以用一个特殊的登记文件把所有更动记录下来，或是在不同的磁盘上维护两个一样的数据库。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_close/0====&lt;br /&gt;
&lt;br /&gt;
调用db_close会关闭打开的数据库。如果数据库是放在磁盘上的文件，则文件会关闭。数据库关闭时不会被删除，就是放在内存的数据库也不会删除，还可以通过调用db_open重新打开它。可以用db_delete删除在内存中的已经关闭了的数据库。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_delete/2====&lt;br /&gt;
&lt;br /&gt;
数据库放在内存中时，db_delete会释放它所占用的空间。&lt;br /&gt;
如果数据库是放在文件中的，db_delete会删除这个文件。如果在指定的地方没有相应的数据库文件，则db_delete会给出一个错误消息。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_garbagecollect/0====&lt;br /&gt;
&lt;br /&gt;
db_garbagecollect扫视整个数据库垃圾回收列表的自由空间，并尽可能把若干碎片合成一个大的自由空间。数据库放在内存中时，这种扫视与合并是自动进行的。 &lt;br /&gt;
&lt;br /&gt;
正常情况下，不需要调用这个谓词。但当插入新项时如果数据库中有太多的回收空间没有得到重新利用，db_garbagecollect有助于得到可利用的空间。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_btrees/1====&lt;br /&gt;
&lt;br /&gt;
回溯时，db_btrees会连续将BtreeName约束为指定数据库中各个B+树的名称。&lt;br /&gt;
&lt;br /&gt;
各个名称是依排定的顺序返回的。关于B+树后面还要介绍。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_chains/1====&lt;br /&gt;
&lt;br /&gt;
回溯时，db_chains会连续将ChainName约束为指定数据库中各个链的名称。各个名称是依排定的顺序返回的。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_statistics/4====&lt;br /&gt;
&lt;br /&gt;
db_statistics返回数据库的统计信息。 各参数所表示的意义如下：&lt;br /&gt;
&lt;br /&gt;
参数NoOfTerms表示数据库中的总项数。参数MemSizeis表示数据库存放于内存中的内表所占字节数。参数DbaSizeis表示数据库中项与描述符占用的总字节数。如果数据库是在文件中，而这个值比该文件占用的字节数小很多，这个文件就应该可以用db_copy进行压缩。参数FreeSize表示空闲内存的值，依数据库当前位置不同这个值也会不同：&lt;br /&gt;
&lt;br /&gt;
*数据库中内存中时，FreeSize表示全局栈顶与堆顶之间空闲的字节数（注意：可能还有若干空间的字节数没有包含在这个值中）。&lt;br /&gt;
*数据库在文件中时，FreeSize表示硬盘文件中未使用的字节数。&lt;br /&gt;
&lt;br /&gt;
===操控链===&lt;br /&gt;
&lt;br /&gt;
To要把一个项插入到外部数据库的链中，可以使用谓词 chain_inserta、chain_insertz 或 chain_insertafter。可以用 chain_terms 连续地把它的参数约束为链的项及其索引号，还可以用 chain_delete 删除外部数据库中某个链的所有项。&lt;br /&gt;
&lt;br /&gt;
还有四个标准谓词可以返回数据库的索引号，这四个标准谓词是：chain_first、chain_last、chain_next 和 chain_prev。&lt;br /&gt;
&lt;br /&gt;
===操控项===&lt;br /&gt;
&lt;br /&gt;
有三个外部数据库管理的标准谓词都与项有关，它们是：term_replace、term_delete 和 ref_term。无论调用哪个与项有关的外部数据库标准谓词，都需要将项的域作为一个参数给出来。正因为如此，所以把一个数据库中所有项的域声明为单个域中的可选项会是个好办法。例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;domains&lt;br /&gt;
    terms_for_my_stock_control_database =&lt;br /&gt;
        customer(Customer, Name, ZipCode, Address);&lt;br /&gt;
        supplier(SupplierNo, Name, Address);&lt;br /&gt;
        parts(PartNo, Description, Price, SupplierNo).&amp;lt;/vip&amp;gt;&lt;br /&gt;
注意，对外部数据库没有类型（域）上的混杂限制要求。一个链可以含有文本串而另一个链上的可以是整数，第三个链中还可以是某种复合结构，等等。但是，外部数据库的数据项并不是按类型描述符存放的，比如一个整数未必就是刚好占用两个字节。取回数据时恰好就是存放时的那种样子（同样的域），这是编程人员应该保证的。如果弄混了域，会产生运行时错误。&lt;br /&gt;
&lt;br /&gt;
==B+树==&lt;br /&gt;
&lt;br /&gt;
B+树是一种数据结构，用于实现快速有效地对大量数据进行分类排序的方法。B+树还有比较高效的搜索算法。可以认为B+树对数据库提供了一个索引，这也是为什么有时它又被称为“索引”的原因。&lt;br /&gt;
&lt;br /&gt;
在Visual Prolog中，B+树是在外部数据库中的。B+树的每个项有一对值：一个键串和相关联的数据库索引号。创建数据库时，先在数据库中插入记录并为该记录建立一个键。Visual Prolog B+树谓词接着就可以用来把这个键及该记录相应的数据库索引号插入到一个B+树中。&lt;br /&gt;
&lt;br /&gt;
要搜索数据库中的某个记录时，只需要找到该记录的键，而B+树会给出相应的索引号。用这个索引号，就可以从数据库中得到需要的记录。当B+树增长变化时，它会保持键的顺序，这也就意味着我们可以很容易地得到排序好的记录列表。&lt;br /&gt;
&lt;br /&gt;
B+树类似于二进制树，只不过在B+树的各个节点上保存了不止一个键串。B+树也是平衡树，这就是说对树中各个叶上的每个键的搜索路径长度是一样的。由于这个特点，对于在数以百万计的键中搜索某个键是有保证的，就是在最坏的情况下，需要对磁盘进行访问，也只需要不多的几次，这和每个节点上保存有多少键相关。&lt;br /&gt;
&lt;br /&gt;
尽管B+树是放在外部数据库中的，但并非一定要与它所指的项放在同一个数据库中，可以是一个数据库中含有一些链，而另一个数据库放指向这些链中项的B+树。&lt;br /&gt;
&lt;br /&gt;
===Pages(页） Order（阶） Key Length（键长）===&lt;br /&gt;
&lt;br /&gt;
在B+树中，键是聚合在页里的，每页有相同的大小，所有的页都包含有同样数量的键，这意味着所有为B+树保存的键必须大小相同。键的大小取决于KeyLen参数，这是在创建B+树时需要指定的一个参数。如果要在B+树中插入比KeyLen还要长的串，Visual Prolog会把多出来的部分截去。通常应该尽可能选小一些的KeyLen值以节省空间、提高速度。&lt;br /&gt;
&lt;br /&gt;
创建B+树时还需要指定称为阶的一个参数。这个参数决定了树的各个节点可以存储多少个键，而通常它是需要反复试验才能取得最佳值。阶的起始值用4是不错的选择，它允许各节点上保存4～8个键。阶的值必须要通过实验来选取，因为B+树的搜索速度与键长与阶的值、B+中键的数量以及计算机硬件配置都有关系。&lt;br /&gt;
&lt;br /&gt;
===重复键===&lt;br /&gt;
 &lt;br /&gt;
设置B+树时，需要允许键的重复。比如，设置一个客户数据库的B+树，用客户的姓做键，键就会有重复出现的可能。由于这个原因，B+树是可以有重复键的。&lt;br /&gt;
&lt;br /&gt;
要删除数据库中的一个项，需要给出相应的B+树键及数据库索引号（因此键重复也不会有问题）。&lt;br /&gt;
&lt;br /&gt;
===多重操作===&lt;br /&gt;
&lt;br /&gt;
为了能对B+进行多重操作，每个B+树内部都有多个指针，允许对一个树多次打开。不过要注意，如果更新了一个B+树的拷贝，而此时又有已经打开了的副本，则副本的指针会重定位到树的顶端。&lt;br /&gt;
&lt;br /&gt;
===B+树的标准谓词===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog提供了一些标准谓词来处理B+树，这些谓词的工作方式与相应的 db_... 谓词是一致的。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_create/4 and chainDB::bt_create/5====&lt;br /&gt;
&lt;br /&gt;
调用这个谓词可以创建一个新的B+树。&lt;br /&gt;
&lt;br /&gt;
参数BtreeName指定新树的名称，以后可以把这个名称作为参数用于bt_open。B+树的KeyLen参数和Order参数是在树创建时给出的，以后不能再更改。如果调用bt_create/4或bt_create/5时参数Duplicates设为1，则在这个B+树中允许重复；若是设为0，则不能在该树中插入重复的项。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_open/2====&lt;br /&gt;
&lt;br /&gt;
bt_open打开名为BtreeName的已经创建了的B+树。&lt;br /&gt;
&lt;br /&gt;
打开一个B+树时，这个调用会返回一个用于该B+树的选择器Btree_Sel，它就是该B+树的引用，无论系统的搜索或定位操作都要用到它。B+树的名称与选择器之间的关系就如同一个实际的文件名称与相应的符号文件名之间的关系。&lt;br /&gt;
&lt;br /&gt;
可以多次打开同一个B+树以便同时进行多项操作。每打开一次该B+树，就分配一个描述符，各个描述符都对应有自己的B+树内部指针。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_close/1 and chainDB::bt_delete/1====&lt;br /&gt;
&lt;br /&gt;
可以用bt_close关闭一个打开的B+树，也可以用bt_delete删除整个B+树。&lt;br /&gt;
&lt;br /&gt;
调用bt_close会释放打开的名为BtreeName的B+树分配的内部缓冲区。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_copyselector/2====&lt;br /&gt;
&lt;br /&gt;
bt_copyselector对已经打开的B+树复制一个新的选择器。&lt;br /&gt;
&lt;br /&gt;
新选择器开始时其指针定位在与老的选择器相同的位置上。之后，B+树的两个选择器定位就都可以自行确定，互不相干。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_statistics/7====&lt;br /&gt;
&lt;br /&gt;
bt_statistics返回由Btree_Sel指定的B+树的统计信息。各参数意义如下：&lt;br /&gt;
&lt;br /&gt;
参数Btree_Sel是该B+树的bt_selector；NumKeys是该树中键的总数；NumPages是该树的总页数；Depth是该树的深度；KeyLen是该树键长；Order是该树的阶；PgSize是页大小（以字节计）。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_insert/3 and chainDB::key_delete/3====&lt;br /&gt;
&lt;br /&gt;
标准谓词key_insert和key_delete用于更改B+树。&lt;br /&gt;
&lt;br /&gt;
给定Key和Ref时，可以删除有重复项的B+树中的某个确定项。&lt;br /&gt;
&lt;br /&gt;
chainDB::key_first/2, chainDB::key_last/2, and chainDB::key_search/3&lt;br /&gt;
每个B+树都维护有指向自身节点的内部指针。key_first和key_last可以分别把指针置于树的第一个键和最后一个键的位置上，而key_search则可以把指针置于指定键的位置上。&lt;br /&gt;
&lt;br /&gt;
如果找到了指定的键，key_search就成功；反之则失败，并将B+的内部指针置于该指定键应该存放的位置之后。这时可以用key_current返回这个键及其数据库索引号。如果想要在一个有重复项的B+树中准确定位，也可以把索引号做为一个条件输入。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_next/2 and chainDB::key_prev/2====&lt;br /&gt;
&lt;br /&gt;
可以用谓词key_next和key_prev在经过排序树中向前或向后移动B+树指针。&lt;br /&gt;
&lt;br /&gt;
If如果已经到了树的端头，想进一步移动指针会引起失败，但指针本身还是会移出树外。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_current/3====&lt;br /&gt;
&lt;br /&gt;
key_current返回B+树当前指针指向的键及其数据库索引号。&lt;br /&gt;
&lt;br /&gt;
调用谓词bt_open、bt_create、key_insert或key_delete之后立即使用key_current谓词会失败，当指针由于key_prev或key_next已经位于第一个键之前或最后一个键之后使用key_current谓词也会失败。&lt;br /&gt;
&lt;br /&gt;
==外部数据库编程==&lt;br /&gt;
&lt;br /&gt;
本节要介绍使用Visual Prolog外部数据库系统的一些基本方法与原则。主要内容有：&lt;br /&gt;
&lt;br /&gt;
*“遍历数据库” 介绍顺序扫遍外部数据库中的链或B+树的方法。&lt;br /&gt;
*“数据库的防护” 说明如何应对没想到的电源故障及其它可能的问题以保护数据库。&lt;br /&gt;
*“使用B+树内部指针” 说明如何在打开的B+树中使用定位指针的一些谓词。&lt;br /&gt;
&lt;br /&gt;
===遍历数据库===&lt;br /&gt;
在使用数据库系统时，头脑中一定要清楚Visual Prolog的存储机制。每当Visual Prolog用ref_term谓词从外部数据库中取回一个项时，会把这个项放在全局栈中。这个项占用的空间，系统要一直等到程序失败并回溯到ref_term调用之前的点才会释放。这意味着，要想连续地遍历外部数据库中某个链，总应该使用下面这样的结构：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 连续访问一个链所用的结构 */&lt;br /&gt;
scan(ChainObj, Chain, ....) :-&lt;br /&gt;
    ChainObj:chain_first(Chain, Ref),&lt;br /&gt;
    scanloop(ChainObj, Ref).&lt;br /&gt;
scanloop(ChainObj, Ref) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:ref_term(Ref, Term),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scanloop(ChainObj, _) :-&lt;br /&gt;
    ChainObj:chain_next(Ref, NextRef),&lt;br /&gt;
    scanloop(ChainObj, NextRef).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
同样，要遍历索引，应该用如下的结构：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 连续访问索引所用的结构 */&lt;br /&gt;
scan(ChainObj, Bt_selector) :-&lt;br /&gt;
    ChainObj:key_first(Bt_selector, FirstRef),&lt;br /&gt;
    scanloop(ChainObj, Bt_selector, FirstRef).&lt;br /&gt;
scanloop(ChainObj, Bt_selector, Ref) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:ref_term(Ref, Term),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scanloop(ChainObj, Bt_selector, _) :-&lt;br /&gt;
    ChainObj:key_next(Bt_selector, NextRef),&lt;br /&gt;
    scanloop(ChainObj, Bt_selector, NextRef).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以用chain_terms来遍历外部数据库中的链，像这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 遍历链的另一种方法 */&lt;br /&gt;
scan(ChainObj, Chain) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:chain_terms(Chain, Term, Ref),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scan(_, _).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
要遍历B+树，可以通过已经定义和使用过的bt_keys谓词来实现。回溯时，这个谓词就会逐一返回（指定B+和数据库）各个键及相应的数据库索引号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/vip&amp;gt;predicates&lt;br /&gt;
    bt_keys : (chainDB, bt_selector, string, ref).&lt;br /&gt;
    bt_keysloop : (chainDB, bt_selector, string, ref).&lt;br /&gt;
clauses&lt;br /&gt;
    bt_keys(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_first(Bt_selector, _),&lt;br /&gt;
        bt_keysloop(ChainObj, Bt_selector, Key, Ref).&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_current(Bt_selector, Key, Ref).&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_next(Bt_selector, _),&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref).&amp;lt;/vip&amp;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;
===使用B+树的内部指针===&lt;br /&gt;
&lt;br /&gt;
每个打开的B+树都有一个指向其节点的指针，打开或更新B+树时，这个指针指向树的起点之前；当指针指向树的最后一个键时，调用key_next会使指针指向树的末端之后的位置。只要指针超出了树，key_current就都会失败。如果这样一些安排不能满足特定应用程序的要求，就需要自行构造其它的谓词了。&lt;br /&gt;
&lt;br /&gt;
比如，可以构造像下面这样一些mykey_next、mykey_prev、mykey_search的谓词，让B+树指针总是在B+树的内部（如果树中有一些键的话）。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    mykey_next(db_selector, bt_selector, ref).&lt;br /&gt;
    mykey_prev(db_selector, bt_selector, ref).&lt;br /&gt;
    mykey_search(db_selector, bt_selector, string, ref).&lt;br /&gt;
clauses&lt;br /&gt;
    mykey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref), !.&lt;br /&gt;
    mykey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref), fail.&lt;br /&gt;
    mykey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref), !.&lt;br /&gt;
    mykey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref), fail.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, Key, Ref) :-&lt;br /&gt;
        Dba:key_search(Bt_selector, Key, Ref), !.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, _, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, _, Ref), !.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, _, Ref) :-&lt;br /&gt;
        Dba:key_last(Bt_selector, Ref).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
还可以使用下面定义的samekey_next和samekey_prev谓词，将索引指针定位到具有重复键的B+树中同一键值的下一个键上：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    samekey_next(db_selector, bt_selector, ref).&lt;br /&gt;
    try_next(db_selector, bt_selector, ref, string).&lt;br /&gt;
    samekey_prev(db_selector, bt_selector, ref).&lt;br /&gt;
    try_prev(db_selector, bt_selector, ref, string).&lt;br /&gt;
clauses&lt;br /&gt;
    samekey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, OldKey, _),&lt;br /&gt;
        try_next(Dba, Bt_selector, Ref, OldKey).&lt;br /&gt;
    try_next(Dba, Bt_selector, Ref, OldKey) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref),&lt;br /&gt;
        Dba:key_current(Bt_selector, NewKey, _),&lt;br /&gt;
        NewKey = OldKey, !.&lt;br /&gt;
    try_next(Dba, Bt_selector, _, _) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, _),&lt;br /&gt;
        fail.&lt;br /&gt;
    samekey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, OldKey, _),&lt;br /&gt;
        try_prev(Dba, Bt_selector, Ref, OldKey).&lt;br /&gt;
    try_prev(Dba, Bt_selector, Ref, OldKey) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref),&lt;br /&gt;
        Dba:key_current(Bt_selector, NewKey, _),&lt;br /&gt;
        NewKey = OldKey, !.&lt;br /&gt;
    try_prev(Dba, Bt_selector, _, _) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, _),&lt;br /&gt;
        fail.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===外部数据库与文件共享===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog支持文件共享型外部数据库，也就是说一个文件可以被若干用户或是进程同时打开，这对在局域网中或多任务平台上使用外部数据库是很有用处的。&lt;br /&gt;
&lt;br /&gt;
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;
域accessMode = read或readWrite；域denyMode = denyNone或denyWrite或denyAll。 &lt;br /&gt;
&lt;br /&gt;
===共享模式下打开数据库===&lt;br /&gt;
&lt;br /&gt;
为了使打开的外部数据库可以共享，需要使用四元维版本的db_open打开已有的数据库文件，并指定AccessMode及DenyMode。 &lt;br /&gt;
&lt;br /&gt;
如果AccessMode是read，打开的文件就只能读，任何对文件的更新都会导致运行时错误；如果是readwrite，该文件就可读可写。AccessMode在谓词db_begintransaction中也会用到。&lt;br /&gt;
&lt;br /&gt;
如果DenyMode是denyNone，那么所有其他的用户都可以更新和读取该文件；若是denyWrite，则其他用户不能以 AccessMode = readWrite 模式打开该文件，但如果该文件是按 AccessMode = readWrite 模式打开的，还是可以对文件进行更新的。如果db_open是带 DenyMode = denyAll 的，则任何其他用户都不能访问该文件。&lt;br /&gt;
&lt;br /&gt;
打开文件的第一个用户使用的DenyMode，决定了后续所有打开该文件的条件，如果试图用不相容的模式打开该文件，就会产生运行时错误。下表归纳了打开文件的结果及后续再打开同一个文件时的各种情况：&lt;br /&gt;
&lt;br /&gt;
Domain（域）  用途  &lt;br /&gt;
bt_selector  bt_open返回的用于选取B+树的唯一值  &lt;br /&gt;
place  数据库的位置：内存中或是文件中 &lt;br /&gt;
accessmode  确定文件的使用方式 &lt;br /&gt;
denymode  确定其他用户可以打开该文件的方式 &lt;br /&gt;
ref  项在链中位置的索引 &lt;br /&gt;
&lt;br /&gt;
===Transactions（事务处理）与文件共享===&lt;br /&gt;
&lt;br /&gt;
如果一个数据库文件是打开共享的，则所有以任何方式访问数据库文件的数据库谓词必须要集中在事务处理内部，它是通过把对这些谓词的调用包围在db_begintransaction和db_endtransaction之间来实现的。&lt;br /&gt;
&lt;br /&gt;
根据选择的AccessMode和DenyMode不同，共享的文件有可能在事务处理期间会被锁定。再根据锁定的程度，事务处理时其他用户可能还不能读或更新该文件。这对于避免读写的混乱当然是必要的，但文件共享要有意义就一定不要有过多的锁定。这可以通过尽量减小（缩短）事务处理来实现，只把那些访问数据库的谓词包含在事务处理中。&lt;br /&gt;
&lt;br /&gt;
涉及文件共享的事务处理这个概念非常重要，两者之间的需求是矛盾的，即同时要实现数据库的安全和最少的文件锁定。&lt;br /&gt;
&lt;br /&gt;
db_begintransaction是确保数据库一致性的，它实现文件的适当锁定，文件可由若干用户同时读但只允许有一个进程在当下更新数据库。谓词db_setretry可以用来设置db_begintransaction在返回运行时错误之前需要等待多长时间才可以访问该文件。当文件AccessMode是按read打开的，而调用db_begintransaction时AccessMode设成了readWrite也会产生运行时错误。如果已经调用了db_begintransaction，对同一个数据库调用新的db_begintransaction之前必须调用db_endtransaction，否则就会出现运行时错误。&lt;br /&gt;
&lt;br /&gt;
下表归纳了db_begintransaction对不同的AccessMode和DenyMode组合所做的反应：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;AccessMode read readWrite DenyMode denyNone WLock\Reload RWLock\Reload denyWrite None RWLock denyAll None NoneActions:&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
WLock ：不允许写，只允许读。&lt;br /&gt;
RWLock ：读和写都不允许。&lt;br /&gt;
Reload ：重新载入文件描述符。&lt;br /&gt;
由于重新载入及锁定都是需要时间的，所以要小心选用AccessMode和DenyMode。比如，若不会有用户更新数据库，将AccessMode设为read而DenyMode设为denyWrite就可以使开销最小化。&lt;br /&gt;
 &lt;br /&gt;
===文件共享谓词===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog中有一些文件共享谓词，它们是db_open、db_begintransaction、db_endtransaction、db_updated、bt_updated和db_setretry。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_begintransaction/1====&lt;br /&gt;
这个谓词标记事务处理的起点，应该在访问共享的数据库之前调用它，哪怕是以denyAll打开的数据库，也应该调用它。db_begintransaction调用时AccessMode必须为read或readwrite。进一步的了解可以参看PFC的帮助文件。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_endtransaction/0====&lt;br /&gt;
db_endtransaction标记一个事务处理的结束并解锁数据库。必须在每个db_begintransaction调用之后且在下一个db_begintransaction调用之前调用db_endtransaction。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_updated/0====&lt;br /&gt;
如果有其他用户更新了数据库，db_begintransaction的调用会保持数据库的一致性。调用谓词db_updated可以检查变更情况：如果在一个事务处理中调用它，而db_begintransaction之后有其他用户更新过数据库，就会成功；如果没有更新，就会失败。如果在事务处理之外调用这个谓词，就会产生运行时错误。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_updated/1====&lt;br /&gt;
这个谓词很像db_updated，只不过它是在指定的B+树更新了才会成功。 &lt;br /&gt;
&lt;br /&gt;
====chainDB::db_setretry/2====&lt;br /&gt;
如果由于某个进程锁定了一个文件而导致另一个进程访问该文件被拒绝，这另一个进程可以进入一个等待期，期满后再试着访问该文件。谓词db_setretry可以改变缺省的SleepPeriod设置，这是以百分之一秒为单位的等待期；这个谓词还可以设置RetryCount，这是重试访问的最大次数。缺省的设置，RetryCount是100而SleepPeriod是10。&lt;br /&gt;
&lt;br /&gt;
===文件共享时的编程===&lt;br /&gt;
使用文件共享谓词一定要十分小心。尽管使用恰当时它们能够保证共享数据库低层的一致性，但防止某个程序危害高层的一致性则是应用程序编写人员的责任。这中间所用的所谓“事务处理（transaction）”是将文件访问集中在一起的一个方法，但一定要清楚它并没有提供什么恢复机制，不论是软件还是硬件失效引起的程序中断，都有可能导致数据库文件的不完整。&lt;br /&gt;
&lt;br /&gt;
若干进程共享一个数据库时，还需要特别注意涉及到的域，它们必须要严格一致，使用时顺序也要一致。&lt;br /&gt;
&lt;br /&gt;
为避免不要地锁定数据库文件，事务处理一定要尽量短小以使文件锁定时间尽可能短。同时，要把对数据库项的定位与访问的谓词放在一个事务处理中，这也很重要。 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;.....&lt;br /&gt;
ChainObj:db_begintransaction(readWrite),&lt;br /&gt;
  ChainObj:key_current(Btree, Key, Ref),&lt;br /&gt;
  ChainObj:ref_term(Ref, Term),&lt;br /&gt;
ChainObj:db_endtransaction(),&lt;br /&gt;
write(Term),&lt;br /&gt;
.....&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
上面的代码中，谓词key_current和ref_term不应该分开放在不同的事务处理中，因为Ref中存储的项可能会在不同事务处理间就被其他用户删除了。 &lt;br /&gt;
&lt;br /&gt;
当B+树被别的用户更新并且文件缓冲区重新加载后，B+树的指针会重新定位到第一项之前。调用谓词bt_updated可以确定是否需要重新对B+树指针定位。把当前的键暂存在内部数据库中，既可以列表完整的索引号，同时也可以使用事务处理短小。 &lt;br /&gt;
&lt;br /&gt;
===实现高级锁定===&lt;br /&gt;
对于一个共享的数据库文件，用户可以进行所有独自享有该文件时相同的操作。把对文件的访问集中于db_begintransaction和db_endtransaction的内部，可以保证Visual Prolog系统自身描述符表的一致性。但是，在网络环境中多用户条件下应用程序的各种逻辑约束, 需要在较高层级上由编程人员确保得以实现。&lt;br /&gt;
&lt;br /&gt;
W我们把这称之为高层级的锁定或应用程序级的锁定。通过使用简单的db_begintransaction和db_endtransaction，可以有多种方法实现高级锁定的方法。&lt;br /&gt;
&lt;br /&gt;
需要使用高级锁定的一个很普通的例子，就是用户想要改变数据库中记录的时候。确定要改变一个记录时，就需要采取一些步骤使应用程序对该记录锁定，直到更改结束、新的记录写回到磁盘中，然后对该记录解锁。&lt;br /&gt;
&lt;br /&gt;
下面是对应用程序级锁定的一些建议：&lt;br /&gt;
&lt;br /&gt;
*在记录中设置一个专门的字段来表示它是否为锁定状态；&lt;br /&gt;
*设置一个专门的B+树或链，存放所有被用户锁定的记录的索引；&lt;br /&gt;
*用一个REF存放所有锁定记录的索引表。&lt;br /&gt;
或许还需要实现某种超级用户机制，以便特殊的用户可以解除对记录的锁定。&lt;br /&gt;
&lt;br /&gt;
这只是个例子，比如需要实现对表或表组，或知识组等的高级锁定。&lt;br /&gt;
&lt;br /&gt;
注意： 如果要在以共享方式打开的数据库文件中删除一个B+树，必须要使用高级锁定来确保没有其他用户打开这同一个树。 Visual Prolog系统中没有对一个B+树名是否因其被删除了而不可用做检查。&lt;br /&gt;
&lt;br /&gt;
===一个完整的文件共享实例===&lt;br /&gt;
在这个完整的例子中，可以看到如何通过使用自己实现的锁定系统来方便地共享文件。如果自己管理锁，可以避免不必要的文件锁定，其他用户不必因为文件锁定而长时间等待对文件的访问。&lt;br /&gt;
&lt;br /&gt;
该程序允许若干用户对一个共享的文件进行文本的创建、编辑、查看及删除。创建和编辑文本时，需要进行锁定直到编辑完成。文本锁定时，其他用户不能删除或编辑该文本，但可以查看它。用不同的 db_open和db_setretry设置运行这个程序，体验一下吧。&lt;br /&gt;
&lt;br /&gt;
===Visual Prolog文件共享的实现方面===&lt;br /&gt;
Visual Prolog中的文件共享很有效率，很快，因为其它用户对数据库进行了更新之后只是必需的数据库文件描述符部分要重新加载。前面已经介绍过，只是在特定环境下才需要进行文件缓冲区的重载及文件锁定，而数据库文件内部完善的管理保证了更新后只进行最小必要的磁盘操作。 &lt;br /&gt;
&lt;br /&gt;
数据库有一个六字节整数序列号，每次更新它都会增加并被写到磁盘上。db_begintransaction谓词会将当前的序列号副本与磁盘上的进行比较，如果不一样，就重载描述符。 锁使用的是一个可以容纳256个用户的数组。当一个用户需要访问文件时，就会为其在这个数组中分配一个未使用的位置，并在事务处理期间一直占用这个位置。这种方法允许文件同时有若干个读者进行访问。如果db_begintransaction是以AccessMode = readWrite方式调用的，则需要等待当前已有读者都从这个数组中退出并放弃所占用的位置之后，再锁定整个的数组，使其他用户不能再访问该文件。&lt;br /&gt;
&lt;br /&gt;
==小结==&lt;br /&gt;
Visual Prolog外部数据库系统为用户的数据库应用程序添加了强大、快速和高效的功能。在本文中主要介绍了：&lt;br /&gt;
&lt;br /&gt;
*外部数据库的项是存储在链中的，它可以直接用数据库索引号进行访问；索引号属于预定义的ref域。&lt;br /&gt;
*各数据库可以由数据库选择符来指定，它是属于标准域db_selector的。&lt;br /&gt;
*外部数据库可以放在两个地方，这与预定义的place域有关：&lt;br /&gt;
*in_file把数据库放在磁盘文件中，&lt;br /&gt;
*in_memory把数据库放在内在中。&lt;br /&gt;
*如果要在数据库中对项进行排序，会要用到B+树。与数据库一样，各个B+树是通过B+树选择符来指定的，它属于标准域bt_selector。 &lt;br /&gt;
*B+树节点的每个项由一个标识记录的键串（也叫索引）和与记录关联的数据库索引号构成。&lt;br /&gt;
*B+树的键组成页，而存储在一个节点上的键的数量是由树的阶指定的。&lt;br /&gt;
*文件共享是通过将访问数据库的谓词组合在事务处理中实现的。&lt;br /&gt;
&lt;br /&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>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84%E5%A4%96%E9%83%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F&amp;diff=4122</id>
		<title>Visual Prolog的外部数据库系统</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84%E5%A4%96%E9%83%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F&amp;diff=4122"/>
		<updated>2015-06-17T12:43:48Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* Chains（链） */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Visual Prolog External Database System。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
下载 本文所用工程例子的源文件：&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 7.1 版. &lt;br /&gt;
&lt;br /&gt;
Visual Prolog 7.2 版. &lt;br /&gt;
&lt;br /&gt;
本教程讨论Visual Prolog的外部数据库系统。 外部数据库（external database） 是由外部链接项集合构成的，这些链可以让我们直接访问并非Prolog程序一部分的那种数据。 外部数据库既可以保存在文件中也可以保存在内存里，它支持B+树（一种可以快速取得数据及排序的数据结构），还通过内部的串行文件访问机制支持多用户访问。&lt;br /&gt;
&lt;br /&gt;
*Visual Prolog中的外部数据库 &lt;br /&gt;
*B+树&lt;br /&gt;
*外部数据库编程&lt;br /&gt;
*小结 &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Visual Prolog中的外部数据库==&lt;br /&gt;
&lt;br /&gt;
Visual Prolog中使用asserta、assertz、retract及retractall的内部事实数据库，用起来很简单，适用于很多场合。但是，它对内存的需求会很快地超出计算机的能力，外部数据库系统部分地就是设计用来解决面临的这个问题。举例来说，我们可能需要下述一种或多种的实现：&lt;br /&gt;
&lt;br /&gt;
*有大量记录的股票控制系统；&lt;br /&gt;
*有大量关系但仅有少量复杂结构记录的专家系统；&lt;br /&gt;
*在数据库中存储大量文本文件的文件系统；&lt;br /&gt;
*自己的数据库产品——它或许与关系数据库系统毫不相干——其中的数据相互链接而又没什么规则；&lt;br /&gt;
*包含上面若干种可能的一个系统；&lt;br /&gt;
Visual Prolog外部数据库系统支持这些不同类型的应用，并且能够满足数据库系统必须在更新操作时甚至是电源故障时不丢失数据的要求。&lt;br /&gt;
&lt;br /&gt;
Visual Prolog外部数据库谓词可以：&lt;br /&gt;
&lt;br /&gt;
*高效处理磁盘中的海量数据；&lt;br /&gt;
*将数据库放在文件中或内存中；&lt;br /&gt;
*进行多用户访问；&lt;br /&gt;
*提供比Visual Prolog自动回溯机制的顺序处理更好的数据处理灵活性；&lt;br /&gt;
*以二进制形式加载或保存外部数据库。&lt;br /&gt;
&lt;br /&gt;
===概览：外部数据库里有什么？===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog外部数据库是由两部分组成的：存储在链中的数据项——实际上是Prolog项，还有相应的用于快速访问数据项的B+树。&lt;br /&gt;
&lt;br /&gt;
外部数据库把数据项放在链中（而不是单独存放）以使相关项集中在一起。比如，一个链可能存放的是库存部件号而另一个链中含有客户名单。简单的数据库操作，如添加新项或替换删除旧项，是用不着B+树的。当要对数据排序或查找数据库中指定项时，才需要使用它，详细内容后面会介绍。&lt;br /&gt;
&lt;br /&gt;
====命名约定====&lt;br /&gt;
&lt;br /&gt;
所有涉及数据库管理的标准谓词，其命名有明确规则：&lt;br /&gt;
&lt;br /&gt;
*名称的第一部分（db_，chain_，term_，等等）指明了需要的输入,&lt;br /&gt;
*名称的第二部分（flush，btrees，delete，等等）表示动作，或是返回的内容，或是受影响的内容。&lt;br /&gt;
例如，db_delete是删除整个数据库，chain_delete是删除整个链，而term_delete只删除单独的一个项。&lt;br /&gt;
&lt;br /&gt;
[[Image:External_DB_01.gif]] &lt;br /&gt;
&lt;br /&gt;
图1：一个Visual Prolog外部数据库结构示例&lt;br /&gt;
&lt;br /&gt;
====外部数据库的选择====&lt;br /&gt;
&lt;br /&gt;
在磁盘上和内存中，可以同时存在多个外部数据库。利用这种特性，可以把外部数据库放在合适的地方以取得速度与占用内存空间的最佳均衡。&lt;br /&gt;
&lt;br /&gt;
为了区分多个打开的外部数据库，需要使用chainDB对象。打开或创建数据库时，调用chainDB类中适当的构造器，使用返回的对象进行数据库访问。&lt;br /&gt;
&lt;br /&gt;
===Chains（链）===&lt;br /&gt;
&lt;br /&gt;
外部数据库是Prolog terms（项）的集合。项，就是整数、实数、串、符号值以及复合对象，例如：32，-194，3.1417, &amp;quot;Wally&amp;quot;， wages，book(&amp;quot;dickens&amp;quot;, &amp;quot;Wally goes to the zoo&amp;quot;)等等。 &lt;br /&gt;
&lt;br /&gt;
在外部数据库内，项是存储在chains（链）中的。一个链里可以包含任意数量的项，而一个外部数据库又可以包含任意数量的链。每个链是用名称来区分的，而名称就是个串。&lt;br /&gt;
&lt;br /&gt;
下图是链结构示意图。&lt;br /&gt;
&lt;br /&gt;
[[Image:External_DB_02.gif]] &lt;br /&gt;
&lt;br /&gt;
图2：链的结构:&lt;br /&gt;
&lt;br /&gt;
数据库关系及数据库表模型的基础是项链。举例来说，假设有客户、供货商及一个部件数据库，要想把所有数据按三个关系放在一个数据库里。这就可以把客户放在一个链里，叫它customers；把供货商放在一个链里，叫suppliers；再把部件放在一个名为parts的链中。&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;
bt_selector： 由bt_open返回的B+树选择符值&lt;br /&gt;
&lt;br /&gt;
place： 数据库的位置，可以在内存中，也可以在文件中&lt;br /&gt;
&lt;br /&gt;
accessmode： 确定如何使用文件&lt;br /&gt;
&lt;br /&gt;
denymode： 确定别的用户可以怎么用文件 &lt;br /&gt;
&lt;br /&gt;
ref： 项在链中位置的索引号 &lt;br /&gt;
&lt;br /&gt;
====数据库索引号====&lt;br /&gt;
 &lt;br /&gt;
每个插入到外部数据库的项，Visual Prolog都会对其赋予一个database reference number（数据库索引号）。可以使用项的数据库索引号进行取出、移动或替换操作，还可以对链中的前一项或后一项进行类似的操作。可以把数据库索引号插入到一个B+树中（后面会有介绍），再用这个B+对项进行排序或对项做快速搜索。&lt;br /&gt;
&lt;br /&gt;
数据库索引号与数据库放在什么地方以及对数据库做了些什么操作都没有关系。一旦它与项关联之后，不管数据库做了什么，都可以用这个号去访问对应的项，一直到这个项被删除为止。&lt;br /&gt;
&lt;br /&gt;
====ref域====&lt;br /&gt;
&lt;br /&gt;
数据库索引号是很特殊的，可以把它们插入到事实段中，也可以用stdIO::write或stdIO::writef把它们写出来，但却不能从键盘上输入它们。对处理数据库索引号的谓词，它们作为参数时必须声明为预定义的ref域。&lt;br /&gt;
&lt;br /&gt;
用term_delete删除了一个项之后，系统会在一个新项插入到外部数据库时重用刚删除的那个项的数据库索引号，这是自动进行的。但如果索引号已经存入事实段或一个B+树中时，保证索引号与对应项的正确关联就是编程人员的责任了。&lt;br /&gt;
&lt;br /&gt;
有一个错误检查选项对此会有帮助，这个选项是用谓词db_reuserefs激活的。db_reuserefs(1)可以激活对释放项的检查，用db_reuserefs(0)可以关闭这个检查。激活检查的开销不大（每项多四个字节，几乎不增加CPU开销），但增加的四个字节总也不会释放了。如果经常地创建和删除项，则数据库就会持续地变大。db_reuserefs的主要目的还是帮助程序开发过程中跟踪定位错误。&lt;br /&gt;
&lt;br /&gt;
===操控整个外部数据库===&lt;br /&gt;
&lt;br /&gt;
创建新的或是打开已有的外部数据库时，可以把数据库放在文件里，也可以放在内存里，这是由调用db_create或db_openon时的参数Place的值决定的。外部数据库使用完毕后，调用db_close关闭它。chainDB接口谓词的相关信息可以参看Visual Prolog帮助文件中的PFC章节。&lt;br /&gt;
&lt;br /&gt;
当外部数据库放在内存中时，用db_close关闭数据库并不会将其从内存中删除。如果要释放数据库占用的内存，需要明确地调用db_delete才行。这样的外部数据库如果只是关闭了但并没有删除，稍后还可以用db_open谓词再重新打开它。&lt;br /&gt;
&lt;br /&gt;
例如，下面是两个不同的db_create调用：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;ChainDbObj1 = chaindb::db_create(&amp;quot;MYFILE.DBA&amp;quot;, in_file),&lt;br /&gt;
        /* 创建磁盘文件MYFILE.DBA */&lt;br /&gt;
ChainDbObj2 = chaindb::db_create(&amp;quot;SymName2&amp;quot;, in_memory),&lt;br /&gt;
        /* 创建内存数据库SymName2 */&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
而下面的这db_copy调用： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;ChainDbObj:db_copy(&amp;quot;dd.dat&amp;quot;, in_file)&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Visual Prolog会把指定的数据库ChainDbObj拷贝到放在硬盘的新数据库文件 &amp;quot;dd.dat&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
数据库拷贝时，原来的仍在那里，这时就会有两份相同的数据库，直到明确地删除一个为止。&lt;br /&gt;
&lt;br /&gt;
数据库的移动不会影响对它的任何处理，因为所有外部数据库的索引号仍是有效的。因此，如果起先一个数据库是放在内存中的，运行时感到内存紧张了又把它拷贝到文件中，这不会影响对这个数据库的任何操作。对内存数据库建立的索引，即使把数据库拷贝到文件中后，还是一样可用的。&lt;br /&gt;
&lt;br /&gt;
db_copy有多种用途，可以：&lt;br /&gt;
&lt;br /&gt;
*从磁盘加载数据库到内存，以后再把它保存为二进制形式，而不是用 file::save 和 file::consult 保存为文本文件。 &lt;br /&gt;
*把合适规模的数据库从磁盘拷贝到内存中以加快访问速度。&lt;br /&gt;
*压缩数据库：数据库拷贝到另一个文件时，会消去所有末使用的空间。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_openinvalid/3====&lt;br /&gt;
&lt;br /&gt;
db_openinvalid可以打开已经标记为无效的数据库。如果在数据库更新时发生了电源故障，数据库中的数据可能会因为部分缓冲区中的数据还没有写到磁盘而全部丢失。数据库有一个标志指示更新后是否为无效有状态。&lt;br /&gt;
&lt;br /&gt;
调用任何一个要改变数据库内容的谓词之后，数据库都会被标记为无效。这些谓词有chain_inserta、chain_insertz、chain_insertafter、term_replace、term_delete、chain_delete、bt_create、key_insert及key_delete。一旦数据库用db_close关闭，或是调用了db_flush清空缓冲区，则数据库就会被标记为有效。&lt;br /&gt;
&lt;br /&gt;
用db_openinvalid，有时可以对标记为无效的数据库进行操作。如果没有以前的备份数据可用，这或许可以恢复部分数据。但是，对用db_openinvalid打开的数据库进行的所有操作，都有可能产生预想不到的结果。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_flush/0====&lt;br /&gt;
&lt;br /&gt;
db_flush把缓冲区的内容写到相应的数据库中并清空缓冲区。数据库被更新时，会被标记为无效状态，直到db_flush或关闭。&lt;br /&gt;
&lt;br /&gt;
对数据库要采取怎样的安全措施，当然与数据的重要程度有关。最起码的数据安全措施是在磁盘上保留备份。中等一些的，可以在每次重要的数据库更新之后调用db_flush。不过清缓冲区是相当费时的一个操作，如果用得太多数据库系统就会慢慢停下来。如果外部数据库的内容是非常重要的，还可以用一个特殊的登记文件把所有更动记录下来，或是在不同的磁盘上维护两个一样的数据库。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_close/0====&lt;br /&gt;
&lt;br /&gt;
调用db_close会关闭打开的数据库。如果数据库是放在磁盘上的文件，则文件会关闭。数据库关闭时不会被删除，就是放在内存的数据库也不会删除，还可以通过调用db_open重新打开它。可以用db_delete删除在内存中的已经关闭了的数据库。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_delete/2====&lt;br /&gt;
&lt;br /&gt;
数据库放在内存中时，db_delete会释放它所占用的空间。&lt;br /&gt;
如果数据库是放在文件中的，db_delete会删除这个文件。如果在指定的地方没有相应的数据库文件，则db_delete会给出一个错误消息。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_garbagecollect/0====&lt;br /&gt;
&lt;br /&gt;
db_garbagecollect扫视整个数据库垃圾回收列表的自由空间，并尽可能把若干碎片合成一个大的自由空间。数据库放在内存中时，这种扫视与合并是自动进行的。 &lt;br /&gt;
&lt;br /&gt;
正常情况下，不需要调用这个谓词。但当插入新项时如果数据库中有太多的回收空间没有得到重新利用，db_garbagecollect有助于得到可利用的空间。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_btrees/1====&lt;br /&gt;
&lt;br /&gt;
回溯时，db_btrees会连续将BtreeName约束为指定数据库中各个B+树的名称。&lt;br /&gt;
&lt;br /&gt;
各个名称是依排定的顺序返回的。关于B+树后面还要介绍。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_chains/1====&lt;br /&gt;
&lt;br /&gt;
回溯时，db_chains会连续将ChainName约束为指定数据库中各个链的名称。各个名称是依排定的顺序返回的。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_statistics/4====&lt;br /&gt;
&lt;br /&gt;
db_statistics返回数据库的统计信息。 各参数所表示的意义如下：&lt;br /&gt;
&lt;br /&gt;
参数NoOfTerms表示数据库中的总项数。参数MemSizeis表示数据库存放于内存中的内表所占字节数。参数DbaSizeis表示数据库中项与描述符占用的总字节数。如果数据库是在文件中，而这个值比该文件占用的字节数小很多，这个文件就应该可以用db_copy进行压缩。参数FreeSize表示空闲内存的值，依数据库当前位置不同这个值也会不同：&lt;br /&gt;
&lt;br /&gt;
*数据库中内存中时，FreeSize表示全局栈顶与堆顶之间空闲的字节数（注意：可能还有若干空间的字节数没有包含在这个值中）。&lt;br /&gt;
*数据库在文件中时，FreeSize表示硬盘文件中未使用的字节数。&lt;br /&gt;
&lt;br /&gt;
===操控链===&lt;br /&gt;
&lt;br /&gt;
To要把一个项插入到外部数据库的链中，可以使用谓词 chain_inserta、chain_insertz 或 chain_insertafter。可以用 chain_terms 连续地把它的参数约束为链的项及其索引号，还可以用 chain_delete 删除外部数据库中某个链的所有项。&lt;br /&gt;
&lt;br /&gt;
还有四个标准谓词可以返回数据库的索引号，这四个标准谓词是：chain_first、chain_last、chain_next 和 chain_prev。&lt;br /&gt;
&lt;br /&gt;
===操控项===&lt;br /&gt;
&lt;br /&gt;
有三个外部数据库管理的标准谓词都与项有关，它们是：term_replace、term_delete 和 ref_term。无论调用哪个与项有关的外部数据库标准谓词，都需要将项的域作为一个参数给出来。正因为如此，所以把一个数据库中所有项的域声明为单个域中的可选项会是个好办法。例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;domains&lt;br /&gt;
    terms_for_my_stock_control_database =&lt;br /&gt;
        customer(Customer, Name, ZipCode, Address);&lt;br /&gt;
        supplier(SupplierNo, Name, Address);&lt;br /&gt;
        parts(PartNo, Description, Price, SupplierNo).&amp;lt;/vip&amp;gt;&lt;br /&gt;
注意，对外部数据库没有类型（域）上的混杂限制要求。一个链可以含有文本串而另一个链上的可以是整数，第三个链中还可以是某种复合结构，等等。但是，外部数据库的数据项并不是按类型描述符存放的，比如一个整数未必就是刚好占用两个字节。取回数据时恰好就是存放时的那种样子（同样的域），这是编程人员应该保证的。如果弄混了域，会产生运行时错误。&lt;br /&gt;
&lt;br /&gt;
==B+树==&lt;br /&gt;
&lt;br /&gt;
B+树是一种数据结构，用于实现快速有效地对大量数据进行分类排序的方法。B+树还有比较高效的搜索算法。可以认为B+树对数据库提供了一个索引，这也是为什么有时它又被称为“索引”的原因。&lt;br /&gt;
&lt;br /&gt;
在Visual Prolog中，B+树是在外部数据库中的。B+树的每个项有一对值：一个键串和相关联的数据库索引号。创建数据库时，先在数据库中插入记录并为该记录建立一个键。Visual Prolog B+树谓词接着就可以用来把这个键及该记录相应的数据库索引号插入到一个B+树中。&lt;br /&gt;
&lt;br /&gt;
要搜索数据库中的某个记录时，只需要找到该记录的键，而B+树会给出相应的索引号。用这个索引号，就可以从数据库中得到需要的记录。当B+树增长变化时，它会保持键的顺序，这也就意味着我们可以很容易地得到排序好的记录列表。&lt;br /&gt;
&lt;br /&gt;
B+树类似于二进制树，只不过在B+树的各个节点上保存了不止一个键串。B+树也是平衡树，这就是说对树中各个叶上的每个键的搜索路径长度是一样的。由于这个特点，对于在数以百万计的键中搜索某个键是有保证的，就是在最坏的情况下，需要对磁盘进行访问，也只需要不多的几次，这和每个节点上保存有多少键相关。&lt;br /&gt;
&lt;br /&gt;
尽管B+树是放在外部数据库中的，但并非一定要与它所指的项放在同一个数据库中，可以是一个数据库中含有一些链，而另一个数据库放指向这些链中项的B+树。&lt;br /&gt;
&lt;br /&gt;
===Pages(页） Order（阶） Key Length（键长）===&lt;br /&gt;
&lt;br /&gt;
在B+树中，键是聚合在页里的，每页有相同的大小，所有的页都包含有同样数量的键，这意味着所有为B+树保存的键必须大小相同。键的大小取决于KeyLen参数，这是在创建B+树时需要指定的一个参数。如果要在B+树中插入比KeyLen还要长的串，Visual Prolog会把多出来的部分截去。通常应该尽可能选小一些的KeyLen值以节省空间、提高速度。&lt;br /&gt;
&lt;br /&gt;
创建B+树时还需要指定称为阶的一个参数。这个参数决定了树的各个节点可以存储多少个键，而通常它是需要反复试验才能取得最佳值。阶的起始值用4是不错的选择，它允许各节点上保存4～8个键。阶的值必须要通过实验来选取，因为B+树的搜索速度与键长与阶的值、B+中键的数量以及计算机硬件配置都有关系。&lt;br /&gt;
&lt;br /&gt;
===重复键===&lt;br /&gt;
 &lt;br /&gt;
设置B+树时，需要允许键的重复。比如，设置一个客户数据库的B+树，用客户的姓做键，键就会有重复出现的可能。由于这个原因，B+树是可以有重复键的。&lt;br /&gt;
&lt;br /&gt;
要删除数据库中的一个项，需要给出相应的B+树键及数据库索引号（因此键重复也不会有问题）。&lt;br /&gt;
&lt;br /&gt;
===多重操作===&lt;br /&gt;
&lt;br /&gt;
为了能对B+进行多重操作，每个B+树内部都有多个指针，允许对一个树多次打开。不过要注意，如果更新了一个B+树的拷贝，而此时又有已经打开了的副本，则副本的指针会重定位到树的顶端。&lt;br /&gt;
&lt;br /&gt;
===B+树的标准谓词===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog提供了一些标准谓词来处理B+树，这些谓词的工作方式与相应的 db_... 谓词是一致的。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_create/4 and chainDB::bt_create/5====&lt;br /&gt;
&lt;br /&gt;
调用这个谓词可以创建一个新的B+树。&lt;br /&gt;
&lt;br /&gt;
参数BtreeName指定新树的名称，以后可以把这个名称作为参数用于bt_open。B+树的KeyLen参数和Order参数是在树创建时给出的，以后不能再更改。如果调用bt_create/4或bt_create/5时参数Duplicates设为1，则在这个B+树中允许重复；若是设为0，则不能在该树中插入重复的项。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_open/2====&lt;br /&gt;
&lt;br /&gt;
bt_open打开名为BtreeName的已经创建了的B+树。&lt;br /&gt;
&lt;br /&gt;
打开一个B+树时，这个调用会返回一个用于该B+树的选择器Btree_Sel，它就是该B+树的引用，无论系统的搜索或定位操作都要用到它。B+树的名称与选择器之间的关系就如同一个实际的文件名称与相应的符号文件名之间的关系。&lt;br /&gt;
&lt;br /&gt;
可以多次打开同一个B+树以便同时进行多项操作。每打开一次该B+树，就分配一个描述符，各个描述符都对应有自己的B+树内部指针。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_close/1 and chainDB::bt_delete/1====&lt;br /&gt;
&lt;br /&gt;
可以用bt_close关闭一个打开的B+树，也可以用bt_delete删除整个B+树。&lt;br /&gt;
&lt;br /&gt;
调用bt_close会释放打开的名为BtreeName的B+树分配的内部缓冲区。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_copyselector/2====&lt;br /&gt;
&lt;br /&gt;
bt_copyselector对已经打开的B+树复制一个新的选择器。&lt;br /&gt;
&lt;br /&gt;
新选择器开始时其指针定位在与老的选择器相同的位置上。之后，B+树的两个选择器定位就都可以自行确定，互不相干。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_statistics/7====&lt;br /&gt;
&lt;br /&gt;
bt_statistics返回由Btree_Sel指定的B+树的统计信息。各参数意义如下：&lt;br /&gt;
&lt;br /&gt;
参数Btree_Sel是该B+树的bt_selector；NumKeys是该树中键的总数；NumPages是该树的总页数；Depth是该树的深度；KeyLen是该树键长；Order是该树的阶；PgSize是页大小（以字节计）。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_insert/3 and chainDB::key_delete/3====&lt;br /&gt;
&lt;br /&gt;
标准谓词key_insert和key_delete用于更改B+树。&lt;br /&gt;
&lt;br /&gt;
给定Key和Ref时，可以删除有重复项的B+树中的某个确定项。&lt;br /&gt;
&lt;br /&gt;
chainDB::key_first/2, chainDB::key_last/2, and chainDB::key_search/3&lt;br /&gt;
每个B+树都维护有指向自身节点的内部指针。key_first和key_last可以分别把指针置于树的第一个键和最后一个键的位置上，而key_search则可以把指针置于指定键的位置上。&lt;br /&gt;
&lt;br /&gt;
如果找到了指定的键，key_search就成功；反之则失败，并将B+的内部指针置于该指定键应该存放的位置之后。这时可以用key_current返回这个键及其数据库索引号。如果想要在一个有重复项的B+树中准确定位，也可以把索引号做为一个条件输入。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_next/2 and chainDB::key_prev/2====&lt;br /&gt;
&lt;br /&gt;
可以用谓词key_next和key_prev在经过排序树中向前或向后移动B+树指针。&lt;br /&gt;
&lt;br /&gt;
If如果已经到了树的端头，想进一步移动指针会引起失败，但指针本身还是会移出树外。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_current/3====&lt;br /&gt;
&lt;br /&gt;
key_current返回B+树当前指针指向的键及其数据库索引号。&lt;br /&gt;
&lt;br /&gt;
调用谓词bt_open、bt_create、key_insert或key_delete之后立即使用key_current谓词会失败，当指针由于key_prev或key_next已经位于第一个键之前或最后一个键之后使用key_current谓词也会失败。&lt;br /&gt;
&lt;br /&gt;
==外部数据库编程==&lt;br /&gt;
&lt;br /&gt;
本节要介绍使用Visual Prolog外部数据库系统的一些基本方法与原则。主要内容有：&lt;br /&gt;
&lt;br /&gt;
*“遍历数据库” 介绍顺序扫遍外部数据库中的链或B+树的方法。&lt;br /&gt;
*“数据库的防护” 说明如何应对没想到的电源故障及其它可能的问题以保护数据库。&lt;br /&gt;
*“使用B+树内部指针” 说明如何在打开的B+树中使用定位指针的一些谓词。&lt;br /&gt;
&lt;br /&gt;
===遍历数据库===&lt;br /&gt;
在使用数据库系统时，头脑中一定要清楚Visual Prolog的存储机制。每当Visual Prolog用ref_term谓词从外部数据库中取回一个项时，会把这个项放在全局栈中。这个项占用的空间，系统要一直等到程序失败并回溯到ref_term调用之前的点才会释放。这意味着，要想连续地遍历外部数据库中某个链，总应该使用下面这样的结构：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 连续访问一个链所用的结构 */&lt;br /&gt;
scan(ChainObj, Chain, ....) :-&lt;br /&gt;
    ChainObj:chain_first(Chain, Ref),&lt;br /&gt;
    scanloop(ChainObj, Ref).&lt;br /&gt;
scanloop(ChainObj, Ref) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:ref_term(Ref, Term),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scanloop(ChainObj, _) :-&lt;br /&gt;
    ChainObj:chain_next(Ref, NextRef),&lt;br /&gt;
    scanloop(ChainObj, NextRef).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
同样，要遍历索引，应该用如下的结构：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 连续访问索引所用的结构 */&lt;br /&gt;
scan(ChainObj, Bt_selector) :-&lt;br /&gt;
    ChainObj:key_first(Bt_selector, FirstRef),&lt;br /&gt;
    scanloop(ChainObj, Bt_selector, FirstRef).&lt;br /&gt;
scanloop(ChainObj, Bt_selector, Ref) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:ref_term(Ref, Term),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scanloop(ChainObj, Bt_selector, _) :-&lt;br /&gt;
    ChainObj:key_next(Bt_selector, NextRef),&lt;br /&gt;
    scanloop(ChainObj, Bt_selector, NextRef).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以用chain_terms来遍历外部数据库中的链，像这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 遍历链的另一种方法 */&lt;br /&gt;
scan(ChainObj, Chain) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:chain_terms(Chain, Term, Ref),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scan(_, _).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
要遍历B+树，可以通过已经定义和使用过的bt_keys谓词来实现。回溯时，这个谓词就会逐一返回（指定B+和数据库）各个键及相应的数据库索引号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/vip&amp;gt;predicates&lt;br /&gt;
    bt_keys : (chainDB, bt_selector, string, ref).&lt;br /&gt;
    bt_keysloop : (chainDB, bt_selector, string, ref).&lt;br /&gt;
clauses&lt;br /&gt;
    bt_keys(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_first(Bt_selector, _),&lt;br /&gt;
        bt_keysloop(ChainObj, Bt_selector, Key, Ref).&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_current(Bt_selector, Key, Ref).&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_next(Bt_selector, _),&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref).&amp;lt;/vip&amp;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;
===使用B+树的内部指针===&lt;br /&gt;
&lt;br /&gt;
每个打开的B+树都有一个指向其节点的指针，打开或更新B+树时，这个指针指向树的起点之前；当指针指向树的最后一个键时，调用key_next会使指针指向树的末端之后的位置。只要指针超出了树，key_current就都会失败。如果这样一些安排不能满足特定应用程序的要求，就需要自行构造其它的谓词了。&lt;br /&gt;
&lt;br /&gt;
比如，可以构造像下面这样一些mykey_next、mykey_prev、mykey_search的谓词，让B+树指针总是在B+树的内部（如果树中有一些键的话）。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    mykey_next(db_selector, bt_selector, ref).&lt;br /&gt;
    mykey_prev(db_selector, bt_selector, ref).&lt;br /&gt;
    mykey_search(db_selector, bt_selector, string, ref).&lt;br /&gt;
clauses&lt;br /&gt;
    mykey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref), !.&lt;br /&gt;
    mykey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref), fail.&lt;br /&gt;
    mykey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref), !.&lt;br /&gt;
    mykey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref), fail.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, Key, Ref) :-&lt;br /&gt;
        Dba:key_search(Bt_selector, Key, Ref), !.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, _, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, _, Ref), !.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, _, Ref) :-&lt;br /&gt;
        Dba:key_last(Bt_selector, Ref).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
还可以使用下面定义的samekey_next和samekey_prev谓词，将索引指针定位到具有重复键的B+树中同一键值的下一个键上：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    samekey_next(db_selector, bt_selector, ref).&lt;br /&gt;
    try_next(db_selector, bt_selector, ref, string).&lt;br /&gt;
    samekey_prev(db_selector, bt_selector, ref).&lt;br /&gt;
    try_prev(db_selector, bt_selector, ref, string).&lt;br /&gt;
clauses&lt;br /&gt;
    samekey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, OldKey, _),&lt;br /&gt;
        try_next(Dba, Bt_selector, Ref, OldKey).&lt;br /&gt;
    try_next(Dba, Bt_selector, Ref, OldKey) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref),&lt;br /&gt;
        Dba:key_current(Bt_selector, NewKey, _),&lt;br /&gt;
        NewKey = OldKey, !.&lt;br /&gt;
    try_next(Dba, Bt_selector, _, _) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, _),&lt;br /&gt;
        fail.&lt;br /&gt;
    samekey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, OldKey, _),&lt;br /&gt;
        try_prev(Dba, Bt_selector, Ref, OldKey).&lt;br /&gt;
    try_prev(Dba, Bt_selector, Ref, OldKey) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref),&lt;br /&gt;
        Dba:key_current(Bt_selector, NewKey, _),&lt;br /&gt;
        NewKey = OldKey, !.&lt;br /&gt;
    try_prev(Dba, Bt_selector, _, _) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, _),&lt;br /&gt;
        fail.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===外部数据库与文件共享===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog支持文件共享型外部数据库，也就是说一个文件可以被若干用户或是进程同时打开，这对在局域网中或多任务平台上使用外部数据库是很有用处的。&lt;br /&gt;
&lt;br /&gt;
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;
域accessMode = read或readWrite；域denyMode = denyNone或denyWrite或denyAll。 &lt;br /&gt;
&lt;br /&gt;
===共享模式下打开数据库===&lt;br /&gt;
&lt;br /&gt;
为了使打开的外部数据库可以共享，需要使用四元维版本的db_open打开已有的数据库文件，并指定AccessMode及DenyMode。 &lt;br /&gt;
&lt;br /&gt;
如果AccessMode是read，打开的文件就只能读，任何对文件的更新都会导致运行时错误；如果是readwrite，该文件就可读可写。AccessMode在谓词db_begintransaction中也会用到。&lt;br /&gt;
&lt;br /&gt;
如果DenyMode是denyNone，那么所有其他的用户都可以更新和读取该文件；若是denyWrite，则其他用户不能以 AccessMode = readWrite 模式打开该文件，但如果该文件是按 AccessMode = readWrite 模式打开的，还是可以对文件进行更新的。如果db_open是带 DenyMode = denyAll 的，则任何其他用户都不能访问该文件。&lt;br /&gt;
&lt;br /&gt;
打开文件的第一个用户使用的DenyMode，决定了后续所有打开该文件的条件，如果试图用不相容的模式打开该文件，就会产生运行时错误。下表归纳了打开文件的结果及后续再打开同一个文件时的各种情况：&lt;br /&gt;
&lt;br /&gt;
Domain（域）  用途  &lt;br /&gt;
bt_selector  bt_open返回的用于选取B+树的唯一值  &lt;br /&gt;
place  数据库的位置：内存中或是文件中 &lt;br /&gt;
accessmode  确定文件的使用方式 &lt;br /&gt;
denymode  确定其他用户可以打开该文件的方式 &lt;br /&gt;
ref  项在链中位置的索引 &lt;br /&gt;
&lt;br /&gt;
===Transactions（事务处理）与文件共享===&lt;br /&gt;
&lt;br /&gt;
如果一个数据库文件是打开共享的，则所有以任何方式访问数据库文件的数据库谓词必须要集中在事务处理内部，它是通过把对这些谓词的调用包围在db_begintransaction和db_endtransaction之间来实现的。&lt;br /&gt;
&lt;br /&gt;
根据选择的AccessMode和DenyMode不同，共享的文件有可能在事务处理期间会被锁定。再根据锁定的程度，事务处理时其他用户可能还不能读或更新该文件。这对于避免读写的混乱当然是必要的，但文件共享要有意义就一定不要有过多的锁定。这可以通过尽量减小（缩短）事务处理来实现，只把那些访问数据库的谓词包含在事务处理中。&lt;br /&gt;
&lt;br /&gt;
涉及文件共享的事务处理这个概念非常重要，两者之间的需求是矛盾的，即同时要实现数据库的安全和最少的文件锁定。&lt;br /&gt;
&lt;br /&gt;
db_begintransaction是确保数据库一致性的，它实现文件的适当锁定，文件可由若干用户同时读但只允许有一个进程在当下更新数据库。谓词db_setretry可以用来设置db_begintransaction在返回运行时错误之前需要等待多长时间才可以访问该文件。当文件AccessMode是按read打开的，而调用db_begintransaction时AccessMode设成了readWrite也会产生运行时错误。如果已经调用了db_begintransaction，对同一个数据库调用新的db_begintransaction之前必须调用db_endtransaction，否则就会出现运行时错误。&lt;br /&gt;
&lt;br /&gt;
下表归纳了db_begintransaction对不同的AccessMode和DenyMode组合所做的反应：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;AccessMode read readWrite DenyMode denyNone WLock\Reload RWLock\Reload denyWrite None RWLock denyAll None NoneActions:&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
WLock ：不允许写，只允许读。&lt;br /&gt;
RWLock ：读和写都不允许。&lt;br /&gt;
Reload ：重新载入文件描述符。&lt;br /&gt;
由于重新载入及锁定都是需要时间的，所以要小心选用AccessMode和DenyMode。比如，若不会有用户更新数据库，将AccessMode设为read而DenyMode设为denyWrite就可以使开销最小化。&lt;br /&gt;
 &lt;br /&gt;
===文件共享谓词===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog中有一些文件共享谓词，它们是db_open、db_begintransaction、db_endtransaction、db_updated、bt_updated和db_setretry。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_begintransaction/1====&lt;br /&gt;
这个谓词标记事务处理的起点，应该在访问共享的数据库之前调用它，哪怕是以denyAll打开的数据库，也应该调用它。db_begintransaction调用时AccessMode必须为read或readwrite。进一步的了解可以参看PFC的帮助文件。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_endtransaction/0====&lt;br /&gt;
db_endtransaction标记一个事务处理的结束并解锁数据库。必须在每个db_begintransaction调用之后且在下一个db_begintransaction调用之前调用db_endtransaction。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_updated/0====&lt;br /&gt;
如果有其他用户更新了数据库，db_begintransaction的调用会保持数据库的一致性。调用谓词db_updated可以检查变更情况：如果在一个事务处理中调用它，而db_begintransaction之后有其他用户更新过数据库，就会成功；如果没有更新，就会失败。如果在事务处理之外调用这个谓词，就会产生运行时错误。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_updated/1====&lt;br /&gt;
这个谓词很像db_updated，只不过它是在指定的B+树更新了才会成功。 &lt;br /&gt;
&lt;br /&gt;
====chainDB::db_setretry/2====&lt;br /&gt;
如果由于某个进程锁定了一个文件而导致另一个进程访问该文件被拒绝，这另一个进程可以进入一个等待期，期满后再试着访问该文件。谓词db_setretry可以改变缺省的SleepPeriod设置，这是以百分之一秒为单位的等待期；这个谓词还可以设置RetryCount，这是重试访问的最大次数。缺省的设置，RetryCount是100而SleepPeriod是10。&lt;br /&gt;
&lt;br /&gt;
===文件共享时的编程===&lt;br /&gt;
使用文件共享谓词一定要十分小心。尽管使用恰当时它们能够保证共享数据库低层的一致性，但防止某个程序危害高层的一致性则是应用程序编写人员的责任。这中间所用的所谓“事务处理（transaction）”是将文件访问集中在一起的一个方法，但一定要清楚它并没有提供什么恢复机制，不论是软件还是硬件失效引起的程序中断，都有可能导致数据库文件的不完整。&lt;br /&gt;
&lt;br /&gt;
若干进程共享一个数据库时，还需要特别注意涉及到的域，它们必须要严格一致，使用时顺序也要一致。&lt;br /&gt;
&lt;br /&gt;
为避免不要地锁定数据库文件，事务处理一定要尽量短小以使文件锁定时间尽可能短。同时，要把对数据库项的定位与访问的谓词放在一个事务处理中，这也很重要。 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;.....&lt;br /&gt;
ChainObj:db_begintransaction(readWrite),&lt;br /&gt;
  ChainObj:key_current(Btree, Key, Ref),&lt;br /&gt;
  ChainObj:ref_term(Ref, Term),&lt;br /&gt;
ChainObj:db_endtransaction(),&lt;br /&gt;
write(Term),&lt;br /&gt;
.....&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
上面的代码中，谓词key_current和ref_term不应该分开放在不同的事务处理中，因为Ref中存储的项可能会在不同事务处理间就被其他用户删除了。 &lt;br /&gt;
&lt;br /&gt;
当B+树被别的用户更新并且文件缓冲区重新加载后，B+树的指针会重新定位到第一项之前。调用谓词bt_updated可以确定是否需要重新对B+树指针定位。把当前的键暂存在内部数据库中，既可以列表完整的索引号，同时也可以使用事务处理短小。 &lt;br /&gt;
&lt;br /&gt;
===实现高级锁定===&lt;br /&gt;
对于一个共享的数据库文件，用户可以进行所有独自享有该文件时相同的操作。把对文件的访问集中于db_begintransaction和db_endtransaction的内部，可以保证Visual Prolog系统自身描述符表的一致性。但是，在网络环境中多用户条件下应用程序的各种逻辑约束, 需要在较高层级上由编程人员确保得以实现。&lt;br /&gt;
&lt;br /&gt;
W我们把这称之为高层级的锁定或应用程序级的锁定。通过使用简单的db_begintransaction和db_endtransaction，可以有多种方法实现高级锁定的方法。&lt;br /&gt;
&lt;br /&gt;
需要使用高级锁定的一个很普通的例子，就是用户想要改变数据库中记录的时候。确定要改变一个记录时，就需要采取一些步骤使应用程序对该记录锁定，直到更改结束、新的记录写回到磁盘中，然后对该记录解锁。&lt;br /&gt;
&lt;br /&gt;
下面是对应用程序级锁定的一些建议：&lt;br /&gt;
&lt;br /&gt;
*在记录中设置一个专门的字段来表示它是否为锁定状态；&lt;br /&gt;
*设置一个专门的B+树或链，存放所有被用户锁定的记录的索引；&lt;br /&gt;
*用一个REF存放所有锁定记录的索引表。&lt;br /&gt;
或许还需要实现某种超级用户机制，以便特殊的用户可以解除对记录的锁定。&lt;br /&gt;
&lt;br /&gt;
这只是个例子，比如需要实现对表或表组，或知识组等的高级锁定。&lt;br /&gt;
&lt;br /&gt;
注意： 如果要在以共享方式打开的数据库文件中删除一个B+树，必须要使用高级锁定来确保没有其他用户打开这同一个树。 Visual Prolog系统中没有对一个B+树名是否因其被删除了而不可用做检查。&lt;br /&gt;
&lt;br /&gt;
===一个完整的文件共享实例===&lt;br /&gt;
在这个完整的例子中，可以看到如何通过使用自己实现的锁定系统来方便地共享文件。如果自己管理锁，可以避免不必要的文件锁定，其他用户不必因为文件锁定而长时间等待对文件的访问。&lt;br /&gt;
&lt;br /&gt;
该程序允许若干用户对一个共享的文件进行文本的创建、编辑、查看及删除。创建和编辑文本时，需要进行锁定直到编辑完成。文本锁定时，其他用户不能删除或编辑该文本，但可以查看它。用不同的 db_open和db_setretry设置运行这个程序，体验一下吧。&lt;br /&gt;
&lt;br /&gt;
===Visual Prolog文件共享的实现方面===&lt;br /&gt;
Visual Prolog中的文件共享很有效率，很快，因为其它用户对数据库进行了更新之后只是必需的数据库文件描述符部分要重新加载。前面已经介绍过，只是在特定环境下才需要进行文件缓冲区的重载及文件锁定，而数据库文件内部完善的管理保证了更新后只进行最小必要的磁盘操作。 &lt;br /&gt;
&lt;br /&gt;
数据库有一个六字节整数序列号，每次更新它都会增加并被写到磁盘上。db_begintransaction谓词会将当前的序列号副本与磁盘上的进行比较，如果不一样，就重载描述符。 锁使用的是一个可以容纳256个用户的数组。当一个用户需要访问文件时，就会为其在这个数组中分配一个未使用的位置，并在事务处理期间一直占用这个位置。这种方法允许文件同时有若干个读者进行访问。如果db_begintransaction是以AccessMode = readWrite方式调用的，则需要等待当前已有读者都从这个数组中退出并放弃所占用的位置之后，再锁定整个的数组，使其他用户不能再访问该文件。&lt;br /&gt;
&lt;br /&gt;
==小结==&lt;br /&gt;
Visual Prolog外部数据库系统为用户的数据库应用程序添加了强大、快速和高效的功能。在本文中主要介绍了：&lt;br /&gt;
&lt;br /&gt;
*外部数据库的项是存储在链中的，它可以直接用数据库索引号进行访问；索引号属于预定义的ref域。&lt;br /&gt;
*各数据库可以由数据库选择符来指定，它是属于标准域db_selector的。&lt;br /&gt;
*外部数据库可以放在两个地方，这与预定义的place域有关：&lt;br /&gt;
*in_file把数据库放在磁盘文件中，&lt;br /&gt;
*in_memory把数据库放在内在中。&lt;br /&gt;
*如果要在数据库中对项进行排序，会要用到B+树。与数据库一样，各个B+树是通过B+树选择符来指定的，它属于标准域bt_selector。 &lt;br /&gt;
*B+树节点的每个项由一个标识记录的键串（也叫索引）和与记录关联的数据库索引号构成。&lt;br /&gt;
*B+树的键组成页，而存储在一个节点上的键的数量是由树的阶指定的。&lt;br /&gt;
*文件共享是通过将访问数据库的谓词组合在事务处理中实现的。&lt;br /&gt;
&lt;br /&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>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84%E5%A4%96%E9%83%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F&amp;diff=4121</id>
		<title>Visual Prolog的外部数据库系统</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84%E5%A4%96%E9%83%A8%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F&amp;diff=4121"/>
		<updated>2015-06-17T12:42:51Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* 命名约定 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的Visual Prolog External Database System。）&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
下载 本文所用工程例子的源文件：&lt;br /&gt;
&lt;br /&gt;
Visual Prolog 7.1 版. &lt;br /&gt;
&lt;br /&gt;
Visual Prolog 7.2 版. &lt;br /&gt;
&lt;br /&gt;
本教程讨论Visual Prolog的外部数据库系统。 外部数据库（external database） 是由外部链接项集合构成的，这些链可以让我们直接访问并非Prolog程序一部分的那种数据。 外部数据库既可以保存在文件中也可以保存在内存里，它支持B+树（一种可以快速取得数据及排序的数据结构），还通过内部的串行文件访问机制支持多用户访问。&lt;br /&gt;
&lt;br /&gt;
*Visual Prolog中的外部数据库 &lt;br /&gt;
*B+树&lt;br /&gt;
*外部数据库编程&lt;br /&gt;
*小结 &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
==Visual Prolog中的外部数据库==&lt;br /&gt;
&lt;br /&gt;
Visual Prolog中使用asserta、assertz、retract及retractall的内部事实数据库，用起来很简单，适用于很多场合。但是，它对内存的需求会很快地超出计算机的能力，外部数据库系统部分地就是设计用来解决面临的这个问题。举例来说，我们可能需要下述一种或多种的实现：&lt;br /&gt;
&lt;br /&gt;
*有大量记录的股票控制系统；&lt;br /&gt;
*有大量关系但仅有少量复杂结构记录的专家系统；&lt;br /&gt;
*在数据库中存储大量文本文件的文件系统；&lt;br /&gt;
*自己的数据库产品——它或许与关系数据库系统毫不相干——其中的数据相互链接而又没什么规则；&lt;br /&gt;
*包含上面若干种可能的一个系统；&lt;br /&gt;
Visual Prolog外部数据库系统支持这些不同类型的应用，并且能够满足数据库系统必须在更新操作时甚至是电源故障时不丢失数据的要求。&lt;br /&gt;
&lt;br /&gt;
Visual Prolog外部数据库谓词可以：&lt;br /&gt;
&lt;br /&gt;
*高效处理磁盘中的海量数据；&lt;br /&gt;
*将数据库放在文件中或内存中；&lt;br /&gt;
*进行多用户访问；&lt;br /&gt;
*提供比Visual Prolog自动回溯机制的顺序处理更好的数据处理灵活性；&lt;br /&gt;
*以二进制形式加载或保存外部数据库。&lt;br /&gt;
&lt;br /&gt;
===概览：外部数据库里有什么？===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog外部数据库是由两部分组成的：存储在链中的数据项——实际上是Prolog项，还有相应的用于快速访问数据项的B+树。&lt;br /&gt;
&lt;br /&gt;
外部数据库把数据项放在链中（而不是单独存放）以使相关项集中在一起。比如，一个链可能存放的是库存部件号而另一个链中含有客户名单。简单的数据库操作，如添加新项或替换删除旧项，是用不着B+树的。当要对数据排序或查找数据库中指定项时，才需要使用它，详细内容后面会介绍。&lt;br /&gt;
&lt;br /&gt;
====命名约定====&lt;br /&gt;
&lt;br /&gt;
所有涉及数据库管理的标准谓词，其命名有明确规则：&lt;br /&gt;
&lt;br /&gt;
*名称的第一部分（db_，chain_，term_，等等）指明了需要的输入,&lt;br /&gt;
*名称的第二部分（flush，btrees，delete，等等）表示动作，或是返回的内容，或是受影响的内容。&lt;br /&gt;
例如，db_delete是删除整个数据库，chain_delete是删除整个链，而term_delete只删除单独的一个项。&lt;br /&gt;
&lt;br /&gt;
[[Image:External_DB_01.gif]] &lt;br /&gt;
&lt;br /&gt;
图1：一个Visual Prolog外部数据库结构示例&lt;br /&gt;
&lt;br /&gt;
====外部数据库的选择====&lt;br /&gt;
&lt;br /&gt;
在磁盘上和内存中，可以同时存在多个外部数据库。利用这种特性，可以把外部数据库放在合适的地方以取得速度与占用内存空间的最佳均衡。&lt;br /&gt;
&lt;br /&gt;
为了区分多个打开的外部数据库，需要使用chainDB对象。打开或创建数据库时，调用chainDB类中适当的构造器，使用返回的对象进行数据库访问。&lt;br /&gt;
&lt;br /&gt;
===Chains（链）===&lt;br /&gt;
&lt;br /&gt;
外部数据库是Prolog terms（项）的集合。项，就是整数、实数、串、符号值以及复合对象，例如：32，-194，3.1417, &amp;quot;Wally&amp;quot;， wages，book(&amp;quot;dickens&amp;quot;, &amp;quot;Wally goes to the zoo&amp;quot;)等等。 &lt;br /&gt;
&lt;br /&gt;
在外部数据库内，项是存储在chains（链）中的。一个链里可以包含任意数量的项，而一个外部数据库又可以包含任意数量的链。每个链是用名称来区分的，而名称就是个串。&lt;br /&gt;
&lt;br /&gt;
下图是链结构示意图。&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
图2：链的结构:&lt;br /&gt;
&lt;br /&gt;
数据库关系及数据库表模型的基础是项链。举例来说，假设有客户、供货商及一个部件数据库，要想把所有数据按三个关系放在一个数据库里。这就可以把客户放在一个链里，叫它customers；把供货商放在一个链里，叫suppliers；再把部件放在一个名为parts的链中。&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;
bt_selector： 由bt_open返回的B+树选择符值&lt;br /&gt;
&lt;br /&gt;
place： 数据库的位置，可以在内存中，也可以在文件中&lt;br /&gt;
&lt;br /&gt;
accessmode： 确定如何使用文件&lt;br /&gt;
&lt;br /&gt;
denymode： 确定别的用户可以怎么用文件 &lt;br /&gt;
&lt;br /&gt;
ref： 项在链中位置的索引号 &lt;br /&gt;
&lt;br /&gt;
====数据库索引号====&lt;br /&gt;
 &lt;br /&gt;
每个插入到外部数据库的项，Visual Prolog都会对其赋予一个database reference number（数据库索引号）。可以使用项的数据库索引号进行取出、移动或替换操作，还可以对链中的前一项或后一项进行类似的操作。可以把数据库索引号插入到一个B+树中（后面会有介绍），再用这个B+对项进行排序或对项做快速搜索。&lt;br /&gt;
&lt;br /&gt;
数据库索引号与数据库放在什么地方以及对数据库做了些什么操作都没有关系。一旦它与项关联之后，不管数据库做了什么，都可以用这个号去访问对应的项，一直到这个项被删除为止。&lt;br /&gt;
&lt;br /&gt;
====ref域====&lt;br /&gt;
&lt;br /&gt;
数据库索引号是很特殊的，可以把它们插入到事实段中，也可以用stdIO::write或stdIO::writef把它们写出来，但却不能从键盘上输入它们。对处理数据库索引号的谓词，它们作为参数时必须声明为预定义的ref域。&lt;br /&gt;
&lt;br /&gt;
用term_delete删除了一个项之后，系统会在一个新项插入到外部数据库时重用刚删除的那个项的数据库索引号，这是自动进行的。但如果索引号已经存入事实段或一个B+树中时，保证索引号与对应项的正确关联就是编程人员的责任了。&lt;br /&gt;
&lt;br /&gt;
有一个错误检查选项对此会有帮助，这个选项是用谓词db_reuserefs激活的。db_reuserefs(1)可以激活对释放项的检查，用db_reuserefs(0)可以关闭这个检查。激活检查的开销不大（每项多四个字节，几乎不增加CPU开销），但增加的四个字节总也不会释放了。如果经常地创建和删除项，则数据库就会持续地变大。db_reuserefs的主要目的还是帮助程序开发过程中跟踪定位错误。&lt;br /&gt;
&lt;br /&gt;
===操控整个外部数据库===&lt;br /&gt;
&lt;br /&gt;
创建新的或是打开已有的外部数据库时，可以把数据库放在文件里，也可以放在内存里，这是由调用db_create或db_openon时的参数Place的值决定的。外部数据库使用完毕后，调用db_close关闭它。chainDB接口谓词的相关信息可以参看Visual Prolog帮助文件中的PFC章节。&lt;br /&gt;
&lt;br /&gt;
当外部数据库放在内存中时，用db_close关闭数据库并不会将其从内存中删除。如果要释放数据库占用的内存，需要明确地调用db_delete才行。这样的外部数据库如果只是关闭了但并没有删除，稍后还可以用db_open谓词再重新打开它。&lt;br /&gt;
&lt;br /&gt;
例如，下面是两个不同的db_create调用：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;ChainDbObj1 = chaindb::db_create(&amp;quot;MYFILE.DBA&amp;quot;, in_file),&lt;br /&gt;
        /* 创建磁盘文件MYFILE.DBA */&lt;br /&gt;
ChainDbObj2 = chaindb::db_create(&amp;quot;SymName2&amp;quot;, in_memory),&lt;br /&gt;
        /* 创建内存数据库SymName2 */&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
而下面的这db_copy调用： &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;ChainDbObj:db_copy(&amp;quot;dd.dat&amp;quot;, in_file)&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Visual Prolog会把指定的数据库ChainDbObj拷贝到放在硬盘的新数据库文件 &amp;quot;dd.dat&amp;quot;。&lt;br /&gt;
&lt;br /&gt;
数据库拷贝时，原来的仍在那里，这时就会有两份相同的数据库，直到明确地删除一个为止。&lt;br /&gt;
&lt;br /&gt;
数据库的移动不会影响对它的任何处理，因为所有外部数据库的索引号仍是有效的。因此，如果起先一个数据库是放在内存中的，运行时感到内存紧张了又把它拷贝到文件中，这不会影响对这个数据库的任何操作。对内存数据库建立的索引，即使把数据库拷贝到文件中后，还是一样可用的。&lt;br /&gt;
&lt;br /&gt;
db_copy有多种用途，可以：&lt;br /&gt;
&lt;br /&gt;
*从磁盘加载数据库到内存，以后再把它保存为二进制形式，而不是用 file::save 和 file::consult 保存为文本文件。 &lt;br /&gt;
*把合适规模的数据库从磁盘拷贝到内存中以加快访问速度。&lt;br /&gt;
*压缩数据库：数据库拷贝到另一个文件时，会消去所有末使用的空间。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_openinvalid/3====&lt;br /&gt;
&lt;br /&gt;
db_openinvalid可以打开已经标记为无效的数据库。如果在数据库更新时发生了电源故障，数据库中的数据可能会因为部分缓冲区中的数据还没有写到磁盘而全部丢失。数据库有一个标志指示更新后是否为无效有状态。&lt;br /&gt;
&lt;br /&gt;
调用任何一个要改变数据库内容的谓词之后，数据库都会被标记为无效。这些谓词有chain_inserta、chain_insertz、chain_insertafter、term_replace、term_delete、chain_delete、bt_create、key_insert及key_delete。一旦数据库用db_close关闭，或是调用了db_flush清空缓冲区，则数据库就会被标记为有效。&lt;br /&gt;
&lt;br /&gt;
用db_openinvalid，有时可以对标记为无效的数据库进行操作。如果没有以前的备份数据可用，这或许可以恢复部分数据。但是，对用db_openinvalid打开的数据库进行的所有操作，都有可能产生预想不到的结果。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_flush/0====&lt;br /&gt;
&lt;br /&gt;
db_flush把缓冲区的内容写到相应的数据库中并清空缓冲区。数据库被更新时，会被标记为无效状态，直到db_flush或关闭。&lt;br /&gt;
&lt;br /&gt;
对数据库要采取怎样的安全措施，当然与数据的重要程度有关。最起码的数据安全措施是在磁盘上保留备份。中等一些的，可以在每次重要的数据库更新之后调用db_flush。不过清缓冲区是相当费时的一个操作，如果用得太多数据库系统就会慢慢停下来。如果外部数据库的内容是非常重要的，还可以用一个特殊的登记文件把所有更动记录下来，或是在不同的磁盘上维护两个一样的数据库。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_close/0====&lt;br /&gt;
&lt;br /&gt;
调用db_close会关闭打开的数据库。如果数据库是放在磁盘上的文件，则文件会关闭。数据库关闭时不会被删除，就是放在内存的数据库也不会删除，还可以通过调用db_open重新打开它。可以用db_delete删除在内存中的已经关闭了的数据库。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_delete/2====&lt;br /&gt;
&lt;br /&gt;
数据库放在内存中时，db_delete会释放它所占用的空间。&lt;br /&gt;
如果数据库是放在文件中的，db_delete会删除这个文件。如果在指定的地方没有相应的数据库文件，则db_delete会给出一个错误消息。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_garbagecollect/0====&lt;br /&gt;
&lt;br /&gt;
db_garbagecollect扫视整个数据库垃圾回收列表的自由空间，并尽可能把若干碎片合成一个大的自由空间。数据库放在内存中时，这种扫视与合并是自动进行的。 &lt;br /&gt;
&lt;br /&gt;
正常情况下，不需要调用这个谓词。但当插入新项时如果数据库中有太多的回收空间没有得到重新利用，db_garbagecollect有助于得到可利用的空间。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_btrees/1====&lt;br /&gt;
&lt;br /&gt;
回溯时，db_btrees会连续将BtreeName约束为指定数据库中各个B+树的名称。&lt;br /&gt;
&lt;br /&gt;
各个名称是依排定的顺序返回的。关于B+树后面还要介绍。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_chains/1====&lt;br /&gt;
&lt;br /&gt;
回溯时，db_chains会连续将ChainName约束为指定数据库中各个链的名称。各个名称是依排定的顺序返回的。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_statistics/4====&lt;br /&gt;
&lt;br /&gt;
db_statistics返回数据库的统计信息。 各参数所表示的意义如下：&lt;br /&gt;
&lt;br /&gt;
参数NoOfTerms表示数据库中的总项数。参数MemSizeis表示数据库存放于内存中的内表所占字节数。参数DbaSizeis表示数据库中项与描述符占用的总字节数。如果数据库是在文件中，而这个值比该文件占用的字节数小很多，这个文件就应该可以用db_copy进行压缩。参数FreeSize表示空闲内存的值，依数据库当前位置不同这个值也会不同：&lt;br /&gt;
&lt;br /&gt;
*数据库中内存中时，FreeSize表示全局栈顶与堆顶之间空闲的字节数（注意：可能还有若干空间的字节数没有包含在这个值中）。&lt;br /&gt;
*数据库在文件中时，FreeSize表示硬盘文件中未使用的字节数。&lt;br /&gt;
&lt;br /&gt;
===操控链===&lt;br /&gt;
&lt;br /&gt;
To要把一个项插入到外部数据库的链中，可以使用谓词 chain_inserta、chain_insertz 或 chain_insertafter。可以用 chain_terms 连续地把它的参数约束为链的项及其索引号，还可以用 chain_delete 删除外部数据库中某个链的所有项。&lt;br /&gt;
&lt;br /&gt;
还有四个标准谓词可以返回数据库的索引号，这四个标准谓词是：chain_first、chain_last、chain_next 和 chain_prev。&lt;br /&gt;
&lt;br /&gt;
===操控项===&lt;br /&gt;
&lt;br /&gt;
有三个外部数据库管理的标准谓词都与项有关，它们是：term_replace、term_delete 和 ref_term。无论调用哪个与项有关的外部数据库标准谓词，都需要将项的域作为一个参数给出来。正因为如此，所以把一个数据库中所有项的域声明为单个域中的可选项会是个好办法。例如：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;domains&lt;br /&gt;
    terms_for_my_stock_control_database =&lt;br /&gt;
        customer(Customer, Name, ZipCode, Address);&lt;br /&gt;
        supplier(SupplierNo, Name, Address);&lt;br /&gt;
        parts(PartNo, Description, Price, SupplierNo).&amp;lt;/vip&amp;gt;&lt;br /&gt;
注意，对外部数据库没有类型（域）上的混杂限制要求。一个链可以含有文本串而另一个链上的可以是整数，第三个链中还可以是某种复合结构，等等。但是，外部数据库的数据项并不是按类型描述符存放的，比如一个整数未必就是刚好占用两个字节。取回数据时恰好就是存放时的那种样子（同样的域），这是编程人员应该保证的。如果弄混了域，会产生运行时错误。&lt;br /&gt;
&lt;br /&gt;
==B+树==&lt;br /&gt;
&lt;br /&gt;
B+树是一种数据结构，用于实现快速有效地对大量数据进行分类排序的方法。B+树还有比较高效的搜索算法。可以认为B+树对数据库提供了一个索引，这也是为什么有时它又被称为“索引”的原因。&lt;br /&gt;
&lt;br /&gt;
在Visual Prolog中，B+树是在外部数据库中的。B+树的每个项有一对值：一个键串和相关联的数据库索引号。创建数据库时，先在数据库中插入记录并为该记录建立一个键。Visual Prolog B+树谓词接着就可以用来把这个键及该记录相应的数据库索引号插入到一个B+树中。&lt;br /&gt;
&lt;br /&gt;
要搜索数据库中的某个记录时，只需要找到该记录的键，而B+树会给出相应的索引号。用这个索引号，就可以从数据库中得到需要的记录。当B+树增长变化时，它会保持键的顺序，这也就意味着我们可以很容易地得到排序好的记录列表。&lt;br /&gt;
&lt;br /&gt;
B+树类似于二进制树，只不过在B+树的各个节点上保存了不止一个键串。B+树也是平衡树，这就是说对树中各个叶上的每个键的搜索路径长度是一样的。由于这个特点，对于在数以百万计的键中搜索某个键是有保证的，就是在最坏的情况下，需要对磁盘进行访问，也只需要不多的几次，这和每个节点上保存有多少键相关。&lt;br /&gt;
&lt;br /&gt;
尽管B+树是放在外部数据库中的，但并非一定要与它所指的项放在同一个数据库中，可以是一个数据库中含有一些链，而另一个数据库放指向这些链中项的B+树。&lt;br /&gt;
&lt;br /&gt;
===Pages(页） Order（阶） Key Length（键长）===&lt;br /&gt;
&lt;br /&gt;
在B+树中，键是聚合在页里的，每页有相同的大小，所有的页都包含有同样数量的键，这意味着所有为B+树保存的键必须大小相同。键的大小取决于KeyLen参数，这是在创建B+树时需要指定的一个参数。如果要在B+树中插入比KeyLen还要长的串，Visual Prolog会把多出来的部分截去。通常应该尽可能选小一些的KeyLen值以节省空间、提高速度。&lt;br /&gt;
&lt;br /&gt;
创建B+树时还需要指定称为阶的一个参数。这个参数决定了树的各个节点可以存储多少个键，而通常它是需要反复试验才能取得最佳值。阶的起始值用4是不错的选择，它允许各节点上保存4～8个键。阶的值必须要通过实验来选取，因为B+树的搜索速度与键长与阶的值、B+中键的数量以及计算机硬件配置都有关系。&lt;br /&gt;
&lt;br /&gt;
===重复键===&lt;br /&gt;
 &lt;br /&gt;
设置B+树时，需要允许键的重复。比如，设置一个客户数据库的B+树，用客户的姓做键，键就会有重复出现的可能。由于这个原因，B+树是可以有重复键的。&lt;br /&gt;
&lt;br /&gt;
要删除数据库中的一个项，需要给出相应的B+树键及数据库索引号（因此键重复也不会有问题）。&lt;br /&gt;
&lt;br /&gt;
===多重操作===&lt;br /&gt;
&lt;br /&gt;
为了能对B+进行多重操作，每个B+树内部都有多个指针，允许对一个树多次打开。不过要注意，如果更新了一个B+树的拷贝，而此时又有已经打开了的副本，则副本的指针会重定位到树的顶端。&lt;br /&gt;
&lt;br /&gt;
===B+树的标准谓词===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog提供了一些标准谓词来处理B+树，这些谓词的工作方式与相应的 db_... 谓词是一致的。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_create/4 and chainDB::bt_create/5====&lt;br /&gt;
&lt;br /&gt;
调用这个谓词可以创建一个新的B+树。&lt;br /&gt;
&lt;br /&gt;
参数BtreeName指定新树的名称，以后可以把这个名称作为参数用于bt_open。B+树的KeyLen参数和Order参数是在树创建时给出的，以后不能再更改。如果调用bt_create/4或bt_create/5时参数Duplicates设为1，则在这个B+树中允许重复；若是设为0，则不能在该树中插入重复的项。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_open/2====&lt;br /&gt;
&lt;br /&gt;
bt_open打开名为BtreeName的已经创建了的B+树。&lt;br /&gt;
&lt;br /&gt;
打开一个B+树时，这个调用会返回一个用于该B+树的选择器Btree_Sel，它就是该B+树的引用，无论系统的搜索或定位操作都要用到它。B+树的名称与选择器之间的关系就如同一个实际的文件名称与相应的符号文件名之间的关系。&lt;br /&gt;
&lt;br /&gt;
可以多次打开同一个B+树以便同时进行多项操作。每打开一次该B+树，就分配一个描述符，各个描述符都对应有自己的B+树内部指针。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_close/1 and chainDB::bt_delete/1====&lt;br /&gt;
&lt;br /&gt;
可以用bt_close关闭一个打开的B+树，也可以用bt_delete删除整个B+树。&lt;br /&gt;
&lt;br /&gt;
调用bt_close会释放打开的名为BtreeName的B+树分配的内部缓冲区。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_copyselector/2====&lt;br /&gt;
&lt;br /&gt;
bt_copyselector对已经打开的B+树复制一个新的选择器。&lt;br /&gt;
&lt;br /&gt;
新选择器开始时其指针定位在与老的选择器相同的位置上。之后，B+树的两个选择器定位就都可以自行确定，互不相干。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_statistics/7====&lt;br /&gt;
&lt;br /&gt;
bt_statistics返回由Btree_Sel指定的B+树的统计信息。各参数意义如下：&lt;br /&gt;
&lt;br /&gt;
参数Btree_Sel是该B+树的bt_selector；NumKeys是该树中键的总数；NumPages是该树的总页数；Depth是该树的深度；KeyLen是该树键长；Order是该树的阶；PgSize是页大小（以字节计）。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_insert/3 and chainDB::key_delete/3====&lt;br /&gt;
&lt;br /&gt;
标准谓词key_insert和key_delete用于更改B+树。&lt;br /&gt;
&lt;br /&gt;
给定Key和Ref时，可以删除有重复项的B+树中的某个确定项。&lt;br /&gt;
&lt;br /&gt;
chainDB::key_first/2, chainDB::key_last/2, and chainDB::key_search/3&lt;br /&gt;
每个B+树都维护有指向自身节点的内部指针。key_first和key_last可以分别把指针置于树的第一个键和最后一个键的位置上，而key_search则可以把指针置于指定键的位置上。&lt;br /&gt;
&lt;br /&gt;
如果找到了指定的键，key_search就成功；反之则失败，并将B+的内部指针置于该指定键应该存放的位置之后。这时可以用key_current返回这个键及其数据库索引号。如果想要在一个有重复项的B+树中准确定位，也可以把索引号做为一个条件输入。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_next/2 and chainDB::key_prev/2====&lt;br /&gt;
&lt;br /&gt;
可以用谓词key_next和key_prev在经过排序树中向前或向后移动B+树指针。&lt;br /&gt;
&lt;br /&gt;
If如果已经到了树的端头，想进一步移动指针会引起失败，但指针本身还是会移出树外。&lt;br /&gt;
&lt;br /&gt;
====chainDB::key_current/3====&lt;br /&gt;
&lt;br /&gt;
key_current返回B+树当前指针指向的键及其数据库索引号。&lt;br /&gt;
&lt;br /&gt;
调用谓词bt_open、bt_create、key_insert或key_delete之后立即使用key_current谓词会失败，当指针由于key_prev或key_next已经位于第一个键之前或最后一个键之后使用key_current谓词也会失败。&lt;br /&gt;
&lt;br /&gt;
==外部数据库编程==&lt;br /&gt;
&lt;br /&gt;
本节要介绍使用Visual Prolog外部数据库系统的一些基本方法与原则。主要内容有：&lt;br /&gt;
&lt;br /&gt;
*“遍历数据库” 介绍顺序扫遍外部数据库中的链或B+树的方法。&lt;br /&gt;
*“数据库的防护” 说明如何应对没想到的电源故障及其它可能的问题以保护数据库。&lt;br /&gt;
*“使用B+树内部指针” 说明如何在打开的B+树中使用定位指针的一些谓词。&lt;br /&gt;
&lt;br /&gt;
===遍历数据库===&lt;br /&gt;
在使用数据库系统时，头脑中一定要清楚Visual Prolog的存储机制。每当Visual Prolog用ref_term谓词从外部数据库中取回一个项时，会把这个项放在全局栈中。这个项占用的空间，系统要一直等到程序失败并回溯到ref_term调用之前的点才会释放。这意味着，要想连续地遍历外部数据库中某个链，总应该使用下面这样的结构：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 连续访问一个链所用的结构 */&lt;br /&gt;
scan(ChainObj, Chain, ....) :-&lt;br /&gt;
    ChainObj:chain_first(Chain, Ref),&lt;br /&gt;
    scanloop(ChainObj, Ref).&lt;br /&gt;
scanloop(ChainObj, Ref) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:ref_term(Ref, Term),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scanloop(ChainObj, _) :-&lt;br /&gt;
    ChainObj:chain_next(Ref, NextRef),&lt;br /&gt;
    scanloop(ChainObj, NextRef).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
同样，要遍历索引，应该用如下的结构：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 连续访问索引所用的结构 */&lt;br /&gt;
scan(ChainObj, Bt_selector) :-&lt;br /&gt;
    ChainObj:key_first(Bt_selector, FirstRef),&lt;br /&gt;
    scanloop(ChainObj, Bt_selector, FirstRef).&lt;br /&gt;
scanloop(ChainObj, Bt_selector, Ref) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:ref_term(Ref, Term),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scanloop(ChainObj, Bt_selector, _) :-&lt;br /&gt;
    ChainObj:key_next(Bt_selector, NextRef),&lt;br /&gt;
    scanloop(ChainObj, Bt_selector, NextRef).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
也可以用chain_terms来遍历外部数据库中的链，像这样：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;/* 遍历链的另一种方法 */&lt;br /&gt;
scan(ChainObj, Chain) :-&lt;br /&gt;
    hasDomain(myDom, Term),&lt;br /&gt;
    ChainObj:chain_terms(Chain, Term, Ref),&lt;br /&gt;
    /* ... 进行相应的处理 ... */&lt;br /&gt;
    fail.&lt;br /&gt;
scan(_, _).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
要遍历B+树，可以通过已经定义和使用过的bt_keys谓词来实现。回溯时，这个谓词就会逐一返回（指定B+和数据库）各个键及相应的数据库索引号。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/vip&amp;gt;predicates&lt;br /&gt;
    bt_keys : (chainDB, bt_selector, string, ref).&lt;br /&gt;
    bt_keysloop : (chainDB, bt_selector, string, ref).&lt;br /&gt;
clauses&lt;br /&gt;
    bt_keys(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_first(Bt_selector, _),&lt;br /&gt;
        bt_keysloop(ChainObj, Bt_selector, Key, Ref).&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_current(Bt_selector, Key, Ref).&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref) :-&lt;br /&gt;
        ChainObj:key_next(Bt_selector, _),&lt;br /&gt;
    bt_keysloop(ChainObj, Bt_selector, Key, Ref).&amp;lt;/vip&amp;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;
===使用B+树的内部指针===&lt;br /&gt;
&lt;br /&gt;
每个打开的B+树都有一个指向其节点的指针，打开或更新B+树时，这个指针指向树的起点之前；当指针指向树的最后一个键时，调用key_next会使指针指向树的末端之后的位置。只要指针超出了树，key_current就都会失败。如果这样一些安排不能满足特定应用程序的要求，就需要自行构造其它的谓词了。&lt;br /&gt;
&lt;br /&gt;
比如，可以构造像下面这样一些mykey_next、mykey_prev、mykey_search的谓词，让B+树指针总是在B+树的内部（如果树中有一些键的话）。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    mykey_next(db_selector, bt_selector, ref).&lt;br /&gt;
    mykey_prev(db_selector, bt_selector, ref).&lt;br /&gt;
    mykey_search(db_selector, bt_selector, string, ref).&lt;br /&gt;
clauses&lt;br /&gt;
    mykey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref), !.&lt;br /&gt;
    mykey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref), fail.&lt;br /&gt;
    mykey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref), !.&lt;br /&gt;
    mykey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref), fail.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, Key, Ref) :-&lt;br /&gt;
        Dba:key_search(Bt_selector, Key, Ref), !.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, _, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, _, Ref), !.&lt;br /&gt;
    mykey_search(Dba, Bt_selector, _, Ref) :-&lt;br /&gt;
        Dba:key_last(Bt_selector, Ref).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
还可以使用下面定义的samekey_next和samekey_prev谓词，将索引指针定位到具有重复键的B+树中同一键值的下一个键上：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;predicates&lt;br /&gt;
    samekey_next(db_selector, bt_selector, ref).&lt;br /&gt;
    try_next(db_selector, bt_selector, ref, string).&lt;br /&gt;
    samekey_prev(db_selector, bt_selector, ref).&lt;br /&gt;
    try_prev(db_selector, bt_selector, ref, string).&lt;br /&gt;
clauses&lt;br /&gt;
    samekey_next(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, OldKey, _),&lt;br /&gt;
        try_next(Dba, Bt_selector, Ref, OldKey).&lt;br /&gt;
    try_next(Dba, Bt_selector, Ref, OldKey) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, Ref),&lt;br /&gt;
        Dba:key_current(Bt_selector, NewKey, _),&lt;br /&gt;
        NewKey = OldKey, !.&lt;br /&gt;
    try_next(Dba, Bt_selector, _, _) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, _),&lt;br /&gt;
        fail.&lt;br /&gt;
    samekey_prev(Dba, Bt_selector, Ref) :-&lt;br /&gt;
        Dba:key_current(Bt_selector, OldKey, _),&lt;br /&gt;
        try_prev(Dba, Bt_selector, Ref, OldKey).&lt;br /&gt;
    try_prev(Dba, Bt_selector, Ref, OldKey) :-&lt;br /&gt;
        Dba:key_prev(Bt_selector, Ref),&lt;br /&gt;
        Dba:key_current(Bt_selector, NewKey, _),&lt;br /&gt;
        NewKey = OldKey, !.&lt;br /&gt;
    try_prev(Dba, Bt_selector, _, _) :-&lt;br /&gt;
        Dba:key_next(Bt_selector, _),&lt;br /&gt;
        fail.&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===外部数据库与文件共享===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog支持文件共享型外部数据库，也就是说一个文件可以被若干用户或是进程同时打开，这对在局域网中或多任务平台上使用外部数据库是很有用处的。&lt;br /&gt;
&lt;br /&gt;
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;
域accessMode = read或readWrite；域denyMode = denyNone或denyWrite或denyAll。 &lt;br /&gt;
&lt;br /&gt;
===共享模式下打开数据库===&lt;br /&gt;
&lt;br /&gt;
为了使打开的外部数据库可以共享，需要使用四元维版本的db_open打开已有的数据库文件，并指定AccessMode及DenyMode。 &lt;br /&gt;
&lt;br /&gt;
如果AccessMode是read，打开的文件就只能读，任何对文件的更新都会导致运行时错误；如果是readwrite，该文件就可读可写。AccessMode在谓词db_begintransaction中也会用到。&lt;br /&gt;
&lt;br /&gt;
如果DenyMode是denyNone，那么所有其他的用户都可以更新和读取该文件；若是denyWrite，则其他用户不能以 AccessMode = readWrite 模式打开该文件，但如果该文件是按 AccessMode = readWrite 模式打开的，还是可以对文件进行更新的。如果db_open是带 DenyMode = denyAll 的，则任何其他用户都不能访问该文件。&lt;br /&gt;
&lt;br /&gt;
打开文件的第一个用户使用的DenyMode，决定了后续所有打开该文件的条件，如果试图用不相容的模式打开该文件，就会产生运行时错误。下表归纳了打开文件的结果及后续再打开同一个文件时的各种情况：&lt;br /&gt;
&lt;br /&gt;
Domain（域）  用途  &lt;br /&gt;
bt_selector  bt_open返回的用于选取B+树的唯一值  &lt;br /&gt;
place  数据库的位置：内存中或是文件中 &lt;br /&gt;
accessmode  确定文件的使用方式 &lt;br /&gt;
denymode  确定其他用户可以打开该文件的方式 &lt;br /&gt;
ref  项在链中位置的索引 &lt;br /&gt;
&lt;br /&gt;
===Transactions（事务处理）与文件共享===&lt;br /&gt;
&lt;br /&gt;
如果一个数据库文件是打开共享的，则所有以任何方式访问数据库文件的数据库谓词必须要集中在事务处理内部，它是通过把对这些谓词的调用包围在db_begintransaction和db_endtransaction之间来实现的。&lt;br /&gt;
&lt;br /&gt;
根据选择的AccessMode和DenyMode不同，共享的文件有可能在事务处理期间会被锁定。再根据锁定的程度，事务处理时其他用户可能还不能读或更新该文件。这对于避免读写的混乱当然是必要的，但文件共享要有意义就一定不要有过多的锁定。这可以通过尽量减小（缩短）事务处理来实现，只把那些访问数据库的谓词包含在事务处理中。&lt;br /&gt;
&lt;br /&gt;
涉及文件共享的事务处理这个概念非常重要，两者之间的需求是矛盾的，即同时要实现数据库的安全和最少的文件锁定。&lt;br /&gt;
&lt;br /&gt;
db_begintransaction是确保数据库一致性的，它实现文件的适当锁定，文件可由若干用户同时读但只允许有一个进程在当下更新数据库。谓词db_setretry可以用来设置db_begintransaction在返回运行时错误之前需要等待多长时间才可以访问该文件。当文件AccessMode是按read打开的，而调用db_begintransaction时AccessMode设成了readWrite也会产生运行时错误。如果已经调用了db_begintransaction，对同一个数据库调用新的db_begintransaction之前必须调用db_endtransaction，否则就会出现运行时错误。&lt;br /&gt;
&lt;br /&gt;
下表归纳了db_begintransaction对不同的AccessMode和DenyMode组合所做的反应：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;AccessMode read readWrite DenyMode denyNone WLock\Reload RWLock\Reload denyWrite None RWLock denyAll None NoneActions:&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
WLock ：不允许写，只允许读。&lt;br /&gt;
RWLock ：读和写都不允许。&lt;br /&gt;
Reload ：重新载入文件描述符。&lt;br /&gt;
由于重新载入及锁定都是需要时间的，所以要小心选用AccessMode和DenyMode。比如，若不会有用户更新数据库，将AccessMode设为read而DenyMode设为denyWrite就可以使开销最小化。&lt;br /&gt;
 &lt;br /&gt;
===文件共享谓词===&lt;br /&gt;
&lt;br /&gt;
Visual Prolog中有一些文件共享谓词，它们是db_open、db_begintransaction、db_endtransaction、db_updated、bt_updated和db_setretry。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_begintransaction/1====&lt;br /&gt;
这个谓词标记事务处理的起点，应该在访问共享的数据库之前调用它，哪怕是以denyAll打开的数据库，也应该调用它。db_begintransaction调用时AccessMode必须为read或readwrite。进一步的了解可以参看PFC的帮助文件。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_endtransaction/0====&lt;br /&gt;
db_endtransaction标记一个事务处理的结束并解锁数据库。必须在每个db_begintransaction调用之后且在下一个db_begintransaction调用之前调用db_endtransaction。&lt;br /&gt;
&lt;br /&gt;
====chainDB::db_updated/0====&lt;br /&gt;
如果有其他用户更新了数据库，db_begintransaction的调用会保持数据库的一致性。调用谓词db_updated可以检查变更情况：如果在一个事务处理中调用它，而db_begintransaction之后有其他用户更新过数据库，就会成功；如果没有更新，就会失败。如果在事务处理之外调用这个谓词，就会产生运行时错误。&lt;br /&gt;
&lt;br /&gt;
====chainDB::bt_updated/1====&lt;br /&gt;
这个谓词很像db_updated，只不过它是在指定的B+树更新了才会成功。 &lt;br /&gt;
&lt;br /&gt;
====chainDB::db_setretry/2====&lt;br /&gt;
如果由于某个进程锁定了一个文件而导致另一个进程访问该文件被拒绝，这另一个进程可以进入一个等待期，期满后再试着访问该文件。谓词db_setretry可以改变缺省的SleepPeriod设置，这是以百分之一秒为单位的等待期；这个谓词还可以设置RetryCount，这是重试访问的最大次数。缺省的设置，RetryCount是100而SleepPeriod是10。&lt;br /&gt;
&lt;br /&gt;
===文件共享时的编程===&lt;br /&gt;
使用文件共享谓词一定要十分小心。尽管使用恰当时它们能够保证共享数据库低层的一致性，但防止某个程序危害高层的一致性则是应用程序编写人员的责任。这中间所用的所谓“事务处理（transaction）”是将文件访问集中在一起的一个方法，但一定要清楚它并没有提供什么恢复机制，不论是软件还是硬件失效引起的程序中断，都有可能导致数据库文件的不完整。&lt;br /&gt;
&lt;br /&gt;
若干进程共享一个数据库时，还需要特别注意涉及到的域，它们必须要严格一致，使用时顺序也要一致。&lt;br /&gt;
&lt;br /&gt;
为避免不要地锁定数据库文件，事务处理一定要尽量短小以使文件锁定时间尽可能短。同时，要把对数据库项的定位与访问的谓词放在一个事务处理中，这也很重要。 &lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;.....&lt;br /&gt;
ChainObj:db_begintransaction(readWrite),&lt;br /&gt;
  ChainObj:key_current(Btree, Key, Ref),&lt;br /&gt;
  ChainObj:ref_term(Ref, Term),&lt;br /&gt;
ChainObj:db_endtransaction(),&lt;br /&gt;
write(Term),&lt;br /&gt;
.....&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
上面的代码中，谓词key_current和ref_term不应该分开放在不同的事务处理中，因为Ref中存储的项可能会在不同事务处理间就被其他用户删除了。 &lt;br /&gt;
&lt;br /&gt;
当B+树被别的用户更新并且文件缓冲区重新加载后，B+树的指针会重新定位到第一项之前。调用谓词bt_updated可以确定是否需要重新对B+树指针定位。把当前的键暂存在内部数据库中，既可以列表完整的索引号，同时也可以使用事务处理短小。 &lt;br /&gt;
&lt;br /&gt;
===实现高级锁定===&lt;br /&gt;
对于一个共享的数据库文件，用户可以进行所有独自享有该文件时相同的操作。把对文件的访问集中于db_begintransaction和db_endtransaction的内部，可以保证Visual Prolog系统自身描述符表的一致性。但是，在网络环境中多用户条件下应用程序的各种逻辑约束, 需要在较高层级上由编程人员确保得以实现。&lt;br /&gt;
&lt;br /&gt;
W我们把这称之为高层级的锁定或应用程序级的锁定。通过使用简单的db_begintransaction和db_endtransaction，可以有多种方法实现高级锁定的方法。&lt;br /&gt;
&lt;br /&gt;
需要使用高级锁定的一个很普通的例子，就是用户想要改变数据库中记录的时候。确定要改变一个记录时，就需要采取一些步骤使应用程序对该记录锁定，直到更改结束、新的记录写回到磁盘中，然后对该记录解锁。&lt;br /&gt;
&lt;br /&gt;
下面是对应用程序级锁定的一些建议：&lt;br /&gt;
&lt;br /&gt;
*在记录中设置一个专门的字段来表示它是否为锁定状态；&lt;br /&gt;
*设置一个专门的B+树或链，存放所有被用户锁定的记录的索引；&lt;br /&gt;
*用一个REF存放所有锁定记录的索引表。&lt;br /&gt;
或许还需要实现某种超级用户机制，以便特殊的用户可以解除对记录的锁定。&lt;br /&gt;
&lt;br /&gt;
这只是个例子，比如需要实现对表或表组，或知识组等的高级锁定。&lt;br /&gt;
&lt;br /&gt;
注意： 如果要在以共享方式打开的数据库文件中删除一个B+树，必须要使用高级锁定来确保没有其他用户打开这同一个树。 Visual Prolog系统中没有对一个B+树名是否因其被删除了而不可用做检查。&lt;br /&gt;
&lt;br /&gt;
===一个完整的文件共享实例===&lt;br /&gt;
在这个完整的例子中，可以看到如何通过使用自己实现的锁定系统来方便地共享文件。如果自己管理锁，可以避免不必要的文件锁定，其他用户不必因为文件锁定而长时间等待对文件的访问。&lt;br /&gt;
&lt;br /&gt;
该程序允许若干用户对一个共享的文件进行文本的创建、编辑、查看及删除。创建和编辑文本时，需要进行锁定直到编辑完成。文本锁定时，其他用户不能删除或编辑该文本，但可以查看它。用不同的 db_open和db_setretry设置运行这个程序，体验一下吧。&lt;br /&gt;
&lt;br /&gt;
===Visual Prolog文件共享的实现方面===&lt;br /&gt;
Visual Prolog中的文件共享很有效率，很快，因为其它用户对数据库进行了更新之后只是必需的数据库文件描述符部分要重新加载。前面已经介绍过，只是在特定环境下才需要进行文件缓冲区的重载及文件锁定，而数据库文件内部完善的管理保证了更新后只进行最小必要的磁盘操作。 &lt;br /&gt;
&lt;br /&gt;
数据库有一个六字节整数序列号，每次更新它都会增加并被写到磁盘上。db_begintransaction谓词会将当前的序列号副本与磁盘上的进行比较，如果不一样，就重载描述符。 锁使用的是一个可以容纳256个用户的数组。当一个用户需要访问文件时，就会为其在这个数组中分配一个未使用的位置，并在事务处理期间一直占用这个位置。这种方法允许文件同时有若干个读者进行访问。如果db_begintransaction是以AccessMode = readWrite方式调用的，则需要等待当前已有读者都从这个数组中退出并放弃所占用的位置之后，再锁定整个的数组，使其他用户不能再访问该文件。&lt;br /&gt;
&lt;br /&gt;
==小结==&lt;br /&gt;
Visual Prolog外部数据库系统为用户的数据库应用程序添加了强大、快速和高效的功能。在本文中主要介绍了：&lt;br /&gt;
&lt;br /&gt;
*外部数据库的项是存储在链中的，它可以直接用数据库索引号进行访问；索引号属于预定义的ref域。&lt;br /&gt;
*各数据库可以由数据库选择符来指定，它是属于标准域db_selector的。&lt;br /&gt;
*外部数据库可以放在两个地方，这与预定义的place域有关：&lt;br /&gt;
*in_file把数据库放在磁盘文件中，&lt;br /&gt;
*in_memory把数据库放在内在中。&lt;br /&gt;
*如果要在数据库中对项进行排序，会要用到B+树。与数据库一样，各个B+树是通过B+树选择符来指定的，它属于标准域bt_selector。 &lt;br /&gt;
*B+树节点的每个项由一个标识记录的键串（也叫索引）和与记录关联的数据库索引号构成。&lt;br /&gt;
*B+树的键组成页，而存储在一个节点上的键的数量是由树的阶指定的。&lt;br /&gt;
*文件共享是通过将访问数据库的谓词组合在事务处理中实现的。&lt;br /&gt;
&lt;br /&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>
	<entry>
		<id>https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84CGI%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%EF%BC%9A%E5%9F%BA%E7%A1%80%E7%AF%87&amp;diff=4120</id>
		<title>Visual Prolog的CGI应用程序：基础篇</title>
		<link rel="alternate" type="text/html" href="https://wiki.visual-prolog.com/index.php?title=Visual_Prolog%E7%9A%84CGI%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%EF%BC%9A%E5%9F%BA%E7%A1%80%E7%AF%87&amp;diff=4120"/>
		<updated>2015-06-17T12:38:12Z</updated>

		<summary type="html">&lt;p&gt;Yiding: /* 看个很简单的用Visual Prolog创建的CGI应用程序，有吗？ */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;（以下内容译自[[:Category:Tutorials]]中的CGI Applications in Visual Prolog: Basics。）&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
CGI 是 Common Gateway Interface （通用网关接口）的缩写。它是万维网联盟（the World Wide Web Consortium）推荐的用于网络服务器输入输出信息流的一种规范。 &lt;br /&gt;
&lt;br /&gt;
读者大约知道，HTML浏览器（如 Internet Explorer、 Netscape 等）要发送信息并从网络服务器接收信息。不过，在万维网上并不只是简单地读一些超链接的HTML文件，还有交互，数据在浏览器中是有进有出的，包括用户在浏览器中填写的表格数据等。有许多事网络服务器自己是应付不了的，它不知道如何处理类似的交互数据。 &lt;br /&gt;
&lt;br /&gt;
其实网络服务器也就是个简单的机器，它通常也就是发送一下HTML文件及其它一些它能认识的mime文件给浏览器。一般它做不了再高级的事了，比如访问后台数据库、向浏览器发回数据库查询等。CGI就是用来增加网络服务器功能的，它可以让网络服务器与驻留在同一个机器上的CGI应用程序对话。而CGI应用程序可以做那些网络服务器做不了的事，如：访问后台数据库或进行复杂搜索等。 使用CGI的网络服务器通常要比不使用的能为浏览器提供更多的信息。&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
===“通用网关”有什么意义？===&lt;br /&gt;
&lt;br /&gt;
“通用”表示所有网络服务器都接受这个协议，“网关”是说好像是通过一扇门、一个关口从另一边的其它处理过程获得数据。打个比方，网络服务器在网关的一边而CGI应用程序在网关的另一边，网络服务器和CGI应用程序都用CGI进行对话。&lt;br /&gt;
&lt;br /&gt;
===什么是流？===&lt;br /&gt;
 &lt;br /&gt;
“流”这个术语，用来表示长长的信息队列是一次一个字符地处理的。可以这样想象：一个直径是任意时刻只能有一个字符通过的管子，在流的形式下，字符是一次一个地从管子另一头出来的。&lt;br /&gt;
&lt;br /&gt;
和管子里的水不能流向两个方向一样，程序中的一个流同一时刻也不能即可读又可写。字符要么是从流中出来（也就是可读的流），要么是到流中去（可写的流），但同一个流一个程序不能用来既读又写。&lt;br /&gt;
&lt;br /&gt;
对于CGI应用程序，字符是 ANSI 格式的，也就是8-bit字符。关于这一点后面还要说，因为Visual Prolog缺省使用的是 Unicode 字符，是16-bit的。Visual Prolog中有合适的谓词采集流中的ANSI字符，所以处理流不成问题。最后还要说一点，我们能浏览网页就是字符流通过网络管道一次一个字符地进入了浏览器，我们察觉不到是因为单个进入浏览器的字符很快就被组合在一起了。 &lt;br /&gt;
&lt;br /&gt;
===什么是mime类型？===&lt;br /&gt;
&lt;br /&gt;
可以认为mime类型就是文件扩展名。它说明网络服务器及其客户（如浏览器）所能处理的文件类型。大多数网络服务器是通过文件扩展名确定mime类型的。浏览器与网络服务器交互时，它在索求资源（特定的mime类型文件，等等）时也会告诉网络服务器自己能处理的mime类型。网络服务器发送浏览器需要的文件，如.html、.jpg、.gif等等，供它显示。不认识的mime类型，网络服务器或者就不送，或者就按普通ASCII或HTML文件送，具体做法由网络服务器确定。&lt;br /&gt;
&lt;br /&gt;
因为信息是当作流送给浏览器的，网络服务器要在流的起始插入一些包含mime类型的头。图1典型地说明了网络服务器给浏览器送了些什么。一但这些带有mime类型的文件到了浏览器，它就要分析头，确定mime类型表示的数据，再按照自己可以处理的mime类型的内部表，显示相应的数据。 &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[Image:cgiApplicationsInVisualProlog06.jpg|frame|图 1]]&lt;br /&gt;
&lt;br /&gt;
===什么是头？===&lt;br /&gt;
头，在这里指的是嵌在流里的、在实际数据之前的、带有回车符的一行文本。图1中，可以看到，数据上面的那些行都是网络服务器传给浏览器的头。在实际数据和头之间有一个分隔空行。&lt;br /&gt;
&lt;br /&gt;
注意图中的“Content-type:...”头，这行就是数据的mime类型。它与CGI应用程序特别相关，因为网络服务器调用CGI应用程序提供要传递给浏览器的数据时，它对CGI应用程序给出数据的mime类型是无能为力的。因此，按约定CGI应用程序在传递其它数据之前先要传递这个特殊的头，头和数据之间用空行分隔。在实际编写CGI应用程序时要特别注意这一点，忘了内容类型的头或是分隔的空行或是两者都忘了是常有的事，这会弄出很多麻烦。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===什么是CGI应用程序？===&lt;br /&gt;
CGI应用程序，或说网关程序（见图2）是一个可执行文件（一般是 .exe 文件），它不接收键盘或鼠标信息，也就是说它不是交互程序（而图形用户界面程序则是交互程序）。这样的程序也叫控制台程序，它用 stdin 流接收输入而用 stdout 流输出。&lt;br /&gt;
其它的头是网络服务器在CGI应用程序之后添加的，整合在一起的信息再送给浏览器。&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[Image:cgiApplicationsInVisualProlog07.jpg|frame|图 2]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
对于CGI应用程序，一经启动就会自己运行直到结束，不再需要什么输入。从这点上看它是自动的，就如同过去DOS里的命令行程序xcopy、format等一样。&lt;br /&gt;
&lt;br /&gt;
CGI应用程序又不是一般的控制台程序，它要求输入符合CGI规范，它的输出也会遵从CGI规范。&lt;br /&gt;
&lt;br /&gt;
有时，CGI应用程序也可以写成 .DLL 文件，但这样的变化要依赖于网络服务器。更特殊地，甚至批命令（.bat文件）也可以当成简单的CGI应用程序，只要网络服务器配置得能把这样的文件用做CGI应用程序。这里我们不涉及这些变形，只说支持CGI规范的可执行的（.exe文件）控制台应用程序。&lt;br /&gt;
&lt;br /&gt;
===什么是 stdin 流和 stdout 流？===&lt;br /&gt;
&lt;br /&gt;
stdin 是各种编程语言都使用的一个名称，它指由底层操作系统接收输入的这样一种特殊的输入流。程序（比如用Visual Prolog编写的程序）可以读 stdin 流，用流中的字符作为输入。相似地，stdout 是由操作系统管理的流，程序可以把字符输出到这个流中去。&lt;br /&gt;
&lt;br /&gt;
stdout 和 stdin 流可以从不同的程序中链接起来（也就是端到端连在一起）。例如，A程序写 stdout 流，而后另一个B程序用 stdin 流读取A程序写的内容。这是网络服务器使用的最基础的传输机制。在前面的例子中，A程序可以表示网络服务器而B程序就可以是CGI应用程序。stdin 和 stdout 流也常常合起来称为控制台（console）。&lt;br /&gt;
&lt;br /&gt;
使用CGI时信息流动是怎么发生的？&lt;br /&gt;
信息的流动起始于客户端（如浏览器）向网络服务器发出的请求。上面说过，客户端会告诉网络服务器自己的能力，还有需要网络服务器提供的信息。参看图3，它说明了浏览器（客户）向网络服务器发送的内容。&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
[[Image:cgiApplicationsInVisualProlog08.jpg|frame|图 3]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
第一行的内容：GET /Tests/file.html HTTP/1.0 有方法域。它指明了期望的HTTP方法、请求的资源、协议版本，等等。尽管 /Test/file.html 看起来像是个路径，但实际上它被称为URL（Uniform Resource Locator），它代表了一大堆东西，包括网络服务器所在机器上的实际文件名和目录等。&lt;br /&gt;
&lt;br /&gt;
常用的方法还有 POST ，它用于从浏览器向网络服务器发送数据（不是URL的一部分）； HEAD ，它用于只接收资源的头信息。其它一些域里包含有客户端的信息：Accept: 这个域告诉网络服务器客户端可以接受的各种数据类型，这些都是按MIME内容类型信息发送的。因此 Accept: text/plain 就表示该客户端可以接收普通文本文件，其它的以此类推。这个MIME类型是很重要的，因为这也是网络服务器告诉客户端数据按什么类型发送的方法。 其它一些头是些相关消息：User-Agent: - 产生请求的程序； From: - 谁的请求； Referer: - 这个请求需要的文件URL，等等。服务器发回需要的数据作为响应。它首先发送服务器响应头，这是用来通知传输状态、发送数据类型及一些其它附属信息的。接下来是一个空行，再接着才是实际的数据，参看图1。CGI在哪儿掺和进来的呢？——读者可能会奇怪。好，我们再来看请求的资源，在上面的例子中就是 /Tests/file.html， 如果替换掉它，换成 /cgi-bin/helloworld.exe。很多网络服务器的配置都是让这部分URL /cgi-bin/ 指示说 helloworld.exe 是个该服务器要调用的CGI程序。&lt;br /&gt;
&lt;br /&gt;
这个时候，helloworld.exe就会被网络服务器调用，而且还会通过环境变量和 stdout 流（它也是helloworld.exe的 stdin 流）传递一些信息给helloworld.exe。接下来，网络服务器就等着CGI应用程序，helloworld.exe，结束它的工作。&lt;br /&gt;
&lt;br /&gt;
helloworld.exe一停，网络服务器就收集它写出来的数据（包括内容类型头）并把它送给等着的浏览器。&lt;br /&gt;
&lt;br /&gt;
这一切都是一眨眼就完成的。&lt;br /&gt;
&lt;br /&gt;
所有网络服务器的设计都很细致，能很好地完成这些工作，用不着我们操心调用CGI应用程序后的整合工作。我们重点来看看CGI应用程序要怎么从网络服务器读信息以及自己做完事情后怎么把数据返回给网络服务器。&lt;br /&gt;
&lt;br /&gt;
===什么是环境变量？===&lt;br /&gt;
&lt;br /&gt;
所有操作系统都允许编程人员在所谓环境变量中加入 name=value 的关系式，这些环境变量是放在计算机内存中的。例如，DOS中的路径设置就是一个环境变量。可以用SET命令来查看为DOS设置的各种环境变量（Windows XP或Windows 2000也可以查看）。只要在命令行提示符后敲入SET命令，控制台就会显示出所有当前可用的环境变量（在列表中就可以看到路径）。&lt;br /&gt;
&lt;br /&gt;
环境变量最妙的是：可以让你构造完全个性化的变量并为其赋值。例如，想把口令保存起来，在操作系统命令行上可以这样用SET命令：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;SET password=xyze&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
尽管这个值是串，但是不必用引号。所有的值在内部保存时都是串，Visual Prolog程序也是按串取出它们的。CGI应用程序中环境变量的意义在于：网络服务器也用环境变量向它调用的CGI应用程序传递信息。下面是个例子。 &lt;br /&gt;
我们已经知道，网络服务器在调用CGI应用程序之前把许多数据放在了 stdout 流中。那么，CGI应用程序怎么知道要从流中读取的字符数量呢？为解决这个问题，网络服务器把内容长度放在一个叫作CONTENT_LENGTH的环境变量里，用这个环境变量的值CGI应用程序就可以从流中读出正确数量的字符。看一下cgi.pro中的Visual Prolog代码（这是Visual Prolog的CGI包里的）是怎么做的：&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    retrieve_POST_string() = Result :- % 取长度&lt;br /&gt;
        LenStr = environment::getVariable(&amp;#039;CONTENT_LENGTH&amp;#039;),&lt;br /&gt;
        Len = toTerm(LenStr),&lt;br /&gt;
        %只从流中读取出需要长度的内容&lt;br /&gt;
        Stream = console::getConsoleInputStream(),&lt;br /&gt;
        Stream:setMode(stream::ansi(ansi())),&lt;br /&gt;
        %流是ANSI模式的！&lt;br /&gt;
        Result = Stream:readString(Len).&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
除了CONTENT_LENGTH，大多数网络服务器在启动CGI应用程序之前还会设置下述标准环境变量，使用的网络服务器不同可能环境变量也会有所增减。CGI应用程序可以查看这些环境变量并使用相应的值。读者可以参看其它读物了解这些环境变量，不然本文就太长了。特别说一下，环境变量HTTP_REFERER在实现一些简单的安全保密措施中是很有用的（参见本文的高级篇）。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;PATH_INFO&lt;br /&gt;
PATH_TRANSLATED&lt;br /&gt;
REMOTE_HOST&lt;br /&gt;
REMOTE_ADDR&lt;br /&gt;
GATEWAY_INTERFACE&lt;br /&gt;
SCRIPT_NAME&lt;br /&gt;
REQUEST_METHOD&lt;br /&gt;
HTTP_ACCEPT&lt;br /&gt;
HTTP_ACCEPT_CHARSET&lt;br /&gt;
HTTP_ACCEPT_ENCODING&lt;br /&gt;
HTTP_ACCEPT_LANGUAGE&lt;br /&gt;
HTTP_FROM HTTP_HOST&lt;br /&gt;
HTTP_REFERER&lt;br /&gt;
HTTP_USER_AGENT&lt;br /&gt;
HTTP_COOKIE&lt;br /&gt;
QUERY_STRING&lt;br /&gt;
SERVER_SOFTWARE&lt;br /&gt;
SERVER_NAME&lt;br /&gt;
SERVER_PROTOCOL&lt;br /&gt;
SERVER_PORT&lt;br /&gt;
CONTENT_TYPE&lt;br /&gt;
CONTENT_LENGTH&lt;br /&gt;
USER_NAME&lt;br /&gt;
USER_PASSWORD&lt;br /&gt;
AUTH_TYPE&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===如何测试CGI应用程序？===&lt;br /&gt;
&lt;br /&gt;
随同CGI应用程序，还需要安装一个网络服务器，这样才能做测试。还需要HTML支持文件，它通过网络服务器来触发CGI应用程序。这些文件对不同的应用程序也都会不同。在学习例子的过程中，会慢慢了解这些支持文件。&lt;br /&gt;
&lt;br /&gt;
前面什么地方说过，CGI应用程序要放在网络服务器所在的同一台机器上。从安全上考虑，网络服务器只能从特定的（web路径）主机目录中获取文件。因此，要把CGI应用程序（CGI可执行文件和HTML文件）放在这样的目录下。如果不这样，就会使恶意访问者获取到不该能获取的文件。&lt;br /&gt;
&lt;br /&gt;
同样，网络服务器也只能调用放在正确位置的CGI应用程序。不能到处放CGI应用程序（尽管也有网络服务器允许这样，但并不是所有的都行）。现在，所有的网络服务器都有配置手段（独立的配置文件，或 .ini 文件，或windows注册表，等等）来配置自己的各种设置。其中关键的一个设置就是CGI应用程序的目录。针对某个网络服务器使用CGI应用程序时，首要的事情就是了解这个网络服务器是如何配置其CGI应用程序目录的。&lt;br /&gt;
&lt;br /&gt;
===使用什么网络服务器？===&lt;br /&gt;
&lt;br /&gt;
以前说过，网络服务器也就是个并不太复杂的软件。有很多可以免费使用的网络服务器，在 http://www.serverwatch.com/stypes/index.php/d2Vi 有列表，表里有商用的也有免费的。网络服务器的难点通常是它的web-path（网络路径）及CGI应用程序目录的配置。&lt;br /&gt;
&lt;br /&gt;
安装网络服务器的计算机里要安装TCP-IP协议（如果计算机已经可以访问因特网则已经安装好了）。安装好网络服务器后，或是按IP地址或是按机器的域名，就可以从网络服务器获取数据了。CGI应用程序可以与大多数网络服务器包括与Windows 2000自带的IIS5网络服务器配合工作得很好。&lt;br /&gt;
&lt;br /&gt;
===CGI应用程序放在哪儿？===&lt;br /&gt;
&lt;br /&gt;
一定要确保CGI应用程序放在网络服务器的适当目录里，这样的目录每个网络服务器各不相同。本文中，我们假设是放在这样的目录里的，它可以用服务器上的浏览器按这个URL: http://localhost/cgi-bin/&amp;lt;APPNAME&amp;gt; 被调用。 &lt;br /&gt;
&lt;br /&gt;
例如，如果有一个名为example1.exe的CGI应用程序，它应该能用URL：http://localhost/cgi-bin/example1.exe 在服务器上被访问到。 &lt;br /&gt;
&lt;br /&gt;
===看个很简单的用Visual Prolog创建的CGI应用程序，有吗？===&lt;br /&gt;
&lt;br /&gt;
有，没问题！只需要解压缩cgitutorial.zip中的example1.zip文件到合适的地方，查看里面的Visual Prolog文件。不过，也可以按下面的步骤创建一个这样的应用程序：&lt;br /&gt;
&lt;br /&gt;
打开一个新的Visual Prolog工程，UI策略是Console（控制台）： &lt;br /&gt;
&lt;br /&gt;
[[Image:cgiApplicationsInVisualProlog01.png]] &lt;br /&gt;
&lt;br /&gt;
主工程树会显示出来，里面有IDE智能创建的可能需要的所有文件。&lt;br /&gt;
&lt;br /&gt;
[[Image:cgiApplicationsInVisualProlog02.png]] &lt;br /&gt;
&lt;br /&gt;
接着，点主菜单上的 Build ，在其中再选 Build 或是按 Ctrl-Shift-B 。注意构建中的变化，工程构造时IDE会把先前工程树中看不到的相关PFC（Prolog Foundation Classes，Prolog基础类）文件拉到工程树中来。构造过程结束后，工程树会像下面这样：&lt;br /&gt;
&lt;br /&gt;
[[Image:cgiApplicationsInVisualProlog03.png]] &lt;br /&gt;
&lt;br /&gt;
恭喜！百分之九十五的活儿完成了。在工程的EXE文件夹里，有了一个控制台应用程序，它现在什么事情都不会做。我们需要加点儿代码把它变成个CGI应用程序。&lt;br /&gt;
&lt;br /&gt;
我们在前面说过，CGI应用程序期望来自 stdin 流（以及一些环境变量）的输入，然后它干活儿，再把输出送到 stdout 流里去。作为一个很简单的CGI应用程序，我们略去来自 stdin 流的输入到这个CGI应用程序的所有信息（可能网络服务器送来）。不过，我们还是要让这个应用程序稍稍有点儿输出。&lt;br /&gt;
&lt;br /&gt;
双击工程树中的 main.pro 文件，往代码的尾部走：&lt;br /&gt;
&lt;br /&gt;
[[Image:cgiApplicationsInVisualProlog04.png]] &lt;br /&gt;
&lt;br /&gt;
可以看到，程序目标从PFC的mainEXE模块中调用了一个名为run的谓词，这个模块又从 main.pro 中调用了名为 run() 的谓词，所以看这里：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    run():-&lt;br /&gt;
console::init(),&lt;br /&gt;
succeed().  % 把自己的代码放在这里&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
像下面这样改一下代码：&lt;br /&gt;
&lt;br /&gt;
&amp;lt;vip&amp;gt;clauses&lt;br /&gt;
    run():-&lt;br /&gt;
        console::init(), %Line 1&lt;br /&gt;
        cgi::init(stream::ansi(core::ansi)), %Line 2&lt;br /&gt;
        stdIO::write(&amp;quot;Content-type:   text/html\n&amp;quot;), %Line 3&lt;br /&gt;
        stdIO::write(&amp;quot;\n&amp;quot;), %Line 4&lt;br /&gt;
        stdIO::write(&amp;quot;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Hello World!&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&amp;quot;). %Line 5&amp;lt;/vip&amp;gt;&lt;br /&gt;
&lt;br /&gt;
逐行说明一下这些代码：&lt;br /&gt;
&lt;br /&gt;
*Line 1: 这是Visual Prolog要求的，所有用 console 干活儿的都要首先 console::init() 。&lt;br /&gt;
*Line 2: 取 stdout 流对象做CGI的输出。我们说过，CGI应用程序要把自己的输出送到 stdout 流去。 &lt;br /&gt;
*Line 3: 现在CGI应用程序准备好了送第一行数据给调用它的网络服务器。 先是头，这是强制的，要设置数据的mime类型，这里它说数据内容mime类型是text/html。注意结尾的回车符（\n）。&lt;br /&gt;
*Line 4: 分隔头与数据需要一个空行。按CGI规范这是强制性的，所以CGI应用程序要送一个空行（\n）。常常会忘了这个，这将引起应用程序的故障。&lt;br /&gt;
*Line 5: 最后把实际数据写到 stdout 。这里做的很简单，创建一个HTML文件并发送这个文件。文件里只有两个单词：Hello World!。&lt;br /&gt;
&lt;br /&gt;
===如何测试Example1.exe？===&lt;br /&gt;
按前面说的，把它放在网络服务器的脚本目录里。然后用浏览器在同一个机器上按URL： http://localhost/cgi-bin/example1.exe 访问。&lt;br /&gt;
&lt;br /&gt;
===这么简单的应用程序有什么功能？===&lt;br /&gt;
&lt;br /&gt;
这个 Hello World CGI应用程序并不像它看上去那么简单。好吧，它很简单，但它可以作为很复杂工作的起点。就看最后一行（Line 5)，它创建了一个完整的HTML文件并用 stdout 流发给了网络服务器，网络服务器又转发给了发请求的浏览器。如果从这里引入数据库，添加一些对后台数据库数据的条件处理，很容易就可以建成一个目录管理系统。&lt;br /&gt;
&lt;br /&gt;
===输入呢？===&lt;br /&gt;
前面为求简单，我们略去了网络服务器可能给这个应用程序的所有输入。如果CGI应用程序需要输入，就要读 stdin 流并处理信息。此外，还要查看CGI应用程序运行时环境变量的设置。这就有些复杂了。不过还好，用Visual Prolog开发CGI应用程序时我们并不需要做很多编程工作。例如，要知道CGI应用程序从网络服务器接收了些什么数据，要做的只是使用 cgi::getString() 谓词（要把PFC里的cgi.pack包括到工程中）。如果需要更方便的数据形式，还可以使用 cgi::getParamList() 谓词，它会把每个数据按名称和值的对应关系组合起来，像在环境变量里那样。&lt;br /&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>