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.
- Parameters
mode
: - are we deleting existing instance (True) or creating new one (False)id
: - id of the instance of interest
-
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.
- Template Parameters
DynamicType
: - actual type of the constructed object, it allows Store to keep objects of subclasses of TArgs
: - constructor arguments types
- Parameters
id
: - id of the newly created managed objectargs
: - constructor arguments
- Return
reference to the newly created object
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).
- Parameters
id
: - id of the newly created managed objectptr
: - pointer to an instance of T or a subclass
- Return
reference to the object just added to the store
-
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.
- Parameters
id
: - id of the newly created managed objectptr
: - pointer to an instance of T or a subclass
- Return
reference to the object just added to the store
-
int
releaseInstance
(mint id) π Release an instance managed by this Store.
- Parameters
id
: - id of the instance to be released
- Return
0 if the id was correct and the operation succeeded, non-negative integer otherwise
- Note
Normally, every instance in the Store has a corresponding WL expression and the instance is released as soon as the corresponding expression goes out of scope (its reference count hits 0). This function can be used to force immediate release of a managed instance.
- See
https://reference.wolfram.com/language/LibraryLink/ref/callback/releaseManagedLibraryExpression.html
-
bool
hasInstance
(mint id) const π Check if instance with given
id
is present in the store.- Parameters
id
: - id to be checked
- Return
true iff the instance with given id is 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.- Parameters
id
: - id of instance of interest
- Return
reference to the managed object
-
std::shared_ptr<T>
getInstancePointer
(mint id) π Get a shared pointer to a managed instance with given
id
.Throw if the
id
is invalid.- Parameters
id
: - id of instance of interest
- Return
shared pointer to the managed object
-
const std::string &
getExpressionName
() const noexcept π Get symbol name that is used in the WL to represent Managed Expressions stored in this Store.
- Return
symbol name
-
size_type
size
() const noexcept π Get the number of currently managed expressions.
- Return
size of the 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
.- Parameters
name
: - name of the Wolfram Language symbol that will be used to manage class TlibData
: - optionally specify WolframLibraryData instance
- Note
This function should typically be called in
WolframLibrary_initialize
-
void
unregisterType
(WolframLibraryData libData = LibraryData::API()) const noexcept π Unregister class T as managed expression.
- Parameters
libData
: - optionally specify WolframLibraryData instance
- Note
This function should typically be called in
WolframLibrary_uninitialize