第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中lineInt的C版本.要在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,&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声明了函数的用法.当在Lineheistance输入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,{p3,p4}] }}],{{p1,{-1,1}},Locator,Appearance -> "L1"},{{p2,{2,Appearance -> "L2"},{{p3,-2}},Appearance -> "M1"},{{p4,3}},Appearance -> "M2"}
]
我们获得的输出如下:
只要输入正确的参数,一切正常.也就是说,2个列表,每个列表是2个双打的2个列表.如果你知道怎么请让我知道,也许还有另一种获取输入的方法.如果我们坚持使用这种方法,我们需要的是让Mathematica用户知道是否有任何错误的方法.一个非常简单的输入是错误的输入.让我们说我输入:
LineDistance[{{0,0},{0}},{{1,-1},{1,1}}]
LineDistance[{{1,1}}]
输出是LineDistance [{{1,1}}].我猜这是因为我们在.tm的Pattern部分描述了函数接受两个列表,因为我们只给了一个它与模式不匹配.这是真的?
无论如何,按照我发现的教程,我们可以修改文件mlwrapper.cpp,如下所示:
#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,&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,"LineDistance::mlink1"))return; ML_GetPoint(L.p1); ML_GetPoint(L.p2); } double LineDistance(void) { Line L,argv); }
: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`"
现在让我们使用make并尝试在Mathematica中犯一些错误.
我发布的是什么输出的截图,而不是写一切.
请注意我们在重复调用后如何获得不同的错误.似乎在遇到错误后该函数在该行继续.如果我不使用函数ML_Attempt中的任何其他ML函数,并且我只使用MLEvaluate发送错误标记,那么MathLink就会被破坏,我必须重新安装链接.有谁知道如何从C向Mathematica发送错误消息?
更新:
根据已经给出的答案和另一个有用的document(第8章),我设法使其工作.目前代码不是那么漂亮,但这让我问下面的问题.是否可以提前终止功能?在常规C程序中,如果遇到错误,我会打印错误消息并使用退出功能.我们可以做类似的事吗?如果我们使用exit函数,链接将被破坏,我们将不得不重新安装该函数.以ML_GetPoint和ML_GetLine为例.如果此处发生错误,则在主函数LineDistance中进行计算的过程中没有任何意义.我们需要清除我们获得的任何错误,向Mathematica发送一条消息,指定错误,暂时退出并等待下一个调用.
解决方法
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,2); MLPutInteger(stdlink,i+i1); MLPutInteger(stdlink,j+j1); MLPutFunction(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,2); MLPutFunction(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,l1 } :ArgumentTypes: { Manual } :ReturnType: Manual :End: :Evaluate: AddEight::usage = "AddEight[{{i_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}}] :Arguments: { Flatten[{fst,sec}]} :ArgumentTypes: { Manual } :ReturnType: Manual :End: :Evaluate: AddAll::usage = "AddAll[{{i_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,8}}] During evaluation of In[9]:= AddEight::badargs: Arguments are expected to be positive numbers Out[9]= $Failed In[10]:= AddAll[{{1,8}}] Out[10]= 36 In[11]:= AddAll[{{-1,8}}] During evaluation of In[11]:= AddAll::badargs: Arguments are expected to be positive numbers Out[11]= $Failed