Examples

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.

A number of sample Wolfram Libraries are included with the documentation. These have a number of short functions that demonstrate various aspects of calling libraries from the Wolfram Language.

Using the Examples

The documentation contains the source for the sample libraries. You will need access to a C compiler to build the libraries, and you might find the CCompilerDriver package useful.

This shows that the demo example can be found for your platform.

This loads a function from the demo example library.

This calls the function.

Source

The source for the examples is found in the LibraryLink paclet. You can find this by evaluating the following input.

demo.csample of basic examples
demo_shared.csample of basic examples of shared passing
demo_error.csample of error catching
demo_string.csample of using string arguments and results
demo_LinkObject.csample of using LinkObject as an argument and result specification
demo_managed.cxxsample of using CreateManagedLibraryExpression
demo_callback.csample of calling back a CompiledFunction from a library
demo_sparse.csample of using SparseArray
demo_image.cxxsample of using Image
demo_numericarray.cxxsample of using NumericArray and ByteArray

Library examples source files.

demo

The demo example contains many functions; here is an example of using it.

demo_shared

The demo_shared example has an example of sharing a packed array with a library function. The following loads a number of functions.

This creates a packed array and loads it into the library. Since shared memory passing was used, the array can be used in other function calls.

This gets the 10 th element, which is 10.

This unloads the array; after this the array cannot be used any more.

This shows the source of some of the functions.

