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.
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:
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.Something like this usually works for me:
You'll still have to return a result, e.g.