用 C 语言开发 WSTP(Linux)
本文叙述了在 Linux 系统中如何编译和运行用 C 语言编写的 Wolfram Symbolic Transfer Protocol (WSTP) 程序. (“WSTP 和外部程序的通讯”叙述了如何用 Wolfram 语言和 C 语言编写 WSTP 程序.)
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 开发工具包(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 安装
WSTP 框架组件
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
wscc
wscc 是一个预处理和编译 WSTP 源文件的脚本.
WSTPExamples 目录
PrebuiltExamples 文件夹
构建 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 程序
使用 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 程序”中的说明描述了如何使用 WSTPExamples 目录中的源代码构建两个 WSTP 程序. 这两个程序,addtwo 和 factor 已存在于 PrebuiltExamples 文件夹. 在构建你自己的之前,应该试着运行预建的范例以验证 WSTP 系统附件是否安装且正常工作,并学习当正确构建时这些范例会有怎样的结果.
有两个 WSTP 的基本类型程序,addtwo 和 factor 程序. 第一个是可安装程序. 一个可安装程序通过调用机制链接 C 程序和内核,为内核提供新的功能. 为了获得此新功能,Wolfram 语言必须运行 Install[] 函数. 在 addtwo 范例中,你会添加一个新的函数,称作 AddTwo[],相加两个数(以参数形式提供). 内核与可安装程序具有特殊的关系,允许它们可互相交流. 当运行可安装程序时,为了连接,它会需要你提供一些信息. 程序的另一个类型是前端. 前端进行所有创建和管理自身链接的工作. 除了 factor 范例之外,Wolfram 系统前端和 Wolfram 语言内核也是前端类型中的一个例子. 前端运行不需要任何其他信息,但是在执行的某点会进行连接.
在 Wolfram 语言内核中运行预建范例
第一个范例程序,addtwo 是安装在 Wolfram 语言中的一个 WSTP 模板程序. 也就是说,该程序在后台运行,为 Wolfram 语言,一个或多个外部编译函数提供服务. 对于 Wolfram 语言用户,这些函数显现为内置的. addtwo 程序使用定义 Wolfram 语言函数的模板文件 AddTwo[] 作为一个对 C 函数 addtwo()的调用. (模板机制的详细描述参见“WSTP 和外部程序通讯".)该程序的源代码为:
:Begin:
:Function: addtwo
:Pattern: AddTwo[i_Integer, j_Integer]
:Arguments: { i, j }
:ArgumentTypes: { Integer, Integer }
:ReturnType: Integer
:End:
:Evaluate: AddTwo::usage = "AddTwo[x, y] gives the sum of two machine integers x and y."
int addtwo( int i, int j)
{
return i+j;
}
int main(int argc; char* argv[])
{
return WSMain(argc, argv);
}
看看如果两个机器整数的和不匹配于一个机器的整数或如果任何一个参数都不是机器整数,会出现什么情况. (2^31-1 是最大的机器整数. 如果您的编译器使用2个字节整数,那么 2^15-1 是最大的 C int.)
从一个预建的范例中调用 Wolfram 语言内核
支持的链接协议
C 函数 WSOpenArgcArgv() 和 Wolfram 语言函数 LinkOpen[] 的细节请参见“WSTP 和外部程序通讯”. 在 Linux 机器中,LinkProtocol 选项的合法值为 "TCPIP"、"TCP"、"SharedMemory"、"Pipes" 和 "IntraProcess". LinkProtocol 是用于从一端到另一端传输数据的机制. "SharedMemory" 是 LinkMode->Launch 链接的默认协议. 所有 LinkMode->Listen 和 LinkMode->Connect 链接的默认值是 "SharedMemory".
请注意,"TCPIP" 和 "TCP" 协议的链接名称是 16 位无符号整数. 虽然 "TCPIP" 链接的名称是整数,但是它们仍然以(数字)字符串形式赋给 WSOpenArgcArgv() 和 LinkOpen[].
- 一旦你的程序正常工作,就可关闭编译器优化功能. 这可使编译速度更快,更容易调试,另外,优化可能被打断,会导致一些问题.(优化代码使用的堆栈和寄存器的方式不同于非优化的代码,可能暴露或掩盖代码中的错误. 例如,返回一个局部变量指针时出现的常见错误可能会或不会导致问题,其取决于堆栈和寄存器使用.)
- 检查从 WSTP 库函数的返回值或在你的程序关键部分调用 WSError(). WSTP 往往可以告诉你什么地方出了错.
- 文件 wstp.h、libWSTP32i4.a 和 libWSTP32i4.so(64位平台为 libWSTP64i4.*)是配套的集合. 如果你在使用早期的 WSTP 版本,当建立你的应用程序时,应该注意不要混淆它们.
- 检查你使用的 C 编译器是否支持雏形,如果不支持,你需要改变代码或构建项目的方式. 详细解释请参见“构建 WSTP 程序”.