DLLEXPORT int loadArray(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {

    tensor = MArgument_getMTensor(Args[0]);
    MArgument_setInteger(Res, 0);
    return LIBRARY_NO_ERROR;
}

DLLEXPORT int getElementVector(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {
    mint pos;
    double value;
    
    pos = MArgument_getInteger(Args[0]);
    value = libData->MTensor_getReal( tensor, pos);
    
    MArgument_setReal(Res, value);
    return LIBRARY_NO_ERROR;
}

DLLEXPORT int unloadArray(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {

    libData->MTensor_disown( tensor);
    MArgument_setInteger(Res, 0);
    return LIBRARY_NO_ERROR;
}

demo_error

The demo_error example has examples of calling functions that trigger errors. The following loads a function from the library.

This is the source of the errordemo1 function. It takes an MTensor argument and then tries to get the real data.

DLLEXPORT int errordemo1(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {
    MTensor T0, T1;
    mint I0, I1, res;
    mint pos[2];
    double *data;

    T0 = MArgument_getMTensor(Args[0]);
    I0 = MArgument_getInteger(Args[1]);
    
    data = libData->MTensor_getRealData(T0);
    MArgument_setReal(Res, data[I0]);
    return LIBRARY_NO_ERROR;
}

The example takes an integer MTensor. The function calls MTensor_getRealData which results in an error and returns a LibraryFunctionError expression.

demo_string

The demo_string example shows some ways that you can use string arguments and results with the required memory management. The following loads a function that does a simple shift cipher for an ASCII string.

This is the source of the encoding function. The arguments are the string to encode and the integer shift to apply.

DLLEXPORT int encodeString(WolframLibraryData libData,
        mint Argc, MArgument *Args, MArgument Res)
{
    mint i = 0, shift;
    
    if (string)
        libData->UTF8String_disown(string);

    string = MArgument_getUTF8String(Args[0]);
    shift = MArgument_getInteger(Args[1]);

    /* Find shift mod 127 so we only
     deal with positive numbers below */
    shift = shift % 127;
    if (shift < 0)
        shift += 127;

    shift -= 1;
        
    while (string[i]) {
        mint c = (mint) string[i];
        /* Error for non ASCII string */
        if (c & 128) return LIBRARY_FUNCTION_ERROR;
        c = ((c + shift) % 127) + 1;
        string[i++] = (char) c;
    }
    MArgument_setUTF8String(Res, string);
    return LIBRARY_NO_ERROR;
}

Here is an example.

Note that the string reference is stored in a variable with scope outside of the function. This reference is disowned when either the function is called again (and a new string is referenced) or when the library is unloaded (in the function WolframLibrary_uninitialize). Since the string argument memory is owned entirely by the library functions, the encoding can be done in place in that memory, saving the need to allocate another string.

demo_LinkObject

The demo_WSTP example shows using LinkObject as an argument and result specification. The following loads a function from the library.

This is the source of the reverseString function. It takes a WSLINK argument and uses the Wolfram Symbolic Transfer Protocol (WSTP) API to read the arguments that come in a list. After it has generated the result, this is written onto the link.

DLLEXPORT int reverseString( WolframLibraryData libData, WSLINK 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 = 0;
retPt:
    if ( inStr != NULL)
        WSReleaseString(mlp, inStr);
    if ( outStr != NULL)
        free( (void*) outStr);
    return res;
}

Here a string is passed in. The result is read from the link and displayed.

demo_managed

The demo_managed example shows how you can use managed library expressions to keep separate instances of a class or objects associated with Wolfram Language expressions.

The functions in the example implement a very simple (and not very random or particularly efficiently implemented) linear congruential generator that allows multiple instances with different states (or even parameters) simultaneously.

A key part of the code is in the initialization and unitialization functions where registerLibraryExpressionManager and unregisterLibraryExpressionManager are used.

/* Initialize Library */
EXTERN_C DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData)
{
    return (*libData->registerLibraryExpressionManager)("LCG", manage_instance);
}

/* Uninitialize Library */
EXTERN_C DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData)
{
    int err = (*libData->unregisterLibraryExpressionManager)("LCG");
}

The code in the file demo_managed.cxx uses C++ only for the hashmap class. This is a convenient way of mapping IDs to instances. The function manage_instance is defined to add the ID to the hashmap when mode is 0 (when CreateManagedLibraryExpression is used) and to free the MTensor and erase the ID from the hashmap when mode is 1.

DLLEXPORT void manage_instance(WolframLibraryData libData, mbool mode, mint id)
{
    if (mode == 0) {
        MTensor *T = new(MTensor);
        map[id] = T;
        *T = 0;
    } else {
        MTensor *T = map[id];
        if (T != 0) {
            if (*T != 0) (*libData->MTensor_free)(*T);
            map.erase(id);
        }
    }
}

The following loads the library (the registration of the manager with name "LCG" is done when the library is first loaded) and defines several LibraryFunctions that manipulate instances.

The following makes several definitions to set up an expression type with head LCG that will be used as a handle to an instance.

Finally, this defines the "random" number generator.

This sets up an instance (the parameters come from Numerical Recipes (1992)), tests it, and generates a number from it.

This sets up another instance and generates two numbers from it. Note that the ID is unique.

This shows all the instances with their states.

After generating one more number, the first and second instances have identical states.

The following sets up a third instance, but being careful to be sure that the assignment is the only reference to the instance expression and use it to generate a matrix.

The following releases the second instance.

Unsetting the value of g2 takes away all references to the third instance, and the manage_instance function is automatically called, removing the instance from the hashmap.

When the library is unloaded, the remaining instances will be removed so g is no longer a managed library expression.

demo_callback

The demo_callback example shows how you can call back from a library to a CompiledFunction in the Wolfram Language.

A important part of the code is in the initialization and unitialization functions where registerLibraryCallbackManager and unregisterLibraryCallbackManager are used.

/* Initialize Library */
DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData)
{
    call_id = 0;
    call_nargs = 0;
    return (*libData->registerLibraryCallbackManager)("demo", manage_callback);
}

/* Uninitialize Library */
DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData)
{
    (*libData->unregisterLibraryCallbackManager)("demo");
}

The callback manager function, manage_callback, is very simple in this example, allowing only one connected function at a time. When the function is called, if there is already a positive ID (all IDs generated by the system will be positive), then the function associated with that ID is released and the new ID is stored. With more elaborate code to store multiple IDs, you could have multiple functions connected at once.

DLLEXPORT mbool manage_callback(WolframLibraryData libData, mint id, MTensor argtypes)
{
    mint i;
    mint *dims;
    mint *typerank;
    if (call_id) {
        (*libData->releaseLibraryCallbackFunction)(call_id);
        call_id = 0;
        free(cbArgs);
        free(tdata);
    }
    call_id = id;
    dims = (*libData->MTensor_getDimensions)(argtypes);
    call_nargs = dims[0] - 1;
    if (call_nargs == 0) {
        call_id = 0;
        return False;
    }
    typerank = (*libData->MTensor_getIntegerData)(argtypes);
    /* Check that the arguments and result (thus i <= call_nargs loop control) are scalar mreal */
    for (i = 0; i <= call_nargs; i++) {
        /* Each row is {type, rank} */
        if ((typerank[0] != MType_Real) || (typerank[1] != 0)) {
            call_id = 0;
            call_nargs = 0;
            return False;
        }
        typerank += 2;
    }
    cbArgs = (MArgument *) malloc((call_nargs + 1)*sizeof(MArgument));
    tdata = (mreal **) malloc(call_nargs*sizeof(mreal *));
    return True;
}

