Mathematica: MathLink error messages
Asked Answered
N

3

9

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:

Output

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.

Output

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.

Nieshanieto answered 29/6, 2011 at 20:29 Comment(10)
I've been looking at MathLink lately myself, and while it should still work, that tutorial is for Mathematica version 2.Panacea
@rcollyer, I noticed that it is for an older version but I found better explanations than in the current ones. We can see that it is very old since it still uses Disown instead of Release.Nieshanieto
That's true, it is much more thorough in many respects. But, I thought I'd warn you if you hadn't noticed that you will have to do some translation, and that there may be newer functionality which will does the job better.Panacea
Regarding your question in the Update, I don't see any problem. Declare your function as void 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 with exit-s, you will get spaghetti code pretty soon.Semibreve
@Leonid, I understand that you can use return to "exit" the function early. But if you have say: FunA, FunB, FunC and we have that FunA calls FunB and FunC and we encounter an error in FunC then we want to send an error report from FunC and terminate the execution of FunA. If it is with the whole program then I would use exit. If I try to use an exit 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 the exit statement is essentially killing the C process.Nieshanieto
@Nieshanieto Arguably you should almost never use exit. 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 by setjmp and longjmp. 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.Semibreve
I had managed to write C++ programs without using exceptions. I guess it is time for me to look into it. Thanks for the tip Leonid.Nieshanieto
@Nieshanieto Exceptions are a heavy weapon. They pay off for large projects with third-party modules, many function calls and fat stack traces. Using them if you only have a few functions may be an overkill. You may be better off by returning an error status for your internal functions, and analyzing that status in your higher-level caller functions. You can also use assertions in C (by including assert.h), which are often more appropriate than exceptions, particularly to test for faulty logic in your code.Semibreve
@Leonid, I agree. This is one of the reasons I don't use them. I try to make functions for speed, thus the reason I want to use them in Mathematica. I was doing some reading on c++faq and I'm thinking I will need them just to take care of possible errors in the wrapper function. I could after all, write another wrapper on Mathematica that calls the wrapper on C++ which would be slower than the wrapper with the exceptions written in C++.Nieshanieto
@Nieshanieto On general grounds, as long as you only pass pointers or native types and don't copy large structures on the stack, one extra function invocation should not matter (unless it is in some inner loop), so I would not worry about it as far as speed is concerned. Besides, exceptions also induce an overhead, but of course YMMV.Semibreve
S
4

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 on addTwo standard example. Here is the template:

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"

and the function:

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");
    }
}

Here are examples of use:

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

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):

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,"List",2);
            MLPutFunction(stdlink,"List",2);
                MLPutInteger(stdlink,i+i1);
                MLPutInteger(stdlink,j+j1);
            MLPutFunction(stdlink,"List",2);
                MLPutInteger(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,"CompoundExpression",2);
        MLPutFunction(stdlink,"Message",1);
                MLPutFunction(stdlink,"MessageName",2);
                    MLPutSymbol(stdlink,fname);
                    MLPutString(stdlink,msgtag);
        MLPutSymbol(stdlink,"$Failed");
}

and the template

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, j, k ,l, i1,j1,k1,l1 }
:ArgumentTypes:  { Manual }
:ReturnType:     Manual
:End:

:Evaluate: AddEight::usage = "AddEight[{{i_Integer, j_Integer},{k_Integer,l_Integer}}, {{i1_Integer, j1_Integer},{k1_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},{_Integer,_Integer}}]
:Arguments:      { Flatten[{fst,sec}]}
:ArgumentTypes:  { Manual }
:ReturnType:     Manual
:End:

:Evaluate: AddAll::usage = "AddAll[{{i_Integer, j_Integer},{k_Integer,l_Integer}},{{i1_Integer, j1_Integer},{k1_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"

Examples:

In[8]:= AddEight[{{1,2},{3,4}},{{5,6},{7,8}}]
Out[8]= {{6,8},{10,12}}

In[9]:= AddEight[{{-1,2},{3,4}},{{5,6},{7,8}}]
During evaluation of In[9]:= AddEight::badargs: Arguments are expected to be positive numbers

