C语言负责高性能的底层计算和系统交互,而 Mathematica 负责复杂的数学分析。 它们之间不能直接互相调用函数,必须通过一个“桥梁”或“中间件”来通信。

目前主要有以下几种方法,我将按照推荐顺序和适用场景详细介绍:
使用 MathLink(官方推荐,功能最强大)
这是 Wolfram 官方提供的、功能最全面、最稳定、也是最强大的通信机制,它允许 C 程序作为“前端”(Frontend)与 Mathematica 内核(Kernel)进行双向通信。
工作原理
MathLink 实际上是一套网络通信协议,它可以在同一台机器上通过本地 TCP 端口(如 12345)通信,也可以通过网络在不同的机器上通信。
- 启动流程:你的 C 程序会启动一个 Mathematica 内核进程,并建立一个 MathLink 连接。
- 数据交换:C 程序通过发送 "MathLink 表达式"(一种文本协议)来告诉 Mathematica 执行什么操作,Mathematica 将结果(表达式、数值、图形等)通过 MathLink 返回给 C 程序。
详细步骤
准备 Mathematica 端(创建一个可被调用的程序)
你需要创建一个 Mathematica 程序包,并使用 Install 和 Uninstall 来创建一个可以被外部程序调用的 "MathLink 可执行程序"。