The following loads the library (the registration of the manager with name "demo_callback_manager" is done when the library is first loaded) and defines a LibraryFunction that will call the callback function for each element of an array of type mreal.

The following compiles the sine function and connects the CompiledFunction to the library.

This calls the LibraryFunction on an array of random reals.

The result is the same as evaluating Sin directly.

To make a comparison of the timing for using the callback, a function that directly uses the standard C sin() function was defined.

The values are the same.

Of course, using the Sin function in the Wolfram Language can be quite a bit faster yet, since it may use parallel evaluation.

In this case there may be small differences, because the parallel evaluation uses functions different from the standard C library.

If you compile the function to C using CompilationTarget->"C", the overhead is quite a bit smaller.

The mechanism still works even when the CompiledFunction needs to use the Wolfram Language evaluator.

However, the function in this case can be changed without recompiling.

Note the management function rejects the connection if the CompiledFunction results and arguments are not all scalar reals, and so ConnectLibraryCallbackFunction returns False.

Now trying to apply the callback will give an error.

The code allows for multiple arguments. Here is an example that iterates the logistic map 1000 times.

To use this, a LibraryFunction overload that uses two arguments is needed.

demo_sparse

The demo_sparse example shows how you can use MSparseArray arguments and results with SparseArray objects in the Wolfram Language.

The function sparse_properties from the demo enables an exploration of how the compressed sparse row (CSR) data structure works. The following loads this function from the library.

This is the source for the sparse_properties functions. The arguments are a string to identify the desired property and an MSparseArray, allowed to be of any type and rank. The use of the "Constant" passing is appropriate since the function is only using read access of the MSparseArray argument.

DLLEXPORT int sparse_properties( WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) 
{
    int err = LIBRARY_NO_ERROR;
    char *what;
    mint *data;
    MSparseArray S;
    MTensor *T, Tres = 0;
    WolframSparseLibrary_Functions sparseFuns = libData->sparseLibraryFunctions;

    if (Argc != 2) return LIBRARY_FUNCTION_ERROR;
        
    what = MArgument_getUTF8String(Args[0]);
    S = MArgument_getMSparseArray(Args[1]);

    if (!strcmp(what, "ImplicitValue")) {
        T = (*(sparseFuns->MSparseArray_getImplicitValue))(S);
    } else if (!strcmp(what, "ExplicitValues")) {
        T = (*(sparseFuns->MSparseArray_getExplicitValues))(S);
    } else if (!strcmp(what, "RowPointers")) {
        T = (*(sparseFuns->MSparseArray_getRowPointers))(S);
    } else if (!strcmp(what, "ColumnIndices")) {
        T = (*(sparseFuns->MSparseArray_getColumnIndices))(S);
    } else if (!strcmp(what, "ExplicitPositions")) {
        err = (*(sparseFuns->MSparseArray_getExplicitPositions))(S, &Tres);
    } else if (!strcmp(what, "Normal")) {
        err = (*(sparseFuns->MSparseArray_toMTensor))(S, &Tres);
    } else {
        err = LIBRARY_FUNCTION_ERROR;
    }
    if (err) return err;
    if (!Tres) (*(libData->MTensor_clone))(*T, &Tres);

    MArgument_setMTensor(Res, Tres);
    return err;
}

One thing to note in the source is that the MTensor references returned for the CSR data by MSparseArray_getImplicitValue, MSparseArray_getImplicitValue, MSparseArray_getColumnIndices, and MSparseArray_getRowPointers belong to the MSparseArray, so to return them, it is necessary to make a copy so that the MTensors in the MSparseArray data do not get unintentionally freed.

Below are a few examples that show some of the properties for different sparse arrays.

This makes a table of the CSR data structures and also shows the explicit positions and the normal array.

When either the implicit value or any of the values have machine precision, all of the values get coerced in the coercion stage of converting from SparseArray to MSparseArray.

