Mathematica:MathLink 错误消息
我想我开始了解如何将 C/C++ 编写的函数链接到 Mathematica。我面临的问题是我不知道如何将错误消息从我的 C 包装器发送到 Mathematica。在谷歌搜索后,我发现了这个MathLink教程。
第 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 中使用的函数之外,我已将此类剥离到最低限度。我想找到从一条线到另一条线的距离。此函数是 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, "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
声明函数的用法
。当 ?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 中,主函数需要更多代码,您可以在 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}}]
。我猜测发生这种情况是因为我们在 .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, "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 就会损坏,并且我有重新安装链接。有谁知道如何从 C 向 Mathematica 发送错误消息?
更新:
基于已给出的答案和另一个有用的文档(第8章)我设法让它发挥作用。目前代码不太漂亮,但这让我提出了以下问题。是否可以提前终止函数?在常规 C 程序中,如果遇到错误,我会打印一条错误消息并使用 exit
函数。我们可以做类似的事情吗?如果我们使用 exit
函数,链接将被破坏,我们将不得不重新安装该函数。以函数 ML_GetPoint
和 ML_GetLine
为例。如果此处发生错误,则在主函数 LineDistance
中继续进行计算是没有意义的。我们需要清除所获得的任何错误,向 Mathematica 发送一条消息来指定错误,暂时退出并等待下一次调用。
I think I am starting to understand how to link functions written in C/C++ to Mathematica. The problem I'm facing is that I don't know how to send error messages from my C wrapper to Mathematica. After searching in google I found this MathLink Tutorial.
Section 1.7 gave me an insight as to how to send error messages but I am getting weird results. Here is the code I am working with.
//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
The file cppFunctions.h declares the classes Point
and Line
. I have stripped this class to the bare minium except for the function that I want to use in Mathematica. I want to find the distance from one line to another. This function is the C version of lineInt
in wireframes in Mathematica. To use this function in Mathematica we need a wrapper function that obtains the input from Mathematica and sends the output back to 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);
}
I created two helper functions: ML_GetPoint
and ML_GetLine
to help me obtain the input from Mathematica. A line is obtained from a list containing two list. Each sublist is a list of 2 real numbers (a point). To try this function in Mathematica we need to more files.
//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`"
This file states that the function LineDistance will obtain the arguments manually and that it will return a real number. The last two lines are important. The first Evaluate
declares the usage
of the function. It gives a brief message about the function when ?LineDistance
is entered into Mathematica. The other Evaluate
is the one I wish to use whenever there is an error (more on this later).
#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} lt;
mlwrappertm.cpp : mlwrapper.tm
${MPREP} $? -o $@
clean :
@ ${RM} -rf *.o *tm.c mlwrappertm.cpp
Last file is the Makefile. At this point we are all set to test the function in Mathematica.
I should have mentioned before that I'm using Mac OS X, I'm not sure how this will work on Windows. In the mlwrapper.cpp the main function needs a lot more code which you can find in one of the examples provided by Mathematica.
In the terminal I know do this:
make > makelog.txt
make clean
This make the executable mlwrapper
. Now we can start using 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"}
]
The output we obtain is the following:
Everything works fine as long as you enter the correct arguments. That is, 2 lists, each one being a list of 2 lists of 2 doubles. Maybe there is another way of obtaining the inputs, if you know how to please let me know. If we stick to this method all we need is a way of letting the Mathematica user know if there are any errors. A very simple one is entering the incorrect input. Lets say I enter this:
LineDistance[{{0, 0}, {0}}, {{1, -1}, {1, 1}}]
The output is $Failed
. How about the following:
LineDistance[{{1, -1}, {1, 1}}]
The output is LineDistance[{{1, -1}, {1, 1}}]
. I'm guessing this happens because we described in Pattern
section of the .tm
that the function accepts two lists and since we only gave one it doesn't match the pattern. Is this true?
In any case, following the tutorial I found lets modify the file mlwrapper.cpp as follows:
#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);
}
And add the following to the end of the mlwrapper.tm file
: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`"
Now lets use make and try to make some mistakes in Mathematica.
I'm posting a screenshot of what output instead of writing everything.
Notice how we get different errors after we repeat the call. It seems that the function continues at the line after the error was encountered. If I don't use any of the other ML functions like in the function ML_Attempt
and I only use the MLEvaluate
to send the error tag then the MathLink is broken and I have to re-install the link. Does anyone know how to send error messages to Mathematica from C?
UPDATE:
Based on the answers that have been given and another useful document (Chapter 8) I managed to make it work. The code isn't so pretty at the moment but this made me ask the following question. Is it possible to terminate a function earlier? In a regular C program if I encounter an error I would print an error message and use the exit
function. Can we do something similar? If we use the exit
function the link will be broken and we will have to re-install the function. Take the functions ML_GetPoint
and ML_GetLine
for example. If an error occurred here then there is no point on procedding doing the calculations in the main function LineDistance
. We need to clear whatever error we have obtained, send a message to Mathematica specifying the error, quit for now and wait for the next call.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
作为 @ragfield 解决方案的替代方案,您可以将函数声明为
void
并手动返回结果。这是一个基于addTwo
标准示例的示例。这是模板:和函数:
这是使用示例:
这是一种更“高级”的方法,这样您就不必显式处理数据包。
但是,我认为最好通过适当的模式在 Mathematica 端执行输入参数的完整错误检查,并为 C 代码中检测到的某些内部错误保存错误消息的选项(实际上我更进一步并返回 Mathematica只是错误代码作为整数或字符串,并让更高级别的 mma 包装器处理它们并发出消息)。您可以将这些模式放入模板文件中,也可以将 MathLink Mathematica 函数包装到另一个将执行错误检查的函数中。不过,我对 Mathlink 的经验非常有限,所以我在这里的意见也许不重要。
编辑
评论中的每个请求(尽管我不确定我是否正确理解了该请求):
以及模板
示例:
As an alternative to the solution by @ragfield, you may declare your function as
void
and return the result manually. Here is an example based onaddTwo
standard example. Here is the template:and the function:
Here are examples of use:
This is a little more "high level" way to do it, in this way you don't have to deal with packets explicitly.
However, I feel that the full error-checking of the input arguments better be performed on the Mathematica side through appropriate patterns, and the option of error messages saved for some internal errors detected in your C code (I actually go further and return to Mathematica just error codes as integers or strings, and let higher-level mma wrappers deal with them and issue messages). You can place those patterns in your template file, or you can wrap your MathLink Mathematica function into another function that will perform the error-checking. I have a very limited experience with Mathlink though, so my opinion here should not perhaps count much.
EDIT
Per request in the comment (although I wasn't sure that I understood the request correctly):
and the template
Examples:
这样的事情通常对我有用:
你仍然需要返回结果,例如
Something like this usually works for me:
You'll still have to return a result, e.g.
这篇文章适合任何对我如何编写最终代码感兴趣的人。该代码基于与@Leonid 的有益讨论。让我们从实用程序文件开始。
该文件包含
MLException
类和函数ML_SendMessage
。这是简单的解释。MLException
类型的对象包含 2 个字符串和一个字符串向量。如果e
是MLException
,则e.sym
必须是有效的 Mathematica 符号且e.tag
一个有效的标签。我们在模板文件中定义这个Symbol::tag
。如果Symbol::tag
包含参数,那么它们将存储在e.err
中。例如,假设您在模板文件中声明了以下内容:那么,如果 C 函数由于某些原因出现错误,您可以通过抛出带有某些消息的异常来退出该错误。像这样:
注意第三个参数是一个整数。这是将放置的消息数,而不是消息中的“1”和“2”。这意味着您将在 Mathematica 中看到的消息是:“错误,程序遇到:这个,它需要那个。”如果您需要在消息中包含数字,我会编写一个字符串,后跟一个数字。例如,如果您想写数字 100,然后写一些其他消息,那么您可以像这样抛出异常:
如果您想包含双精度数,则使用“%d”代替。
ML_sendMessage
函数接收异常,清除错误,向 Mathematica 发送消息并放入$Failed
。这是我的模板文件:
我决定将其放入一个包中。我还声明了函数
EMPH
和LINK
。第一个强调文字,另一个允许我们编写超链接。一旦我学会了如何正确记录,我就会在描述中添加超链接。现在我们可以像在 Mathematica 中一样描述错误:
现在,由于我正在制作一个包,所以我们需要最后一个文件:mlwrapper.m。在此文件中,我们添加以下行:
Install["mlwrapper"];
。当然,我们假设可执行文件mlwrapper
位于同一目录中。最后,我展示了结果的屏幕截图:我编写了最后一条语句来了解异常的开销。我主要希望异常能够处理输入的获取,也许还有一些基于 C/C++ 函数的返回语句的其他错误。无论如何,编写没有异常的包装器并不比编写有异常的包装器好多少。
这样你就得到了。如何从 Mathematica 调用 C/C++ 函数的另一个示例。
我还要感谢 @alexey-popkov 给了我编写
EMPH
和LINK
的想法。如何格式化我的消息让我很头疼。This post is for anybody with interest in how I wrote my final code. This code is based on the helpful discussions with @Leonid. Lets start with a utility file.
This file contains the
MLException
class and the functionML_SendMessage
. Here is the simple explanation. An object of the typeMLException
contains 2 strings and a vector of strings. Ife
is anMLException
thene.sym
must be a valid Mathematica symbol ande.tag
a valid tag. We define thisSymbol::tag
in the template file. If theSymbol::tag
contains parameters then they are stored ine.err
. For instance say that you declared the following in the template file:Then if some for reason there is an error in the C function you can get out of there by throwing an exception with some message. Like this:
Notice how the 3rd argument is an integer. This is the number of messages that will be placed instead of the "1" and "2" in the message. This means that the message that you will see in Mathematica is: "Error, the program encountered: this, it needed that." If you need to include numbers in the message I made it so that you write a string followed by a number. For instance, if you want to write the number 100 and then some other message then you can throw the exception like this:
If you want to include a double then use "%d" instead.
The
ML_sendMessage
function takes in the exception, clears the errors, sends a message to Mathematica and puts$Failed
.Here is my template file:
I decided to make this into a package. I also declared functions
EMPH
andLINK
. The first one emphasizes words and the other one allows us to write hyperlinks. Once I learn how to properly document I will adding hyperlinks to the descriptions.Now we can describe errors in the same way we would in Mathematica:
Now, since I'm making a package we need one last file: mlwrapper.m. In this file we add the this line:
Install["mlwrapper"];
. We are assuming of course that the executablemlwrapper
is in the same directory. Finally, I show a screenshot of the results:I wrote the last statement to have an idea of the overhead of the exceptions. I mainly want the exceptions to handle the acquisition of input and maybe some other errors based on return statements from the C/C++ function. In any case, writing a wrapper without exception didn't do much better than with exceptions.
So there you have it. Another example of how to call C/C++ functions from Mathematica.
I would also like to thank @alexey-popkov for giving me the idea to write
EMPH
andLINK
. It was giving me a headache finding out how to format my messages.