Out[9]= $Failed

In[10]:= AddAll[{{1,2},{3,4}},{{5,6},{7,8}}]
Out[10]= 36

In[11]:= AddAll[{{-1,2},{3,4}},{{5,6},{7,8}}]
During evaluation of In[11]:= AddAll::badargs: Arguments are expected to be positive numbers

Out[11]= $Failed
Semibreve answered 29/6, 2011 at 21:22 Comment(4)
I also thought about creating another wrapper in Mathematica to take care of errors but I'm not too happy about creating another function just to take care of this. I tried using @ragfield's function but it didn't quite work. I also took your code and placed it in his function but I get the same errors. Could you try making the function addEight? so that it can work like this: AddEight[{{1,2},{3,4}},{{5,6},{7,8}}]. I think the problem comes out when I use multiple statements of MLGet.Nieshanieto
@Nieshanieto Just in case you still need it, I added two more functions to illustrate the point.Semibreve
thanks, I think I should avoid gathering input in subfunctions as I showed in my example. Otherwise I won't be able to return early due to an encountered error. Good example by the way. I like your function AddAll in particular. I didn't know I could use Flatten in the Arguments section. I think this will make it a lot easier to gather input.Nieshanieto
@Nieshanieto You can use any Mathematica code in the arguments section. The resulting sequence of arguments will be sent to C. It is very instructive to read the code generated by mprep, and also look at the code for Install (which you can read once you remove its ReadProtected attribute). It then becomes much more clear what is really going on both in the C code and in Mathematica. Thanks for the accept by the way!Semibreve
L
3

Something like this usually works for me:

void putMessage(const char* messageSymbol, const char* messageTag, const char* messageParam)
{
    MLNewPacket(stdlink);
    MLPutFunction(stdlink, "EvaluatePacket", 1);

    MLPutFunction(stdlink, "Message", 2);
        MLPutFunction(stdlink, "MessageName", 2);
            MLPutSymbol(stdlink, messageSymbol);
            MLPutString(stdlink, messageTag);

        MLPutString(stdlink, messageParam);

    MLFlush(stdlink);
    MLNextPacket(stdlink);
    MLNewPacket(stdlink);
}

You'll still have to return a result, e.g.

MLPutSymbol(stdlink, "$Failed");
Leena answered 29/6, 2011 at 20:59 Comment(1)
I'm not sure how this function works. First I changed the return type to Manual. This means that I need to use MLPutReal64 if everything was successful. This seems to work fine if I put my input correctly. How am I supposed to use your function? I tried: if(!MLCheckFunction(stdlink, "List", &n)){putMessage("LineDistance", "mlink2", MLErrorMessage(stdlink));return;}. I wrapped the MLCheckFunctions and the MLGetReal64 functions that way to see if there are any errors. Mathematica now gives me this message: LinkObject::linkd: Unable to communicate with closed link LinkObject.Nieshanieto
N
2

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.


//MLErrors.h
#include <stdarg.h>
#include <vector>
#include <sstream>

#define CCHAR const char*
#define UINT unsigned int
class MLException {
public:
    CCHAR sym;
    CCHAR tag;
    std::vector<std::string> err;
    MLException(CCHAR msgSym, CCHAR msgTag, UINT n, ...): 
    sym(msgSym), tag(msgTag), err(n)
    {
        std::stringstream ss;
        va_list args;
        va_start(args, n);
        for (UINT i=0; i < n; ++i) {
            err[i] = va_arg(args, CCHAR);
            if (err[i][0] == '%') {
                switch (err[i][1]) {
                    case 'i':
                        ss << va_arg(args, int);
                        break;
                    case 'd':
                        ss << va_arg(args, double);
                        break;
                    default:
                        break;
                }
                err[i] = ss.str();
            }
        }
        va_end(args);
    }
};
#undef CCHAR
#undef UINT