Using Normal in the Wolfram Language on the SparseArray with mixed type keeps the exact values, but is not a packed array.

A sparse vector is represented in the CSR as a 1-row matrix.

A rank-4 array has three column indices per entry.

This loads a function that allows you to change the values of a SparseArray in place.

This is the source of the sparse_modify_values function.

DLLEXPORT int sparse_modify_values( WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res)
{
    char *what;
    int err = 0;
    mbool resparsify;
    mint i, nz;
    mreal *t, *v;
    MSparseArray S = 0, Sout = 0;
    MTensor T = 0, *Vp = 0;
    WolframSparseLibrary_Functions sparseFuns = libData->sparseLibraryFunctions;

    if (Argc != 2) return LIBRARY_FUNCTION_ERROR;

    S = MArgument_getMSparseArray(Args[0]);
    Vp = (*sparseFuns->MSparseArray_getExplicitValues)(S);
    if ((*libData->MTensor_getType)(*Vp) != MType_Real) return LIBRARY_TYPE_ERROR;
    nz = (*libData->MTensor_getFlattenedLength)(*Vp);
    v = (*libData->MTensor_getRealData)(*Vp);

    T = MArgument_getMTensor(Args[1]);
    if ((*libData->MTensor_getType)(T) != MType_Real) return LIBRARY_TYPE_ERROR;
    if ((*libData->MTensor_getFlattenedLength)(T) != nz) return LIBRARY_DIMENSION_ERROR;
    t = (*libData->MTensor_getRealData)(T);

    for (i = 0; i < nz; i++) v[i] = t[i];

    /* Recompute explicit positions */
    err = (*sparseFuns->MSparseArray_resetImplicitValue)(S, NULL, &Sout);

    (*sparseFuns->MSparseArray_disown)(S);

    if (!err)
        MArgument_setMSparseArray(Res, Sout);

    return err;
}

The sparse array returned is created by recomputing the explicit positions. This is roughly equivalent to what happens if you use SparseArray[s], where s is a SparseArray. Note that to modify the values in place, the data is copied into the existing MTensor for the values that are owned by the MSparseArray. For the SparseArray in the kernel to be affected, the passing needs to be "Shared".

Because one of the new values was zero, that position no longer needs to be represented explicitly, so the structure for s1 is more compact.

demo_image

The demo_image example shows how you can use MImage arguments and results with Image and Image3D objects in the Wolfram Language.

The function color_negate from the demo shows how to obtain the negative of Image or Image3D objects in which all colors have been negated. The following loads this function from the library.

This is the source for the color_negate function. The argument is an MImage to be negated.

This code goes through all pixels present in an image and negates them, which is the fastest implementation. However, this fails to correctly negate images with alpha channels since the alpha channel is also negated.


template <typename T> static T maxValue() {
return -1; // ERROR
}

template <> char maxValue<char>() { return 1; }

template <> raw_t_ubit8 maxValue<raw_t_ubit8>() { return 255; }

template <> raw_t_ubit16 maxValue<raw_t_ubit16>() { return 65535; }

template <> raw_t_real32 maxValue<raw_t_real32>() { return 1.0f; }

template <> raw_t_real64 maxValue<raw_t_real64>() { return 1.0; }

template <typename T>
static void icolor_negate(void *out0, const void *in0, mint length) {
mint ii;
T *out = reinterpret_cast<T *>(out0);
const T *in = reinterpret_cast<const T *>(in0);
for (ii = 0; ii < length; ii++) {
out[ii] = maxValue<T>() - in[ii];
}
}

/* Negate image colors */
EXTERN_C DLLEXPORT int color_negate(WolframLibraryData libData, mint Argc,
MArgument *Args, MArgument res) {
mint length;
MImage image_in, image_out = 0;
void *data_in, *data_out;
int err = LIBRARY_FUNCTION_ERROR;
imagedata_t type;
WolframImageLibrary_Functions imgFuns = libData->imageLibraryFunctions;

if (Argc < 1) {
return err;
}

image_in = MArgument_getMImage(Args[0]);

err = imgFuns->MImage_clone(image_in, &image_out);
if (err)
return err;

type = imgFuns->MImage_getDataType(image_in);
length = imgFuns->MImage_getFlattenedLength(image_in);

data_in = imgFuns->MImage_getRawData(image_in);
data_out = imgFuns->MImage_getRawData(image_out);
if (data_in == NULL || data_out == NULL)
goto cleanup;

switch (type) {
case MImage_Type_Bit:
icolor_negate<char>(data_out, data_in, length);
break;
case MImage_Type_Bit8:
icolor_negate<raw_t_ubit8>(data_out, data_in, length);
break;
case MImage_Type_Bit16:
icolor_negate<raw_t_ubit16>(data_out, data_in, length);
break;
case MImage_Type_Real32:
icolor_negate<raw_t_real32>(data_out, data_in, length);
break;
case MImage_Type_Real:
icolor_negate<raw_t_real64>(data_out, data_in, length);
break;
default:
goto cleanup;
}

MArgument_setMImage(res, image_out);
return err;

cleanup:
imgFuns->MImage_free(image_out);
return err;
}

