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} $<
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.
Disown
instead ofRelease
. – Nieshanietovoid
with manual return type, and return from it as early as you like (in C). You can factor out the function that forms an error message(s) and call it at several return points in your function. By the way,exit
in C better be used sparingly. If you intersperse many non-top-level functions withexit
-s, you will get spaghetti code pretty soon. – Semibreveexit
. If I try to use anexit
statement in my C code then when I call the function from Mathematica I get a message saying that it could not communicate with closed link LinkObject. This means that theexit
statement is essentially killing the C process. – Nieshanietoexit
. In languages like Java which have good support for exceptions, you may throw an exception, and catch it at a higher level. I am less familiar with C++, but you should be able to use exceptions there as well. In C, you can pass error flags, and chain them (I use preprocessor macros to simplify this process), or you can imitate exceptions bysetjmp
andlongjmp
. In any case, you should give your top-level function a chance to resolve the situation and recover from the error, or handle it somehow, not just exit. – Semibreveassert.h
), which are often more appropriate than exceptions, particularly to test for faulty logic in your code. – Semibreve