利用 MathLink 同高级语言混合编程(一)
概述 同外部程序进行交互是 Mathematica 中令人激动的一项功能。您可能学会了如何在 Mathematica 中通过输入语句的方法进行各种数值和符号运算。然而,您可能曾用一些高级计算机语言编写过一些针对具体应用的计算程序或者函数,现在想在 Mathematica 中调用这 些函数;或者您想在自己的程序中集成 Mathematica 的强大计算功能。利用 Mathematica 提供的 MathLink 系统,这些都能够轻松实现,您甚至可以通过网络调用运行在远程主机上的程序。
Mathematica 采用结构化和非结构化两种方式与外部程序进行通讯。结构化通讯是指把 Mathematica 的表达式交给事先建立起来的处理这些表达式的外部程序,其基础就是 MathLink 系统。非结构化通讯是指从外部程序中接收文本数据,进行读写操作。我们将要介 绍的是结构化的通讯方式,也可以看作是 Mathematica 同高级语言的混合编程。使用 MathLink 可以把 Mathematica 建立的表达式发送给外部程序,或者将外部程序的结果读进来 。我们将重点介绍 Mathematica 同 C/C++ 语言的混合编程,并通过一个实例介绍如何利用 Visuall C++ 编写 MathLink 程序以及如何在利用 Visual C++ 创建的应用程序中集成 Mathematica 的计算功能。
x.1. 初识 MathLink
在深入讨论混合编程的细节之前,我们有必要首先搞清楚 Mathematica 进行结构化通讯的基础 MathLink 的相关问题。
x.1.1 什么是 MathLink
MathLink 是一种在程序之间交换 Mathematica 表达式的机制,它提供了一个外部程序同 Mathematics 通讯的通用接口。通过内置或者插件技术,现有的许多软件都是现了对 MathLink 的兼容性。这些程序都可以透明地建立同 Mathematica 间的连接。这种连接可以是在 同一台计算机上的本地连接,也可以是跨越计算机网络的远程连接,并且连接双方主机 可能是不同类型的计算机,如 Microsoft Windows 和 Macintosh 系统间可以建立连接。同时, Mathematica 提供了 MahLink 开发工具进行 MathLink 程序的设计。利用这个工具,你可以方便地设计自己的兼容 MathLink 的应用程序。 通过 MathLink 实现的典型应用有:
? 在 Mathematica 内部调用外部程序的函数;
? 在外部程序中调用 Mathematica 的功能;
? 构建 Mathematica 可选择的前端应用程序;
? 在 Mathematica 和外部程序间交换数据;
? 在当前的 Mathematica 进程间交换数据。 MathLink 库提包含一些能够实现在外部程序中发送以及接收 Mathematica 表达式的程序集合。 MathLink 的一个重要特征是完全平台无关的。也就是说用户的外部程序完全可以不考虑 MathLink 运行在什么平台上, MathLink 能够透明地使用您计算机系统中的内部程序通讯机制。
x.1.2 安装 MathLink
在缺省的安装情况下,安装 Mathematica 时就会安装 MathLink 开发工具( MathLink Developer's Kit )。一般情况下 MathLink Developer's Kit 的安装目录是: Mathematica/4.0/AddOns/MathLink/DevelopersKits/Windows 。安装 MathLink 主要包括两部分:系统要求的部分和编译程序要求的部分。
第一部分,系统要求的部分包括一些完成 MathLink 功能的动态连结库。大多数的 MathLink 程序运行所需的共享库是: "ML32I2.DLL", "ML32I1.DLL", "MLMAP32.MLP", 和 "MLTCP32.MLP" 。您可以在插件子目录的 SystemAdditions 目录中找到他们。它们通常被安装 Mathematica 时被安装在系统可以找到的默认位置: MathLink 是作为一个共享的动态连接库实现的。当运行一个 MathLink 程序时,操作系统 将在磁盘上找到主动态连结库 ML32I2.DLL 并装载进系统中,并且建立同用户程序的关联。这样,调用的 MathLink 函数被映射到动态连接库中的相应代码。在 Windows 操作系统中, ML32I2.DLL 一般被装载在 Windows 的系统目录下。除了 ML32I2.DLL ,还有一些辅助动态 连结库(如 MLTCP32.MLP 和 MLMAP32.MLP ),这些文件通常放在 Windows 目录下。
第二部分,编译程序要求的部分主要包括支持特定高级语言(这里是 C 语言)编译的一些 文件: .LIB 文件、 "mathlink.h" 头文件、 "MPREP32.EXE" 代码自动生成程序等,分别在安装 Mathematica 时安装在目录中的结构是:
? "MLDev32/LIB 包含库文件
? "MLDev32/INCLUDE 包含头文件
? "MLDev32/BIN 包含代码自动生成程序
当创建一个 MathLink 程序时,需要导入一个库文件 ( 一个 .LIB 文件 ) 。这个导入库文件 被加入到工程中,它并不包含代码,只是为了方便用户输出同共享库中相同的函数名。 MathLink 的 C 语言接口在头文件 "mathlink.h" 被详细说明。在调用 MathLink 共享库的所有 C 或 C++ 源文件中都应该包含这个头文件。 "MPREP" 是一个 32 位的控制台应用程序,它可以根据模板自动地生成 MathLink 程序部分代码。将它拷贝到您的开发工具的 BIN 目录下会给你的开发带来方便。除了这两部分以外,安装 MathLink 时还会拷贝一些文档、例程等,这些都可以在对应的 文件夹中找到。
x.1.3 MathLink 支持的编译器
MathLink 是通过一个共享库实现的,因此它可以被任何遵从微软规定的动态连结库接口 标准的编译器。对于 C/C++ 语言来说,常用编译器有:
? Borland C/C++
? Metrowerks CodeWarrior C/C++
? Microsoft Visual C++ and Visual Basic
? Symantec C/C++
? Watcom C/C++
以上的每一个开发工具都遵从一种 " 工程文件 " 的机制,通过它在开发环境中统一地管理 程序源文件、编译器选项、联接器以及调试器等。除了这种集成开发环境( IDE ),他们 都提供一些从命令行运行的命令来编译生成代码。
x.1.4 怎样用 MathLink 同外部程序交互
图 1 Mathematica 结构图
Mathematica 的结构其实比较清晰: Mathematica Kernel + External Program ,包括 Standard Notebook front end 都可以看作是一个完善的外部程序。显然,要使用 Mathematica 提供的各项计算分析功能,必须启动 Mathematica Kernel 并建立外部程序与其的一 个联系通道。 上图说明, MathLink 实际上提供了一种访问 Mathematica 内核以及内核访问外部程序的途 径。
--
蓝色,忧郁的颜色
蓝色,我最喜欢的颜色
蓝色,澄静,纯洁的感觉
这个世界有了蓝色,更加精彩 .....
MathLink 混合编程(二)
x.2 从 Mathematica 中调用外部程序
我们首先来看一下如何在 Mathematica 中装载外部的 MathLink 程序,如何利用程序中的函 数、如何卸载程序;然后通过一个实例重点介绍如何利用 Visual C++ 6.0 创建并生成一个 MathLink 程序;最后简单介绍如何处理各种类型的表达式。
x.2.1 调用的步骤及相应的命令
MathLink 最通常的用法是在 Mathematica 中调用外部 MathLink 程序。当外部程序建立完成 后,我们就可以装载这个程序,并且在当前的 Mathematica 进程中调用程序中的函数。装载及卸载外部 MathLink 程序的语句是:
? Install["prog"] 装载一个外部 MathLink 程序
? Uninstall[link] 卸载外部程序
例一:我们举一个例子,这个例子中使用的例子可以在 MathLink 开发工具文件夹的 "MathLinkExamples" 中找到源代码,已经编译好的程序 "addtwo.exe" 在 "PrebuiltExamples" 中可以找到。外部函数的源代码如下:
in addtwo(int i, int j)
{
return(i+j); // 返回两数之和
}
下面,我们就在 Mathematica 中调用这个函数实现两数相加的运算:
运行 Mathematica ,输入下面的命令:
In[1] := link = Install[LinkLaunch[]]
确认后会出现一个文件打开对话框选择要加载联接的外部程序,选择编译好的程序 "addtwo.exe" 。这样,外部程序就被加载了,返回的信息是:
Out[1] = LinkObject[C:/MathLink/addtoe.exe,2,2]
其中, "C:/MathLink/addtoe.exe" 是选择的外部程序。然后,就可以使用外部程序中的函数了。
2 显示该外部函数 AddTwo 的用法:
2 调用该函数:
2 如果传递的参数不符合原型函数的要求,将不会得到返回值:
2 卸载该外部程序:
常用的处理外部函数连结的命令主要有:
命令格式 用法说明
Install["prog"] 装载一个外部程序
Unstall[link] 卸载一个外部程序
Links["prog"] 显示同外部程序 "prog" 相关联的活动连结
Links[] 显示所有的活动连结
LinksPatterns[link] 显示特定连结上的求值模式
注:其他处理 MathLink 的 Mathematica 命令请参考帮助文档。
x.2.2 创建 MathLink 外部程序的一般步骤
如果已经在外部文件中定义并且实现了函数原型,那您将要作的就是在源代码中加入一些 MathLink 相关的代码,从而实现从 Mathematica 中调用这个函数并得到函数的返回值。简单的情况下,可以使用建立 MathLink 临时文件,通过代码自动生成程序自动生成调用 MathLink 相关函数的 C 语言源代码。一般地说,建立一个 MathLink 外部程序主要要完成三步工作:
? 建立 MathLink 临时文件;
? 处理临时文件生成的源代码;
? 提取源代码,加入函数实现的主代码并编译生成外部程序。
MathLink 临时文件由一系列指令组成,可以看作是一个代码生成模版。其中的指令规定了外部程序如何在 Mathematica 中被调用。临时文件建立了在 Matheamtica 中定义函数同外部程序相应函数的联系关系。临时文件用生成的源代码必须包含在外部文件中,以使外部程序具有 MathLink 兼容性。
下面是 MathLink 模板中包含的元素:
指令 说明
: Begin : 标志对应特定函数的模板的起始
: Function : 外部程序中的函数名
: Pattern : 调用此函数方式的定义
: Arguments : 函数的参数
: ArgumentTypes : 函数参数值类型
: ReturnType : 返回值类型
: End : 标志对应特定函数的模板的结束
: Evaluate : 在函数加载时 Mathematica 的输入计算
注:函数参数值类型及函数返回值类型类表请参看下表。
例一:接着我们给出一个 MathLink 模板的例子:
: Begin :
: Function : addtwo
: Pattern : AddTwo[x_Integer , y_Integer]
: Arguments : {x , y}
: ArgumentTypes : {Integer , Integer}
: ReturnType : Integer
: End :
:Evaluate: AddTwo::usage = "AddTwo[x, y] gives the sum of two machine integers x and y."
这个例子定义了例一中 AddTwo 函数的 MathLink 模板,并保存在文件 addtwo.tm 中。编写好模板后将其保存在模板文件( *.tm )中,利用代码生成程序生成源代码。不同平台不同操作系统上对应不同版本的代码生成程序,一般是 mprep 处理程序。 Windows 系统下的用户在 MathLink 的安装目录中可以找到 "MPREP16.EXE" 或者 "MPREP32.EXE" 程序(分别对应 16 位和 32 位处理程序),利用这两个工具可以生成 Windows 平台下 C/C++ 编译器可以识别编译的源代码。
利用命令:
mprep addtwo.tm -o addtwom.c
将会在生成文件 addtwom.c 中包含生成的源代码。需要注意的是,如果文件 mprep.exe 和 addtwo.tm 不在当前目录下则要在命令中指定其路径。
接下来,我们将编写外部程序主源码,并将生成的代码加入到外部程序源文件中,最后编译生成一个 MathLink 兼容的外部程序。
2 在外部程序源文件中包含 MathLink 的标准头文件
#include "mathlink.h"
2 加入实际函数代码
in addtwo(int i, int j)
{
return(i+j); // 返回两数之和
}
2 编写主函数以使外部程序接收来自 Mathematica 的调用请求
int main(int argc, char* argv[])
{
return MLMain(argc, argv);
}
2 最后利用 C/C++ 编译器编译这个外部程序,生成最终可执行程序。下面一节将专门讨论利用 Visual C++ 编译器编译外部程序的步骤。
x.2.3 使用 Visual C++ 6.0 创建 MathLink 程序
我们将利用 VC6.0 实现一个 MathLink 程序 ,通过这个程序说明创建外部程序的详细步骤。我们的程序中包含一个根据经验公式编写的查表计算程序。
我们简化一下这个查表函数,去掉同具体算法相关的细节,把重点放在实现同 MathLink 的接口实现上。该函数 myfun 根据调用时传递的两个参数值 int line 和 int row 在一个数据表查找对应的数据项,并将查到的数据返回。
下面,我们来看怎样分步实现这个程序:
1. 启动 Microsoft Developer Studio ,进入 Visual C++ 的集成开发环境;
2. 在 File 菜单中选择 New 将会打开一个新建对话框;
3. 在 New 对话框中单击 Projects 标签;
4. 在 Location 文本框中选择工程将被创建的路径;
5. 在 Project Name 文本框中键入工程名 MYFUN ;
图二 在 VC 中新建一个工程 MYFUN
6. 在左边的 Project 类型列表中选择 Win32 Application ;
7. 在右下的 Platforms 列表框中确保 Win32 被选中了;
完成后如图二所示
8. 单击 OK 确认,将出现 Win32 Application 对话框;
9. 选择 empty project 并单击 Finish 完成,将出现 New Project Information 对话框;
10. 单击 OK 确认;
接下来,我们在工程中添加我们的代码文件。我们将需要编辑两个文件,分别是 myfun.c 和 myfun.tm ,对应主程序源文件和 MathLink 的模板文件。
11. 在 Project 菜单的 Add To Project 子菜单中选择 New …,将出现新建对话框,选中 Files 标签;
12. 在文件名中键入以下文件名,以空格隔开: "myfun.c" "myfun.tm" "myfuntm.c"
13. 在 Project 菜单的 Add To Project 子菜单中选择 Files ,将出现插入文件对话框。定位并插入文件 "ml32i2m.lib" 。注意,这个文件是在安装 MathLink 时拷贝到硬盘上的,我们前面在介绍安装 MathLink 时提到过。我的 Mathematica 安装在 D:/Program Files/Wolfram Research ,我在 MathLink 的安装目录中找到了这个文件:
D:/ProgramFiles/WolframResearch/Mathematica/4.0/AddOns/MathLink/DevelopersKits/Windows/CompilerAdditions/mldev32/lib
您可能觉得这个文件隐藏地太深了。没关系,您可以将 MathLink 拷贝到一个适当地位置,如 Microsoft Developer Studio 安装目录下。
下面,我们编写主程序以及模板文件:
14. 在 Workspace 中打开文件 myfun.tm ,加入下面的代码:
int myfun P(( int, int));
:Begin:
:Function: myfun
:Pattern: MyFun[line_Integer, row_Integer]
:Arguments: { line, row }
:ArgumentTypes: { Integer, Integer }
:ReturnType: Real
:End:
:Evaluate: MyFun::usage = "MyFun[line, row] retrun float value in the line/row position."
15. 在 Workspace 中打开文件 myfun.c ,加入下面的代码:
#include "mathlink.h"
extern double myfun( int line, int row);
double myfun( int line, int row)
{
double ret=0.8979*line-row;
/* 这里省略了具体处理的代码 */
return ret;
}
#if MACINTOSH_MATHLINK
int main( int argc, char* argv[])
{
/* Due to a bug in some standard C libraries that have shipped with
* MPW, zero is passed to MLMain below. (If you build this program
* as an MPW tool, you can change the zero to argc.)
*/
argc = argc; /* suppress warning */
return MLMain( 0, argv);
}
#elif WINDOWS_MATHLINK
#if __BORLANDC__
#pragma argsused
#endif
int PASCAL WinMain( HINSTANCE hinstCurrent, HINSTANCE hinstPrevious, LPSTR lpszCmdLine, int nCmdShow)
{
char buff[512];
char FAR * buff_start = buff;
char FAR * argv[32];
char FAR * FAR * argv_end = argv + 32;
hinstPrevious = hinstPrevious; /* suppress warning */
if( !MLInitializeIcon( hinstCurrent, nCmdShow)) return 1;
MLScanString( argv, &argv_end, &lpszCmdLine, &buff_start);
return MLMain( argv_end - argv, argv);
}
#else
int main(argc, argv)
int argc; char* argv[];
{
return MLMain(argc, argv);
}
#endif
下面设置编译选项以及编译前的代码生成:
16. 在 Project 菜单中选择 Settings ,将出现 Project Settings 对话框;
17. 在左边的设置列表中展开工程 MYFUN ,并选择文件 myfun.tm ;
18. 单击 Custom Build 标签显示 Custom Build 页;
19. 在 Description 文本框中输入编译的描述,如: "mpreping ..." ;
20. 在 Build Commands 列表框的第一行中输入:
mprep myfun.tm -o myfuntm.c
21. 在 Output Files 列表框的第一行中输入:
./myfuntm.c
图三 设置编译选项
22. 单击 Ok 确认
在编译之前,我们还要将相应版本的 mathlink.h 拷贝到编译器默认的包含文件目录下或者该工程文件夹中。选择 Tools 菜单下的 Options …子菜单,在出现的对话框中的 Directories 标签栏内可以查看和设置编译器的缺省目录。另外,还要将相应的 mprep.exe 文件拷贝到工程文件夹中。
下面可以开始编译了:
23. 选择 Build 菜单中选择 Buile MYFUN.EXE 或者按快捷建 F7 ,将会编译生成一个 MathLink 兼容程序 MYFUN.EXE 。
最后,简单介绍一下编译的过程:
2 系统首先执行用户定制的步骤,即在图三所示界面上的设置,通过这一步系统调用 mprep 程序自动生成了 MathLink 相关源代码,存放在文件 myfuntm.c 中。我们可以打开该文 件看看到底生成了些什么代码。有兴趣的读者可以参考 Mathematica 的帮助文档理解这些代码。
2 接着是常规地编译,按照顺序分别编译了 myfun.c 、 myfuntm.c 两个文件。
2 最后是连接并生成目标代码。
可以看到,除了编译前的 " 执行用户定制的步骤 " ,并没有特殊的地方。我们完全可以通过手工调用命令 mprep myfun.tm -o myfuntm.c 而取代 " 执行用户定制的步骤 " 。但是这样每次对模板文件 myfun.tm 改动后都要输入该命令重新生成一次,比较麻烦。所以我们建议在编译器参数设置中加入用户定制的步骤以自动生成 myfuntm.c 文件。
x.2.4 处理各种交换数据类型
在上面的例子中,我们的从 Mathematica 传递给外部含数的数据是整型变量,函数返回的是符点实型变量。事实上,利用 MathLink 可以同外部程序交换任何类型的数据。可以在模板文件中的: ArgumentTypes :和: ReturnType :中指定函数参数以及返回值的数据类型。
下表给出了在模板文件中定以的基本数据类型以及 C 语言中相对应的类型:
Mathematica 中的定义 说明 C 语言中的定义
Integer 整型变量 int
Real 浮点型变量 double
IntegerList 整型变量列表 int *, long
RealList 浮点型变量列表 double *, long
String 字符串变量 char *
Symbol 符号名变量 chat *
Manual 直接调用 MathLink 程序 void
基本数据类型对照表
类似我们上面介绍的例子,您可以在模板中的参数类型段使用上面任何一种变量类型。需要注意的是,如果您使用 IntegerList 或者 RealList 作为变量类型,则必须在外部的 C 函数中包含附加的参数来指定列表的长度。
例如,在模板中您这样定义参数类型:
: ArgumentTypes : {IntergerList, RealList, Interger}
则对应的 C 函数定义可能是:
void f(int *a, long alen, double *b, long blen, int c);
下面将介绍在 MathLink 及 C 语言程序间更加灵活地传递数据的方法。先来看一个例子:
模板文件:
:Begin:
:Function: otherfun
:Pattern: OtherFun[line_Integer, row_Integer]
:Arguments: { line, row }
:ArgumentTypes: { Integer, Integer }
:ReturnType: Manual
:End:
C 语言函数:
Void otherfun(int ling, int row)
{
int a[32], k;
for(k=0;k<32;k++){
……
// 根据具体的算法给数组 a 赋值
}
k++;
MLPutIntegerList(stdlink, a, k);
return;
}
我们看到,在模板中定义的函数返回值类型是 Manual ,即在外部函数中直接调用 MathLink 的函数向 Mathematica 返回数据。
这调用的函数是 MLPutIntegerList ,下面列出其他几个常用的类似的函数,他们在 "mathlink.h" 中都有定义。从函数的名字中可以猜测其意义,这里不再详细介绍了。
MLPutInteger(MLINK link, int i) ;
MLPutReal(MLINK link, double x) ;
int MLPutIntegerList(MLINK link, int *a, long n) ;
int MLPutRealList(MLINK link, double *a, long n) ;
int MLPutIntegerArray(MLINK link, int *a, long *dims, char **heads, long d) ;
int MLPutRealArray(MLINK link, double *a, long *dims, char **heads, long d) ;
int MLPutFunction(MLINK link, char *s, long n) ;
int MLPutString(MLINK link, char *s) ;
int MLPutSymbol(MLINK link, char *s) ;
上面介绍的是从 C 语言中通过调用 MathLink 函数返回数据给 Mathematica ,如果要从外部函数中直接通过调用 MathLink 函数得到传递过来的数据,则要用到另外一组函数:
int MLGetInteger(MLINK link, int *i) ;
int MLGetReal(MLINK link, double *x) ;
int MLGetIntegerList(MLINK link, int **a, long *n) ;
int MLGetRealList(MLINK link, double **a, long *n) ;
int MLGetIntegerArray(MLINK link, int **a, long **dims, char ***heads, long *d) ;
int MLGetRealArray(MLINK link, double **a, long **dims, char ***heads, long *d) ;
下面看一个具体的例子:
模板文件:
:Begin:
:Function: getfun
:Pattern: GetFun[a,b,c]
:Arguments: { a,b,c}
:ArgumentTypes: { Integer, Manual }
:ReturnType: Real
:End:
C 语言函数:
Void getfun(int a)
{
double b,c;
MLGeReal(stdlink, &b);
MLGeReal(stdlink, &c);
return a+b*c;
}
在这个函数中,我们利用函数参数传递整型变量 int a ,通过 MathLink 函数 MLGetReal 得到另外两个浮点型变量 b 和 c 。通过调用这些 MathLink 外部函数,可以很灵活在 Mathematica 以及外部 C 语言函数间交换数据。
事实上,利用 mprep 程序生成的也正是一些调用 MathLink 库的外部函数的代码,系统利用这些自动生成的代码,建立了 Mathmatica 同外部程序之间的连接。我们可以通过阅读这些代码深刻理解 MathLink 是怎样工作的。
如果读者想了解有关 MathLink 外部函数更详细的资料,可以参考 Mathematica 的帮助文档。
--
蓝色,忧郁的颜色
蓝色,我最喜欢的颜色
蓝色,澄静,纯洁的感觉
这个世界有了蓝色,更加精彩 .....
MathLink 混合编程(三)
x.3 从外部程序中同 Mathematica 交互
这一节里,我们将向您介绍如何在外部程序中集成 Mathematica 的强大计算功能。这同上一节中介绍的内容是不同的,上一节中我们关注的是如何在 Mathematica 中调用外部程序中的函数以扩充 Mathematica 处理具体问题的能力。两者思路刚好相反,但都是混合编程方面的内容,都达到了结合 C 语言灵活地处理能力和 Mathematica 强大数值、符号处理能力的目的。 我们首先介绍在外部程序中调用 Mathematica 计算模块的原理,然后介绍编写此类外部程 序的一般步骤,最后给出一个 " 代数多项式展开 " 的例子。
x.3.1 建立同 Mathematica 连接的原理
要在外部程序中利用 Mathematica 内部计算功能需要使用 MathLink 的许多一般的特征。我们前面提到过,通过编写 MathLink 模板并调用相关程序我们可以得到一些 MathLink 自动生成的代码。这些代码向外部程序提供与 MathLink 连结通讯的手段。也就是说,这些代码封装了实际调用 MathLink 库的细节,我们所需要作的就是在程序中调用 MLMain(argc, argv) 函数,这在我们前面的外部程序中的主函数中可以看出。
我们先来看一下,调用 MLMain(argc, argv) 函数是如何建立这个连接的,我们以 x.2.3 中 提到程序的为例:
int MLMain( int argc, charpp_ct argv)
{
return _MLMain( argv, argv + argc, (charp_ct)0);
}
该函数直接调用了自动生成的另一个函数
_MLMain : static int _MLMain( charpp_ct argv, charpp_ct argv_end, charp_ct commandline)
{
MLINK mlp;
long err;
if( !stdenv)
stdenv = MLInitialize( (MLParametersPointer)0);
if( stdenv == (MLEnvironment)0) goto R0;
if( !stdyielder)
stdyielder = MLCreateYieldFunction( stdenv,
NewMLYielderProc( MLDefaultYielder), 0);
if( !stdhandler)
stdhandler = MLCreateMessageHandler( stdenv,
NewMLHandlerProc( MLDefaultHandler), 0);
mlp = commandline
? MLOpenString( stdenv, commandline, &err)
: MLOpenArgv( stdenv, argv, argv_end, &err);
if( mlp == (MLINK)0){
MLAlert( stdenv, MLErrorString( stdenv, err));
goto R1;
}
if( MLIconWindow){
char textbuf[64];
int len;
len = GetWindowText(MLIconWindow, textbuf, sizeof(textbuf)-2);
strcat( textbuf + len, "(");
_fstrncpy( textbuf + len + 1, MLName(mlp), sizeof(textbuf) - len -3);
textbuf[sizeof(textbuf) - 2] = '/0';
strcat( textbuf, ")");
SetWindowText( MLIconWindow, textbuf);
}
if( MLInstance){
if( stdyielder) MLSetYieldFunction( mlp, stdyielder);
if( stdhandler) MLSetMessageHandler( mlp, stdhandler);
}
if( MLInstall( mlp))
while( MLAnswer( mlp) == RESUMEPKT){
if( ! refuse_to_be_a_frontend( mlp)) break;
}
MLClose( mlp);
R1: MLDeinitialize( stdenv);
stdenv = (MLEnvironment)0;
R0: return !MLDone;
} /* _MLMain */
我们简单分析一下这个函数:
2 MLInitialize 函数执行初始化过程以初始化 MathLink 库函数,是整个连接过程的第一 步。调用任何 MathLink 库函数之前都要调用这个函数以完成连接环境的建立,系统将返回一个 MLEnvironment 变量表明库初始化成功。
2 MLCreateYieldFunction 函数创建输出函数并返回 MLYieldFunctionObject 变量表明创建成功。这不是建立连接所必需调用的函数。
2 MLCreateMessageHandler 函数创建了连接的消息处理句柄并返回一个 MLMessageHandlerObject 变量表明创建成功。这也不是建立连接所必需调用的函数。
2 MLOpenArgv 和 MLOpenString 是两个比较重要的函数,他们实际完成建立同 Mathematica Kernel 的连接。两者的区别在于: MLOpenArgv 将与程序主函数相同的参数传递给 MathLink 以建立一个连接,而 MLOpenString 函数将所有参数集中放入一个字符串中传递给 MathLink 以建立连接。主函数 _MLMain 通过变量 commandline 来区分到底调用两者中的哪一个来建立连接。最后,函数返回一个 MLINK 变量标志创建连接的结果。
2 接下来是创建窗口,并利用函数 MLSetYieldFunction 和 MLSetMessageHandler 将创建的 输出函数和消息处理句柄同已经建立的连接联系起来。
2 主函数末尾的 while 语句是外部程序程序的主循环。上面,我们介绍了利用 mprep 生成的源代码是如何建立同 Mathematica 连接的。有时为了增加外部程序的灵活性或者出于其他的一些考虑,我们需要直接在程序中调用内部的这些 MathLink 库函数以建立连接。当然,更多的情况下,我们希望能在外部程序中调用 MathLink 的库函数以实现利用内核计算的目的。下面的一节里,我们着重介绍如何编写这类外部程序。
x.3.2 怎样编写此类外部程序
编写此类 MathLink 外部程序的重点是如何处理同 Mathematica 的数据交互问题。外部程序在成功建立连接之后,通过调用 MathLink 库函数向 Mathematica Kernel 发送调用其内部 计算模块的请求, Kernel 接收请求调用相应的模块进行处理之后将计算结果发送给外部 程序,外部程序调用相应的函数接收返回的计算结果。 Mathematica 表达式提供了一种处理各种数据的通用的方法。我们可能希望在外部程序中处理这些表达式。然而,类似 C 语言这样的高级计算机语言并没有提供直接存储、处理这些表达式的方法。幸运的是, MathLink 提供了使用环回连接( loopback links )来处理 Mathematica 表达式的方法,进而达到与 Mathematica 交互的目的。 MLINK MLLoopbackOpen(stdenv, long *erreo) 函数打开一个环回连接; void MLClose(MLINK link) 函数关闭一个环回连接; MLTransferExpression(MLINK dst, MLINK src) 函数将表达式从源连接 src 传送到目标连接 dst 。 我们稍后会介绍这种使用环回使用 Mathematica 表达式的方法。 下面,我们先来看三个问题:
第一, 外部程序如何发出请求?
我们首先来看下面的这个程序段:
MLENV ep = (MLENV)0;
MLINK lp = (MLINK)0;
// 初始化 MathLink 库
ep = MLInitialize( (MLParametersPointer)0);
……
// 打开一个连接
lp = MLOpenArgv( ep, argv, argv + argc, &err);
……
// 调用 MLPut* 函数向内核传递命令及数据
MLPutFunction( lp, "EvaluatePacket", 1L);
MLPutFunction( lp, "FactorInteger", 1L);
MLPutInteger( lp, n); //n 是参数
// 同步函数表明包已经传送完毕
MLEndPacket( lp);
这一段函数完成了向内核传递数据包的工作,下面等待内核处理完毕后的相应。 MLPutFunction 函数向内核传递了一个调用函数名, MLPutInteger 向内核传递了一个整型变量。有关这些函数的详细用法,请参考帮助文档。
第二, 外部程序同内核两者如何保持同步?也就是说,外部程序如何判断 Kernel 是否 处 理完毕,以作进一步的处理:例如,将结果打印出来。下面的代码段通过判断 MLNextPacket 的返回值是否为 RETURNPKT 来同步内核的处理过程。
while ((p = MLNextPacket(link)) && p != RETURNPKT)
MLNewPacket(link);
下表列出了 MLNextPacket 可能的返回值及其意义:
Mathematica 包类型 返回的常量 说明
ReturnPacket[expr] RETURNPKT 计算的结果
ReturnTextPacket["string"] RETURNTEXTPKT 文本形式结果
InputNamePacket["name"] INPUTNAMEPKT 输入行名称
OutputNamePacket["name"] OUTPUTNAMEPKT 输出行名称
TextPacket["string"] TEXTPKT 函数的文本形式输出
MessagePacket[symb,"tag","string" MESSAGEPKT Mathematica 产生的消息
DisplayPacket["string"] DISPLAYPKT 图片后文的一部分
DisplayEndPacket["string"] DISPLAYENDPKT 图片后文的结尾
InputPacket["prompt"] INPUTPKT 请求一个输入函数的相应
CallPacket[I,list] CALLPKT 请求调用一个外部函数
表三 MLNextPacket 的返回值及其意义
第三 , 外部程序如何处理这些返回的数据?我们可以调用 MLGetNext 库函数依次得到 kernel 传递给外部程序的每一个对象。
根据 MLGetNext 的不同返回值,我们可以编写相应的 C 语言语句作出处理。
表列出了 MLGetNext 函数可能的返回值及其意义:
MLTKERR 出错标志
MLRKINT 整数变量
MLTKFUNC 复合函数
MLTKREAL 实数变量
MLTKSTR 字符串
MLTKSYM 符号变量
表四 MLGetNext 返回值及其意义
下面我们来看一段代码,着重说明 MLGetNext 函数的用法:
static void read_and_print_expression( MLINK lp)
{
kcharp_ct s;
int n;
long i, len;
double r;
static int indent;
switch( MLGetNext( lp)) {
case MLTKSYM:
MLGetSymbol( lp, &s);
printf( "%s ", s);
MLDisownSymbol( lp, s);
break;
case MLTKSTR:
MLGetString( lp, &s);
printf( "/"%s/" ", s);
MLDisownString( lp, s);
break;
case MLTKINT:
MLGetInteger( lp, &n);
printf( "%d ", n);
break;
case MLTKREAL:
MLGetReal( lp, &r);
printf( "%g ", r);
break;
case MLTKFUNC:
indent += 3;
printf( "/n %*.*s", indent, indent, "");
if( MLGetArgCount( lp, &len) == 0){
error( lp);
}else{
read_and_print_expression( lp);
printf( "[");
for( i = 1; i <= len; ++i){
read_and_print_expression( lp);
if( i != len) printf( ", ");
}
printf( "]");
}
indent -= 3;
break;
case MLTKERROR:
default:
error( lp);
}
}
上面的程序使用 switch 语句判断 MLGetNext( lp) 的返回值,以分别调用处理不同类型返回对象的代码。然后将返回的数据储存在预先定义的几个内部变量中,最后将结果打印出来。
2 MLGetSymbol 得到返回的符号变量, MLDisownSymbol 释放为其分配的内存;
2 MLGetString 得到返回的字符串变量; MLDisownString 释放为其分配的内存;
2 MLGetInteger 得到返回的整型变量;
2 MLGetReal 得到返回的实型变量;
2 对返回 MLTKFUNC 的处理相对复杂一些:输出程序先调用 MLGetArgCount 得到了该复合函数的参数个数,然后递归调用 read_and_print_expression 本身将每个参数输出。注意, indent 是控制缩进格式的变量;
2 如果 MLGetNext 返回了 MLTKERROR 就调用错误处理函数 error( lp) 输出错误信息。 接下来,我们给出利用上面说明的三个步骤编写的外部程序的主程序。该程序计算一个代数方程式 ax+b=0 ,其中 x 是方程的变元。
int main(int argc, char* argv[])
{
char* s="3+6";
char* q="(x-1)^2==0";
int pkt;
double result;
init and openlink( argc, argv);
printf( "Computing... /n");
MLPutFunction(lp,"Solve", 2);
MLPutFunction(lp,"Equal", 2);
MLPutFunction(lp,"Plus", 2);
MLPutFunction(lp,"Times",2);
MLPutSymbol(lp,"a");
MLPutSymbol(lp,"x");
MLPutSymbol(lp,"b");
MLPutInteger(lp,0);
MLPutSymbol(lp,"x");
MLEndPacket(lp);
while( (pkt = MLNextPacket( lp), pkt) && pkt != RETURNPKT) {
MLNewPacket( lp);
if (MLError( lp))
error( lp);
}
read_and_print_expression(lp);
MLPutFunction( lp, "Exit", 0);
return 0;
}
程序的输出如下: List[
List[
Rule[x,
Time[-1,
Power[a,-1],b]]]]
我们可以将其转换成通常的形式:
要说明一点:外部程序同 Mathematica 之间的数据都是以表达式的形式传递的。例如, "5+6" 应该转化为表达式 Plus[5,6] 才能为内核识别,同样内核向外部程序返回 Mathematica 表达式。用户的外部程序要能够处理这些表达式。上面的程序只是简单地传送和显示 计算结果,并不复杂。如果读者想要编写更加复杂的混合程序,就要熟练掌握 Mathematica 的表达式。有关 Mathematica 表达式的相关问题,请参考前面的相关章节及 Mathematica 的帮助文档。到现在为止,我们介绍了在外部程序中同 Mathematica 进行交互的基本的问题,并简单分析了一些代码。下面,我们将介绍本节开始时提到的在外部程序中利用环回处理 Mathematica 表达式的方法。 在使用环回同前需要建立一个连接并打开这个环回,使用完毕后要及时关闭。假设我们 已经建立了一条连接:
MLINK lp = (MLINK)0;
lp = MLOpenArgv( ep, argv, argv + argc, &err);
下面的代码显示了利用环回处理表达式的框架:
ml = MLLoopbackOpen(stdenv, &errno); // 打开一个环回
// 将表达式 Power[x, 3] 放入这个环回连接上
MLPutFunction(ml, "Power", 2);
MLPutSymbol(ml, "x");
MLPutInteger(ml, 3);
……
// 从环回连接上得到一个表达式
MLGetFunction(ml, &head, &n);
MLGetSymbol(ml, &sname);
MLGetInteger(ml, &k);
……
MLClose(ml); // 关闭这个环回
上面的这段代码说明了如何打开一个环回,如何通过 MLPut 族函数和 MLGet 族函数操纵这 个环回连接上的表达式以及如何关闭一个环回的方法。那么,究竟如何利用已经建立起来的连接 lp 同 Mathematica 交换表达式呢?这要用到一个重要的库函数 MLTransferExpression() 。 通过调用 MLTransferExpression() ,我们可以将在环回上建立的表达式传送给 Mathematica ,同时也可以接收来自 Mathematica 的表达式并将其存储在环回上以便在外部程序中 作进一步的处理。
假设我们要求一个指数函数 e^2 的值指数函数的值,我们可以如下处理:
double result;
ml = MLLoopbackOpen(ep, &errno); // 打开一个环回
// 将表达式 e^2 放入这个环回连接上
MLPutFunction(ml,"N",1);
MLPutFunction(ml, "Exp",1);
MLPutInteger(ml, 2);
MLTransferExpression(lp,ml); // 向 Mathematica 发送数据
MLEndPacket(lp);
while( (pkt = MLNextPacket( lp), pkt) && pkt != RETURNPKT) {
MLNewPacket( lp);
if (MLError( lp))
error( lp);
}
MLTransferExpression(ml,lp); // 从 Mathematica 接收数据
// 从环回连接上得到一个表达式
MLGetReal(ml, &result);
printf("The result is: %f /n",result);
MLClose(ml); // 关闭这个环回
这个程序建立了一个环回连接,在向环回连接上输入表达式后调用库函数 MLTransferExpression 将环回上的表达式传递给 Mathematica ,然后等待 Mathematica 将结果传回,又 一次调用库函数 MLTransferExpression 将结果传入环回连接。最后调用 MLGetReal 函数将结果(一个实型变量)保存在一个浮点型变量 result 中。在这一节里,我们重点介绍了在外部程序同 Mathematica 交换数据的一般方法并通过几个实例说明如何编写这类外部程序。要编写好 C 语言的混合程序,最重要的就是深刻理解、熟练掌握 Mathematica 的表达式。在此基础上灵活应用各类 MathLink 库函数,就能编写实用的混合计算程序。
x.3.3 实例
我们在工程实践中往往会遇到需要求问题解析解的情况。例如,有时我们经过某种抽象得到一个以代数式表示的求解公式。对于这个求解公式,我们希望将其表示成为多项式的形式。如果用标准 C 语言编程将会比较困难,但是这在 Mathematica 中是容易做到的。下面的例子演示了如何通过外部程序调用 Mathematica 的计算模块以实现这个功能。
/* math1.c
在命令行中运行这个程序: math1 -linkmode -launch
*/
#include <stdio.h>
#include <stdlib.h>
#include "mathlink.h"
#if MACINTOSH_MATHLINK
extern int mlmactty_init( char*** argvp);
#endif
static void init_and_openlink( int argc, char* argv[]);
static void error( MLINK lp);
MLENV ep = (MLENV)0;
MLINK lp = (MLINK)0;
MLINK ml = (MLINK)0;
FILE* fp; // 将计算的结果输出到文件中
static void read_and_print_expression_tofile( MLINK lp)
{
kcharp_ct s;
int n;
long i, len;
double r;
static int indent;
switch( MLGetNext( lp)) {
case MLTKSYM:
MLGetSymbol( lp, &s);
fprintf(fp, "%s ", s);
MLDisownSymbol( lp, s);
break;
case MLTKSTR:
MLGetString( lp, &s);
fprintf(fp, "/"%s/" ", s);
MLDisownString( lp, s);
break;
case MLTKINT:
MLGetInteger( lp, &n);
fprintf(fp, "%d ", n);
break;
case MLTKREAL:
MLGetReal( lp, &r);
fprintf(fp, "%g ", r);
break;
case MLTKFUNC:
indent += 3;
fprintf(fp, "/n %*.*s", indent, indent, "");
if( MLGetArgCount( lp, &len) == 0){
error( lp);
}else{
read_and_print_expression_tofile( lp);
fprintf(fp, "[");
for( i = 1; i <= len; ++i){
read_and_print_expression_tofile( lp);
if( i != len) fprintf(fp, ", ");
}
fprintf(fp, "]");
}
indent -= 3;
break;
case MLTKERROR:
default:
error( lp);
}
}
int main(int argc, char* argv[])
{
char* s="3+6";
char* q="(x-1)^2==0";
int pkt;
double result;
if((fp=fopen("e://math.txt","w"))==NULL)
{
printf("Open files Error!/n");
exit(1);
}
init_and_openlink( argc, argv);
printf( "Computing... /n");
// 在这里将代数式转换成为 MathLink 表达式的形式
MLPutFunction(lp,"Collect", 2);
MLPutFunction(lp,"Expand", 1);
MLPutFunction(lp,"Power", 2);
MLPutFunction(lp,"Plus", 4);
MLPutInteger(lp,1);
MLPutSymbol(lp,"x");
MLPutFunction(lp,"Times", 2);
MLPutInteger(lp,2);
MLPutSymbol(lp,"y");
MLPutFunction(lp,"Times", 2);
MLPutInteger(lp,3);
MLPutSymbol(lp,"z");
MLPutInteger(lp,3);
MLPutFunction(lp,"List", 2);
MLPutSymbol(lp,"x");
MLPutSymbol(lp,"y");
MLEndPacket(lp);
while( (pkt = MLNextPacket( lp), pkt) && pkt != RETURNPKT) {
MLNewPacket( lp);
if (MLError( lp))
error( lp);
}
read_and_print_expression_tofile(lp);
MLPutFunction( lp, "Exit", 0);
fclose(fp);
return 0;
}
static void error( MLINK lp)
{
if( MLError( lp)){
fprintf( stderr, "Error detected by MathLink: %s./n",
MLErrorMessage(lp));
}else{
fprintf( stderr, "Error detected by this program./n");
}
exit(3);
}
static void deinit( void)
{
if( ep) MLDeinitialize( ep);
}
static void closelink( void)
{
if( lp) MLClose( lp);
}
static void init_and_openlink( int argc, char* argv[])
{
long err;
#if MACINTOSH_MATHLINK
MLYieldFunctionObject yielder;
argc = mlmactty_init( &argv);
#endif
ep = MLInitialize( (MLParametersPointer)0);
if( ep == (MLENV)0) exit(1);
atexit( deinit);
#if MACINTOSH_MATHLINK
yielder = MLCreateYieldFunction( ep, NewMLYielderProc(MLDefaultYielder), 0);
#endif
lp = MLOpenArgv( ep, argv, argv + argc, &err);
if(lp == (MLINK)0) exit(2);
atexit( closelink);
#if MACINTOSH_MATHLINK
MLSetYieldFunction( lp, yielder);
#endif
}
程序的输出如下:
Plus [3 ,
Times [6 , y ],
Times [9 , z ]]],
Times [
Power [y , 2 ],
Plus [12 ,
Times [36 , z ]]],
Times [y ,
Plus [6 ,
Times [36 , z ],
Times [54 ,
Power [z , 2 ]]]],
Times [x ,
Plus [3 ,
Times [12 ,
Power [y , 2 ]],
Times [18 , z ],
Times [27 ,
Power [z , 2 ]],
Times [y ,
Plus [12 ,
Times [36 , z ]]]]]]
在上面的程序中,我们需要展开的代数式是已知的。在一些情况下,根据工程应用的具体情况,我们需要动态地生成这些代数式。这时,可以对上面的程序作出某些改动, " 动态 " 地调用 MLPut 族函数生成相应的 Mahtematica 表达式。
下面的例子是一个工程中经常遇到得线性规划问题。
目标函数是 w=x+5y-8z ,我们求其在约束条件: x+7y>10 , 2x-8z<=19 和 x+y+z<10 条件下的最小值。为了节省篇幅,我们仅给出表达式转换的代码以及程序的输出。
MLPutFunction(lp,"N", 1);
MLPutFunction(lp,"ConstrainedMin", 3);
MLPutFunction(lp,"Plus", 3); // 目标函数 w=x+5y-8z
MLPutSymbol(lp,"x");
MLPutFunction(lp,"Times", 2);
MLPutInteger(lp,5);
MLPutSymbol(lp,"y");
MLPutFunction(lp,"Times", 2);
MLPutInteger(lp,-8);
MLPutSymbol(lp,"z");
MLPutFunction(lp,"List", 3);
MLPutFunction(lp,"Greater", 2); // 条件 x+7y>10
MLPutFunction(lp,"Plus", 2);
MLPutSymbol(lp,"x");
MLPutFunction(lp,"Times", 2);
MLPutInteger(lp,7);
MLPutSymbol(lp,"y");
MLPutInteger(lp,10);
MLPutFunction(lp,"LessEqual", 2); // 条件 2x-8z<=19
MLPutFunction(lp,"Minus", 2);
MLPutFunction(lp,"Times", 2);
MLPutInteger(lp,2);
MLPutSymbol(lp,"x");
MLPutFunction(lp,"Times", 2);
MLPutInteger(lp,8);
MLPutSymbol(lp,"z");
MLPutInteger(lp,19);
MLPutFunction(lp,"Less", 2); // 条件 x+y+z<10
MLPutFunction(lp,"Plus", 3);
MLPutSymbol(lp,"x");
MLPutSymbol(lp,"y");
MLPutSymbol(lp,"z");
MLPutInteger(lp,10);
MLPutFunction(lp,"List", 3);
MLPutSymbol(lp,"x");
MLPutSymbol(lp,"y");
MLPutSymbol(lp,"z");
程序的输出结果为:
List [-61.4286 ,
List [
Rule [x , 0 ],
Rule [y , 1.42857 ],
Rule [z , 8.57143 ]]]
即:在 x->0 , y-> 1.42847 , z->8.57143 时,在上述条件 x+7y>10 , 2x-8z<=19 和 x+y+z<10 的约束下,表达式 x+5y-8z 趋于最小值 -61.4286
--
蓝色,忧郁的颜色
蓝色,我最喜欢的颜色
蓝色,澄静,纯洁的感觉
这个世界有了蓝色,更加精彩 .....
MathLink 混合编程(四)
x.4 高级主题
在本章的最后一节里,我们打算讨论几个同 MathLink 相关的高级主题。有些内容可能超出了混合编程的范围,然而我们希望通过这对几个主题的讨论加深大家对 MathLink 结构 的认识。
x.4.1 Session 间的通讯
Mathematica 中,几个 Session 之间进行通讯是可能的。这可能出于以下两种考虑:
2 在两个 Session 之间简单地交换数据,取代利用中间文件交换数据的方法;
2 将一个计算任务分配到不同的 Session 上;另外利用操作系统提供的网络通讯功能,我们将能在几台主机上同时进行一个计算任务 不同部分的运算。这也可以借助 Session 间的通讯机制来实现。其实, Mathematica 的这 种 Session 间的通讯机制同样是由 MathLink 来实现的,每一个 Session 都相当于一个 MathLink 外部程序。
下表列出了常用的进行 Session 间通讯的 Mathematica 函数:
Mathematica 函数 说明
LinkCreate["name"] 创建一个 MathLink 链接
LinkConnect["name"] 连接由其他程序创建的 MathLink 链接
LinkClose[link] 关闭 MathLink 连接
LinkWrite[link,expr] 向 MathLink 连接写入表达式
LinkRead[link] 从 MathLink 连接中读出表达式
LinkRead[link,Hold] 读表达时,并立即用 Hold 绑定它
LinkReadyQ[link] 查找是否有准备要从链接读出的数据
表五 Session 间通讯的 Mathematica 函数
下面我们看几个例子:
2 利用端口 8000 建立一条连接
Session A :
In[1]:=link=LinkCreate["8000"]
Out[1]=LinkObject[8000,4,4]
Session B :
In[1]:=link=LinkConnect["8000"]
Out[1]=LinkObject[8000,4,4]
2 发送 / 接收数据
Session A :
In[1]:=LinkWrite[link , 15!]
Session B :
In[1]:=LinkRead[link]
Out[1]=1307674368000
2 发送 / 接收表达式
Session A :
In[1]:=LinkWrite[link , Unevaluated[2 + 2]]
Session B :
In[1]:=LinkRead[link , Hold]
Out[1]=Hold[2 +2]
2 利用未分配的端口建立一条连接
Session HostA :
In[1]:=link=LinkCreate[]
Session HostB :
In[1]:=LinkRead[link]
2 关闭连接
Session HostA :
In[1]:=LinkClose[link]
MathLink 利用操作系统提供的网络通讯服务,并不依赖具体的操作系统和通讯协议,因此可以在两种使用不同操作系统的主机间建立连接。
x.4.2 同前端( front ends )的通讯
Mathematica 内核用 MathLink 与 Mathematica 前端通信。如果从一个前端启动了一个 Mathematica 内核,则可以通过到该前端 MathLink 连接来控制该内核。 全局变量 $ParentLink 指定了特定核心用来输入输出的 MathLink 连接。在一个 Mathematica 会话( Session )中间重置 $ParentLink 有时会很有用,可以有效地改变核心要链接的 前端。 就象 Mathematica 核心,标准的 Mathematica NoteBook 前端操作指定的 MathLink 包。通常如果要从核心控制 Mathematica 前端,那最好使用象 NotebookWrite 和 FrontEndExecute 这样的进程。但在某些时候,直接用 LinkWrite 给前端发送包还方便些。
x.4.3 通过网络运行远程机上的 MathLink 程序
MathLink 允许调用 Mathematica 的外部程序,即使这个程序在远程机上运行。在一般情况 下,可以从远程机的操作系统中直接启动程序,但可以在自己的 Mathematica 会话中用命 令来连接它。
我们用上面创建的外部程序 addtwo 为例子说明建立连接的过程。我们在局域网的主机 Tiger 上运行这个外部程序:
addtwo -linkcreate 8000
这将建立一条 8000 端口的连接。
然后在另外一台主机 Circle 上运行 Mathematica 并打开一个会话,输入: Install[LinkConnect["8000@Tiger"]]
这将加载这条连接对应的外部程序。接下来就可以利用前面提到的方法调用外部程序中的函数了。需要注意的是,使用完毕要卸载这个外部程序,并中断连接。另外,用 mprep 创建的外部程序通常包含设置 MathLink 连接的代码。如果直接从操作系统中启动这样的程序,那么他们就要提示您指定您想要的那种类型的连接。 但如果您的操 作系统支持,您还可以把这信息作为命令行给外部程序。
x.4.4MathLink 的错误及中断处理
自己编写的 MathLink 外部程序在进行数据交换的过程中可能会出现各种各样的错误。当发生错误以后, MathLink 将会转换到非活动的状态。这时,你调用的所有 MathLink 库函数将返回 0 。 我们通常需要在调用完一系列复杂的函数后处理这一过程中可能出现的错误。如果发现 已经产生了一个错误,就必须调用函数 MLClearError() 以重新激活 MathLink 。例如我们在前面的程序中介绍过,在向内核发送数据后需要调用 MLNextPacket 检查是否 返回了计算结果。下面的代码中调用了 MLError 函数以捕捉可能出现的错误,并调用错误处理函数 error 进行处理。
while( (pkt = MLNextPacket( lp), pkt) && pkt != RETURNPKT) {
MLNewPacket( lp);
if (MLError( lp))
error( lp);
}
下面列出三个针对错误处理的函数:
函数 说明
Long MLError(MLINK link) 返回当前错误的代码,如果没有出错就返回 0
Char *MLErrorMessage(MLINK link) 返回描述当前错误的字符串
int MLClearError(MLINK link) 清除当前的错误,返回值表示是否能将 Mathlink 转换到活动状态
表六 MathLink 错误处理函数
发生错误以后,通常需要将这条连接上正在处理的剩余的包或者表达式丢弃。可以调用 MLNewPacket() 达到目的,正如上面的代码中所示的那样:在尚未接收到内核返回的数据 包之前用 MLNewPacket( lp) 将刚才接收到的包丢弃。
最后介绍一下 MathLink 的中断处理:
如果在 Mathematica 执行外部函数的过程中试图中断它的运行, Mathemaica 将会将外部程 序中的全局变量 MLAbort 置为 1 。 MathLink 无法自动从一个外部函数调用中返回。所以, 如果你的外部函需要进行较长时间的运算或处理,最好在程序中的适当位置添加检验 MLAbort 全局变量的代码以识别 Mathematica 的中断请求并适时返回。
--
蓝色,忧郁的颜色
蓝色,我最喜欢的颜色
蓝色,澄静,纯洁的感觉
这个世界有了蓝色,更加精彩 .....
应用软件 Mathematica (1)
---------------------------------------------------------------------
注:为了对 Mathematica 有一定了解的同学系统掌握 Mathematica 的强大 功能,我们把它的一些资料性的东西整理了一下,希望能对大家有所帮助。 ---------------------------------------------------------------------
一、运算符及特殊符号
Line1; 执行 Line ,不显示结果
Line1,line2 顺次执行 Line1 , 2 ,并显示结果
?name 关于系统变量 name 的信息
??name 关于系统变量 name 的全部信息
!command 执行 Dos 命令
n! N 的阶乘
!!filename 显示文件内容
<<filename 读入文件并执行
Expr>> filename 打开文件写
Expr>>>filename 打开文件从文件末写
() 结合率
[] 函数
{} 一个表
<*Math Fun*> 在 c 语言中使用 math 的函数
(*Note*) 程序的注释
#n 第 n 个参数
## 所有参数
rule& 把 rule 作用于后面的式子
% 前一次的输出
%% 倒数第二次的输出
%n 第 n 个输出
var::note 变量 var 的注释
"Astring " 字符串
Context ` 上下文
a+b 加
a-b 减
a*b 或 a b 乘
a/b 除
a^b 乘方
base^^num 以 base 为进位的数
a-b 减
a*b 或 a b 乘
a/b 除
a^b 乘方
base^^num 以 base 为进位的数
lhs&&rhs 且
lhs||rhs 或
!lha 非
++,-- 自加 1 ,自减 1
+=,-=,*=,/= 同 C 语言
>,<,>=,<=,==,!= 逻辑判断(同 c )
lhs=rhs 立即赋值
lhs:=rhs 建立动态赋值
lhs:>rhs 建立替换规则
lhs->rhs 建立替换规则
expr//funname 相当于 filename[expr]
expr/.rule 将规则 rule 应用于 expr
expr//.rule 将规则 rule 不断应用于 expr 知道不变为止
param_ 名为 param 的一个任意表达式(形式变量)
param__ 名为 param 的任意多个任意表达式(形式变量)
--
蓝色,忧郁的颜色
蓝色,我最喜欢的颜色
蓝色,澄静,纯洁的感觉
这个世界有了蓝色,更加精彩 .....
附注 : ( 二 ) 的原始版本 ... 格式比较混乱 标 题 : MathLink 混合编程(二)
发信站 : 同舟共济站 (2002 年 04 月 11 日 16:53:35 星期四 ), 站内信件
x.2 从 Mathematica 中调用外部程序
我们首先来看一下如何在 Mathematica 中装载外部的 MathLink 程序,如何利用程序中的函数 、如何卸载程序;然后通过一个实例重点介绍如何利用 Visual C++ 6.0 创建并生成一个 MathLink 程序;最后简单介绍如何处理各种类型的表达式。
x.2.1 调用的步骤及相应的命令
MathLink 最通常的用法是在 Mathematica 中调用外部 MathLin k 程序。当外部程序建立完成后,我们就可以装载这个程序,并且在当前的 Mathematica 进程中调用程序中的函数。
装载及卸载外部 MathLink 程序的语句是:
? Install["prog"] 装载一个外部 MathLink 程序
? Uninstall[link] 卸载外部程序
例一:我们举一个例子,这个例子中使用的例子可以在 MathLink 开发工具文件夹的 "MathLinkExamples" 中找到源代码,已经编译好的程序 "addtwo.exe" 在 "PrebuiltExamples" 中可以找到。
外部函数的源代码如下:
in addtwo(int i, int j){ return(i+j); // 返回两数之和 }
下面,我们就在 Mathematica 中调用这个函数实现两数相加的运算:
运行 Mathematica ,输入下面的命令: In[1] := link = Install[LinkLaunch[]] 确认后会出 现
一个文件打开对话框选择要加载联接的外部程序,选择编译好的程序 "addtwo.exe" 。这样,外部程序就被加载了,返回的信息是:
Out[1] = LinkObject[C:/MathLink/addtoe.exe,2,2]
其中, "C:/MathLink/addtoe.exe" 是选择的外部程序。然后,就可以使用外部程序中的函数了。
2 显示该外部函数 AddTwo 的用法:
2 调用该函数:
2 如果传递的参数不符合原型函数的要求,将不会得到返回值:
2 卸载该外部程序:
常用的处理外部函数连结的命令主要有:
命令格式 用法说明
Install["prog"] 装载一个外部程序
Unstall[link] 卸载一个外部程序
Links["prog"] 显示同外部程序 "prog" 相关联的活动连结
Links[] 显示所有的活动连结
LinksPatterns[link] 显示特定连结上的求值模式注:
其他处理 MathLink 的 Mathematica 命令请参考帮助文档。
x.2.2 创建 MathLink 外部程序的一般步骤
如果已经在外部文件中定义并且实现了函数原型,那您将要作的就是在源代码中加入一些
MathLink 相关的代码,从而实现从 Mathematica 中调用这个函数并得到函数的返回值。简单 的
情况下,可以使用建立 MathLink 临时文件,通过代码自动生成程序自动生成调用 MathLink 相
关函数的 C 语言源代码。一般地说,建立一个 MathLink 外部程序主要要完成三步工作:
? 建立 MathLink 临时文件;
? 处理临时文件生成的源代码;
? 提取源代码,加入函数实现的主代码并编译生成外部程序。
MathLink 临时文件由一系列指令组成,可以看作是一个代码生成模版。其中的指令规定了外 部
程序如何在 Mathematica 中被调用。临时文件建立了在 Mathematica 中定义函数同外部程序相
应函数的联系关系。临时文件用生成的源代码必须包含在外部文件中,以使外部程序具有 MathLink 兼容性。
下面是 MathLink 模板中包含的元素:
指令 说明:
Begin : 标志对应特定函数的模板的起始:
Function : 外部程序中的函数名:
Pattern : 调用此函数方式的定义:
Arguments : 函数的参数:
ArgumentTypes : 函数参数值类型:
ReturnType : 返回值类型:
End : 标志对应特定函数的模板的结束:
Evaluate : 在函数加载时 Mathematica 的输入计算
注:函数参数值类型及函数返回值类型类表请参看下表。
例一:接着我们给出一个 MathLink 模板的例子:
: Begin :: Function :
addtwo : Pattern :
AddTwo[x_Integer , y_Integer] : Arguments :
{x , y} : ArgumentTypes :
{Integer , Integer} : ReturnType :
Integer : End : :Evaluate: AddTwo::usage = "AddTwo[x, y] gives the sum of two
machine integers x and y."
这个例子定义了例一中 AddTwo 函数的 MathLink 模板,并保存在文件 addtwo.tm 中。编写好模板后将其保存在模板文件( *.tm )中,利用代码生成程序生成源代码。不同平台不同操作系统上对应不同版本的代码生成程序,一般是 mprep 处理程序。 Windows 系统下的用户在 MathLink 的安装目录中可以找到 "MPREP16.EXE" 或者 "MPREP32.EXE" 程序(分别对应 16 位和 32 位处理程序),利用这两个工具可以生成 Windows 平台下 C/C++ 编译器可以识别编译的源代码。利用命令: mprep ad dtwo.tm -o addtwom.c 将会在生成文件 addtwom.c 中包含生成的源代码。需要 注意的是,如果文件 mprep.exe 和 addtwo.tm 不在当前目录下则要在命令中指定其路径。接下来,我们将编写外部程序主源码,并将生成的代码加入到外部程序源文件中,最后编译生成一个 MathLink 兼容的外部程序。
2 在外部程序源文件中包含 MathLink 的标准头文件 #include "mathlink.h"2 加入实际函
数代码 in addtwo(int i, int j){ return(i+j); // 返回两数之和 }2 编写主函数以使 外
部程序接收来自 Mathematica 的调用请求 int main(int argc, char* argv[]){ return
MLMain(argc, argv);}2 最后利用 C/C++ 编译器编译这个外部程序,生成最终可执行程序。
下面一节将专门讨论 利用 Visual C++ 编译器编译外部程序的步骤。
x.2.3 使用 Visual C++ 6.0 创建 MathLink 程序
我们将利用 VC6.0 实现一个 MathLink 程序 ,通
过这个程序说明创建外部程序的详细步骤。我们的程序中包含一个根据经验公式编写的查表计
算程序。我们简化一下这个查表函数,去掉同具体算法相关的细节,把重点放在实现同
MathLink 的接口实现上。该函数 myfun 根据调用时传递的两个参数值 int line 和 int row 在
一个数据表查找对应的数据项,并将查到的数据返回。
下面,我们来看怎样分步实现这个程序:
1. 启动 Microsoft Developer Studio ,进入 Visual C++ 的集成开发环境;
2. 在 File 菜单中选择 New 将会打开一个新建对话框;
3. 在 New 对话框中单击 Projects 标签;
4. 在 Location 文本框中选择工程将被创建的路径;
5. 在 Project Name 文本框中键入工程名 MYFUN ;图二 在 VC 中新建一个工程 MYFUN
6. 在左边的 Project 类型列表中选择 Win32 Application ;
7. 在右下的 Platforms 列表框中确保 Win32 被选中了;完成后如图二所示
8. 单击 OK 确认,将出现 Win32 Application 对话框;
9. 选择 empty project 并单击 Finish 完成,将出现 New Project Information 对话框;
10. 单击 OK 确认;接下来,我们在工程中添加我们的代码文件。我们将需要编辑两个文件,
分别是 myfun.c 和 myfun.tm ,对应主程序源文件和 MathLink 的模板文件。
11. 在 Project 菜单的 Add To Project 子菜单中选择 New … ,将出现新建对话框,选中 Files
标签;
12. 在文件名中键入以下文件名,以空格隔开: "myfun.c" "myfun.tm" "myfuntm.c"
13. 在 Project 菜单的 Add To Project 子菜单中选择 Files ,将出现插入文件对话框。定位 并
插入文件 "ml32i2m.lib" 。注意,这个文件是在安装 MathLink 时拷贝到硬盘上的,我们前面
在介绍安装 MathLink 时提到过。我的 Mathematica 安装在 D:/Program Files/Wolfram
Research ,我在 MathLink 的安装目录中找到了这个文件:
D:/ProgramFiles/WolframResearch/Mathematica/4.0/AddOns/MathLink/DevelopersKits/Windows/CompilerAdditions/mldev32/lib 您可能觉得这个文件隐藏地太深了。没关系,您以将 MathLink 拷贝到一个适当地位置,如 Microsoft Developer Studio 安装目录下。下面,我们
编写主程序以及模板文件:
14. 在 Workspace 中打开文件 myfun.tm ,加入下面的代码:
int myfun P(( int, int));
:Begin::Function:
myfun:Pattern:
MyFun[line_Integer, row_Integer]
:Arguments: { line, row }
:ArgumentTypes: { Integer, Integer }
:ReturnType: Real
:End::
Evaluate: MyFun:
:usage = "MyFun[line, row]
retrun float value in the line/row position."
在 Workspace 中打开文件 myfun.c ,加入下面的代码:
#include "mathlink.h"
extern double myfun( int line, int row);
double myfun( int line, int row)
{ double ret=0.8979*line-row; /* 这里省略了具体处理的代码 */
return ret;}
#if MACINTOSH_MATHLINK
int main( int argc, char* argv[])
{ /* Due to a bug in some standard C libraries that have shipped with * MPW ,
zero is passed to MLMain below. (If you build this program * as an MPW tool,
you can change the zero to argc.) */
argc = argc; /* suppress warning */
return MLMain( 0, argv);
}
#elif WINDOWS_MATHLINK#if __BORLANDC__#pragma argsused#endifint PASCAL
WinMain( HINSTANCE hinstCurrent, HINSTANCE hinstPrevious, LPSTR lpszCmdLine, intnCmdShow)
{
char buff[512];
char FAR * buff_start = buff;
char FAR *argv[32];
char FAR * F AR * argv_end = argv + 32;
hinstPrevious =hinstPrevious; /* suppress warning */
if( !MLInitializeIcon( hinstCurrent,nCmdShow)) return 1;
MLScanString( argv, &argv_end, &lpszCmdLine, &buff_start);
return MLMain( argv_end - argv, argv);
}
#else
int main(argc, argv) int argc;
char* argv[];{
return MLMain(argc, argv);
}
#endif
下面设置编译选项以及编译前的代码生成:
在 Project 菜单中选择 Settings ,将出现 Project Settings 对话框;
在左边的设置列表中展开工程 MYFUN ,并选择文件 myfun.tm ;
单击 Custom Build 标签显示 Custom Build 页;
在 Description 文本框中输入编译的描述,如: "mpreping ..." ;
在 Build Commands 列表框的第一行中输入: mprep myfun.tm -o myfuntm.C
21. 在 Output Files 列表框的第一行中输入: ./myfuntm.c 图三 设置编译选项
22. 单击 Ok 确认在编译之前,我们还要将相应版本的 mathlink.h 拷贝到编译器默认的包含文
件目录下或者该工程文件夹中。选择 Tools 菜单下的 Options … 子菜单,在出现的对话框中的
Directories 标签栏内可以查看和设置编译器的缺省目录。另外,还要将相应的 mprep.exe 文
件拷贝到工程文件夹中。下面可以开始编译了:
23. 选择 Build 菜单中选择 Buile MYFUN.EXE 或者按快捷建 F7 ,将会编译生成一个 MathLink 兼容程序 MYFUN.EXE 。最后,简单介绍一下编译的过程:
2 系统首先执行用户定制的步骤,即在图三所示界面上的设置,通过这一步系统调用 mprep 程序自动生成了 MathLink 相关源代码,存放在文件 myfuntm.c 中。我们可以打开该文件看看到底生成了些什么代码。有兴趣的读者可以参考 Mathematica 的帮助文档理解这些代码。
2 接着是常规地编译,按照顺序分别编译了 myfun.c 、 myfuntm.c 两个文件。
2 最后是连接并生成目标代码。可以看到,除了编译前的 " 执行用户定制的步骤 " ,并没有特殊的地方。我们完全可以通过手工 调用命令 mprep myfun.tm -o myfuntm.c 而取代 " 执行用户定制的步骤 " 。但是这样每次对模板文件 myfun.tm 改动后都要输入该命令重新生成一次,比较麻烦。所以我们建议在编译器参数设置中加入用户定制的步骤以自动生成 myfuntm.c 文件。
x.2.4 处理各种交换数据类型
在上面的例子中,我们的从 Mathematica 传递给外部含数的数据
是整型变量,函数返回的是符点实型变量。事实上,利用 MathLink 可以同外部程序交换任何类型的数据。可以在模板文件中的: ArgumentTypes :和: ReturnType :中指定函数参数以及返回值的数据类型。下表给出了在模板文件中定以的基本数据类型以及 C 语言中相对应的类型: Mathematica 中的定义 说明 C 语言中的定义 Integer 整型变量 intReal 浮点型变量 doubleIntegerList 整型变量列表 int *, longRealList 浮点型变量列表 double *, longString 字符串变量 char *Symbol 符号名变量 chat *Manual 直接调用 MathLink 程序 void 基本数据类型对照表类似我们上面介绍的例子,您可以在模板中的参数类型段使用上面任何一种变量类型 。需要注意的是,如果您使用 IntegerList 或者 RealList 作为变量类型,则必须在外部的 C 函数中包含附加的参数来指定列表的长度。
例如,在模板中您这样定义参数类型:
: ArgumentTypes : {IntergerList, RealList, Interger}
则对应的 C 函数定义可能是:
voidf(int *a, long alen, double *b, long blen, int c);
下面将介绍在 MathLink 及 C 语言程 序间更加灵活地传递数据的方法。
先来看一个例子:模板文件:
:Begin::Function:
otherfun:Pattern: OtherFun[line_Integer, row_Integer]:Argume nts:
{ line, row }:ArgumentTypes: { Integer, Integer }:ReturnType: Manual:End:C 语
言函数: Void otherfun(int ling, int row){ int a[32], k;
for(k=0;k<32;k++){ …… // 根据具体的算法给数组 a 赋值 } k++;
MLPutIntegerList(stdlink, a, k); return;} 我们看到,在模板中定义的函数返回值类型 是
Manual ,即在外部函数中直接调用 MathLink 的函数向 Mathematica 返回数据。这调用的函数 是 MLPutIntegerList ,下面列出其他几个常用的类似的函数,他们在 "mathlink.h" 中都有定义。从函数的名字中可以猜测其意义,这里不再详细介绍了。
MLPutInteger(MLINK link, int i) ;
MLPutReal(MLINK link, double x) ;
int MLPutIntegerList(MLINK link, int *a, long n) ;
int MLPu tRealList(MLINK link, double *a, long n) ;
int MLPutIntegerArray(MLINK
link, int *a, long *dims, char **heads, long d) ; int MLPutRealArray(MLINK link,double *a, long *dims, char **heads, long d) ;
int MLPutFunction(MLINK link, char*s, long n) ;
int MLPutString (MLINK link, char *s) ;
int MLPutSymbol(MLINK link,char *s) ;
上面介绍的是从 C 语言中通过调用 MathLink 函数返回数据给 Mathematica ,如果 要从外部函数中直接通过调用 MathLink 函数得到传递过来的数据,则要用到另外一组函数: int MLGetInteger(MLINK link, int *i) ;
int MLGetReal(MLINK link, double *x) ;
int MLGetIntegerList(MLINK link, i nt **a, long *n) ;
int MLGetRealList(MLINK link,double **a, long *n) ;
int MLGetIntegerArray(MLINK link, int **a, long **dims, char ***heads, long*d) ;
int MLGetRealArray(MLINK link, double **a, long **dims, char***heads, long*d) ;
下面看一个具体的例子:模板文件:
:Begin::Function:
getfun:Pattern: GetFun[a,b,c]:Arguments: { a,b,c}:ArgumentTypes:
{ Integer, Manual }:ReturnType: Real:End:C 语言函数: Void getfun(int
a){ double b,c; MLGeReal(stdlink, &b);MLGeReal (stdlink, &c);return a+b*c; }
在这个函数中,我们利用函数参数传递整型变量 int a ,通过 MathLink 函数 MLGetReal 得到 另外两个浮点型变量 b 和 c 。通过调用这些 MathLink 外部函数,可以很灵活在 Mathematica 以 及外部 C 语言函数间交换数据。事实上,利用 mprep 程序生成的也正是一些调用 MathLink 库的 外部函数的代码,系统利用这些自动生成的代码,建立了 Mathmatica 同外部程序之间的连接。我们可以通过阅读这些代码深刻理解 MathLink 是怎样工作的。如 果读者想了解有关 MathLink 外部函数更详细的资料,可以参考 Mathematica 的帮助文档。
--
蓝色,忧郁的颜色
蓝色,我最喜欢的颜色
蓝色,澄静,纯洁的感觉
这个世界有了蓝色,更加精彩 .....