Library Structure and Life Cycle

Wolfram LibraryLink allows dynamic libraries to be directly loaded into the Wolfram Language kernel so that functions in the libraries can be immediately called from the Wolfram Language. You can exchange not only C-like data types such as integers, reals, packed arrays, and strings, but also arbitrary Wolfram Language expressions. In addition, there are useful functions such as sending errors and calling back to the Wolfram Language.

This section describes the structure of a Wolfram Library and how they are written. It also covers the different parts of the life cycle of a library, from initialization through usage to uninitialization.

The first time that a function is loaded from a Wolfram Library with LibraryFunctionLoad, its initialization function is called. This sets up a structure to support callback functions; it can also be used to carry out any initialization specific to the library itself.

Once a library has been loaded, you can call its functions through the LibraryFunction returned by LibraryFunctionLoad. You can also load additional functions.

When the library is no longer required it can be unloaded. This happens when all the functions are unloaded with LibraryFunctionUnload, or when the entire library is unloaded with LibraryUnload. At this point the uninitialize function is called.

Note that unloading a library completely is not supported on all platforms. The uninitialize function will always be called and functions from the library will not be usable from the Wolfram Language. However, only platforms that fully support dynamic library unloading will let you modify the library and then reload it. When this documentation was written, Windows and Mac OS X 10.5 fully supported unloading a library.

Initialization

When a library is loaded for the first time using LibraryFunctionLoad, some important initialization is carried out. This sets up the structure that allows the library function to call back to the Wolfram Language; it also takes care of versioning. The library allows initialization by providing a function called WolframLibrary_initialize. Typically, the first part of the library is similar to that shown below.

#include "WolframLibrary.h"

DLLEXPORT mint WolframLibrary_getVersion( ) {
    return WolframLibraryVersion;
}

DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData) {
    return 0;
}

Here the header "WolframLibrary.h", which sets up various important definitions, is loaded. This is followed by the function WolframLibrary_getVersion, which returns the version of the WolframLibrary header. The Wolfram Language then calls WolframLibrary_initialize, passing in a data structure that matches the version of the WolframLibrary header used to build the library. Since the function structure is adapted for your library, this means that you do not have to recompile your library when it is loaded into a new version of the Wolfram Language. However, you should note that you cannot use a library built with a newer version of the header into an older version of the Wolfram Language. The WolframLibrary_initialize function can contain other initializations that you might need to run functions in the library. If it returns a nonzero value, that will be considered a fatal error and the library file will not be loaded.

If your library is going to use the Wolfram Symbolic Transfer Protocol (WSTP) for communicating with the Wolfram Language, the "mathlink.h" header must be included before the "WolframLibrary.h" header. This is shown below.

#include "wstp.h"

#include "WolframLibrary.h"

Functions, Arguments, and Results

The library provides functions that are called from the Wolfram Language. These functions are named in the call to LibraryFunctionLoad. Because the interface uses C calling conventions, each function has to have the same signature, namely int demoI_I(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res). A sample is shown below.