假设你的文件名为 mlDemo.m:
(* mlDemo.m *)
(* 定义一个函数,用于被外部C程序调用 *)
(* 第一个参数是 LinkObject,后面的是函数参数 *)
AddTwoNumbers[link_, a_?NumberQ, b_?NumberQ] := Module[{result},
result = a + b;
(* 将结果通过LinkObject发回给C程序 *)
Put[result, link];
(* 返回1表示成功 *)
Return[1];
]
(* 定义一个函数,用于向C程序发送字符串 *)
SendStringToC[link_, str_String] := Module[{},
(* 将字符串发回给C程序 *)
Put[str, link];
Return[1];
]
(* 定义一个函数,让C程序获取一个2D图形数据 *)
(* 注意:直接发送图形对象比较复杂,通常发送生成图形所需的命令或数据 *)
GetPlotData[link_] := Module[{},
(* 发送一个Mathematica表达式,让C程序去绘图 *)
(* 发送一个ListPlot命令 *)
Put[ListPlot[Table[{x, Sin[x]}, {x, 0, 2 Pi, 0.1}]], link];
Return[1];
]
(* 主函数,当程序被启动时执行 *)
(* 这个函数必须存在,并且返回一个LinkObject *)
(* 它会等待C程序通过特定的端口连接过来 *)
(* 这里我们使用默认的端口12345,也可以指定其他端口 *)
MathLink`CreateFunction[] := (
(* 打开一个Link,等待连接 *)
(* -linkmode launch 表示启动一个新内核 *)
(* -linkname "math -mathlink" 指定启动的命令 *)
(* "12345" 是指定的端口号 *)
link = MathLink`Open[-linkmode -> "launch", -linkname -> "math -mathlink", "12345"];
(* 返回LinkObject *)
Return[link];
)
编译 Mathematica 端
你需要使用 Wolfram Compiler 将 .m 文件编译成一个可执行文件(在 Windows 上是 .exe,在 Linux/macOS 上是)。
打开 Mathematica 或 WolframScript,运行:
Wolfram`CompileExecutable["mlDemo", "mlDemo.m"]
这会生成一个 mlDemo.exe (Windows) 或 mlDemo (Linux/macOS) 文件,这个文件包含了 Mathematica 内核和你的代码,可以被外部程序调用。

准备 C 语言端(编写调用程序)
你需要安装 Wolfram System,它会包含 MathLink 开发头文件 (mathlink.h) 和库文件。
下面是一个简单的 C 程序 caller.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mathlink.h" /* Wolfram 提供的头文件 */
/* 函数声明 */
int run_mathlink_program(const char* program_name, int argc, char *argv[]);
int main(int argc, char *argv[]) {
/* 调用我们编译好的 Mathematica 程序 */
/* 假设 mlDemo.exe 和 caller.exe 在同一目录下 */
return run_mathlink_program("./mlDemo", argc, argv);
}
int run_mathlink_program(const char* program_name, int argc, char *argv[]) {
MLINK link;
int argCount, result;
double a = 15.0, b = 27.0;
char* strToSend = "Hello from C!";
const char* exprToSend = "ListPlot[Table[{x, Sin[x]}, {x, 0, 2 Pi, 0.1}]]";
FILE *fp;
/* 1. 初始化 MathLink 连接 */
/* 第一个参数是argc, argv,可以传递命令行参数给MathLink程序 */
/* 第二个参数是错误处理函数,可以设为NULL */
/* 第三个参数是程序名 */
link = MLNew((int)argc, argv, NULL, program_name);
if (link == (MLINK)NULL) {
printf("无法启动 MathLink 程序: %s\n", program_name);
return 1;
}
/* 2. 调用 AddTwoNumbers 函数 */
printf("调用 AddTwoNumbers(%f, %f)...\n", a, b);
MLPutFunction(link, "EvaluatePacket", 1); /* 发送一个计算包 */
MLPutFunction(link, "AddTwoNumbers", 3); /* 函数名和参数个数 */
MLPutDouble(link, a);
MLPutDouble(link, b);
MLEndPacket(link);
/* 等待并读取结果 */
if (!MLNextPacket(link) || !MLGetType(link) == RETURNPKT) {
printf("接收 AddTwoNumbers 结果时出错,\n");
goto cleanup;
}
if (!MLGetDouble(link, &result)) {
printf("解析 AddTwoNumbers 结果时出错,\n");
goto cleanup;
}
printf("结果: %f\n", result);
/* 3. 调用 SendStringToC 函数 */
printf("\n调用 SendStringToC...\n");
MLPutFunction(link, "EvaluatePacket", 1);
MLPutFunction(link, "SendStringToC", 2);
MLPutString(link, strToSend);
MLEndPacket(link);
if (!MLNextPacket(link) || !MLGetType(link) == RETURNPKT) {
printf("接收字符串时出错,\n");
goto cleanup;
}
char* receivedStr;
if (!MLGetString(link, &receivedStr)) {
printf("解析字符串时出错,\n");
goto cleanup;
}
printf("从 Mathematica 收到: \"%s\"\n", receivedStr);
MLReleaseString(link, receivedStr);
/* 4. 调用 GetPlotData 函数 */
printf("\n调用 GetPlotData...\n");
MLPutFunction(link, "EvaluatePacket", 1);
MLPutFunction(link, "GetPlotData", 1);
MLEndPacket(link);
if (!MLNextPacket(link) || !MLGetType(link) == RETURNPKT) {
printf("接收图形数据时出错,\n");
goto cleanup;
}
/* 这里我们收到的是一个图形表达式,可以将其保存为文件 */
/* 保存为 .nb 文件 */
fp = fopen("plot_from_mathematica.nb", "w");
if (fp) {
printf("将图形表达式保存到 plot_from_mathematica.nb\n");
/* 使用 MLScan 和 MLPut 将表达式写入文件 */
/* 这是一个简化的例子,实际处理更复杂的表达式需要更多代码 */
fprintf(fp, "(* Generated by C program *)\n");
MLScan(link); /* 跳过函数名 */
MLPutFunction(fp, "Put", 1);
MLPutArg(fp, link);
fprintf(fp, ";");
fclose(fp);
} else {
printf("无法创建图形文件,\n");
}
cleanup:
/* 5. 关闭连接 */
MLClose(link);
return 0;
}
编译 C 程序
你需要链接 MathLink 库,编译命令会因操作系统而异。
Windows (使用 Visual Studio 命令行):
假设 Wolfram 安装在 C:\Program Files\Wolfram Research\Wolfram Engine\12.3
cl caller.c /I "C:\Program Files\Wolfram Research\Wolfram Engine\12.3\SystemFiles\Links\MathLink\DeveloperKit\Windows\CompilerAdditions" /link /LIBPATH:"C:\Program Files\Wolfram Research\Wolfram Engine\12.3\SystemFiles\Links\MathLink\DeveloperKit\Windows\CompilerAdditions" mathlink.lib
Linux/macOS:
假设 Wolfram 安装在 /usr/local/Wolfram/Mathematica/12.3
gcc caller.c -o caller -I"/usr/local/Wolfram/Mathematica/12.3/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" -L"/usr/local/Wolfram/Mathematica/12.3/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" -lmathlink -lpthread -ldl -lm
运行
- 确保
mlDemo(或mlDemo.exe) 在你的路径下,或者在运行 C 程序时指定正确的路径。 - 在终端中运行你的 C 程序:
./caller
预期输出:
调用 AddTwoNumbers(15.000000, 27.000000)...
结果: 42.000000
调用 SendStringToC...
从 Mathematica 收到: "Hello from C!"
调用 GetPlotData...
将图形表达式保存到 plot_from_mathematica.nb
你会发现目录下多了一个 plot_from_mathematica.nb 文件,用 Mathematica 打开它可以看到生成的正弦图。
使用 WSTP(旧称 MathLink,更现代的接口)
WSTP (Wolfram Symbolic Transfer Protocol) 是 MathLink 的现代继承者,对于新项目,推荐使用 WSTP 的 API,它更清晰、更安全,上面的 MathLink 示例代码,如果使用 WSTP,API 会有所不同(MLPut 变为 WSPut),但核心概念和流程是完全一样的,对于大多数开发者来说,可以认为 MathLink 和 WSTP 指的是同一种技术。
使用文件作为中间介质(最简单,性能最低)
如果对性能和实时性要求不高,这是一种非常简单的替代方案。
工作原理
C 程序将输入数据写入一个文本文件(input.txt),然后通过系统调用(如 system())启动一个 Mathematica 脚本(.m 文件),这个脚本读取 input.txt,进行计算,并将结果写入另一个文本文件(output.txt),C 程序读取 output.txt 并处理结果。
C 程序示例 (file_caller.c)
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp_in, *fp_out;
double a = 10.0, b = 5.0;
double result;
// 1. 写入输入文件
fp_in = fopen("input.txt", "w");
if (fp_in == NULL) {
perror("无法打开 input.txt");
return 1;
}
fprintf(fp_in, "%f %f\n", a, b);
fclose(fp_in);
// 2. 调用 Mathematica 脚本
// 注意:需要确保 math.exe 和 script.m 在路径中
printf("调用 Mathematica 脚本...\n");
int status = system("math -script script.m");
// 3. 读取输出文件
fp_out = fopen("output.txt", "r");
if (fp_out == NULL) {
perror("无法打开 output.txt");
return 1;
}
if (fscanf(fp_out, "%lf", &result) == 1) {
printf("从 Mathematica 收到结果: %f\n", result);
} else {
printf("从 output.txt 读取结果失败,\n");
}
fclose(fp_out);
return 0;
}
Mathematica 脚本示例 (script.m)
(* script.m *) (* 读取输入文件 *) inputData = ReadList["input.txt", Number]; a = inputData[[1]]; b = inputData[[2]]; (* 进行计算 *) result = a + b; (* 将结果写入输出文件 *) OpenWrite["output.txt"]; Write[% result]; Close[];
优点:
- 非常简单,不需要理解复杂的 MathLink API。
- 适合批处理任务,一次计算,一次结果交换。
缺点:
- 性能极差:频繁的磁盘 I/O 和进程创建开销巨大。
- 非实时:C 程序必须等待 Mathematica 进程完全结束。
- 容易出错:需要处理文件读写权限、文件锁定等并发问题。
使用 Wolfram LibraryLink(高级,高性能)
这是为高性能数值计算设计的接口,如果你需要从 C 调用用 C/C++/Fortran 编写的高性能数值库,并让 Mathematica 使用它,或者反过来,LibraryLink 是最佳选择。
工作原理
LibraryLink 允许你将 C/C++ 代码编译成一个共享库(.dll 或 .so),然后使用 LibraryFunctionLoad 在 Mathematica 中直接加载和调用这些 C 函数,通信非常高效,直接在内存中传递数据,没有文本解析的开销。
注意:LibraryLink 是 Mathematica 调用 C,而不是 C 调用 Mathematica,但你可以利用它来实现一个混合模式:
- C 程序启动。
- C 程序通过
system()或子进程的方式启动一个 Mathematica 进程。 - Mathematica 进程使用 LibraryLink 加载一个共享库(这个库可以是你的 C 程序的一部分,或者是一个专门的通信模块)。
- Mathematica 和这个库之间通过 LibraryLink 的高效通道交换数据。
- 数据最终被用于 Mathematica 的计算。
这比纯文件交换高效,但架构比直接的 MathLink 调用更复杂。
总结与对比
| 方法 | 通信机制 | 性能 | 实时性 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|---|
| MathLink/WSTP | 专用网络协议 | 高 | 实时 | 高 | 标准方案,需要双向、实时、复杂数据(图形、大表达式)交换。 |
| 文件交换 | 文件系统 | 极低 | 非实时 | 极低 | 简单的批处理任务,一次性计算,性能要求不高。 |
| LibraryLink | 共享库内存 | 极高 | 实时 | 高 | Mathematica 调用高性能 C/C++ 代码,间接实现 C 调用 Mathematica 的一种高性能方式。 |
最终建议
- 对于绝大多数集成需求,请使用 MathLink,它是官方支持、功能最全、最可靠的方案,虽然初期学习曲线稍陡,但一旦掌握,非常强大。
- 对于简单的、一次性的计算任务,或者作为快速原型验证,可以使用 文件交换。
- 如果你关心的是在 Mathematica 中获得 C 语言的计算性能,那么你应该研究 LibraryLink,并思考如何设计你的系统架构来利用它。
希望这个详细的指南能帮助你成功地在 C 语言中调用 Mathematica!
