Mathematica: MathLink error messages

♀尐吖头ヾ 提交于 2019-12-05 04:27:17

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

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

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:

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!