I have recently added LibraryLink
Support on mathematica++
. It is still in develop branch. It'll take a few days before releasing it to the master branch.
It already have features to stream WSPut
/MLPut
calls streamed through the mlink or wslink.
shell << Values(FindRoot(ArcTan(1000 * Cos(x)), List(x, 1, 2), Rule("Method") = method));
And it can cast tokens fetched from mathematica into generic stl container, like std::vector
, std::pair
, boost::tuple
etc..
Exploiting these two features the aim of the Library Link support is to wrap only the difficult parts while letting the programmer focus on the implementation functions. I started the development process with the following 4 concerns in mind.
- Unharmed Skeleton The boilerplate of 4 functions,
initialize
, uninitialize
, version
and the actual function to be exported is left intact with the exact signature, so that the developer can easily follow up after reading the LibraryLink documentation on Mathematica website. It also doesn't restrict the developer to code whatever he/she could with plain C API.
- C++ Environment The developer should be able to develop and unit test the module only using C++ based environment without using Mathematica fronted. However the developer is not barred from using the frontend in the middle of the development process. The concern is a C++ developer should feel home like while developing a module.
- Support Generics standard C++ generic containers should be supported.
- Extensible The library should be extensible to support 3rd party C++ libraries. (Eigen Matrix is already supported)
Following is an example how link based Library development is abstracted. The native_link
can still be used with the C API. The resolver
redirects calls to different impl functions either based on heads or based on number of arguments.
EXTERN_C DLLEXPORT int SomeFunctionX(WolframLibraryData libData, WMK_LINK native_link){
mathematica::wtransport shell(libData, native_link);
try{
mathematica::resolver resolver(shell);
resolver, overload(&some_function_impl_geo, shell) = {"GeoPosition", "GeoPosition"}
, overload(&some_function_impl_complex) = {"Complex", "Complex"}
, overload(&some_function_impl_binary)
, overload(&some_function_impl_unary);
return resolver.resolve();
}catch(...){
return shell.pass();
}
return 0;
}
Following are the signatures of the impl functions in the above example. overload
checks the function argument types and cast
accordingly. This can can handle user defined types like the point_2d
in the example through different specialized serialization policies.
double some_function_impl_geo(mathematica::transport& shell, point_2d<double> p1, point_2d<double> p2);
double some_function_impl_complex(std::complex<double> p1, std::complex<double> p2)
int some_function_impl_binary(double x, double y);
mathematica::m some_function_impl_unary(double x);
The shell
(first argument in the first overload) comes from libData
and can be used to do computations with mathematica. In the example some_function_impl_geo
converts point_2d
to GeoPosition
and then passes to GeoDistance
as arguments.
double some_function_impl_geo(mathematica::transport& shell, point_2d<double> p1, point_2d<double> p2){
std::string unit("Kilometers");
double res;
shell << QuantityMagnitude(GeoDistance(p1, p2, Rule("!UnitSystem") = unit));
shell >> res;
return res;
}
The returned output is sent back to the frontend. The last overload in the example returns a mathematica expression.
As previously mentioned no flavours are abandoned. The MArgument
based style is abstracted in mathematica::mtransport
. mtransport
doesn't support overloads, type of its input is expected to be fixed with LibraryFunctionLoad
. Input output with tensor of any type, rank and dimension is supported. Type of nesting level of std::vector
determines the expected type and rank of the tensor. Currently only std::vector is supported, however there are plans to support eigen and other containers.
args = shell
is used to capture the input arguments, whereas shell = ret
is used to get the returned value.
EXTERN_C DLLEXPORT int SomeFunctionMXT(WolframLibraryData libData, mint argc, MArgument* argv, MArgument res){
mathematica::mtransport shell(libData, argc, argv, res);
typedef std::vector<std::vector<double>> matrix_type;
try{
boost::tuple<matrix_type, matrix_type> args = shell; // fetch input
matrix_type matl, matr, mato; // declare variables to hold the input and output
boost::tie(matl, matr) = args; // tie tuple to variables
shell << Dot(matl, matr); // To lazy to write matrix multiplication code in C++ so using mathematica instead
// This converts matl and matr to the corresponding List[..] expression
shell >> mato; // retrieve multiplication result as List[...] and cast to matrix_type
shell = mato; // return the result
}catch(...){
return shell.pass();
}
return LIBRARY_NO_ERROR;
}
shell.pass()
passes the exceptions to the frontend and aborts.
I'd be glad to hear reviews and comments from others.