2.12.4 处理列表、数组和其它表达式MathLink 可以与外部程序交换任何类型的数据. 对更常用的数据类型, 需要在 MathLink 模板文件中给出适当的:ArgumentTypes: 或:ReturnType: 规格. 基本类型规定 这里是一个函数的 MathLink 模板,该函数的变量是一个整数列表
:Begin: :Function: h :Pattern: h[a_List] :Arguments: a :ArgumentTypes: IntegerList :ReturnType: Integer :End:
这里是该函数的 C 源代码,注意额外的变量 alen 用来传递列表的长度
int h(int *a, long alen) 
int i, tot=0;
for(i=0; i<alen; i++) tot += a[i];
return tot;
安装一个包含有函数 h 规定的外部程序
Out[1]= |  |
这里调用外部代码
Out[2]= |  |
这个与模式 h[a_List] 不匹配, 所以没有调用外部程序
Out[3]= |  |
这里的模式是匹配的,但列表中的元素与外部代码中的类型不同,所以返回到 $Failed
Out[4]= |  |
变量的这几种基本类型可以混合使用. 但使用 IntegerList 或 RealList 时,在 C 程序中必须包含一个额外的变量去代表该 列表的长度. 这是一个 :ArgumentTypes: 指定
:ArgumentTypes: IntegerList, RealList, Integer
这里是可能对应的 C 函数陈述
void f(int *a, long alen, double *b, long blen, int c)
MathLink 把一个列表传递给 C 程序时,它的第一个元素在 C 中标准位置是零, 而不是像在 Mathematica 中的那样标准位置是 1. 另外,按照 C 标准,用 String 规定的字符串传递为 char * 对象,用 \0 空字节结束. 在 2.12.5 节中将讨论怎样处理特殊字符. 向 Mathematica 送数据的 MathList 函数 在使用 MathLink 模板文件时,mpreg 和 mcc 实际上是产生一个包含明确调用 MathLink 库函数的 C 程序. 如果要理解 MathLink 的工作过程,就可以查看这 个程序的源代码. 注意,当使用 mcc 时一般需要给出一个 -g 选项,否则,所产 生的源代码将会被自动删除. 当外部程序返回单个整数或浮点数时,可在以 MathLink 模板文件中对 :ReturnType: 指定 Integer 或 Real. 但由于 C 中的内存分配和撤消方式,不能直接对 :ReturnType: 给出 IntegerList 或 RealList 等指定. 要返回这些结构时,必须在 C 程序中直接调用 MathLink 库函数,并对 :ReturnType: 指定 Manual. 这是一个函数的 MathLink 模板,该函数的变量是一个整数,直接用 MathLink 函数返回它的值
:Begin: :Function: bits :Pattern: bits[i_Integer] :Arguments: i :ArgumentTypes: Integer :ReturnType: Manual :End:
这个函数被说明是一个空函数
void bits(int i) {
int a[32], k;
这里给 C 数组 a 赋值
for(k=0; k<32; k++)  a[k] = i%2; i > 1; if (i 0) break; 
if (k<32) k++;
这里将数组 a 的 k 个元素送回到 Mathematica
MLPutIntegerList(stdlink, a, k); return ; }
安装包含外部函数 bits 的程序
Out[5]= |  |
这个外部函数返回一个位列表
Out[6]= |  |
在 C 中用 int a[n1][n2][n3] 说明一个数组时,可以用 MLPutIntegerArray( ) 将它作为一个深度为 3 的列表送入 Mathematica.
...
说明三维的 C 数组
int a[8][16][100];
这里建立一个数组 dims,其初始值定义为 a 的维数
long dims[] = 8, 16, 100 ;
...
将这个三维数组送入 Mathematica,产生一个深度为 3 的列表
MLPutIntegerArray(stdlink, a, dims, NULL, 3);
...
用 MathLink 函数完全能产生任何 Mathematica 表达式. 其基本思想是调用一系列 直接与 Mathematica 表达式的 FullForm 表示对应的 MathLink 函数. 建立有两个变量的 Mathematica 函数 Plus
MLPutFunction(stdlink, "Plus", 2);
指定第一个变量是整数 77
MLPutInteger(stdlink, 77);
指定第二个变量是符号 x
MLPutSymbol(stdlink, "x");
MLPutFunction() 一般地,先调用 MLPutFunction() 给出拟产生的 Mathematica 函数的头和所含变量的个数. 随后调用其它 MathLink 函数轮流 填入每个变量. 2.1 节讨论了 Mathematica 表达式的一般结构和头的概念. 产生有两个元素的 Mathematica 列表
MLPutFunction(stdlink, "List", 2);
该列表的第一个元素是从 C 数组 r 中得到的 10 个整数的列表
MLPutIntegerList(stdlink, r, 10);
该列表中的第 2 个元素本身是 2 个元素的列表
MLPutFunction(stdlink, "List", 2);
这个子列表的第一个元素是一个浮点数
MLPutReal(stdlink, 4.5);
第二个元素是一个整数
MLPutInteger(stdlink, 11);
MLPutIntegerArray()和 MLPutRealArray() 可以输送由 C 预分配在内存中按一维 形式存放的数组. 在执行 C 程序过程中要产生数组时,通常将它们设置为 指针的嵌套集合. 可以将这种数组送入 Mathematica,其方法是一系列 MLPutFunction() 调用和一个结束调用 MLPutIntegerList().
...
这里说明 a 是一个整数数组的列表嵌套
int ***a;
...
这里产生一个 n1 个元素的 Mathematica 列表
MLPutFunction(stdlink, "List", n1);
for (i=0; i<n1; i++) {
这里产生一个有 n2 个元素的子列表
MLPutFunction(stdlink, "List", n2);
for (j=0; j<n2; j++) {
这里写出整数列表
MLPutIntegerList(stdlink, a[i][j], n3);
} }
...
重要的一点是要知道用 MathLink 函数产生的表达式当送入 Mathematica 后就立即 计算. 例如,要对送到 Mathematica 的一个数组进行转置时,只需要将 Transpose 放在该数组表达式外即可. 为此,在产生表示这个数组的表达式之前要 MLPutFunction(stdlink, "Transpose", 1). 送到 Mathematica 的后处理数据的思想有许多用途. 例如,它是输出事 先不知道其长度的列表的途径. 通过直接追加相继元素在 Mathematica 中产生一个列表
Out[7]= |  |
产生每个后继元素在一个嵌套子列表中的列表
Out[8]= |  |
Flatten 压平了这个列表
Out[9]= |  |
Sequence 自动压平了自己
Out[10]= |  |
调用 MLPutIntegerList() 时要知道拟发送的列表长度. 但通过产生一个嵌套 序列的 Sequence 对象后,就不需要事先知道整个列表的长度. 建立围绕结果的 List
MLPutFunction(stdlink, "List", 1);
while( condition ) { generate an element
产生下一层的 Sequence 对象
MLPutFunction(stdlink, "Sequence", 2);
放入元素
MLPutInteger(stdlink, i );
}
关闭最后一个 Sequence 对象
MLPutFunction(stdlink, "Sequence", 0);
直接从 Mathematica 得到数据的函数 MathLink 提供的 MLPutInteger() 等函数可以从外部程序向 Mathematica 发送数据.同样,MathLink 也提供了 MLGetInteger() 等函数从Mathematica 向外部程序发送数据. MathLink 模板中对 :ArgumentType: 给出的列表可以用 Manual 结束. 它表明当其它变量接收完后将调用 MathLink 函数去得到额外的表达式.
:Begin: :Function: f
Mathematica 中的函数 f 有 3 个变量
:Pattern: f[i_Integer, x_Real, y_Real]
这些变量全部直接传递给了外部程序
:Arguments: i, x, y
仅第一个变量直接传给了外部函数
:ArgumentTypes: Integer, Manual
:ReturnType: Real :End:
这个外部函数仅显含一个变量
double f(int i) {
这里对变量 x 和 y 进行说明
double x, y;
MLGetReal() 从链接直接得了数据
MLGetReal(stdlink, &x); MLGetReal(stdlink, &y);
return i+x+y; }
MathLink 函数 MLGetInteger(link, pi) 等与标准 C 库函数 fscanf(fp, "%d", pi) 等的工作过程相似. 其第一个变量指定要得到数据的链接, 最后一个变量给出数据应该存放的地址. 通过 MathLink 得到一个函数
:Begin: :Function: f
Mathematica 中的函数 f 的变量是一个整数列表
:Pattern: f[a: ___Integer ]
该列表直接传给了外部程序
:Arguments: a
外部程序重新手工地得到这些变量
:ArgumentTypes: Manual
:ReturnType: Integer :End:
该外部函数不显含变量
int f(void) {
检查正被发送的函数是一
long n, i; int a[MAX];
个列表,和在 n 中存储了多少元素
MLCheckFunction(stdlink, "List", &n);
得到列表中的每个元素将它存在 a[i] 中
for (i=0; i<n; i++) MLGetInteger(stdlink, a+i);
... }
在简单情况下,在 Mathematica 这一方可以保证发送到外部程序的数据 具有客观存在所需要的结构. 但仅当数据是由给定名称的函数时 MLCheckFunction() 的返回值将是非零的. 注意,要得到嵌套列表集合或其它对象时,可以调用适当的 MLCheckFunction() 函数来实现. 得到数字列表 外部程序从 Mathematica 得到数据后要安排它们的放存位置.如果该数据像 MLGetInteger(stdlink, &n)一样是单个整数,则用 int n 对其进行说明即可. 但是当该数据可能是有任意长度的整数列表时,在实际调用外部 程序时就要分配保存它的内存. MLGetIntegerList(stdlink, &a, &n) 将自动地进行 这种分配,并将 a 设置为结果的指针. 注意 MLGetIntegerList() 等 函数分配的内存总是放在一个特殊的区域中,不能直接修改或释放它. 这是一个发送整数列表的外部程序
int f(void) {
这里说明局部变量,其中 a 是一个整数数组
long n; int *a;
得到一个整数列表,将 a 作为结果指针
MLGetIntegerList(stdlink, &a, &n);
...
这里释放用于储存该整数列表的内存
MLDisownIntegerList(stdlink, a, n);
... }
由 IntegerList 指定 :Argument: 时,在外部程序退出时 MathLink 将自动释放 由该列表所占用的内存. 而用 MLGetIntegerList() 直接得到一个整数列表时, 与该列表有关的任务完成后用户不要忘记释放内存. 得到数字数组 MLGetIntegerList() 从单个 Mathematica 列表得到一维整数数组. MLGetIntegerArray() 从列表集合或任意深度的嵌套函数得到一个 整数数组. 在一个结构第 层的 Mathematica 函数名作为一个字符串存在 heads[i] 层该结构的大小存在 dims[i] 中. 向外部程序传递复数列表时,MLGetRealArray() 将产生一个二维数组, 该数组是实部和虚部组成的序列.此时 heads[0] 将是 "List",而 heads[1] 将是 "Complex". 可以与外部程序交换任意精度的数,其方法是用 IntegerDigits 和 RealDigits 在 Mathematica 中将它们转化为一个数字列表. 得到字符串和符号名 在 :ArgumentTypes: 指定中用 String 时,MathLink 在函数撤消后将自动释放 该字符串所占用的内存. 这意味着还要继续引用这个字符串时, 就要对它分配内存,并将它所含的字符明确拷贝进去. 用 MLGetString() 得到一个字符时,MathLink 退出所用函数时不会自动释放 该字符串所占用的内存,所以可以继续引用这个字符串. 而不再使用这个 字符串时,就要明确调用 MLDisownString() 去释放与它有关的内存. 获取任意函数 知道外部程序中需要的函数时,就可以简单地调用 MLCheckFunction(). 而不知道外部程序中需要的函数时,就只好调用 MLGetFunction(). 这样做时,就一定要调用 MLDisownSymbol() 去释放与 MLGetFunction() 所找出的函数的名称有关的内存.
|