用 C 语言开发 WSTP(Linux)

本文叙述了在 Linux 系统中如何编译和运行用 C 语言编写的 Wolfram Symbolic Transfer Protocol (WSTP) 程序. (WSTP 和外部程序的通讯叙述了如何用 Wolfram 语言和 C 语言编写 WSTP 程序.)
本文并不教你如何使用编译器和其它开发工具,也不会教你如何用 C 语言编程. 如果你有任何开发或运行 WSTP 程序的问题,请参阅本文末尾的疑难解答章节.
本文所叙述的大部分内容是专门面向 Linux 并适用于所有被支持的 Linux 平台. 若想学习如何在其它平台上编译和运行用 WSTP 程序,请参阅相关平台的开发指南.
支持的开发平台
作为一个共享库,WSTP 可以用于任何符合标准调用约定和由下列编译器指定的二进制接口的开发环境.
当下面的某些编译器与由编译器创建产生的集成开发环境相集成时,它们的功能等价于 make 实用程序.
C compiler
C++ compiler
"Linux"
gcc (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)
g++ (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)
"Linux-x86-64"
gcc (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)
g++ (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)
除了上面所说的,编译 WSTP API 还需要 libuuid 开发库,在 Debian 系统内称为 uuid-dev,在 Red Hat 和 Suse 中则称为 libuuid-devel.
安装 WSTP 组件
WSTP 开发工具包(WSDK)位于 Wolfram 系统目录内的$InstallationDirectory/SystemFiles/Links/WSTP/DeveloperKit/$SystemID 目录中.

建议安装

CompilerAdditions 安装

构建 WSTP 程序所需要的 WSTP 组件已由 Wolfram 系统安装器安装. 使用这些组件的一种方法是让它们保留在 Wolfram 系统目录中,当调用编译器时指定它们的完整路径. 该方法列在构建 WSTP 程序章节中的范例makefiles中.
另一种方法是把这些组件 (wstp.h、libWSTP32i4.a、libWSTP32i4.so、libWSTP64i4.a 和 libWSTP64i4.so) 复制到编译器会自动搜索这些文件的目录中. 这些目录一般是 /usr/include 和 /usr/lib,但是你的系统可能会有所不同. 在许多系统中,不是所有用户都可以读写这些目录.

WSTPExamples 安装

把 WSTPExamples 目录复制到主目录.

WSTP 框架组件

下面是 WSDK 中每个文件或目录的说明.

CompilerAdditions 目录

wstp.h
wstp.h 是一个必须包含在 C 和 C++ 源文件中的头文件. 它应该放在你的编译器可以找到的地方. 你可以把它复制到和源文件同样的目录下,或复制到和标准头文件同样的目录下,或者不用管它,但是把 WSTP 目录添加到头文件的搜索路径中.
libWSTP32i4.a/libWSTP64i4.a
这是一个包含所有 WSTP 函数的静态库. 你的项目应该包含它. 你可以把该库复制到与源文件同样的目录下或不做任何改变,但是把 WSTP 目录添加到库文件的搜索路径中. 32/64 表明库是 WSTP 库的 32 位或 64 位版本.
libWSTP32i4.so/libWSTP64i4.so
这是一个包含 WSTP 所有函数的动态共享库. 你的项目应该包含它. 你可以把该库复制到与源文件同样的目录下,或复制到系统位置,例如 /lib 或 /usr/lib,或不做任何改变,但是把 WSTP 目录添加到库搜索路径中. 32/64 表明库是 WSTP 库的 32 位或 64 位版本.
wsprep
wsprep 是一个应用程序,通过处理模板文件自动编写 WSTP 程序. 把该应用程序复制到你的项目目录中或创建一个别名会方便应用.
wscc
wscc 是一个预处理和编译 WSTP 源文件的脚本.

WSTPExamples 目录

该目录包含一些非常简单的 WSTP 程序源代码. 通过使用源代码,你可以学习如何构建和运行 WSTP 程序,而无需自己编写任何代码.

PrebuiltExamples 文件夹

