LibraryLink Utilities

Rafał Chojna, WRI

LibraryLink

LibraryLink

LibraryLink

LibraryLink meets modern C++

LibraryLink meets modern C++

LibraryLink Utilities

LibraryLink Utilities

Data containers in LibraryLink

MNumericArray

?NumericArray

NumericArray[RandomInteger[{-1000,1000},{1000,1000}],"Integer64"]

ByteCount[%]

ByteCount@NumericArray[RandomInteger[{-1000,1000},{1000,1000}],"Integer16"]

Supported types:

MNumericArray is a C type corresponding to NumericArray.

MTensor

MTensor is a C representation of PackedArray

Predecessor of NumericArrary, limited to only 3 data types: Integer, Real, Complex

Data containers in LibraryLink

MImage

C type corresponding to Image and Image3D expressions

i=LinearGradientImage[{Blue,Yellow,Red}]

ImageType[%]

Information[i]

MSparseArray

C type corresponding to SparseArray

SparseArray[Table[{2^i,3^i+i}->1,{i,10}]]

ByteCount[%]

LibraryLink Containers - API

Container types (MImage, MNumericArray) are pointers to structures.

WolframLibrary provides structures with function pointers that operate on each container type:

typedef struct st_WolframNumericArrayLibrary_Functions {

// MNumericArray management
	errcode_t  (*MNumericArray_new)(const numericarray_data_t, const mint, mint const*, MNumericArray*);
	void (*MNumericArray_free)(MNumericArray);
	errcode_t  (*MNumericArray_clone)(const MNumericArray, MNumericArray*);
	void (*MNumericArray_disown)(MNumericArray);
	void (*MNumericArray_disownAll)(MNumericArray);
	mint (*MNumericArray_shareCount)(const MNumericArray);

// MNumericArray metadata access
	mint (*MNumericArray_getRank)(const MNumericArray);
	mint (*MNumericArray_getFlattenedLength)(const MNumericArray);
	mint const* (*MNumericArray_getDimensions)(const MNumericArray);
	numericarray_data_t  (*MNumericArray_getType)(const MNumericArray);
	
// MNumericArray data access
	void* 	  (*MNumericArray_getData)(MNumericArray);
	errcode_t (*MNumericArray_convertType)(MNumericArray*, const MNumericArray, numericarray_data_t);

} *WolframNumericArrayLibrary_Functions;

errcode_t is just an int, usually 0 for “no error” and non-zero value otherwise

LLU containers

Proper classes with inheritance hierarchy and RAII

LLU containers

Easily constructed from other containers or ranges

std::vector<mint> v {3, 5, 7, 9};

LLU::Tensor<mint> t1 {v};

LLU::Tensor<mint> t2 {std::begin(v), std::end(v), {2, 2}};

// t1 = {3, 5, 7, 9}, t2 = {{3, 5}, {7, 9}}

LLU containers

Similar interface to STL containers: begin(), end(), operator[], ::value_type, etc.

template<typename T>
auto MeanValue(const NumericArray<T>& array) {
	
	auto total = std::accumulate(array.begin(), array.end(), T{});

	return total / array.size();
}

Error handling - LibraryLink

A single integer per library function.

8 predefined error codes

enum {
    LIBRARY_NO_ERROR = 0,
    LIBRARY_TYPE_ERROR,
    LIBRARY_RANK_ERROR,
    LIBRARY_DIMENSION_ERROR,
    LIBRARY_NUMERICAL_ERROR,
    LIBRARY_MEMORY_ERROR,
    LIBRARY_FUNCTION_ERROR,
    LIBRARY_VERSION_ERROR
};

The following loads a function that does a simple shift cipher for a string:

encode = LibraryFunctionLoad["demo_string", "encodeString", {String, Integer}, String];

encode["abcdef",2]

encode["aąbcćdeę",-2]

Hard to tell what went wrong.

Error handling- LLU

Dedicated class ErrorManager for exception-based error handling

Libraries may register custom exceptions:

ErrorManager::registerPacletErrors({
  {"InvalidCharacterError", "Input string `str` must contain only letters from the English alphabet."},
  {"NegativeShiftError", "Negative shift `shift` is not supported."},
});

Exceptions are thrown like this:

auto shift = mngr.getInteger<mint>(1);
if (shift < 0) {
	LLU::ErrorManager::throwException("NegativeShiftError", shift);
}

Let’s see it in action:

encodeLLU=WTCDemo`Private`SafeLibraryFunction["EncodeString",{String,Integer},String];

encodeLLU["aąbcćdeę",-2]

encodeLLU["aąbcćdeę",2]

encodeLLU["abcdef",2]

WSTP (MathLink) support - LibraryLink

int myFunctionName(WolframLibraryData libData, MLINK mlp);

strReverse=LibraryFunctionLoad["demo_LinkObject","reverseString",LinkObject, LinkObject];

strReverse["LibraryLink"]

WSTP (MathLink) support - LibraryLink