Below are a few examples that show how to negate colors in images.

Another example shows how to convert an RGB image to grayscale. This loads the function rgb_to_gray.

This is the source of the rgb_to_gray function.

template <typename T>
static void irgb_to_gray(void *out0, const void *in0, mint rows, mint cols,
mbool alphaQ) {
mint row;
mint col;
T r, g, b;

T *out = reinterpret_cast<T *>(out0);
const T *in = reinterpret_cast<const T *>(in0);

if (alphaQ) {
for (row = 0; row < rows; row++) {
for (col = 0; col < cols; col++) {
mint idx = row * cols + col;
r = in[4 * idx];
g = in[4 * idx + 1];
b = in[4 * idx + 2];
out[2 * idx] = (T)(.299 * r + .587 * g + .114 * b);
out[2 * idx + 1] = in[4 * idx + 3];
}
}
} else {
for (row = 0; row < rows; row++) {
for (col = 0; col < cols; col++) {
mint idx = row * cols + col;
r = in[3 * idx];
g = in[3 * idx + 1];
b = in[3 * idx + 2];
out[idx] = (T)(.299 * r + .587 * g + .114 * b);
}
}
}
}

/* Convert RGB image to grayscale */
EXTERN_C DLLEXPORT int rgb_to_gray(WolframLibraryData libData, mint Argc,
MArgument *Args, MArgument res) {
mint rows, columns;
mbool alphaQ;
int err = 0;
imagedata_t type;
MImage image_in, image_out;
void *data_in, *data_out;
WolframImageLibrary_Functions imgFuns = libData->imageLibraryFunctions;

if (Argc < 1) {
return LIBRARY_FUNCTION_ERROR;
}

image_in = MArgument_getMImage(Args[0]);
if (imgFuns->MImage_getColorSpace(image_in) != MImage_CS_RGB)
return LIBRARY_FUNCTION_ERROR;

/*This function accepts only 2D images, but can be easily extended to work
* with Image3D.*/
if (imgFuns->MImage_getRank(image_in) == 3)
return LIBRARY_FUNCTION_ERROR;

type = imgFuns->MImage_getDataType(image_in);
rows = imgFuns->MImage_getRowCount(image_in);
columns = imgFuns->MImage_getColumnCount(image_in);
alphaQ = imgFuns->MImage_alphaChannelQ(image_in);

err = imgFuns->MImage_new2D(columns, rows, alphaQ ? 2 : 1, type,
MImage_CS_Gray, True, &image_out);
if (err)
return LIBRARY_FUNCTION_ERROR;

data_in = imgFuns->MImage_getRawData(image_in);
data_out = imgFuns->MImage_getRawData(image_out);
if (data_in == NULL || data_out == NULL)
return LIBRARY_FUNCTION_ERROR;

switch (type) {
case MImage_Type_Bit:
// RGB binary images are not allowed
imgFuns->MImage_free(image_out);
return LIBRARY_FUNCTION_ERROR;
case MImage_Type_Bit8:
irgb_to_gray<raw_t_ubit8>(data_out, data_in, rows, columns, alphaQ);
break;
case MImage_Type_Bit16:
irgb_to_gray<raw_t_ubit16>(data_out, data_in, rows, columns, alphaQ);
break;
case MImage_Type_Real32:
irgb_to_gray<raw_t_real32>(data_out, data_in, rows, columns, alphaQ);
break;
case MImage_Type_Real:
irgb_to_gray<raw_t_real64>(data_out, data_in, rows, columns, alphaQ);
break;
default:
imgFuns->MImage_free(image_out);
return LIBRARY_FUNCTION_ERROR;
}
MArgument_setMImage(res, image_out);
return LIBRARY_NO_ERROR;
}