该目录包含范例程序的预建版本. 运行 WSTP 程序描述了如何运行两个这样的程序. 构建 WSTP 程序描述了如何使用 WSTPExamples 文件夹中的源代码构建自己的程序.
构建 WSTP 程序
构建 WSTP 程序的一般过程是在任何调用 WSTP 函数的 C 或 C++ 源文件中包含 wstp.h,编译源文件,然后把 libWSTP32i4.a、libWSTP64i4.a、libWSTP32i4.so 或 libWSTP64i4.so 与结果对象代码相链接. 如果你的应用程序使用 WSTP 模板机制,那么你的模板文件必须首先使用 wsprep 处理成 C 源文件.

使用 WSTP 模板文件

如果你的程序使用 WSTP 和外部程序通讯 中描述的 WSTP 模板机制,你必须同时使用 wsprep 应用程序预处理包含模板项的源文件.(模板项是包含模板关键字的行序列,每一项定义当调用相关 C 函数时的 Wolfram 语言函数.)当 wsprep 处理这类文件时,它会把模板项转换成 C 函数,传递未改变的文本,并使用 WSTP 应用远程过程调用机制编写其他 C 函数. 结果是可用于编译的 C 源文件.
例如,命令
会从模板项中产生一个 C 源文件 addtwotm.c,其他文本保留在 addtwo.tm. 然后你可以使用 C 编译器编译输出文件. 如果你使用 make 工具构建你的程序,你可以添加类似下面的规则至你的 makefile.

构建 WSTP 程序

以下是一个构建 WSDK 中样本程序需要的样本 makefile,包括 addtwofactor. 要构建样本程序,例如 addtwo,在 WSTPExamples 目录中计算以下命令.

使用 Makefile

# This makefile can be used to build all or some of the sample
# programs. To build all of them, use the command
# 'make all'. To build one, say addtwo, use the command
# 'make addtwo'.

WSTPLINKDIR = /usr/local/Wolfram/Mathematica/11.2/SystemFiles/Links/WSTP/DeveloperKit
SYS = Linux # Set this value with the result of evaluating $SystemID
CADDSDIR = ${WSTPLINKDIR}/${SYS}/CompilerAdditions

INCDIR = ${CADDSDIR}
LIBDIR = ${CADDSDIR}

EXTRALIBS = -lm -lpthread -lrt -lstdc++ -ldl -libuuid # Set these with appropriate libs for your system.
WSTPLIB = WSTP32i4 # Set this to WSTP64i4 if using a 64-bit system

WSPREP = ${CADDSDIR}/wsprep

all : addtwo bitops counter factor factor2 factor3 quotient reverse sumalist

addtwo : addtwotm.o addtwo.o
    ${CC} -I${INCDIR} addtwotm.o addtwo.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

bitops : bitopstm.o bitops.o
    ${CC} -I${INCDIR} bitopstm.o bitops.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

counter : countertm.o
    ${CC} -I${INCDIR} countertm.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

factor : factor.o
    ${CC} -I${INCDIR} factor.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

factor2 : factor2.o
    ${CC} -I${INCDIR} factor2.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

factor3 : factor3.o
    ${CC} -I${INCDIR} factor3.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

quotient : quotient.o
    ${CC} -I${INCDIR} quotient.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

reverse : reversetm.o
    ${CC} -I${INCDIR} reversetm.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

sumalist : sumalisttm.o sumalist.o
    ${CC} -I${INCDIR} sumalisttm.o sumalist.o -L${LIBDIR} -l${WSTPLIB} ${EXTRALIBS} -o $@

.c.o :
    ${CC} -c -I${INCDIR} $<

addtwotm.c : addtwo.tm
    ${WSPREP} $? -o $@

bitopstm.c : bitops.tm
    ${WSPREP} $? -o $@

countertm.c : counter.tm
    ${WSPREP} $? -o $@

reversetm.c : reverse.tm
    ${WSPREP} $? -o $@

sumalisttm.c : sumalist.tm
    ${WSPREP} $? -o $@

使用下表确定在你的系统中链接 WSTP 程序时是否需要其他库.

$SystemID
EXTRALIBS
"Linux"
-lm -lpthread -lrt -lstdc++ -ldl -luuid
"Linux-x86-64"
-lm -lpthread -lrt -lstdc++ -ldl -luuid

使用 wscc

