我想我开始理解如何将用 C/C++ 编写的函数链接到数学。我面临的问题是我不知道如何将错误消息从我的 C 包装器发送到 Mathematica。在谷歌搜索后我发现了这个MathLink 教程 http://www.edenwaith.com/development/tutorials/mathlink/ML_Tut.pdf.
第 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
and Line
。除了我想使用的函数之外,我已将此类剥离到最低限度数学。我想找到从一条线到另一条线的距离。这个函数是C版本的lineInt
in Mathematica 中的线框图 https://stackoverflow.com/questions/6353337/mathematica-3d-wire-frames。要使用此功能数学我们需要一个包装函数来获取输入数学并将输出发送回数学.
//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
and ML_GetLine
帮助我获得输入数学。从包含两个列表的列表中获取一行。每个子列表都是 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
被输入到数学。另一个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 中,主函数需要更多代码,您可以在以下提供的示例之一中找到这些代码数学.
在终端我知道这样做:
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 个列表组成的列表。也许还有另一种获取输入的方法,如果您知道如何请告诉我。如果我们坚持这种方法,我们所需要的只是一种让数学用户知道是否有任何错误。一个非常简单的问题就是输入错误。假设我输入这个:
LineDistance[{{0, 0}, {0}}, {{1, -1}, {1, 1}}]
输出是$Failed
。怎么样:
LineDistance[{{1, -1}, {1, 1}}]
输出是LineDistance[{{1, -1}, {1, 1}}]
。我猜发生这种情况是因为我们在Pattern
的部分.tm
该函数接受两个列表,并且由于我们只给出了一个列表,因此它与模式不匹配。这是真的?
无论如何,按照我找到的教程,让我们修改文件 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, "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);
}
并将以下内容添加到 mlwrapper.tm 文件的末尾
: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 函数ML_Attempt
我只使用MLEvaluate
发送错误标签,然后 MathLink 已损坏,我必须重新安装该链接。有谁知道如何发送错误消息到数学 from C?
UPDATE:
基于已经给出的答案和另一个有用的答案document http://library.wolfram.com/infocenter/Books/3710/(第 8 章)我设法让它发挥作用。目前代码不太漂亮,但这让我提出了以下问题。是否可以提前终止函数?在常规 C 程序中,如果遇到错误,我会打印一条错误消息并使用exit
功能。我们可以做类似的事情吗?如果我们使用exit
函数链接将被破坏,我们将不得不重新安装该函数。取函数ML_GetPoint
and ML_GetLine
例如。如果此处发生错误,则在主函数中继续进行计算是没有意义的LineDistance
。我们需要清除所获得的任何错误,向 Mathematica 发送一条消息来指定错误,暂时退出并等待下一次调用。