MMU is a small utility library that provides abstractions over memory mapped files.
Installation
To build MMU from source you will need a C++14 compiler and CMake.
Usage
MMU offers the following utilities:
- Abstract untyped buffers encapsulating a data pointer and a size that can be allocated from the C standard library using
MMU::make_buffer, mapped from a file usingMMU::file_buffer, or wrap a pre-allocated memory area usingMMU::static_buffer. - Typed one-dimensional arrays wrapping a buffer, offset, stride, and size that can be allocated from the C standard library using
MMU::make_array, mapped from a file usingMMU::file_array, or wrap a pre-allocated array usingMMU::static_array. - Typed two-dimensional arrays wrapping a buffer, offset, two strides, and two sizes that can be allocated from the C standard library using
MMU::make_array2d, mapped from a file usingMMU::file_array2d, or wrap a pre-allocated array usingMMU::static_array2d. - An untyped allocator managing a pool of swap files that can be created with
MMU::buffer_pool. - Typed allocators wrapping a pool of swap files for use with STL containers represented by the
MMU::Allocator<T>class. - A convenient abstraction around the dynamic linker that can be instantiated using
MMU::shared_library,MMU::managed_library, orMMU::compiled_library. - A small options container
MMU::MMapOptionsto configure the properties of a memory mapping. - A small options container
MMU::PoolOptionsto configure the properties of a swap file pool. - A small options container
MMU::LibraryOptionsto configure the behaviour when loading a shared library. - Utility functions to find the executable file (
MMU::program_file()), identify the host system (MMU::runtime_id()), get the system page size (MMU::page_size()), and get the memory used by the current process (MMU::get_resident_size(),MMU::max_resident_size()).
Here's a simple usage example to get you started:
#include <cstdio>
#include <string>
#include <algorithm>
#include <MMU/MMU.hpp>
int main(int nargs, const char **args) {
MMU::set_program_name(nargs > 0 ? args[0] : __FUNCTION__);
# if defined(__cpp_exceptions) || defined(__EXCEPTIONS)
try
# endif
{
auto array = MMU::file_array<const char>(__FILE__);
MMU_ASSERT(array.contiguous());
MMU_ASSERT(array.begin() != array.end());
auto slice = array.slice(1, 7, 2);
MMU_ASSERT(!slice.contiguous());
MMU_ASSERT(std::equal(slice.begin(), slice.end(), "icue<sd"));
typedef std::basic_string<
char, std::char_traits<char>,
MMU::Allocator<char>
> mmu_string_t;
auto pool = MMU::buffer_pool("buffer_pool.d");
mmu_string_t hello("Hello world!", pool);
MMU_ASSERT(hello[6] == 'w');
auto libc = MMU::shared_library(nullptr);
auto puts = libc->get<void, const char *>("puts");
if (MMU_UNLIKELY(!puts)) MMU_ASSERT_ERROR(libc->last_error());
puts(hello.c_str());
return 0;
}
# if defined(__cpp_exceptions) || defined(__EXCEPTIONS)
catch (const std::exception &exn) {
fprintf(stderr, "%s: Error: %s\n", MMU::program_name(), exn.what());
return 1;
}
# endif
}
Loading Managed Assemblies
The function MMU::managed_library behaves similar to MMU::shared_library on the client side, taking the path to a .NET assembly it should load. The MMU::ILibrary object returned by the function can be used to retrieve native delegates from that assembly.
In the background, MMU::managed_library loads the hostfxr library and makes sure a suitable .NET runtime is attached to the process.
Support for this feature must be enabled when compiling MMU.
In case of managed assemblies, the symbol names passed to MMU::ILibrary::get or get_pointer have a certain structure: Namespace and class qualifiers separated by the usual dots must precede the static method name. In addition, a delegate type used to convert the method into a native function pointer can be specified, otherwise the method must be compatible with the signature int (void *, int32_t). Assembly qualifiers can be omitted from the types, in which case the basename of the loaded assembly is added automatically. So the full format of a symbol name is NAMESPACE.METHOD[, ASSEMBLY][: NAMESPACE.DELEGATE[, ASSEMBLY]].
If the literal string UnmanagedCallersOnly is given as the delegate type, the target method must be marked with the System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute and observe the associated rules.
Loading Libraries from Source
The function MMU::compiled_library behaves similar to MMU::shared_library (in fact it calls the latter eventually), but it takes a single source file as an argument and compiles it for you. The mechanism can even inject package dependencies into the build environment.
To work, CMake 4.1, the necessary compilers, and the used package managers have to be installed.
Dependency Comments
The special comments recognized by MMU::compiled_library all match the regular expression ^//([-+ILlr])\s*("[^"]*"|\S+):
//I "PATH"
Add a fixed include PATH to the C(++) compiler's command line.
//L "PATH"
Add a fixed library PATH to the C(++) compiler's command line.
//l "NAME"
Add a library called NAME to the linker's command line.
//r "pkg-config: NAME, VERSION"
Add a dependency on a pkg-config package. The version specification may be omitted.
If the dependency is written as //r "@pkg-config: …", it is processed with cmake_pkg_config(POPULATE …) rather than cmake_pkg_config(IMPORT …).
//r "conan: NAME, VERSION"
Add a dependency on a Conan package.
If the dependency is written as //r "@conan: …", it is added to the conanfile.txt, but not referenced in CMake. This can be useful for packages with different names for the package manager artifact, the conceptual CMake package, and the CMake library target.
//r "nuget: NAME, VERSION"
Add a dependency on a NuGet package. The version specification may be omitted.
//r "NAME, VERSION"
Add a dependency on a CMake package. The version specification may be omitted.
If the package name has the form FIND_NAME::LINK_NAME, the FIND_NAME is used in find_package calls, while the full name is used in target_link_libraries calls.
//+CONDITION
The effect of following magic comments will be wrapped in a CMake if(CONDITION) block.
//-CONDITION
Generates a CMake endif(CONDITION) statement. The condition must be empty, or match the last open condition block.
Condition blocks left open are closed automatically after the effects of all magic comments have been issued.