Skip to content
Wolfram Mathematica LibraryLink utilities showcase, implementing path coalesce in random walks
C++
Find file
Latest commit 7af583a @pisto fix typos

README.md

This is a simple toolkit that automatizes the boring boilerplate that is needed to write function for Mathematica LibraryLink. The code is in C++11, exceptions aware, and needs g++ 4.8 (or any other compiler that has a fairly complete C++11 support). With some tweaks (remove a thread_local usage, maybe some decltype around) it might compile with older versions of g++.

Structure

Your LibraryLink library should reuse the following files from this repo:

  1. c++11_tools.hpp
    1. metaprogramming stuff
    2. stackguard (automatic cleanup at exception thrown/return)
    3. generic coding tools
  2. mathematica.hpp
    1. main mathematica interaction interface
    2. helper template classes
  3. mathematica.cpp (partially, see comment in the source)
    1. Definition of global variables and functions:
      1. thread_local WolframLibraryData mathematica::mathenv;
      2. WolframLibrary_getVersion(), WolframLibrary_initialize(WolframLibraryData), WolframLibrary_uninitialize(WolframLibraryData)

Building

Compilation process vary based on your OS and your needs, and little it has to do with the actual code. As it will be described, there is a macro that does its best to make a function of the compiled object visible from the outside, and with a proper name/ABI to be loaded from Mathematica. For linux, I compile my program as a relocatable executable: this allows me to invoce the compiled object as a normal executable, but also load it as a .so object and run functions. Hence I suggest compiling with "-fvisibility=hidden -fvisibility-inlines-hidden -fPIC" and link with "-rdynamic -pie" (you will have to provide a main()). The visibility and "-rdynamic" flag serve to select which functions to export (without the former all would be exported, without the latter none). You can cd into the Debug or Realease folder and invoke make to see what exact flags I use.

Exporting functions for LibraryLink

The assumed workflow is that you have a C++ function that does the calculation, returns the result, and you want a wrapper that takes care of translating the arguments/returns for Mathematica (this fits well with the aforementioned executable-kind of build, because you will call this function both from Mathematica and from your main()). To declare a wrapper, you use the mathexport(full_signature) macro. It takes care of declaring your function extern "C", marking it default visible, selecting the proper calling convention, and declaring it noexcept. Right after the macro there should be the body of the wrapper (if you only want the declaration, use mathexport_d). Furthemore, since all the Mathematica functionality is provided as a bunch of function pointers in the WolframLibraryData argument, but you might want to callback Mathematica (e.g. call AbortQ()) from other functions without passing around the argument, a global and thread_local variable should be set as soon as the exported function is executed. This is done through the dummy object struct setmathenv: being an object assures that the global variable is set as long as the execution is within the exported function, or in its called functions. Last, you should wrap all your body with a try/catch clause. A proper catch clause that deals with out of memory errors and any other unexpected exceptions is defined as a macro in mathematica_catch. Note that this uses std::cerr, but <iostream> is not included automatically in mathematica.hpp, so you need to include it by yourself.

Summing everything up, thisis how you define a LibraryLink wrapper function:

mathexport(int mystuff(WolframLibraryData env, mint Argc, MArgument* Args, MArgument Res)){
    setmathenv mathenv(env);
    try{
        /* Do your stuff here*/
        return LIBRARY_NO_ERROR;
    }
    mathematica_catch
}

Argument parsing

First tedious process is reading the arguments passed by Mathematica to the library. My solution is a iostream-like object, struct mathematica::parseargs, which has the following public interface:

struct parseargs{
    parseargs(mint& Argc, MArgument*& Args);
    template<typename T> const parseargs& operator>>(T& o) const;
    operator bool() const;
}

This object is bound at creation to the Argc and Args arguments of the standard LibraryLink function signature. The operator >> has overloads for all the types that can be passed by Mathematica (boolean, integer, real, complex, tensor) and possible translations to C++ types (bool, unsigned int, std::complex<T>...). Every time an argument is extracted with the operator >>, Argc is decreased and Args increased. You can check whether there are arguments left to read with the bool() cast operator (like the normal iostream). The operator >> throws exceptions if there are no arguments left to read or if an unsigned integer is being read, but a negative value has been passed from Mathematica.

Example:

/*Inside a mathexport()*/
parseargs args(Argc, Args);
float a, b;
int c, d;
args>>a>>b>>c>>d;       //Throws and exception if something cannot be read
do_my_stuff(a, b, c,d);

MTensor to and from containers conversion

Second most boring boilerplate is reading/writing a MTensor object. Most of the times the rank of the tensor is known, as well as which containers need to be used to store each level of the tensor. This pseudo-function (which is a bunch of template overloads) use this type information to automatically "assign" lefto-to-right one structure to another:

TOtype[&] MTensor_assign(FROMtype from[, TOtype to]);

FROMtype and TOtype (which is optional) are MTensor, or any nested combination of containers or arrays. The interface that a container must expose to be usable in this function is:

  1. writing (use as to): member function push_back()
  2. reading (use as from): be iterable on a range-based for loop, and have a member function size()

The return is a reference only if the to argument is provided and is not a MTensor. If instead FROMtype is not a MTensor and to is not provided, a new MTensor is allocated, and its memory management is up to the caller.

Example:

/*Inside a mathexport()*/
parseargs args(Argc, Args);
MTensor input;
std::vector<std::array<std::complex<double>>> input_stl;
MTensor_assign(input, input_stl);
//do_my_stuff() returns a std::vector<double>
MArgument_setMTensor(Res, MTensor_assign(do_my_stuff(input_stl)));

Extra utilities

Terminating for Abort[]

You may use mathematica::AbortQ() to check if an abort is in progress: if so, an exception of type struct mathabort is thrown.

Stack cleanup

This code is written with the assumption that exceptions might be thrown. To ensure proper stack unwinding (deletion of resources allocated with operator new, MTensor with MTensor_free()), an auxiliary template class is provided, struct stackguard. You implement your specialization of stackguard with the macro implement_stackguard(type) (which creates a typedef "g" plus the name of your type, "gtype"), and provide a init() and fini() method. For example, to implement a stackguard for MTensor, this is done in mathematica.hpp:

implement_stackguard(MTensor){
    void init(MTensor ptr){}
    void fini(MTensor ptr){ mathematica::mathenv->MTensor_free(ptr); }
};

The init() method is called when the guard is initialized, and the fini() method is called when the guard goes out of scope (the desctructor is called). You can disown a guard with the member function disown(). Move semantics is supported. For generic pointers or dynamically allocated arrays, you can use the readily available gptr<type> and garray<type>.

Complete example

You can run the code in this repo from Mathematica by executing make in the Release folder, and then evaluating the test.nb notebook. It implements the random walk path coalescing described in Phys. Rev. E 68, 040101(R) (2003) (http://pre.aps.org/abstract/PRE/v68/i4/e040101).

Something went wrong with that request. Please try again.