C++ integration into Wolfram Language

Rafał Chojna, WRI

Mathematica Day @ UW, 9.12.2017

Wolfram Language vs the World

Wolfram Language vs the World

Wolfram Language vs the World

Wolfram Language vs the World

LibraryLink

LibraryLink

LibraryLink

LibraryFunctionLoad syntax

LibraryFunctionLoad["MyLibrary", "helloWorld", {}, "UTF8String"]

Library name:

absolute or relative path

with or without extension

?$LibraryPath

$LibraryPath//Short

?FindLibrary

FindLibrary["demo"]

LibraryFunctionLoad syntax

LibraryFunctionLoad["MyLibrary", "helloWorld", {}, "UTF8String"]

Function name:

string, must be precisely the same as in C/C++ source code

extern "C" is crucial with C++

me@mycomp:~$ nm demo.so
...
000000000000 a2f0 T demo_I_I

vs.

me@mycomp:~$ nm demo.so
...
000000000000a320 T _Z8demo_I_IP21st_WolframLibraryDatalP9MArgumentS1_

LibraryFunctionLoad syntax

LibraryFunctionLoad["MyLibrary", "helloWorld", {}, "UTF8String"]

List of function arguments:

only types

Possible argument types, and their corresponding C type, include:

LibraryFunctionLoad syntax

LibraryFunctionLoad["MyLibrary", "helloWorld", {}, "UTF8String"]

Type of the return value:

Possible return types, and their corresponding C type, include:

LibraryLink

C  library function signature

int myFunctionName(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res)

Main data structure in Wolfram Library

struct st_WolframLibraryData {
	void (*UTF8String_disown) (char*);
// MTensor 
	int	 (*MTensor_new)	  (mint, mint, mint const*, MTensor*);
	void (*MTensor_free)  (MTensor);
	int	 (*MTensor_clone) (MTensor, MTensor*);
	...
// MTensor data access
	int   (*MTensor_setInteger)		(MTensor, mint*, mint);
	int   (*MTensor_getInteger)		(MTensor, mint*, mint*);
	mint* (*MTensor_getIntegerData) (MTensor);
	...
//MTensor metadata
	mint const* (*MTensor_getDimensions) (MTensor);
	mint 		(*MTensor_getType) 		 (MTensor);
 	...
 // managed library expressions
	int (*registerLibraryExpressionManager) (const char* mname, void (*mfun)(WolframLibraryData, mbool, mint));
	int (*releaseManagedLibraryExpression)  (const char* mname, mintid);
	...
 // callbacks            
	int (*registerLibraryCallbackManager) (const char* name, mbool (*mfun)(WolframLibraryData, mint, MTensor));
	int (*callLibraryCallbackFunction)	  (mint id, mint ArgC, MArgument* Args, MArgument Res);
	...
// other structures                         
	struct st_WolframSparseLibrary_Functions* 	sparseLibraryFunctions;
	struct st_WolframImageLibrary_Functions* 	imageLibraryFunctions;
	struct st_WolframRawArrayLibrary_Functions* rawarrayLibraryFunctions;
	...
 // Miscellaneous
	void (*Message) (const char*);
	mint (*AbortQ)  (void);           
};

C  library function signature

int myFunctionName(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res)

Represents any argument that can be passed between Mathematica and C code.

typedef union {
	mbool*	boolean;
	mint*	integer;
	mreal*	real;
	mcomplex*	cmplex;
	MTensor*	tensor;
	MSparseArray*	sparse;
	MRawArray*	raw;
	MImage*		image;
	char**	utf8string;
} MArgument;

RawArray

Memory efficient version of Tensor. Supported in LibraryLink as MRawArray. [RawArray has not been documented yet and it’s design may change in the future]

r=RawArray["UnsignedInteger8",{2,3,5,7,11}]

ByteCount[r]

RawArray["UnsignedInteger8",{2}]//ByteCount

Normal[r]

{2,3,5,7,11}//ByteCount