Notice that this function accepts only 2D images but can be easily extended to work with Image3D.

Helper functions such as MImage_getByte and MImage_setByte can be used to avoid index arithmetic in C. These functions use the same numbering scheme as the Wolfram Language parts, i.e. the first element is 1.

demo_numericarray

The demo_numericarray example shows how you can use MNumericArray arguments and results with NumericArray and ByteArray objects in the Wolfram Language.

The function numericArrayReverse from the demo shows how to reverse elements in a one-dimensional NumericArray object. The following loads this function from the library.

This is the source for the numericArrayReverse function. The argument is an MNumericArray to be reversed. This code goes through all elements in an input array and reverses their order.

template <typename T>
static void tplNumericArrayReverse(void *out0, const void *in0, mint length) {
    T *out = reinterpret_cast<T *>(out0);
    const T *in = reinterpret_cast<const T *>(in0);

    for (mint i = 0; i < length; i++) {
        out[length - i - 1] = in[i];
    }
}

/* Reverses elements in a one-dimensional NumericArray */
EXTERN_C DLLEXPORT int numericArrayReverse(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument res) {
    mint length;
    MNumericArray na_in = NULL, na_out = NULL;
    void *data_in = NULL, *data_out = NULL;
    int err = LIBRARY_FUNCTION_ERROR;
    int rank = 0;
    numericarray_data_t type = MNumericArray_Type_Undef;
    WolframNumericArrayLibrary_Functions naFuns = libData->numericarrayLibraryFunctions;

    if (Argc < 1) {
        return err;
    }

    na_in = MArgument_getMNumericArray(Args[0]);

    err = naFuns->MNumericArray_clone(na_in, &na_out);
    if (err) {
        return err;
    }
    rank = naFuns->MNumericArray_getRank(na_in);
if(rank != 1) {
        return err;
    }    
    type = naFuns->MNumericArray_getType(na_in);
    length = naFuns->MNumericArray_getFlattenedLength(na_in);
    data_in = naFuns->MNumericArray_getData(na_in);
    data_out = naFuns->MNumericArray_getData(na_out);
    if (data_in == NULL || data_out == NULL) {
        goto cleanup;
    }

    switch (type) {
    case MNumericArray_Type_Bit8:
        tplNumericArrayReverse<std::int8_t>(data_out, data_in, length);
        break;
    case MNumericArray_Type_UBit8:
        tplNumericArrayReverse<std::uint8_t>(data_out, data_in, length);
        break;
    case MNumericArray_Type_Bit16:
        tplNumericArrayReverse<std::int16_t>(data_out, data_in, length);
        break;
    case MNumericArray_Type_UBit16:
        tplNumericArrayReverse<std::uint16_t>(data_out, data_in, length);
        break;
    case MNumericArray_Type_Bit32:
        tplNumericArrayReverse<std::int32_t>(data_out, data_in, length);
        break;
    case MNumericArray_Type_UBit32:
        tplNumericArrayReverse<std::uint32_t>(data_out, data_in, length);
        break;
    case MNumericArray_Type_Bit64:
        tplNumericArrayReverse<std::int64_t>(data_out, data_in, length);
        break;
    case MNumericArray_Type_UBit64:
        tplNumericArrayReverse<std::uint64_t>(data_out, data_in, length);
        break;
    case MNumericArray_Type_Real32:
        tplNumericArrayReverse<float>(data_out, data_in, length);
        break;
    case MNumericArray_Type_Real64:
        tplNumericArrayReverse<double>(data_out, data_in, length);
        break;
    case MNumericArray_Type_Complex_Real32:
        tplNumericArrayReverse<std::complex<float>>(data_out, data_in, length);
        break;
    case MNumericArray_Type_Complex_Real64:
        tplNumericArrayReverse<std::complex<double>>(data_out, data_in, length);
        break;
    default:
        goto cleanup;
    }

    MArgument_setMNumericArray(res, na_out);
    return err;

cleanup:
    naFuns->MNumericArray_free(na_out);
    return err;
}

Below is an example that shows how to reverse elements in a NumericArray.

