Managed expressions π
One of the features offered by LibraryLink is Managed Library Expressions (MLEs). The idea is to create C/C++ objects that will be automatically deleted when they are no longer referenced in the Wolfram Language code. More information can be found in the official LibraryLink documentation.
This allows for an object-oriented programming paradigm and it is the recommended way of referencing C++ objects from the Wolfram Language. The two most notable alternatives are:
recreating C++ objects every time a library function is called
maintaining some sort of global cache with referenced objects, where each object is added on first use and manually deleted at some point.
LLU uses methods similar to the second alternative to facilitate using MLEs and decrease the amount of boilerplate code needed from developers. Namely, for each class that you register to be used as MLE, LLU will maintain a map, which associates managed C++ objects with IDs assigned to them by the Wolfram Language.
Register a class as Managed Expression π
Imagine you have a class A whose objects you want to manage from the Wolfram Language:
struct A {
A(int n) : myNumber{n} {}
int getMyNumber() const { return myNumber; }
private:
int myNumber;
};
Then you must create the corresponding Store and specialize a callback function for LibraryLink (this is a technicality that just needs to be done):
LLU::ManagedExpressionStore<ClassName> AStore; //usually <class name>Store is a good name
//specialize manageInstanceCallback, this should just call manageInstance function from your Store
template<>
void LLU::manageInstanceCallback<A>(WolframLibraryData, mbool mode, mint id) {
AStore.manageInstance(mode, id);
}
-
template<class
T
>
voidLLU
::
manageInstanceCallback
(WolframLibraryData, mbool, mint) π A template for library callback used by LibraryLink to manage instances of ManagedLibraryExpressions.
Specializations should typically just call manageInstance method from the ManagedExpressionStore corresponding to class T.
- Template Parameters
T
: - class to be managed
- Note
This function must be explicitly specialized for any class that is supposed to be managed. Therefore instantiation of the general template will trigger compilation error.
Alternatively, you can use a macro:
-
DEFINE_MANAGED_STORE_AND_SPECIALIZATION
(ClassName) π Use this macro to define an instance of ManagedExpressionStore corresponding to your class and a template specialization of manageInstanceCallback for the managed class.
but the macro has some limitations:
it must be invoked from the global namespace
the definition of
ClassName
must be visible at the point of invocationClassName
must be an unqualified name (which combined with 1. means thatClassName
must be a class defined in the global namespace)
Lastly, you need to register and unregister your type when library gets loaded or unloaded, respectively.
1 2 3 4 5 6 7 8 9 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) {
LLU::LibraryData::setLibraryData(libData);
AStore.registerType("A"); // the string you pass is the name of a symbol that will be used in the Wolfram Language for managing
return 0; // objects of your class, it is a good convention to just use the class name
}
EXTERN_C DLLEXPORT void WolframLibrary_uninitialize(WolframLibraryData libData) {
AStore.unregisterType(libData);
}
|
With MLEs in LibraryLink it is not possible to pass arguments for construction of managed expressions. LLU extends the MLE implementation by letting the developer define a library function that LLU will call from the Wolfram Language when a new instance of a managed expression is created. In other words, define a wrapper for constructor of your class. Typically, it will look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | EXTERN_C DLLEXPORT int OpenManagedA(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) {
auto err = LLU::ErrorCode::NoError;
try {
LLU::MArgumentManager mngr(libData, Argc, Args, Res);
auto id = mngr.getInteger<mint>(0); // id of the object to be created
auto arg1 = mngr.getXXXX(1);
auto arg2 = mngr.getYYYY(2);
... // read the rest of parameters for constructor of your managed class
AStore.createInstance(id, arg1, arg2, ...);
} catch (const LLU::LibraryLinkError& e) {
err = e.which();
}
return err;
}
|
It is simpler to register an MLE in the Wolfram Language. You only need to load your constructor wrapper:
`LLU`Constructor[A] = `LLU`PacletFunctionLoad["OpenManagedA", {`LLU`Managed[A], Arg1Type (*, ...*)}, "Void"];
Using Managed Expressions π
After the registration is done, using MLEs is very simple. In C++ code, MLEs can be treated as another MArgument type,
for example letβs define a wrapper library function over A::getMyNumber()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | LIBRARY_LINK_FUNCTION(GetMyNumber) {
auto err = LLU::ErrorCode::NoError;
try {
// create an instance of MArgumentManger for this function
LLU::MArgumentManager mngr {Argc, Args, Res};
// get a reference to the Managed Expression of type A, on which this function was called in the Wolfram Language
const A& myA = mngr.getManagedExpression(0, AStore);
// set the value of myA.getMyNumber() as the result of this library function
mngr.set(myA.getMyNumber());
} catch (const LLU::LibraryLinkError &e) {
err = e.which();
}
return err;
}
|
In the Wolfram Language, wrappers over member functions can be conveniently loaded:
`LLU`LoadMemberFunction[A][
getMyNumber, (* fresh symbol for the member function *)
"GetMyNumber", (* function name in the library *)
{}, (* argument list *),
Integer (* result type *)
];
The only thing left now is to create an MLE instance and call the member function on it:
myA = `LLU`NewManagedExpression[A][17];
myA @ getMyNumber[]
(* = 17 *)
API Reference π
-
template<typename
T
>
classLLU
::
ManagedExpressionStore
π ManagedExpressionStore will keep track of instances of managed class T and will provide safe access to them.
- Template Parameters
T
: - managed class
Public Types
-
using
iterator
= typename std::unordered_map<mint, std::shared_ptr<T>>::iterator π Iterator over ManagedExpressionStore - it iterates over the underlying hash map.
-
using
const_iterator
= typename std::unordered_map<mint, std::shared_ptr<T>>::const_iterator π Constant iterator over ManagedExpressionStore - it βconst-iteratesβ over the underlying hash map.
Public Functions
-
void
manageInstance
(mbool mode, mint id) π Function that will actually be called by LibraryLink when an instance of Managed Expression is created or deleted.
Notice that this function does not actually create a new object of class T. This is because there is no way to pass constructor arguments here.
-
template<class
DynamicType
= T, typename ...Args
>
T &createInstance
(mint id, Args&&... args) π Create new object of class T that will be managed from Wolfram Language and place it in the map of managed objects.
Create instance in the store from a pointer to the managed class object.
This is useful when you have an existing object to be managed or when objects of class T cannot be constructed directly (e.g. because T is an abstract class).
-
T &
createInstance
(mint id, std::unique_ptr<T> ptr) π Create instance in the store from a unique pointer to the managed class object.
The store will claim shared ownership of the managed object.
-
int
releaseInstance
(mint id) π Release an instance managed by this Store.
-
bool
hasInstance
(mint id) const π Check if instance with given
id
is present in the store.
-
T &
getInstance
(mint id) π Get managed instance with given
id
.Throw if the
id
is invalid or if there is no corresponding instance.
-
std::shared_ptr<T>
getInstancePointer
(mint id) π Get a shared pointer to a managed instance with given
id
.Throw if the
id
is invalid.
-
const std::string &
getExpressionName
() const noexcept π Get symbol name that is used in the WL to represent Managed Expressions stored in this Store.
-
const_iterator
begin
() const noexcept π Get the const iterator to the first element of the Store.
-
const_iterator
cbegin
() const noexcept π Get the const iterator to the first element of the Store.
-
const_iterator
end
() const noexcept π Get the const iterator past the last element of the Store.
-
const_iterator
cend
() const noexcept π Get the const iterator past the last element of the Store.
-
void
registerType
(std::string name, WolframLibraryData libData = LibraryData::API()) noexcept π Register class T as managed expression under given
name
.
-
void
unregisterType
(WolframLibraryData libData = LibraryData::API()) const noexcept π Unregister class T as managed expression.