With[{t=RandomInteger[255,100]},ListPlot[AssociationThread[{"Tensor","RawArray"},Map[ByteCount,{Take[t,#]&/@Range[1,50,2],RawArray["UnsignedInteger8",Take[t,#]]&/@Range[1,100,2]},{2}]],PlotLabels->Automatic,PlotLegends->None,PlotLabel->"Container size comparison",AxesLabel->{"Number of elems","Size in Bytes"},DataRange->{0,100},GridLines->{None,{100,200}}]]

RawArray

Supported data types:

TextGrid[{{Style["Mathematica type",Bold,RGBColor[0.45098, 0.223529, 0.678431]],"Integer8","UnsignedInteger8","Integer16","UnsignedInteger16","Integer32","UnsignedInteger32","Integer64","UnsignedInteger64","Real32","Real64","Complex64","Complex128"},{Style["LibraryLink type",Bold,RGBColor[0.45098, 0.223529, 0.678431]],"MRawArray_Type_Bit8 (= 1)","MRawArray_Type_Ubit8","MRawArray_Type_Bit16","MRawArray_Type_Ubit16","MRawArray_Type_Bit32","MRawArray_Type_Ubit32","MRawArray_Type_Bit64","MRawArray_Type_Ubit64","MRawArray_Type_Real32","MRawArray_Type_Real64","MRawArray_Type_Float_Complex","MRawArray_Type_Double_Complex"}}\[Transpose],Dividers->{{2->Gray},{2->Gray}},Frame->True,FrameStyle->Gray,Alignment->{Center,Center},Spacings->{2,1}]

RawArray

MRawArray C interface:

typedef struct st_WolframRawArrayLibrary_Functions {

// MRawArray management
	int  (*MRawArray_new)(rawarray_t, mint, mint const*, MRawArray*);
	void (*MRawArray_free)(MRawArray);
	int  (*MRawArray_clone)(MRawArray, MRawArray*);
	void (*MRawArray_disown)(MRawArray);
	void (*MRawArray_disownAll)(MRawArray);
	mint (*MRawArray_shareCount)(MRawArray);

// MRawArray metadata access
	mint (*MRawArray_getRank)(MRawArray);
	mint (*MRawArray_getFlattenedLength)(MRawArray);
	mint const* (*MRawArray_getDimensions)(MRawArray);
	rawarray_t  (*MRawArray_getType)(MRawArray);
	
// MRawArray data access
	void* 	  (*MRawArray_getData)(MRawArray);
	MRawArray (*MRawArray_convertType)(MRawArray, rawarray_t);

} *WolframRawArrayLibrary_Functions;

Building the library

Platform-dependent, manual way:

Locate the C/C++ compiler on your system

Set appropriate flags to build a dynamic library

Add necessary include paths - the location of WolframLibrary.h among them

Add paths to external libraries used in linking phase

Specify source files

me@mycomp:~$ /usr/bin/gcc  -shared -o "MyLibrary.so" -m64 -fPIC -std=c++14 -Wall

-I"/Wolfram/Mathematica/11.2/SystemFiles/IncludeFiles/C" -I"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" 

-L"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" -L"/Wolfram/Mathematica/11.2/SystemFiles/Libraries/Linux-x86-64"

"myProject/Example.cpp" "myProject/Core.cpp" "myProject/Utilities.cpp"

Building the library

Platform-dependent, manual way:

Locate the C/C++ compiler on your system

Set appropriate flags to build a dynamic library

Add necessary include paths - the location of WolframLibrary.h among them

Add paths to external libraries used in linking phase

Specify source files

me@mycomp:~$ /usr/bin/gcc  -shared -o "MyLibrary.so" -m64 -fPIC -std=c++14 -Wall

-I"/Wolfram/Mathematica/11.2/SystemFiles/IncludeFiles/C" -I"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" 

-L"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" -L"/Wolfram/Mathematica/11.2/SystemFiles/Libraries/Linux-x86-64"

"myProject/Example.cpp" "myProject/Core.cpp" "myProject/Utilities.cpp"

Building the library

Platform-dependent, manual way:

Locate the C/C++ compiler on your system

Set appropriate flags to build a dynamic library

Add necessary include paths - the location of WolframLibrary.h among them

Add paths to external libraries used in linking phase

Specify source files

me@mycomp:~$ /usr/bin/gcc  -shared -o "MyLibrary.so" -m64 -fPIC -std=c++14 -Wall

-I"/Wolfram/Mathematica/11.2/SystemFiles/IncludeFiles/C" -I"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" 

-L"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" -L"/Wolfram/Mathematica/11.2/SystemFiles/Libraries/Linux-x86-64"

"myProject/Example.cpp" "myProject/Core.cpp" "myProject/Utilities.cpp"

Building the library

Platform-dependent, manual way:

Locate the C/C++ compiler on your system

Set appropriate flags to build a dynamic library

Add necessary include paths - the location of WolframLibrary.h among them

Add paths to external libraries used in linking phase

Specify source files

me@mycomp:~$ /usr/bin/gcc  -shared -o "MyLibrary.so" -m64 -fPIC -std=c++14 -Wall

-I"/Wolfram/Mathematica/11.2/SystemFiles/IncludeFiles/C" -I"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" 

-L"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" -L"/Wolfram/Mathematica/11.2/SystemFiles/Libraries/Linux-x86-64"

"myProject/Example.cpp" "myProject/Core.cpp" "myProject/Utilities.cpp"

Building the library

Platform-dependent, manual way:

Locate the C/C++ compiler on your system

Set appropriate flags to build a dynamic library

Add necessary include paths - the location of WolframLibrary.h among them

Add paths to external libraries used in linking phase

Specify source files

me@mycomp:~$ /usr/bin/gcc  -shared -o "MyLibrary.so" -m64 -fPIC -std=c++14 -Wall

-I"/Wolfram/Mathematica/11.2/SystemFiles/IncludeFiles/C" -I"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" 

-L"/Wolfram/Mathematica/11.2/SystemFiles/Links/MathLink/DeveloperKit/Linux-x86-64/CompilerAdditions" -L"/Wolfram/Mathematica/11.2/SystemFiles/Libraries/Linux-x86-64"

"myProject/Example.cpp" "myProject/Core.cpp" "myProject/Utilities.cpp"

Building the library

Easy way - with CCompilerDriver:

Needs["CCompilerDriver`"]

?CCompilerDriver`*

CCompilers[]

DefaultCCompiler[]

?CreateLibrary

?CreateExecutable

Building the library

Easy way - with CCompilerDriver:

Available options for CreateLibrary:

Dataset`$DatasetTargetRowCount=29;
Dataset@Association[Options[CreateLibrary]]

LibraryLink limitations

Certain important types are not supported

List of strings

Unsigned 64-bit integer

64-bit integer on 32-bit platform

LibraryLink limitations

No argument verification in LibraryFunctionLoad

LibraryFunctionLoad["MyLibrary", "helloWorld", {}, "UTF8String"];
LibraryFunctionLoad["MyLibrary", "helloWorld", {Integer, Real}, "Void"];
LibraryFunctionLoad["MyLibrary", "helloWorld", {{Complex,_}}, Integer];

are correct even if the C function looks like this

int helloWorld (WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
 	mbool b = MArgument_getBoolean(Args[0]);
 	MArgument_setBoolean(Res, !b);
 	return LIBRARY_NO_ERROR;
}

LibraryLink limitations

Problems when used in C++ projects

Macros and global namespace pollution

// some macros could easily be enums
#define True 	1
#define False	0

#define MType_Integer 2
#define MType_Real 3
#define MType_Complex 4

// some macros try to emulate a namespace
#define MArgument_getBoolean(marg)		...
#define MArgument_getInteger(marg)		...
#define MArgument_getReal(marg)			...

// types will be defined in the global namespace
typedef int type_t;
typedef unsigned int UBIT32;

Memory management of “UTF8String” argument

int helloWorld (WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) {
 	char* s1 = MArgument_getUTF8String(Args[0]);
 	// do something with s1
 	libData->UTF8String_disown(s1);
 	char* s2 = getStringThatCallerIsSupposedToDeallocate(...);
 	MArgument_setUTF8String(Res, s2);
 	// we can't deallocate the string here!
 	return LIBRARY_NO_ERROR; 
}

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!

LibraryLink Utilities for C++

LibraryLink is great for writing C libraries that communicate with Mathematica. What about modern C++?

We are working on a set of utilities that provide modern interface to LibraryLink.

Automatic resource management

>> LibraryLink

int MRawArray_new(rawarray_t, mint, mint const *, MRawArray *);
int MTensor_new(mint, mint, mint const *, MTensor *);
... 
void MTensor_free(MTensor);
void MRawArray_free(MRawArray);
void UTF8String_disown(char*);

>> Redesigned

RAII - Resource Acquisition Is Initialization

Tensor::Tensor(...);
RawArray::RawArray(...);
...
Tensor::~Tensor();
RawArray::~RawArray();

LibraryLink Utilities for C++

LibraryLink is great for writing C libraries that communicate with Mathematica. What about modern C++?

We are working on a set of utilities that provide modern interface to LibraryLink.

Exceptions

>> LibraryLink

err = MTensor_new(..., t);
if (err) {
	// do cleanup and return error
}
err = MTensor_setInteger(t, 2, 5);
if (err) {
	// do cleanup and return error
}

>> Redesigned

try {

	Tensor t {...};
	t[2] = 5; 	// throws if index is out-of-bound 
	
} catch (LibraryLinkException& err) {
	// handle the exception
}

LibraryLink Utilities for C++

LibraryLink is great for writing C libraries that communicate with Mathematica. What about modern C++?

We are working on a set of utilities that provide modern interface to LibraryLink.

Iterators for LibraryLink containers

>> LibraryLink

mint len = MTensor_getFlattenedLength(t);
mint* rawData = MTensor_getIntegerData(t);
long sum = 0;
for (mint i = 0; i < len; ++i) {
 	sum += rawData[i];
}

>> Redesigned

long sum = 0;
for (auto& elem : t) {
	sum += elem;
}

// or

auto sum = std::accumulate(t.begin(), t.end(), 0L);

LibraryLink Utilities for C++

Object-oriented interface

>> LibraryLink

#define  MType_Integer 2
#define  MType_Real 3
#define  MType_Complex 4
	
int  MTensor_new(mint, mint, mint const*, MTensor*);
void MTensor_free(MTensor);
int  MTensor_clone(MTensor, MTensor*);

mint MTensor_getType(MTensor);
mint MTensor_getRank(MTensor);
mint MTensor_getFlattenedLength(MTensor);

int  MTensor_setInteger(MTensor, mint*, mint);
int  MTensor_getInteger(MTensor, mint *, mint*);
mint* MTensor_getIntegerData(MTensor); 
// similarly for real and complex ...

>> Redesigned

template<typename T>
class Tensor {
	Tensor();
	~Tensor();
	Tensor(const Tensor&);
	mint getRank();
	mint getFlattenedLength();
	T operator[](mint) const;
	T& operator[](mint);
	T* getData();
};

LibraryLink Utilities for C++

Type safety

>> LibraryLink

Type mismatches can only be detected at run-time:

void swapFirst(MTensor t, MTensor s) {
	mint typeT = MTensor_getType(t);
 	mint typeS = MTensor_getType(s);
 	if (typeT != typeS) {
 	 	// whoops 	
 	} 	
 	if (typeT == MType_Integer) {
 	 	mint t1, s1; 
 	 	MTensor_getInteger(t, 1, &t1); 
 	 	MTensor_getInteger(s, 1, &s1); 
 	 	MTensor_setInteger(t, 1, s1); 
 	 	MTensor_setInteger(s, 1, t1); 	
 	} else if (typeT == MType_Real) {
 	 	... 
 	}

>> Redesigned

By making Tensor data type a template parameter we get support from the compiler and type mismatches may be detected at compile-time:

template<typename T>
void swapFirst(Tensor<T>& t, Tensor<T>& s) {
 	std::swap(t[0], s[0]); 
}