Another example shows how to compute the complex conjugate of elements stored in a NumericArray. This loads the function numericArrayComplexConjugate.

This is the source of the numericArrayComplexConjugate function.

template <typename T>
static void tplNumericArrayComplexConjugate(void *inout0, mint length) {
    T *inout = reinterpret_cast<T *>(inout0);
    
    for (mint i = 0; i < length; i++) {
        inout[i] = std::conj(inout[i]);
    }
}

/* Computes the complex conjugate of each element in a NumericArray.
NumericArrays of non-complex types are converted to MNumericArray_Type_Complex_Real64 */
EXTERN_C DLLEXPORT int numericArrayComplexConjugate(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument res) {
    mint length;
    MNumericArray na_in = NULL, na_out = NULL;
    void *data_out = NULL;
    int err = LIBRARY_FUNCTION_ERROR;
    numericarray_data_t type = MNumericArray_Type_Undef;
    WolframNumericArrayLibrary_Functions naFuns = libData->numericarrayLibraryFunctions;

    if (Argc < 1) {
        return err;
    }

    na_in = MArgument_getMNumericArray(Args[0]);
    type = naFuns->MNumericArray_getType(na_in);
    if(type != MNumericArray_Type_Complex_Real32 && type != MNumericArray_Type_Complex_Real64) {
        err = naFuns->MNumericArray_convertType(&na_out, na_in, MNumericArray_Type_Complex_Real64, MNumericArray_Convert_Coerce, 0);    
        if(na_out == NULL) {
            return err;
        }
    }
    else {
        err = naFuns->MNumericArray_clone(na_in, &na_out);
        if (err) {
            return err;
        }
        length = naFuns->MNumericArray_getFlattenedLength(na_out);
        data_out = naFuns->MNumericArray_getData(na_out);
        if (data_out == NULL) {
            goto cleanup;
        }

        switch (type) {
        case MNumericArray_Type_Complex_Real32:
            tplNumericArrayComplexConjugate<std::complex<float>>(data_out, length);
            break;
        case MNumericArray_Type_Complex_Real64:
            tplNumericArrayComplexConjugate<std::complex<double>>(data_out, length);
            break;
        default:
            goto cleanup;
        }
    }

    MArgument_setMNumericArray(res, na_out);
    return LIBRARY_NO_ERROR;

cleanup:
    naFuns->MNumericArray_free(na_out);
    return err;
}

Notice that this function accepts all types of numeric arrays. Numeric arrays of non-complex types are converted to "ComplexReal64" type.

The function readBytesFromFile from the demo shows how to read data from a file and store it in a ByteArray object. The following loads this function from the library.

This is the source for the readBytesFromFile function. The argument is a file name passed as "UTF8String". This code reads all bytes from the specified file and returns them as ByteArray.

static mint readBytes(char const* filename, std::uint8_t** bytes) {
    std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
    if (ifs) {
        std::ifstream::pos_type pos = ifs.tellg();
        *bytes = new std::uint8_t[pos];
        ifs.seekg(0, std::ios::beg);
        ifs.read(reinterpret_cast<char*>(*bytes), pos);
        return pos;
    }
    return 0;
}

/* Reads data from a file and returns it as a ByteArray */
EXTERN_C DLLEXPORT int readBytesFromFile(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument res) {
    mint length = 0;
    char *filename = NULL;
    MNumericArray barray = NULL;
    std::uint8_t *barray_data = NULL;
    std::uint8_t *bytes = NULL;
    int err = LIBRARY_FUNCTION_ERROR;
    WolframNumericArrayLibrary_Functions naFuns = libData->numericarrayLibraryFunctions;

    filename = MArgument_getUTF8String(Args[0]);
    length = readBytes(filename, &bytes);
    if (length > 0) {
        err = naFuns->MNumericArray_new(MNumericArray_Type_UBit8, 1, &length, &barray);
        if (err != 0) {
            goto cleanup;
        }
        barray_data = static_cast<std::uint8_t*>(naFuns->MNumericArray_getData(barray));
        std::memcpy(barray_data, bytes, length);
    }
    else {
        goto cleanup;
    }

cleanup:
    if (err == LIBRARY_NO_ERROR) {
        MArgument_setMNumericArray(res, barray);
    }
    delete[] bytes;
    libData->UTF8String_disown(filename);
    return err;
}

Below is an example that shows how to read bytes from a file.