Skip to content

C++ wrappers for conveniently wrapping Wolfram LibraryLink code.

License

Notifications You must be signed in to change notification settings

WolframResearch/LibraryLinkUtilities

Repository files navigation

LibraryLink Utilities

LLU logo

LibraryLink Utilities (abbr. LLU) is an open-source library consisting of modern C++ wrappers over most parts of LibraryLink - the Wolfram Language framework for connecting to C and C++ libraries. The official documentation including use instructions can be found here:

https://wolframresearch.github.io/LibraryLinkUtilities/

This documentation is split into modules. Each describes a core part of LLU providing general overview, examples and detailed description of related C++ entities.

Please use GitHub to report bugs, make suggestions and request features.

Motivation

LibraryLink is a great tool for connecting Wolfram Language to external C/C++ libraries and is widely used by Wolfram Research and Wolfram Language developers for developing paclets. LibraryLink Utilities makes it even easier to use LibraryLink by providing features such as:

  • automatic resource management
  • exception handling
  • container iterators
  • class-like interface for data structures, for example rank() as member function of Tensor class instead of separate function mint MTensor_getRank(MTensor)
  • type safety
  • lazy loading of library functions
  • progress monitoring of library functions
  • standardized approach to exchange custom data types between C++ and WL code

Example

Let's demonstrate some advantages of LLU by comparing the same function written with and without LLU. Below we will implement a simple function repeatCharacters that takes a string s and a tensor t and returns a new string s2 that consists of each character s[i] from original string but repeated t[i] times. So, for example

repeatCharacters("abc", {3, 2, 1})

gives

"aaabbc"

This is the C - style implementation:

// global variable which is the buffer for strings returned to LibraryLink
char* outString = NULL;

EXTERN_C DLLEXPORT int repeatCharacters(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) {
  char* string = NULL;
  MNumericArray counts;
  uint8_t* countsData = NULL;
  size_t outStringIndex = 0;
  size_t len, j;
  mint sum = 0;
  mint c;

  string = MArgument_getUTF8String(Args[0]);
  counts = MArgument_getMNumericArray(Args[1]);

  // check NumericArray type
  if (libData->numericarrayLibraryFunctions->MNumericArray_getType(counts) != MNumericArray_Type_UBit8) {
     libData->UTF8String_disown(string);
     return LIBRARY_TYPE_ERROR;
  }

  // check NumericArray rank
  if (libData->numericarrayLibraryFunctions->MNumericArray_getRank(counts) != 1) {
     libData->UTF8String_disown(string);
     return LIBRARY_RANK_ERROR;
  }

  // check if NumericArray length is equal to input string length
  len = strlen(string);
  if (libData->numericarrayLibraryFunctions->MNumericArray_getFlattenedLength(counts) != len) {
     libData->UTF8String_disown(string);
     return LIBRARY_DIMENSION_ERROR;
  }

  // before we allocate memory for the output string, we have to sum all NumericArray elements
  // to see how many bytes are needed
  countsData = (uint8_t*) libData->numericarrayLibraryFunctions->MNumericArray_getData(counts);
  for (j = 0; j < len; j++) {
     sum += countsData[j];
  }

  // free memory owned by global buffer, if any (for example from the previous call to this function)
  free(outString);
  outString = NULL;

  // allocate memory for output string, outString has to be a global variable,
  // because it will be returned to LibraryLink
  outString = (char*) malloc(sum + 1);
  if (!outString) {
     libData->UTF8String_disown(string);
     return LIBRARY_FUNCTION_ERROR;
  }

  // populate output string
  for (j = 0; j < len; j++) {
     for (c = 0; c < countsData[j]; c++) {
        outString[outStringIndex++] = string[j];
     }
  }

  // add null terminator
  outString[sum] = '\0';

  // clean up and set result
  libData->UTF8String_disown(string);
  MArgument_setUTF8String(Res, outString);

  return LIBRARY_NO_ERROR;
}

and this is the corresponding C++ version written with LibraryLink Utilities:

EXTERN_C DLLEXPORT int repeatCharactersLLU(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) {
  auto err = LLU::ErrorCode::NoError;
  try {
     // Create manager object
     LLU::MArgumentManager mngr {libData, Argc, Args, Res};

     // Read string and NumericArray arguments
     auto string = mngr.getString(0);
     auto counts = mngr.getNumericArray<std::uint8_t>(1);

     // check NumericArray rank
     if (counts.rank() != 1) {
        LLU::ErrorManager::throwException(LLU::ErrorName::RankError);
     }

     // check if NumericArray length is equal to input string length
     if (counts.size() != string.size()) {
        LLU::ErrorManager::throwException(LLU::ErrorName::DimensionsError);
     }

     // before we allocate memory for the output string, we have to sum all NumericArray elements
     // to see how many bytes are needed
     auto sum = std::accumulate(std::cbegin(counts), std::cend(counts), static_cast<size_t>(0));

     // allocate memory for the output string
     std::string outString;
     outString.reserve(sum);

     // populate the output string
     for (mint i = 0; i < counts.size(); i++) {
        outString.append(std::string(counts[i], string[i]));
     }

     // clean up and set the result
     mngr.set(std::move(outString));
  } catch (const LLU::LibraryLinkError& e) {
     err = e.which();
  }
  return err;
}

Limitations with respect to LibraryLink

There are a few LibraryLink features currently not covered by LLU, most notably:

  • Tensor subsetting: MTensor_getTensor
  • Callbacks
  • Wolfram IO Library (asynchronous tasks)

Contributors

  • Rafał Chojna (rafalc) - main developer
  • Sean Cheren (scheren) - top-level code for error handling, CMake improvements
  • Rebecca Frederick (rebeccaf) - CMake improvements