wscc 是一个预处理和编译 WSTP 源文件的脚本. 它会预处理任何以 .tm 结尾的文件中的 WSTP 模板,然后在结果 C 源代码中调用 cc. wscc 会直接传递命令行选项和其他文件至 cc. 以下是使用 wscc 构建 addtwo 应用的命令.
wscc addtwo.tm addtwo.c -o addtwo
运行 WSTP 程序
构建 WSTP 程序中的说明描述了如何使用 WSTPExamples 目录中的源代码构建两个 WSTP 程序. 这两个程序,addtwofactor 已存在于 PrebuiltExamples 文件夹. 在构建你自己的之前,应该试着运行预建的范例以验证 WSTP 系统附件是否安装且正常工作,并学习当正确构建时这些范例会有怎样的结果.
有两个 WSTP 的基本类型程序,addtwofactor 程序. 第一个是可安装程序. 一个可安装程序通过调用机制链接 C 程序和内核,为内核提供新的功能. 为了获得此新功能,Wolfram 语言必须运行 Install[] 函数. 在 addtwo 范例中,你会添加一个新的函数,称作 AddTwo[],相加两个数(以参数形式提供). 内核与可安装程序具有特殊的关系,允许它们可互相交流. 当运行可安装程序时,为了连接,它会需要你提供一些信息. 程序的另一个类型是前端. 前端进行所有创建和管理自身链接的工作. 除了 factor 范例之外,Wolfram 系统前端和 Wolfram 语言内核也是前端类型中的一个例子. 前端运行不需要任何其他信息,但是在执行的某点会进行连接.

在 Wolfram 语言内核中运行预建范例

第一个范例程序,addtwo 是安装在 Wolfram 语言中的一个 WSTP 模板程序. 也就是说,该程序在后台运行,为 Wolfram 语言,一个或多个外部编译函数提供服务. 对于 Wolfram 语言用户,这些函数显现为内置的. addtwo 程序使用定义 Wolfram 语言函数的模板文件 AddTwo[] 作为一个对 C 函数 addtwo()的调用. (模板机制的详细描述参见WSTP 和外部程序通讯".)该程序的源代码为:
编辑路径字符串并计算下面两个单元:
查找最新可用函数的列表,计算下面单元:
以下会显示定义在文件 addtwo.tm 中的 AddTwo[] 函数的用法信息:
尝试一下:
看看如果两个机器整数的和不匹配于一个机器的整数或如果任何一个参数都不是机器整数,会出现什么情况. (2^31-1 是最大的机器整数. 如果您的编译器使用2个字节整数,那么 2^15-1 是最大的 C int.)
addtwo 程序不适于大的整数:
以下不匹配 AddTwo[_Integer, _Integer]
Install[] 调用 LinkOpen[],然后和外部程序交换信息,设置 AddTwo[] 的定义. 你无需考虑这些细节,但是如果你很好奇,可执行以下命令:
当你不再使用外部程序,执行如下命令:

从一个预建的范例中调用 Wolfram 语言内核

第二个范例程序,factor 是运行在后台的 Wolfram 语言内核为 factor 提供服务,内核的计算服务,Wolfram 系统的前端对用户的输入整数给出因式分解.
通过执行以下命令,启动 factor 应用.
过一会会出现提示要求你输入整数. 敲入少于 10 位的整数,然后按下 Enter 键.(其他 factor 例子会放宽对输入整数大小的限制.)
输出由 factor 关闭它与Wolfram 语言的链接.

支持的链接协议

C 函数 WSOpenArgcArgv() 和 Wolfram 语言函数 LinkOpen[] 的细节请参见WSTP 和外部程序通讯. 在 Linux 机器中,LinkProtocol 选项的合法值为 "TCPIP""TCP""SharedMemory""Pipes""IntraProcess". LinkProtocol 是用于从一端到另一端传输数据的机制. "SharedMemory"LinkMode->Launch 链接的默认协议. 所有 LinkMode->ListenLinkMode->Connect 链接的默认值是 "SharedMemory".
请注意,"TCPIP""TCP" 协议的链接名称是 16 位无符号整数. 虽然 "TCPIP" 链接的名称是整数,但是它们仍然以(数字)字符串形式赋给 WSOpenArgcArgv()LinkOpen[].
疑难解答