On this page
Introduction
应用程序程序员与 Python 的接口使 C 和 C 程序员可以在各个级别上访问 Python 解释器。该 API 也可以从 C 使用,但是为了简洁起见,通常将其称为 Python/C API。使用 Python/C API 有两个根本不同的原因。第一个原因是为了特定目的而编写扩展模块。这些是扩展 Python 解释器的 C 模块。这可能是最常见的用法。第二个原因是在大型应用程序中使用 Python 作为组件。该技术通常在应用程序中称为“嵌入” Python。
编写扩展模块是一个相对容易理解的过程,其中的“菜谱”方法效果很好。有几种工具可以在一定程度上实现流程自动化。尽管人们自从 Python 诞生以来就已经将 Python 嵌入到其他应用程序中,但是嵌入 Python 的过程比编写扩展要简单得多。
无论您是嵌入还是扩展 Python,许多 API 函数都非常有用。此外,大多数嵌入 Python 的应用程序也需要提供自定义扩展,因此在try将 Python 嵌入实际应用程序之前,熟悉编写扩展可能是个好主意。
Coding standards
如果您要编写要包含在 CPython 中的 C 代码,则必须**遵循 PEP 7中定义的准则和标准。无论您贡献的 Python 版本如何,这些准则均适用。对于您自己的第三方扩展模块,不需要遵循这些约定,除非您finally希望将它们贡献给 Python。
Include Files
使用 Python/C API 所需的所有函数,类型和宏定义都包含在代码中,并包含以下行:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
这意味着将包含以下标准 Headers:<stdio.h>
,<string.h>
,<errno.h>
,<limits.h>
,<assert.h>
和<stdlib.h>
(如果可用)。
Note
由于 Python 可能会定义一些会影响某些系统上标准 Headers 的预处理器定义,因此必须在包含任何标准 Headers 之前先包含Python.h
。
建议始终在包含Python.h
之前定义PY_SSIZE_T_CLEAN
。有关此宏的说明,请参见解析参数和构建值。
Python.h 定义的所有用户可见名称(包含的标准 Headers 定义的名称除外)具有前缀Py
或_Py
之一。以_Py
开头的名称仅供 Python 实现内部使用,扩展编写者请勿使用。结构成员名称没有保留的前缀。
Note
用户代码不得定义以Py
或_Py
开头的名称。这使 Reader 感到困惑,并危及了用户代码到 FuturePython 版本的可移植性,后者可能会定义以这些前缀之一开头的其他名称。
头文件通常与 Python 一起安装。在 Unix 上,它们位于目录prefix/include/pythonversion/
和exec_prefix/include/pythonversion/
,其中 prefix
和 exec_prefix
是由 Python 的 configure 脚本的相应参数定义的,而* version *是'%d.%d' % sys.version_info[:2]
。在 Windows 上,Headers 安装在prefix/include
中,其中 prefix
是为安装程序指定的安装目录。
要包含 Headers,请将两个目录(如果不同)放在编译器的包含搜索路径上。 不要将父目录放在搜索路径上,然后使用#include <pythonX.Y/Python.h>
;这将break多平台构建,因为 prefix
下独立于平台的 Headers 包括 exec_prefix
中特定于平台的 Headers。
C 用户应注意,尽管 API 完全使用 C 定义,但头文件正确地将入口点语句为extern "C"
。结果,不需要做任何特殊的事情就可以使用 C 语言的 API。
Useful macros
Python 头文件中定义了几个有用的宏。许多定义在更有用的地方(例如Py_RETURN_NONE)。这里定义了其他更通用的工具。这不一定是完整的 Lists。
Py_UNREACHABLE
( )- 当您有不希望到达的代码路径时,请使用此选项。例如,在
switch
语句的default:
子句中,case
语句涵盖了所有可能的值。在您可能会提出assert(0)
或abort()
通话的地方使用此Function。
- 当您有不希望到达的代码路径时,请使用此选项。例如,在
3.7 版中的新Function。
Py_ABS
(x)- 返回
x
的绝对值。
- 返回
版本 3.3 中的新Function。
Py_MIN
(x,y)- 返回介于
x
和y
之间的最小值。
- 返回介于
版本 3.3 中的新Function。
Py_MAX
(x,y)- 返回
x
和y
之间的最大值。
- 返回
版本 3.3 中的新Function。
Py_STRINGIFY
(x)- 将
x
转换为 C 字符串。例如。Py_STRINGIFY(123)
返回"123"
。
- 将
3.4 版的新Function。
Py_MEMBER_SIZE
(类型,成员)- 返回以字节为单位的结构(
type
)member
的大小。
- 返回以字节为单位的结构(
3.6 版的新Function。
Py_CHARMASK
(c)- 参数必须是[-128,127]或[0,255]范围内的字符或整数。此宏将
c
强制转换为unsigned char
。
- 参数必须是[-128,127]或[0,255]范围内的字符或整数。此宏将
Py_GETENV
(s)- 类似于
getenv(s)
,但是如果在命令行上传递了-E(即设置了Py_IgnoreEnvironmentFlag
),则返回NULL
。
- 类似于
Py_UNUSED
(arg)- 将其用于函数定义中未使用的参数,以使编译器警告静音。例如:
int func(int a, int Py_UNUSED(b)) { return a; }
。
- 将其用于函数定义中未使用的参数,以使编译器警告静音。例如:
3.4 版的新Function。
Py_DEPRECATED
(版本)- 将此用于不推荐使用的语句。宏必须放在符号名称之前。
Example:
Py_DEPRECATED(3.8) PyAPI_FUNC(int) Py_OldFunction(void);
在 3.8 版中进行了更改:添加了 MSVC 支持。
PyDoc_STRVAR
(名称,str)- 创建一个名称为
name
的变量,该变量可以在文档字符串中使用。如果构建的 Python 没有文档字符串,则该值为空。
- 创建一个名称为
按照 PEP 7中的说明,使用PyDoc_STRVAR作为文档字符串来支持构建不带文档字符串的 Python。
Example:
PyDoc_STRVAR(pop_doc, "Remove and return the rightmost element.");
static PyMethodDef deque_methods[] = {
// ...
{"pop", (PyCFunction)deque_pop, METH_NOARGS, pop_doc},
// ...
}
PyDoc_STR
(str)- 为给定的 Importing 字符串创建一个文档字符串,如果禁用了文档字符串,则为空字符串。
如 PEP 7所指定,请使用PyDoc_STR指定文档字符串以支持构建不带文档字符串的 Python。
Example:
static PyMethodDef pysqlite_row_methods[] = {
{"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS,
PyDoc_STR("Returns the keys of the row.")},
{NULL, NULL}
};
对象,类型和引用计数
大多数 Python/C API 函数具有一个或多个参数以及类型为PyObject*的返回值。此类型是指向表示任意 Python 对象的不透明数据类型的指针。由于在大多数情况下(例如,赋值,作用域规则和参数传递),Python 语言对所有 Python 对象类型都以相同的方式对待,因此仅用单个 C 类型表示它们是合适的。几乎所有 Python 对象都生活在堆中:您永远不会语句PyObject类型的自动或静态变量,只能语句PyObject*类型的指针变量。唯一的 exception 是类型对象。由于这些绝对不能释放,因此它们通常是静态PyTypeObject对象。
所有 Python 对象(甚至 Python 整数)都具有* type 和 reference count 。对象的类型确定对象的类型(例如,整数,列表或用户定义的函数;还有更多信息,如标准类型层次结构中所述)。对于每个众所周知的类型,都有一个宏来检查对象是否属于该类型。例如,当(且仅当) a *指向的对象是 Python 列表时,PyList_Check(a)
为 true。
Reference Counts
引用计数很重要,因为当今的计算机具有有限的(通常是严格受限的)内存大小;它计算有多少不同的地方引用了一个对象。这样的地方可能是另一个对象,或者是全局(或静态)C 变量,或者是某些 C 函数中的局部变量。当对象的引用计数变为零时,将释放该对象。如果它包含对其他对象的引用,则它们的引用计数将减少。如果此减量使它们的引用计数变为零,则可以依次释放这些其他对象,依此类推。 (这里存在一个互相引用的对象的明显问题;目前,解决方案是“不要这样做”.)
引用计数始终被显式操纵。通常的方法是使用宏Py_INCREF()将对象的引用计数加 1,并使用Py_DECREF()将其引用计数减 1. Py_DECREF()宏比 incref 1 复杂得多,因为它必须检查引用计数是否为零,然后导致调用对象的解除分配器。解除分配器是包含在对象的类型结构中的函数指针。特定于类型的释放器负责减少对象中包含的其他对象的引用计数(如果这是复合对象类型,例如列表),并执行所需的任何其他finally确定。引用计数不可能溢出。至少与虚拟存储器中存在不同的存储器位置(假设sizeof(Py_ssize_t) >= sizeof(void*)
)的位数一样,用于保存参考计数。因此,参考计数增加是简单的操作。
不必为每个包含指向对象的指针的局部变量增加对象的引用计数。从理论上讲,使变量指向对象的引用计数增加一,而当变量超出范围时则减少一。但是,这两个相互抵消,因此最后引用计数没有改变。使用引用计数的唯一真实原因是,只要我们的变量指向该对象,就可以防止该对象被释放。如果我们知道至少有一个对引用的引用,该引用的生存时间至少与变量一样长,则无需临时增加引用计数。发生这种情况的一个重要情况是在对象中,这些对象作为参数传递给在扩展模块中从 Python 调用的 C 函数。调用机制保证在调用期间保留对每个参数的引用。
但是,常见的陷阱是从列表中提取一个对象并保留一段时间,而不会增加其引用计数。可以想到其他一些操作可以从列表中删除该对象,从而减少其引用计数并可能对其进行分配。true 的危险是,看起来无辜的操作可能会调用任意 Python 代码来执行此操作;存在一条代码路径,该路径允许控件从Py_DECREF()流回到用户,因此几乎任何操作都具有潜在的危险。
一种安全的方法是始终使用通用操作(名称以PyObject_
,PyNumber_
,PySequence_
或PyMapping_
开头的函数)。这些操作总是增加它们返回的对象的引用计数。结果完成后,呼叫者将有责任呼叫Py_DECREF();这很快成为第二天性。
参考计数详细信息
Python/C API 中函数的引用计数行为最好pass引用所有权来解释。所有权属于引用,而不是对象(对象不拥有:它们总是共享的)。 “拥有引用”是指在不再需要引用时负责对其调用 Py_DECREF。所有权也可以转移,这意味着接收引用所有权的代码随后负责pass在不再需要它时调用Py_DECREF()或Py_XDECREF()来finally拒绝引用它,或将此责任(通常传递给调用者)。当一个函数将引用的所有权传递给它的调用者时,称调用者收到一个* new *引用。如果没有所有权转移,则称调用者“借用”了引用。无需做任何借用的参考。
相反,当调用函数传入对对象的引用时,有两种可能性:函数窃取对对象的引用,或者没有。 *“隐藏引用” *表示当您将引用传递给函数时,该函数假定它现在拥有该引用,并且您不再对此承担责任。
很少有函数会窃取引用。这两个值得注意的 exception 是PyList_SetItem()和PyTuple_SetItem(),它们窃取了对该项目的引用(但没有引用该项目所在的 Tuples 或列表!)。这些函数的设计目的是窃取引用,因为有一个常见的习惯用法,用新创建的对象填充 Tuples 或列表。例如,创建 Tuples(1, 2, "three")
的代码可能如下所示(暂时忘记了错误处理;下面显示了一种更好的 encodings):
PyObject *t;
t = PyTuple_New(3);
PyTuple_SetItem(t, 0, PyLong_FromLong(1L));
PyTuple_SetItem(t, 1, PyLong_FromLong(2L));
PyTuple_SetItem(t, 2, PyUnicode_FromString("three"));
在这里,PyLong_FromLong()返回一个新的引用,该引用立即被PyTuple_SetItem()窃取。当您想要 continue 使用某个对象(尽管对该对象的引用将被盗)时,请使用Py_INCREF()来获取另一个引用,然后再调用引用窃取函数。
PyTuple_SetItem()是设置 Tuples 项的“唯一”方法; PySequence_SetItem()和PyObject_SetItem()拒绝执行此操作,因为 Tuples 是不可变的数据类型。您只应对自己创建的 Tuples 使用PyTuple_SetItem()。
可以使用PyList_New()和PyList_SetItem()编写用于填充列表的等效代码。
但是,实际上,您将很少使用这些方式来创建和填充 Tuples 或列表。有一个通用函数Py_BuildValue(),可以pass格式字符串指导的 C 值创建最常见的对象。例如,上面的两个代码块可以替换为以下代码(它也负责错误检查):
PyObject *tuple, *list;
tuple = Py_BuildValue("(iis)", 1, 2, "three");
list = Py_BuildValue("[iis]", 1, 2, "three");
PyObject_SetItem()和朋友用于仅借用其引用的项目,例如传递给您正在编写的函数的参数,这种情况更为常见。在那种情况下,由于您不必增加参考计数,因此您可以提供参考(“已将其偷走”),因此它们在参考计数方面的行为要好得多。例如,此函数将列表的所有项目(实际上是任何可变序列)设置为给定项目:
int
set_all(PyObject *target, PyObject *item)
{
Py_ssize_t i, n;
n = PyObject_Length(target);
if (n < 0)
return -1;
for (i = 0; i < n; i++) {
PyObject *index = PyLong_FromSsize_t(i);
if (!index)
return -1;
if (PyObject_SetItem(target, index, item) < 0) {
Py_DECREF(index);
return -1;
}
Py_DECREF(index);
}
return 0;
}
对于函数返回值,情况略有不同。虽然传递对大多数函数的引用不会更改您对该引用的所有权责任,但是许多返回对对象的引用的函数都会为您提供对该引用的所有权。原因很简单:在许多情况下,返回的对象是动态创建的,您获得的引用是对该对象的唯一引用。因此,返回对象引用的通用函数(例如PyObject_GetItem()和PySequence_GetItem())总是返回新的引用(调用者成为该引用的所有者)。
重要的是要认识到,是否拥有一个函数返回的引用取决于您仅调用哪个函数— 全身羽毛(作为参数传递给该函数的对象的类型)*不参与其中!*因此,如果您使用PyList_GetItem()从列表中提取项目,则您不拥有引用-但是如果您使用PySequence_GetItem()从同一列表中获取了相同项目(碰巧采用了完全相同的参数),则您确实拥有一个引用到返回的对象。
这是一个示例,说明如何编写一个函数来计算整数列表中各项的和。一次使用PyList_GetItem(),一次使用PySequence_GetItem()。
long
sum_list(PyObject *list)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject *item;
n = PyList_Size(list);
if (n < 0)
return -1; /* Not a list */
for (i = 0; i < n; i++) {
item = PyList_GetItem(list, i); /* Can't fail */
if (!PyLong_Check(item)) continue; /* Skip non-integers */
value = PyLong_AsLong(item);
if (value == -1 && PyErr_Occurred())
/* Integer too big to fit in a C long, bail out */
return -1;
total += value;
}
return total;
}
long
sum_sequence(PyObject *sequence)
{
Py_ssize_t i, n;
long total = 0, value;
PyObject *item;
n = PySequence_Length(sequence);
if (n < 0)
return -1; /* Has no length */
for (i = 0; i < n; i++) {
item = PySequence_GetItem(sequence, i);
if (item == NULL)
return -1; /* Not a sequence, or other failure */
if (PyLong_Check(item)) {
value = PyLong_AsLong(item);
Py_DECREF(item);
if (value == -1 && PyErr_Occurred())
/* Integer too big to fit in a C long, bail out */
return -1;
total += value;
}
else {
Py_DECREF(item); /* Discard reference ownership */
}
}
return total;
}
Types
在 Python/C API 中,很少有其他数据类型起着重要作用。大多数是简单的 C 类型,例如int
,long
,double
和char*
。几种结构类型用于描述静态表,这些静态表用于列出模块导出的Function或新对象类型的数据属性,另一种结构类型用于描述复数的值。这些将与使用它们的Function一起讨论。
Exceptions
Python 程序员仅在需要特定的错误处理时才需要处理异常。未处理的异常会自动传播到调用者,然后传播到调用者的调用者,依此类推,直到到达顶级解释器为止,在此将其报告给用户并伴随堆栈回溯。
但是,对于 C 程序员,错误检查总是必须明确的。除非在函数的文档中另有明确语句,否则 Python/C API 中的所有函数都可以引发异常。通常,当一个函数遇到错误时,它会设置一个异常,丢弃它拥有的所有对象引用,并返回一个错误指示符。如果没有其他说明,则根据函数的返回类型,此指示器为NULL
或-1
。一些函数返回布尔值 true/false,布尔值 false 表示错误。很少有函数不返回显式错误指示符或具有不明确的返回值,并且需要对PyErr_Occurred()的错误进行显式测试。这些 exception 总是明确记录在案。
在每线程存储中维护异常状态(这等同于在无线程应用程序中使用全局存储)。线程可以处于以下两种状态之一:是否发生异常。函数PyErr_Occurred()可用于检查此情况:发生异常时,它返回对异常类型对象的借用引用,否则返回NULL
。有许多函数可以设置异常状态:PyErr_SetString()是设置异常状态的最常用(尽管不是最通用)Function,而PyErr_Clear()则清除异常状态。
完整的异常状态包含三个对象(所有对象都可以是NULL
):异常类型,相应的异常值和回溯。它们的含义与sys.exc_info()
的 Python 结果相同;但是,它们并不相同:Python 对象表示由 Python try…except语句处理的最后一个异常,而 C 级异常状态仅在 C 函数之间传递异常直到到达 Python 字节码解释器的状态时才存在。主循环,负责将其传送给sys.exc_info()
和朋友。
请注意,从 Python 1.5 开始,从 Python 代码访问异常状态的首选,线程安全的方法是调用函数sys.exc_info(),该函数返回 Python 代码的每个线程异常状态。而且,两种访问异常状态的方式的语义都已更改,因此捕获异常的函数将保存并恢复其线程的异常状态,以保留其调用方的异常状态。这样可以防止由看起来无辜的函数覆盖正在处理的异常而导致的异常处理代码中的常见错误;它还减少了回溯中的堆栈帧所引用的对象的通常不希望的生存期延长。
作为一般原则,调用另一个函数以执行某些任务的函数应检查被调用函数是否引发了异常,如果是,则将异常状态传递给其调用者。它应该舍弃它拥有的所有对象引用,并返回一个错误指示符,但是它不应该设置另一个异常-这将覆盖刚刚引发的异常,并丢失有关错误确切原因的重要信息。
上面的sum_sequence()
示例中显示了一个检测异常并将其传递的简单示例。碰巧的是,此示例在检测到错误时不需要清理任何拥有的引用。以下示例函数显示了一些错误清除。首先,提醒您为什么喜欢 Python,我们显示等效的 Python 代码:
def incr_item(dict, key):
try:
item = dict[key]
except KeyError:
item = 0
dict[key] = item + 1
这是所有方面的对应 C 代码:
int
incr_item(PyObject *dict, PyObject *key)
{
/* Objects all initialized to NULL for Py_XDECREF */
PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL;
int rv = -1; /* Return value initialized to -1 (failure) */
item = PyObject_GetItem(dict, key);
if (item == NULL) {
/* Handle KeyError only: */
if (!PyErr_ExceptionMatches(PyExc_KeyError))
goto error;
/* Clear the error and use zero: */
PyErr_Clear();
item = PyLong_FromLong(0L);
if (item == NULL)
goto error;
}
const_one = PyLong_FromLong(1L);
if (const_one == NULL)
goto error;
incremented_item = PyNumber_Add(item, const_one);
if (incremented_item == NULL)
goto error;
if (PyObject_SetItem(dict, key, incremented_item) < 0)
goto error;
rv = 0; /* Success */
/* Continue with cleanup code */
error:
/* Cleanup code, shared by success and failure path */
/* Use Py_XDECREF() to ignore NULL references */
Py_XDECREF(item);
Py_XDECREF(const_one);
Py_XDECREF(incremented_item);
return rv; /* -1 for error, 0 for success */
}
此示例表示在 C 中认可使用goto
语句!它说明了使用PyErr_ExceptionMatches()和PyErr_Clear()处理特定的异常,以及使用Py_XDECREF()处置可能是NULL
的拥有的引用(请注意名称中的'X'
;当遇到NULL
引用时Py_DECREF()会崩溃)。重要的是,用于保存拥有的引用的变量必须初始化为NULL
才能起作用。同样,建议的返回值初始化为-1
(失败),并且仅在finally调用成功后才设置为成功。
Embedding Python
只有 Python 解释器的嵌入程序(而不是扩展编写器)才需要担心的一项重要任务是 Python 解释器的初始化以及可能的终结。解释器的大多数Function只能在解释器初始化后才能使用。
基本的初始化Function是Py_Initialize()。这将初始化已加载模块的表,并创建基本模块builtins,main和sys。它还会初始化模块搜索路径(sys.path
)。
Py_Initialize()未设置“脚本参数列表”(sys.argv
)。如果稍后要执行的 Python 代码需要此变量,则必须在调用Py_Initialize()之后pass调用PySys_SetArgvEx(argc, argv, updatepath)
对其进行显式设置。
在大多数系统上(尤其是在 Unix 和 Windows 上,尽管细节稍有不同),Py_Initialize()会根据其对标准 Python 解释器可执行文件位置的最佳猜测来计算模块搜索路径,并假设在相对于 Python 解释器可执行文件的固定位置。特别是,它会寻找相对于父目录的名为lib/pythonX.Y
的目录,该目录在 shell 命令搜索路径(环境变量 PATH
)上找到了名为python
的可执行文件。
例如,如果在/usr/local/bin/python
中找到 Python 可执行文件,则将假定这些库在/usr/local/lib/pythonX.Y
中。 (实际上,此特定路径也是“后备”位置,当在 PATH
上找不到名为python
的可执行文件时使用.)用户可以pass设置环境变量 PYTHONHOME来覆盖此行为,或者在之前插入其他目录。pass设置 PYTHONPATH设置标准路径。
嵌入应用程序可以pass在调用_之前调用Py_SetProgramName(file)
来引导搜索。请注意, PYTHONHOME仍然会覆盖此设置,并且 PYTHONPATH仍插入在标准路径的前面。需要完全控制的应用程序必须提供自己的Py_GetPath(),Py_GetPrefix(),Py_GetExecPrefix()和Py_GetProgramFullPath()(均在Modules/getpath.c
中定义)的实现。
有时,需要“取消初始化” Python。例如,应用程序可能想重新开始(再次调用Py_Initialize()),或者仅使用 Python 来完成应用程序,并希望释放 Python 分配的内存。这可以pass调用Py_FinalizeEx()完成。如果 Python 当前处于初始化状态,则函数Py_IsInitialized()返回 true。有关这些Function的更多信息将在下一章中给出。注意Py_FinalizeEx()不会释放 Python 解释器分配的所有内存,例如当前无法释放扩展模块分配的内存。
Debugging Builds
可以使用多个宏来构建 Python,以启用对解释器和扩展模块的额外检查。这些检查往往会增加运行时的开销,因此默认情况下不会启用它们。
各种调试版本的完整列表在 Python 源代码发布的Misc/SpecialBuilds.txt
文件中。有可用的内部版本支持对引用计数的跟踪,调试内存分配器或主解释器循环的低级分析。本节的其余部分将仅描述最常用的内部版本。
使用定义的Py_DEBUG
宏编译解释器会产生通常所说的 Python“调试版本”。pass在./configure
命令中添加--with-pydebug
在 Unix 构建中启用Py_DEBUG
。非 Python 专用的_DEBUG
宏也暗示了这一点。在 Unix 构建中启用Py_DEBUG
时,将禁用编译器优化。
除了下面描述的引用计数调试以外,还执行以下额外检查:
额外的检查将添加到对象分配器。
额外的检查将添加到解析器和编译器。
检查从宽类型到窄类型的向下转换是否丢失信息。
许多 assert 被添加到字典并设置实现。另外,设置对象获取
test_c_api()
方法。Importing 参数的健全性检查将添加到框架创建中。
使用已知的无效模式初始化 int 的存储,以捕获对未初始化数字的引用。
低级跟踪和额外的异常检查已添加到运行时虚拟机。
额外的检查将添加到内存领域的实现。
额外的调试已添加到线程模块。
可能还有此处未提及的其他检查。
定义Py_TRACE_REFS
启用引用跟踪。定义后,将pass向每个PyObject添加两个额外的字段来维护活动对象的圆形双向链接列表。还跟踪总分配。退出时,将打印所有现有参考。 (在交互模式下,此操作在解释器运行的每个语句之后发生.)Py_DEBUG
隐含。
请参阅 Python 源代码发行版中的Misc/SpecialBuilds.txt
以获取更多详细信息。