void ML_SendMessage(const MLException& e){
    if (MLError(stdlink) != MLEOK) MLClearError(stdlink); 
    MLNewPacket(stdlink); 
    MLPutFunction(stdlink, "EvaluatePacket", 1);
    MLPutFunction(stdlink, "Message", e.err.size()+1);
    MLPutFunction(stdlink, "MessageName", 2);
    MLPutSymbol(stdlink, e.sym);
    MLPutString(stdlink, e.tag);
    for (int i=0; i < e.err.size(); ++i) {
        MLPutString(stdlink, e.err[i].c_str());
    }
    MLFlush(stdlink);
    MLNextPacket(stdlink);
    MLNewPacket(stdlink);
    MLPutSymbol(stdlink, "$Failed");
}

This file contains the MLException class and the function ML_SendMessage. Here is the simple explanation. An object of the type MLException contains 2 strings and a vector of strings. If e is an MLException then e.sym must be a valid Mathematica symbol and e.tag a valid tag. We define this Symbol::tag in the template file. If the Symbol::tag contains parameters then they are stored in e.err. For instance say that you declared the following in the template file:

:Evaluate: someSymbol::someTag = "Error, the program encountered: `1`, it needed `2`."

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:

if(ERROR) throw MLException("someSymbol", "someTag", 2, "this", "that");

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(ERROR) throw MLException("someSymbol", "someTag", 2, "%i", 100, "msg");

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:

//mlwrapper.tm
:Evaluate: BeginPackage["mlwrapper`"]

:Evaluate: EMPH[a_] := ToString[Style[a, "TI"], StandardForm]
:Evaluate: LINK[url_, label_ : Style["\[RightSkeleton]", "SR"]] := ToString[Hyperlink[label, url], StandardForm]
:Evaluate: LineDistance::usage = "LineDistance["<>EMPH["L"]<>", "<>EMPH["M"]<>"] gives the distance between two lines. "<>LINK["#"]

:Evaluate: mlwrapper::mlink = "There has been a low-level MathLink error. The message is: `1`"
:Evaluate: GetPoint::narg = "A point is a list of 2 numbers. A list of `1` elements was passed instead. "<>LINK["#"]
:Evaluate: GetLine::narg = "A line is a list of 2 points. A list of `1` elements was passed instead. "<>LINK["#"]

:Evaluate: EndPackage[]

:Evaluate: Begin["mlwrapper`Private`"]

void LineDistance P((void));
:Begin:
:Function: LineDistance
:Pattern: LineDistance[L_List, M_List]
:Arguments: {L, M}
:ArgumentTypes: {Manual}
:ReturnType: Manual
:End:

:Evaluate: End[]

I decided to make this into a package. I also declared functions EMPH and LINK. 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:

//mlwrapper.cpp
#include "mathlink.h"
#include "MLErrors.h"
#include <cmath>
#include "cppFunctions.h"

#define MLINKERROR MLException("mlwrapper", "mlink", 1, MLErrorMessage(stdlink))

void ML_GetPoint(Point &P){
    long n = 0;
    MLCheckFunction(stdlink, "List", &n);
    if (n != 2) throw MLException("GetPoint", "narg", 1, "%i", n);
    MLGetReal64(stdlink, &P.x);
    MLGetReal64(stdlink, &P.y);
    if (MLError(stdlink) != MLEOK) throw MLINKERROR;
}
void ML_GetLine(Line &L){
    long n = 0;
    MLCheckFunction(stdlink, "List", &n);
    if (n != 2) throw MLException("GetLine", "narg", 1, "%i", n);
    ML_GetPoint(L.p1);
    ML_GetPoint(L.p2);
}
void LineDistance(void) {
    Line L, M;
    try {
        ML_GetLine(L);
        ML_GetLine(M);
    }
    catch (MLException& e) {
        ML_SendMessage(e);
        return;
    }
    MLPutReal64(stdlink, L.distanceTo(M));
}
int main(int argc, char* argv[]) {
    return MLMain(argc, argv);
}

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 executable mlwrapper is in the same directory. Finally, I show a screenshot of the results:

Output

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 and LINK. It was giving me a headache finding out how to format my messages.

Nieshanieto answered 1/7, 2011 at 2:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.