DLLEXPORT int reverseString(WolframLibraryData libData, MLINK mlp) {
	int res = LIBRARY_FUNCTION_ERROR;
	long len;
	const char *inStr = NULL;
	char* outStr = NULL;
	
	if(!MLTestHead( mlp, "List", &len)) 
		goto retPt;
	if(len != 1) 
		goto retPt;
	if(!MLGetString(mlp, &inStr))
		goto retPt;
	if(!MLNewPacket(mlp)) 
		goto retPt;
	outStr = reverseStringImpl(inStr);
	if(!MLPutString( mlp,outStr)) 
		goto retPt;
	res = 0;
retPt: 
	if(inStr != NULL)
		MLReleaseString(mlp, inStr);
	if(outStr != NULL)
		free((void*)outStr);
	return res;
}

WSTP (MathLink) support - LLU

MLStream - MLINK wrapper with overloaded operator<< and operator>>

Built-in support for complex C++ types

std::map<std::string, std::map<int, std::vector<double>>> myNestedMap {
	{"Hello", {{1, {11., 12.}}, {7, {71.,72.,73.}}},
	{"World", {{0, {1.2, 3.4}},
};

myMLStream << myNestedMap;

will be received in Wolfram Language as:

<|"Hello"-><|1->{11.,12.},7->{71.,72.,73.}|>,"World"-><|0->{1.2,3.4}|>|>

Easily extendable to custom classes

struct Color {
    double red;
    double green;
    double blue;
};

MLStream& operator<<(MLStream& ms, const Color& c) {
    return ms << ML::Function("RGBColor", 3) << c.red << c.green << c.blue;
}

myMLStream << Color { 0., 0., 1.};

will be received in Wolfram Language as

A number of predefined classes to facilitate reading common expressions from the link, e.g. ML::List, ML::Association, ML::Symbol.

Logging

3 levels of log messages: Error, Warning and Debug. Issuing messages is a no-op unless compiled with appropriate flags.

Log messages are automatically transfered to Wolfram Language and, by default, printed to the notebook:

EXTERN_C int LogDemo(WolframLibraryData, mint, MArgument*, MArgument) {
	LLU_DEBUG("Library function entered with ", Argc, " arguments.");
	auto err = LLErrorCode::NoError;
	try {
		LLU::MArgumentManager mngr(libData, Argc, Args, Res);
		auto index = mngr.getInteger<mint>(0);
		if (index >= Argc) {
			LLU_WARNING("Index ", index, " is too big for the number of arguments: ", Argc, ". Changing to ", Argc - 1);
			index = Argc - 1;
		}
		auto value = mngr.getInteger<mint>(index);
		mngr.setInteger(value);
	} catch (const LibraryLinkError& e) {
		LLU_ERROR("Caught LLU exception ", e.what(), ": ", e.debug());
		err = e.which();
	}
	return err;
}

LogDemo=WTCDemo`LoadLibraryFunction["LogDemo",{Integer,Integer,Integer,Integer,Integer},Integer];

Running LogDemo function in the notebook will give:

LogDemo[1,2,3,4,5]

LogDemo[6,2,3,4,5]

LogDemo[-1,2,3,4,5]

Log message style and contents are fully customizable:

WTCDemo`Private`LLU`Logger`FormattedLog=WTCDemo`Private`LLU`Logger`LogToRow;

LogDemo[1,2,3,4,5]

LogDemo[6,2,3,4,5]

Print logs to Messages window:

WTCDemo`Private`LLU`Logger`PrintLogFunctionSelector=WTCDemo`Private`LLU`Logger`PrintLogToMessagesWindow;

LogDemo[-1,2,3,4,5]

LogDemo[6,2,3,4,5]

WTCDemo`Private`LLU`Logger`PrintLogFunctionSelector=WTCDemo`Private`LLU`Logger`PrintLogToNotebook;

Log messages can be filtered in Wolfram Language:

WTCDemo`Private`LLU`Logger`LogFilterSelector:=WTCDemo`Private`LLU`Logger`FilterByLevel[StringMatchQ["warning",IgnoreCase->True]];

LogDemo[6,2,3,4,5]

WTCDemo`Private`LLU`Logger`LogFilterSelector=WTCDemo`Private`LLU`Logger`FilterAcceptAll;

Managed Library Expressions

Managed Library Expressions

Managed Library Expressions

Managed Library Expressions

Managed Library Expressions

Managed Library Expressions

Managed Library Expressions

Managed Library Expressions

Define a C++ class whose objects are to be managed from Wolfram Language:

class MyExpression {
public:
	MyExpression(mint myID, std::string text) : id {myID}, text {std::move(text)} {
		LLU_DEBUG("MyExpression[", id, "] created.");
	}
	virtual ~MyExpression() {
		LLU_DEBUG("MyExpression[", id, "] is dying now.");
	}
	const std::string& getText() const {
		return text;
	}
	virtual void setText(std::string newText) {
		text = std::move(newText);
	}
protected:
	mint id;
private:
	std::string text;
};

Managed Library Expressions

Create a managed expression Store for MyExpression:

LLU::ManagedExpressionStore<MyExpression> MyExpressionStore;

Wrap MyExpression constructor in a library function:

LIBRARY_LINK_FUNCTION(OpenManagedMyExpression) {
	LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 
	
	auto id = mngr.getInteger<mint>(0);
	auto text = mngr.getString(1);
	MyExpressionStore.createInstance(id, id, text);
	
	return LLU::ErrorCode::NoError;
}

Managed Library Expressions

$CreateNewMyExpression=WTCDemo`LoadLibraryFunction["OpenManagedMyExpression",{LLU`Managed[MyExpression],String},"Void"];

LLU`Constructor[MyExpression]=$CreateNewMyExpression;

myExpr =LLU`NewManagedExpression[MyExpression]["Hello World"];

LLU`NewManagedExpression[MyExpression]["Temporary?"];

$HistoryLength=0;

LLU`NewManagedExpression[MyExpression]["Temporary"];

Managed Library Expressions

Write library functions for MyExpression member methods:

LIBRARY_LINK_FUNCTION(GetText) {
	LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 
	
	const MyExpression& myExpr = mngr.getManagedExpression(0, MyExpressionStore);
	mngr.set(myExpr.getText());
	
	return LLU::ErrorCode::NoError;
}

LIBRARY_LINK_FUNCTION(SetText) {
	LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 
  
	auto& myExpr = mngr.getManagedExpression(0, MyExpressionStore);
	mngr.set(myExpr.getText());
	
	return LLU::ErrorCode::NoError;
}

Managed Library Expressions

LLU`LoadMemberFunction[MyExpression][getText,"GetText",{},String];
LLU`LoadMemberFunction[MyExpression][setText,"SetText",{String},"Void"];

myExpr@getText[]

myExpr@setText["My new text"]

myExpr@getText[]

Monitoring Progress

Dedicated class ProgressMonitor for monitoring progress and checking for aborts.

Check if user requested to abort current computation:

for (int i = 0; i < numOfIterations; ++i) {
	// sophisticated computation happening here
	ProgressMonitor::checkAbort(); // will throw exception on abort
}

Report progress to the Wolfram Language session:

auto progressMon = mngr.getProgressMonitor();
for (int i = 0; i < numOfIterations; ++i) {
	// sophisticated computation happening here
	++pm; // increase progress (default step is 0.1)
}

Monitoring Progress

Progress value can be set in a library function and read asynchronously in WL during the execution of the function.

MyFunction=WTCDemo`LoadLibraryFunction["UniformProgress",{Real},Integer, "ProgressMonitor"->WTCDemo`ProgressMonitor];

MyFunction[5]//WithProgressBar

MyFunction[100]//WithProgressBar

Learn more

LibraryLink tutorial @ Wolfram Documentation Center

https://reference.wolfram.com/language/LibraryLink/tutorial/Overview.html

C/C++ interface guide @ Wolfram Documentation Center

https://reference.wolfram.com/language/guide/CLanguageInterface.html

Wolfram Research @ GitHub

https://github.com/WolframResearch

Mathematica @ StackExchange

https://mathematica.stackexchange.com

Wolfram Community

https://community.wolfram.com/

Q & A

Thank you!

Bonus: Passing modes

The usual call:

LibraryFunctionLoad["MyLibrary","MyFunction",{(Subscript[Type, 1]),...,(Subscript[Type, n])},ResType];

If you want (Subscript[Type, i]) to be a flat Tensor of integers, you can specify one of:

Passing modes - LLU

Passing modes are encoded as template parameters

Tensor<mint, Passing::Manual> t { 1, 2, 3, 4, 5 };    
// fine, new MTensor is allocated and it will be freed when t goes out of scope

Tensor<mint, Passing::Automatic> s { 1, 2, 3, 4, 5 };     
// compile-time error, you cannot create a container with Automatic mode because LibraryLink doesn't know about it and will not free it automatically

auto t = mngr.getGenericImage<Passing::Shared>(0);   // OK

auto copy = t;    // compile-time error, you cannot copy-construct a Shared container because it would not be shared

GenericImage<Passing::Manual> clone {t};   // but this is fine, we make a deep copy which has manual passing mode

Quit

SetDirectory@NotebookDirectory[];
Get["./CppCodeHighlighter.m"]
Cpp[code_]:=CellPrint@CppCodeHighlighter`GetCppCell["InitialText"->code,Background->LightGray];

Cpp[""]

Needs["WTCDemo`"]
WTCDemo`Private`InitWTCDemo[]

WTCDemo`LoadLibraryFunction=WTCDemo`Private`SafeLibraryFunction;
LLU`Managed=WTCDemo`Private`LLU`Managed;
LLU`Constructor=WTCDemo`Private`LLU`Constructor;
LLU`NewManagedExpression=WTCDemo`Private`LLU`NewManagedExpression;
LLU`LoadMemberFunction=WTCDemo`Private`LLU`LoadMemberFunction;