我想我开始理解如何将用C / C ++编写的函数链接到 Mathematica 。我面临的问题是我不知道如何从我的C包装器向Mathematica发送错误消息。在谷歌搜索后,我发现了MathLink Tutorial。
1.7节给了我一个关于如何发送错误消息的见解,但我得到了奇怪的结果。这是我正在使用的代码。
//File cppFunctions.h
#ifndef CPPFUNCTIONS_H
#define CPPFUNCTIONS_H
class Point {
public:
double x, y;
Point(){ x=y=0.0;}
Point(double a, double b): x(a), y(b) {}
};
class Line {
public:
Point p1, p2;
Line(void) {}
Line(const Point &P, const Point &Q): p1(P), p2(Q) {}
double distanceTo(const Line &M, const double &EPS = 0.000001){
double x21 = p2.x - p1.x; double y21 = p2.y - p1.y;
double x43 = M.p2.x - M.p1.x; double y43 = M.p2.y - M.p1.y;
double x13 = p1.x - M.p1.x; double y13 = p1.y - M.p1.y;
double den = y43*x21 - x43*y21;
if (den*den < EPS) return -INFINITY;
double numL = (x43*y13 - y43*x13)/den;
double numM = (x21*y13 - y21*x13)/den;
if (numM < 0 || numM > 1) return -INFINITY;
return numL;
}
};
#endif
文件cppFunctions.h声明了类Point
和Line
。除了我想在 Mathematica 中使用的函数之外,我已经将这个类剥离到了minium。我想找到从一条线到另一条线的距离。此函数是wireframes in Mathematica中{C}的lineInt
版本。要在 Mathematica 中使用此函数,我们需要一个包装函数,它从 Mathematica 获取输入并将输出发送回 Mathematica 。
//mlwrapper.cpp
#include "mathlink.h"
#include <math.h>
#include "cppFunctions.h"
void ML_GetPoint(Point &P){
long n;
MLCheckFunction(stdlink, "List", &n);
MLGetReal64(stdlink, &P.x);
MLGetReal64(stdlink, &P.y);
}
void ML_GetLine(Line &L){
long n;
MLCheckFunction(stdlink, "List", &n);
ML_GetPoint(L.p1);
ML_GetPoint(L.p2);
}
double LineDistance(void) {
Line L, M;
ML_GetLine(L);
ML_GetLine(M);
return L.distanceTo(M);
}
int main(int argc, char* argv[]) {
return MLMain(argc, argv);
}
我创建了两个辅助函数:ML_GetPoint
和ML_GetLine
,以帮助我从 Mathematica 获取输入。从包含两个列表的列表中获取一行。每个子列表是2个实数(一个点)的列表。要在Mathematica中尝试此功能,我们需要更多文件。
//mlwrapper.tm
double LineDistance P((void));
:Begin:
:Function: LineDistance
:Pattern: LineDistance[L_List, M_List]
:Arguments: {L, M}
:ArgumentTypes: {Manual}
:ReturnType: Real
:End:
:Evaluate: LineDistance::usage = "LineDistance[{{x1,y1}, {x2,y2}}, {{x3,y3}, {x4,y4}}] gives the distance between two lines."
:Evaluate: LineDistance::mlink = "There has been a low-level MathLink error. The message is: `1`"
此文件声明函数LineDistance将手动获取参数并返回实数。最后两行很重要。第一个Evaluate
声明函数的usage
。当?LineDistance
输入 Mathematica 时,它会给出关于该功能的简短消息。另一个Evaluate
是我希望在出现错误时使用的(稍后会详细介绍)。
#Makefile
VERSION=8.0
MLINKDIR = .
SYS = MacOSX-x86-64
CADDSDIR = /Applications/Mathematica.app/SystemFiles/Links/MathLink/DeveloperKit/CompilerAdditions
INCDIR = ${CADDSDIR}
LIBDIR = ${CADDSDIR}
MPREP = ${CADDSDIR}/mprep
RM = rm
CXX = g++
BINARIES = mlwrapper
all : $(BINARIES)
mlwrapper : mlwrappertm.o mlwrapper.o
${CXX} -I${INCDIR} mlwrappertm.o mlwrapper.o -L${LIBDIR} -lMLi3 -lstdc++ -framework Foundation -o $@
.cpp.o :
${CXX} -c -I${INCDIR} $<
mlwrappertm.cpp : mlwrapper.tm
${MPREP} $? -o $@
clean :
@ ${RM} -rf *.o *tm.c mlwrappertm.cpp
最后一个文件是Makefile。此时我们都准备在Mathematica中测试函数。
我之前应该提到我使用的是Mac OS X,我不确定它在Windows上是如何工作的。在mlwrapper.cpp中,main函数需要更多代码,您可以在 Mathematica 提供的示例中找到它们。
在终端我知道这样做:
make > makelog.txt
make clean
这使得可执行文件mlwrapper
。现在我们可以开始使用Mathematica:
SetDirectory[NotebookDirectory[]];
link = Install["mlwrapper"];
?LineDistance
Manipulate[
Grid[{{
Graphics[{
Line[{p1, p2}, VertexColors -> {Red, Red}],
Line[{p3, p4}]
},
PlotRange -> 3, Axes -> True],
LineDistance[{p1, p2}, {p3, p4}]
}}],
{{p1, {-1, 1}}, Locator, Appearance -> "L1"},
{{p2, {2, 1}}, Locator, Appearance -> "L2"},
{{p3, {2, -2}}, Locator, Appearance -> "M1"},
{{p4, {2, 3}}, Locator, Appearance -> "M2"}
我们获得的输出如下:
只要输入正确的参数,一切正常。也就是说,2个列表,每个列表包含2个双打的2个列表。如果你知道怎么请让我知道,也许还有另一种获取输入的方法。如果我们坚持使用这种方法,我们需要的是一种让 Mathematica 用户知道是否有任何错误的方法。一个非常简单的输入是错误的输入。让我们说我输入:
LineDistance[{{0, 0}, {0}}, {{1, -1}, {1, 1}}]
输出为$Failed
。如下:
LineDistance[{{1, -1}, {1, 1}}]
输出为LineDistance[{{1, -1}, {1, 1}}]
。我猜这是因为我们在Pattern
的{{1}}部分描述了该函数接受了两个列表,因为我们只给了一个它与模式不匹配。这是真的吗?
在任何情况下,按照我发现的教程,我们可以修改文件mlwrapper.cpp,如下所示:
.tm
并将以下内容添加到mlwrapper.tm文件的末尾
#include "mathlink.h"
#include <math.h>
#include <string>
#include "cppFunctions.h"
bool ML_Attempt(int func, const char* err_tag){
if (!func) {
char err_msg[100];
sprintf(err_msg, "Message[%s,\"%.76s\"]", err_tag, MLErrorMessage(stdlink));
MLClearError(stdlink); MLNewPacket(stdlink); MLEvaluate(stdlink, err_msg);
MLNextPacket(stdlink); MLNewPacket(stdlink); MLPutSymbol(stdlink, "$Failed");
return false;
}
return true;
}
void ML_GetPoint(Point &P){
long n;
if(!ML_Attempt(MLCheckFunction(stdlink, "List", &n), "LineDistance::mlink2"))return;
if(!ML_Attempt(MLGetReal64(stdlink, &P.x), "LineDistance::mlink3")) return;
if(!ML_Attempt(MLGetReal64(stdlink, &P.y), "LineDistance::mlink4")) return;
}
void ML_GetLine(Line &L){
long n;
if(!ML_Attempt(MLCheckFunction(stdlink, "List", &n), "LineDistance::mlink1"))return;
ML_GetPoint(L.p1);
ML_GetPoint(L.p2);
}
double LineDistance(void) {
Line L, M;
ML_GetLine(L);
ML_GetLine(M);
return L.distanceTo(M);
}
int main(int argc, char* argv[]) {
return MLMain(argc, argv);
}
现在让我们使用make并尝试在Mathematica中犯一些错误。
我发布了一个输出截图,而不是写一切。
请注意我们在重复调用后如何获得不同的错误。似乎在遇到错误后函数在该行继续。如果我不使用函数:Evaluate: LineDistance::mlink1 = "There has been a low-level MathLink error. The message is: `1`"
:Evaluate: LineDistance::mlink2 = "There has been a low-level MathLink error. The message is: `1`"
:Evaluate: LineDistance::mlink3 = "There has been a low-level MathLink error. The message is: `1`"
:Evaluate: LineDistance::mlink4 = "There has been a low-level MathLink error. The message is: `1`"
中的任何其他ML函数,我只使用ML_Attempt
发送错误标记,那么MathLink就会被破坏,我必须重新安装链接。有谁知道如何从C?
根据已经给出的答案和另一个有用的document(第8章),我设法使其发挥作用。目前代码不是那么漂亮,但这让我问下面的问题。是否可以提前终止功能?在常规C程序中,如果遇到错误,我会打印错误消息并使用MLEvaluate
函数。我们可以做类似的事吗?如果我们使用exit
函数,链接将被破坏,我们将不得不重新安装该函数。以功能exit
和ML_GetPoint
为例。如果此处发生错误,则在主函数ML_GetLine
中执行计算的过程没有意义。我们需要清除我们获得的任何错误,向Mathematica发送一条消息,指定错误,暂时退出并等待下一个调用。
答案 0 :(得分:2)
这样的事情通常对我有用:
void putMessage(const char* messageSymbol, const char* messageTag, const char* messageParam)
{
MLNewPacket(stdlink);
MLPutFunction(stdlink, "EvaluatePacket", 1);
MLPutFunction(stdlink, "Message", 2);
MLPutFunction(stdlink, "MessageName", 2);
MLPutSymbol(stdlink, messageSymbol);
MLPutString(stdlink, messageTag);
MLPutString(stdlink, messageParam);
MLFlush(stdlink);
MLNextPacket(stdlink);
MLNewPacket(stdlink);
}
你仍然需要返回一个结果,例如
MLPutSymbol(stdlink, "$Failed");
答案 1 :(得分:2)
作为@ragfield解决方案的替代方案,您可以将函数声明为void
并手动返回结果。以下是基于addTwo
标准示例的示例。这是模板:
void addtwo P(( int, int));
:Begin:
:Function: addtwo
:Pattern: AddTwo[i_Integer, j_Integer]
:Arguments: { i, j }
:ArgumentTypes: { Integer, Integer }
:ReturnType: Manual
:End:
:Evaluate: AddTwo::usage = "AddTwo[x, y] gives the sum of two machine
integers x and y."
:Evaluate: AddTwo::badargs = "Arguments are expected to be positive numbers"
和功能:
void addtwo( int i, int j) {
if(i>0&&j>0){
MLPutInteger(stdlink,i+j);
}else{
MLPutFunction(stdlink,"CompoundExpression",2);
MLPutFunction(stdlink,"Message",1);
MLPutFunction(stdlink,"MessageName",2);
MLPutSymbol(stdlink,"AddTwo");
MLPutString(stdlink,"badargs");
MLPutSymbol(stdlink,"$Failed");
}
}
以下是使用示例:
In[16]:= AddTwo[1,2]
Out[16]= 3
In[17]:= AddTwo[-1,2]
During evaluation of In[17]:= AddTwo::badargs: Arguments are expected
to be positive numbers
Out[17]= $Failed
这是一种更高级别的“高级”方式,这样您就不必明确处理数据包了。
但是,我觉得输入参数的完整错误检查最好通过适当的模式在Mathematica端执行,并为C代码中检测到的一些内部错误保存错误消息的选项(我实际上更进一步返回Mathematica只是错误代码为整数或字符串,让更高级别的mma包装器处理它们并发出消息)。您可以将这些模式放在模板文件中,也可以将MathLink Mathematica函数包装到另一个执行错误检查的函数中。我对Mathlink的经验非常有限,所以我在这里的观点也许并不重要。
编辑
评论中的每个请求(虽然我不确定我是否正确理解了请求):
extern void addeight( void );
extern void addall(void);
static void putErrorMessageAndReturnFailure(const char *fname, const char *msgtag);
void addeight(void)
{
int i,j,k,l,i1,j1,k1,l1;
MLGetInteger(stdlink,&i);
MLGetInteger(stdlink,&j);
MLGetInteger(stdlink,&k);
MLGetInteger(stdlink,&l);
MLGetInteger(stdlink,&i1);
MLGetInteger(stdlink,&j1);
MLGetInteger(stdlink,&k1);
MLGetInteger(stdlink,&l1);
if(i<0||j<0||k<0||l<0||i1<0||j1<0||k1<0||l1<0){
putErrorMessageAndReturnFailure("AddEight","badargs");
}else{
MLPutFunction(stdlink,"List",2);
MLPutFunction(stdlink,"List",2);
MLPutInteger(stdlink,i+i1);
MLPutInteger(stdlink,j+j1);
MLPutFunction(stdlink,"List",2);
MLPutInteger(stdlink,k+k1);
MLPutInteger(stdlink,l+l1);
}
}
void addall(){
int *data, len, i = 0,sum = 0;
if(!MLGetIntegerList(stdlink, &data, &len)){
putErrorMessageAndReturnFailure("AddAll","interr");
return;
}
for(i=0;i<len;i++){
if(data[i]<0){
putErrorMessageAndReturnFailure("AddAll","badargs");
return;
}else{
sum+=data[i];
}
}
MLPutInteger(stdlink,sum);
MLReleaseInteger32List(stdlink, data, len);
}
static void putErrorMessageAndReturnFailure(const char *fname, const char *msgtag){
MLPutFunction(stdlink,"CompoundExpression",2);
MLPutFunction(stdlink,"Message",1);
MLPutFunction(stdlink,"MessageName",2);
MLPutSymbol(stdlink,fname);
MLPutString(stdlink,msgtag);
MLPutSymbol(stdlink,"$Failed");
}
和模板
void addeight P(( ));
:Begin:
:Function: addeight
:Pattern: AddEight[{{i_Integer, j_Integer},{k_Integer,l_Integer}},{{i1_Integer,j1_Integer},{k1_Integer,l1_Integer}}]
:Arguments: { i, j, k ,l, i1,j1,k1,l1 }
:ArgumentTypes: { Manual }
:ReturnType: Manual
:End:
:Evaluate: AddEight::usage = "AddEight[{{i_Integer, j_Integer},{k_Integer,l_Integer}}, {{i1_Integer, j1_Integer},{k1_Integer,l1_Integer}}] gives the sum as a list: {{i+i1,j+j1},{k+k1,l+l1}}."
:Evaluate: AddEight::badargs = "Arguments are expected to be positive numbers"
void addall P(( ));
:Begin:
:Function: addall
:Pattern: AddAll[fst:{{_Integer, _Integer},{_Integer,_Integer}},sec:{{_Integer, _Integer},{_Integer,_Integer}}]
:Arguments: { Flatten[{fst,sec}]}
:ArgumentTypes: { Manual }
:ReturnType: Manual
:End:
:Evaluate: AddAll::usage = "AddAll[{{i_Integer, j_Integer},{k_Integer,l_Integer}},{{i1_Integer, j1_Integer},{k1_Integer,l1_Integer}}] gives the total sum of elemens of the sub-lists."
:Evaluate: AddAll::badargs = "Arguments are expected to be positive numbers"
:Evaluate: AddAll::interr = "Internal error - error getting the data from Mathematica"
示例:
In[8]:= AddEight[{{1,2},{3,4}},{{5,6},{7,8}}]
Out[8]= {{6,8},{10,12}}
In[9]:= AddEight[{{-1,2},{3,4}},{{5,6},{7,8}}]
During evaluation of In[9]:= AddEight::badargs: Arguments are expected to be positive numbers
Out[9]= $Failed
In[10]:= AddAll[{{1,2},{3,4}},{{5,6},{7,8}}]
Out[10]= 36
In[11]:= AddAll[{{-1,2},{3,4}},{{5,6},{7,8}}]
During evaluation of In[11]:= AddAll::badargs: Arguments are expected to be positive numbers
Out[11]= $Failed
答案 2 :(得分:1)
这篇文章适合任何对我编写最终代码感兴趣的人。此代码基于与@Leonid的有用讨论。让我们从实用程序文件开始。
//MLErrors.h
#include <stdarg.h>
#include <vector>
#include <sstream>
#define CCHAR const char*
#define UINT unsigned int
class MLException {
public:
CCHAR sym;
CCHAR tag;
std::vector<std::string> err;
MLException(CCHAR msgSym, CCHAR msgTag, UINT n, ...):
sym(msgSym), tag(msgTag), err(n)
{
std::stringstream ss;
va_list args;
va_start(args, n);
for (UINT i=0; i < n; ++i) {
err[i] = va_arg(args, CCHAR);
if (err[i][0] == '%') {
switch (err[i][1]) {
case 'i':
ss << va_arg(args, int);
break;
case 'd':
ss << va_arg(args, double);
break;
default:
break;
}
err[i] = ss.str();
}
}
va_end(args);
}
};
#undef CCHAR
#undef UINT
void ML_SendMessage(const MLException& e){
if (MLError(stdlink) != MLEOK) MLClearError(stdlink);
MLNewPacket(stdlink);
MLPutFunction(stdlink, "EvaluatePacket", 1);
MLPutFunction(stdlink, "Message", e.err.size()+1);
MLPutFunction(stdlink, "MessageName", 2);
MLPutSymbol(stdlink, e.sym);
MLPutString(stdlink, e.tag);
for (int i=0; i < e.err.size(); ++i) {
MLPutString(stdlink, e.err[i].c_str());
}
MLFlush(stdlink);
MLNextPacket(stdlink);
MLNewPacket(stdlink);
MLPutSymbol(stdlink, "$Failed");
}
此文件包含MLException
类和函数ML_SendMessage
。这是一个简单的解释。类型MLException
的对象包含2个字符串和一个字符串向量。如果e
是MLException
,那么e.sym
必须是有效的 Mathematica 符号,而e.tag
必须是有效标记。我们在模板文件中定义了这个Symbol::tag
。如果Symbol::tag
包含参数,则它们存储在e.err
中。例如,假设您在模板文件中声明了以下内容:
:Evaluate: someSymbol::someTag = "Error, the program encountered: `1`, it needed `2`."
然后,如果有一些原因在C函数中有错误,你可以通过抛出一些消息的异常来离开那里。像这样:
if(ERROR) throw MLException("someSymbol", "someTag", 2, "this", "that");
注意第三个参数是一个整数。这是将在消息中放置的消息数而不是“1”和“2”。这意味着您将在Mathematica中看到的消息是:“错误,程序遇到:这,它需要它。”如果你需要在消息中包含数字,我就这样编写一个字符串后跟一个数字。例如,如果你想写数字100然后写一些其他消息,那么你可以抛出这样的异常:
if(ERROR) throw MLException("someSymbol", "someTag", 2, "%i", 100, "msg");
如果要包含双精度,请改用“%d”。
ML_sendMessage
函数接受异常,清除错误,向 Mathematica 发送消息并放置$Failed
。
这是我的模板文件:
//mlwrapper.tm
:Evaluate: BeginPackage["mlwrapper`"]
:Evaluate: EMPH[a_] := ToString[Style[a, "TI"], StandardForm]
:Evaluate: LINK[url_, label_ : Style["\[RightSkeleton]", "SR"]] := ToString[Hyperlink[label, url], StandardForm]
:Evaluate: LineDistance::usage = "LineDistance["<>EMPH["L"]<>", "<>EMPH["M"]<>"] gives the distance between two lines. "<>LINK["#"]
:Evaluate: mlwrapper::mlink = "There has been a low-level MathLink error. The message is: `1`"
:Evaluate: GetPoint::narg = "A point is a list of 2 numbers. A list of `1` elements was passed instead. "<>LINK["#"]
:Evaluate: GetLine::narg = "A line is a list of 2 points. A list of `1` elements was passed instead. "<>LINK["#"]
:Evaluate: EndPackage[]
:Evaluate: Begin["mlwrapper`Private`"]
void LineDistance P((void));
:Begin:
:Function: LineDistance
:Pattern: LineDistance[L_List, M_List]
:Arguments: {L, M}
:ArgumentTypes: {Manual}
:ReturnType: Manual
:End:
:Evaluate: End[]
我决定把它变成一个包。我还声明了函数EMPH
和LINK
。第一个强调单词,另一个允许我们写超链接。一旦我学会了如何正确记录,我将添加超链接到描述。
现在我们可以用与 Mathematica 相同的方式描述错误:
//mlwrapper.cpp
#include "mathlink.h"
#include "MLErrors.h"
#include <cmath>
#include "cppFunctions.h"
#define MLINKERROR MLException("mlwrapper", "mlink", 1, MLErrorMessage(stdlink))
void ML_GetPoint(Point &P){
long n = 0;
MLCheckFunction(stdlink, "List", &n);
if (n != 2) throw MLException("GetPoint", "narg", 1, "%i", n);
MLGetReal64(stdlink, &P.x);
MLGetReal64(stdlink, &P.y);
if (MLError(stdlink) != MLEOK) throw MLINKERROR;
}
void ML_GetLine(Line &L){
long n = 0;
MLCheckFunction(stdlink, "List", &n);
if (n != 2) throw MLException("GetLine", "narg", 1, "%i", n);
ML_GetPoint(L.p1);
ML_GetPoint(L.p2);
}
void LineDistance(void) {
Line L, M;
try {
ML_GetLine(L);
ML_GetLine(M);
}
catch (MLException& e) {
ML_SendMessage(e);
return;
}
MLPutReal64(stdlink, L.distanceTo(M));
}
int main(int argc, char* argv[]) {
return MLMain(argc, argv);
}
现在,由于我正在创建一个包,我们需要最后一个文件:mlwrapper.m。在此文件中,我们添加以下行:Install["mlwrapper"];
。我们假设可执行文件mlwrapper
位于同一目录中。最后,我展示了结果的屏幕截图:
我编写了最后一条语句,以了解异常的开销。我主要想要异常来处理输入的获取以及基于C / C ++函数的return语句的一些其他错误。无论如何,编写一个包装器无异常并没有比异常更好。
所以你有它。另一个如何从 Mathematica 调用C / C ++函数的例子。
我还要感谢@ alexey-popkov给我写EMPH
和LINK
的想法。这让我很头疼,了解如何格式化我的消息。