DLLEXPORT int demo_I_I(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {
    mint I0;
    mint I1;
    I0 = MArgument_getInteger(Args[0]);
    I1 = I0 + 1;
    MArgument_setInteger(Res, I1);
    return LIBRARY_NO_ERROR;
}

MArgument is a typedef for a union of pointers of the supported basic types defined in WolframLibrary.h.

typedef union {
    mbool *boolean;
    mint *integer;
    double *real;
    mcomplex *cmplex;
    MTensor *tensor;
    char **utf8string;
} MArgument;

The function must have the signature int demo_I_I(mint Argc, MArgument *Args, MArgument Res), it must return int, and must take three arguments. The first is a machine integer giving the argument count, the second is an array of MArgument, and the third is an MArgument. These hold the input and the result, respectively. You have to extract the actual value from the argument array and assign the result. Several macros with names starting with MArgument are defined for convenience for this extraction and assignment to MArgument. The returned int value returned is used as an error code: any nonzero return will be considered an error and the evaluation will return a LibraryFunctionError result. More details about error codes are found in the section "Error Result Codes".

More details about how to work with types are found in the section "Type Specification".

In addition, it is typical to add DLLEXPORT; this is a macro defined in WolframLibrary.h. It is needed on Windows where it declares the function as an export from a Windows DLL, but using it will make your library easier to move from one platform to another.

If you want to send arguments or get results from the library function that are not covered by the basic numerical types or an MTensor, you can use a LinkObject for arguments and the result. This will let you send the structure of any Wolfram Language expression to your library and get back any Wolfram Language expression as a result. A sample LinkObject function is shown below. Note that you can work directly with strings without using WSTP.

DLLEXPORT int reverseString( WolframLibraryData libData, WLINK mlp)
{
    int res = LIBRARY_FUNCTION_ERROR;
    long len;
    const char *inStr = NULL;
    char* outStr = NULL;
    
    if ( !WSTestHead( mlp, "List", &len))
        goto retPt;
    if ( len != 1)
        goto retPt;

    if(! WSGetString(mlp, &inStr))
        goto retPt;

    if ( ! WSNewPacket(mlp) )
        goto retPt;

    outStr = reverseStringImpl(inStr);
    if ( !WSPutString( mlp,outStr))
        goto retPt;
    res = LIBRARY_NO_ERROR
retPt:
    if ( inStr != NULL)
        WSReleaseString(mlp, inStr);
    if ( outStr != NULL)
        free((void*)outStr);
    return res;
}

To use WSTP for communicating with the Wolfram Language, the "mathlink.h" header must be included before the "WolframLibrary.h" header.

More information on using WSTP to communicate with the Wolfram Language is found in the section LinkObject Arguments and Result.

Naming Conventions

Note that the name of the function is used to load it into the Wolfram Language. If you compile your library as C++ then you will either have to export the name as a C name or use the C++ modified name in LibraryFunctionLoad. The former can be done by a declaration such as the following.

extern "C" {
    DLLEXPORT int funname(WolframLibraryData libData,
        mint Argc, MArgument *Args, MArgument Res);
}

This makes sure that funname can be used to load the function even though the library is compiled as C++.

Alternatively, you can use a macro defined in WolframLibrary.h to make the code more portable.

EXTERN_C DLLEXPORT int funname(WolframLibraryData libData, 
        mint Argc, MArgument *Args, MArgument Res);

Types

In your library function you can use any types that you want, except for arguments from the Wolfram Language and results that go to the Wolfram Language. These are described below.

mboolBoolean
mintmachine integer
doublemachine double
mcomplexmachine complex
MTensorpacked array
MSparseArraysparse array
MImage2D or 3D image
UTF8StringUTF8 string

There are definitions for mbool, mint, mcomplex, and MTensor in WolframLibrary.h. mint is an integral type that matches the machine integer type in the Wolfram Language; note that this might be 32 or 64 bits.

Callbacks

Often you may need extra functionality that is present in the Wolfram Language. For example, you might want to create a packed array and then work with its elements. This is done via the WolframLibraryData object that is passed in as an argument to the function. In the following example, a packed array is created, and then its elements are filled in.

    T0 = funStruct->MTensor_new(MType_Integer, 1, dims);
    for ( i = 1; i <= I0; i++) {
        libData->MTensorVector_setInteger( T0, i, i*2);
    }

Callback Evaluations in the Wolfram Language

If you want to call the Wolfram Language from your library function to carry out some other task, for example, carrying out some computation or calling some input/output function, you can do this with getWSTP and processWSTP.

The following example shows how you get a WSTP connection and then write an expression inside EvaluatePacket on the link using functions from the WSTP API. When you have finished, you call processWSTP and the Wolfram Language will carry out the evaluation.

    WLINK link = libData->getWSTP(libData);
    WSPutFunction( link, "EvaluatePacket", 1);
    WSPutFunction( link, "Message", 2);
    WSPutFunction( link, "MessageName", 2);
    WSPutSymbol( link, "MyFunction");
    WSPutString( link, "info");
    WSPutString( link, "Message called from within Library function.");
    libData->processWSTP( link);
    pkt = WSNextPacket( link);
    if ( pkt == RETURNPKT) {
        WSNewPacket(link);
    }

The result from the Wolfram Language will follow the standard packet rules for the Wolfram Language; for example, an EvaluatePacket will return a result in ReturnPacket.

Errors

If an error is found in a library function and you want to report this to a user, you can use the callback function Message. For example, the following will cause the message LibraryFunction::rankerror to be emitted. You would have to supply text for the message.

if (libData->MTensor_getRank(T0) != 1) {
    libData->Message("rankerror");
    return LIBRARY_FUNCTION_ERROR;
}

Note that returning LIBRARY_FUNCTION_ERROR from the library will cause a LibraryFunctionError result to be returned.

A more flexible way to send a message would be to use the callback evaluation technique described previously.

Error Result Codes

The result of a library function is expected to be an int and is used to determine if an error took place. If a result of 0 or LIBRARY_NO_ERROR is returned the function is taken to have completed correctly. Alternately, if a different result is returned this indicates an error and the result of the function call is a LibraryFunctionError expression.

There are a number of error codes that you can return. They are listed in the following table.

LIBRARY_NO_ERRORcorrect result from the function (value of 0)
LIBRARY_TYPE_ERRORunexpected type encountered
LIBRARY_RANK_ERRORunexpected rank encountered
LIBRARY_DIMENSION_ERRORinconsistent dimensions encountered
LIBRARY_NUMERICAL_ERRORerror in numerical computation
LIBRARY_MEMORY_ERRORproblem allocating memory
LIBRARY_FUNCTION_ERRORgeneric error from a function

Error codes.

Each nonzero error code leads to a specific type of message being issued when the function call has finished, except for LIBRARY_FUNCTION_ERROR, which issues no message, letting the application making the function call issue its own message.

In the following example a demonstration function is loaded. This function returns its input which lets you see the effect of different codes.

In[1]:=
Click for copyable input
Out[1]=

This returns 6 from the library function which is the value of LIBRARY_FUNCTION_ERROR. The result of the evaluation is a LibraryFunctionError expression, which contains the name of the error code and its numerical value.

In[2]:=
Click for copyable input
Out[2]=

Errors in Callbacks

If an error arises in a callback function it will return an error code.

For example, the MTensor_getInteger callback gets an element from an MTensor of integer type. But, if it is called on an MTensor that does not contain integers or the element does not exist, then an error code is returned.

int err;
...
err = libData->MTensor_getInteger( T0, lens, &res);

If you are certain that the MTensor is of the correct type and has sufficient elements then you do not need to inspect the result.

Uninitialization

When the library is no longer used by the Wolfram Language it will call a function to shut down the library; a sample is shown below. The function receives a WolframLibraryData object and it can use callback functions if required.

DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData) {
    return;
}