C and C++ Libraries and MEX
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
Written against MATLAB R2026b. MATLAB is an interpreted language, and most programs never need anything else. Sometimes a program must call a library that already exists in C or C++, or a hot loop cannot be written as array operations (see Vectorization and Preallocation). MATLAB has two families of tools for that. You can call compiled code that you do not want to change, through a published interface (the clib namespace, since R2019a in its first form; loadlibrary and calllib are the older route for C). Or you can write a compiled function that MATLAB calls like any other, a MEX function ("MATLAB executable"), in C++, C or Fortran. This page covers both, with the heatflow plate (see the heatflow scenario) as the running example: the five-point stencil of the Laplacian is written once as plain C++, tested without MATLAB, and then wrapped as a C++ MEX function and as a C MEX function.
GNU Octave 11.3.0 is not MATLAB here, and none of the MATLAB-side code on this page could be run. The MATLAB R2026b interfaces named below (clibgen, loadlibrary, mex, the MEX headers mex.hpp and mex.h) are not available in Octave, and MATLAB R2026b is not installed on the machine that produced this page. What was run is the plain C++ stencil kernel, compiled standalone with g++ (no MATLAB header involved), and a C helper library compiled and called from a small C program; the output of those runs is shown and labelled. The MATLAB reference value for the stencil test was computed in Octave 11.3.0. Everything else (the clibgen scripts, the loadlibrary calls, the C++ and C MEX sources, the build commands) is labelled "not run here" and was checked line by line against the R2026b documentation on 2026-10-23. The MEX sources were additionally compiled against small stand-in headers that I wrote to catch typing mistakes; those stand-ins are not MathWorks headers, so a successful compile there proves nothing about the real API. Treat every MEX listing as a carefully checked starting point and build it on your machine before relying on it.
The dated facts, with the releases that introduced them:
-
clibgen.generateLibraryDefinition: R2019a. Its definition file has the extension.msince R2022b. It is marked "not recommended" in R2026b, with no plans to remove it. -
clibPublishInterfaceWorkflow(a guided live script) andclibConfiguration: R2023a. -
The
clibgen.apiobjectsInterfaceConfigurationandInterfaceDefinition: R2026b, now the recommended programmatic workflow. -
The C++ MEX API and the MATLAB Data API for C++: usable from R2018a on; the documentation points to the C Matrix API for code that must run in R2017b or earlier.
-
The interleaved complex C API and the typed data access functions (
mxGetDoublesand its family): R2018a, selected withmex -R2018a. -
Out-of-process C++ libraries can be interrupted with Ctrl+C since R2025a.
Choosing a route
| Job | Route | Status in R2026b |
|---|---|---|
Call functions and classes of an existing C or C++ library |
A published MATLAB interface: |
The documented way to call C and C++ libraries. |
Call a C shared library from old code |
|
Still documented; the C topic page is headed "R2021b or earlier" and the reference pages point to the published interface as the recommended way (since R2022a). C++ functions need |
Write a new MATLAB function in C++ |
The C++ MEX API: a class |
The documented choice for new C++ code. R2018a or later. |
Write a new MATLAB function in C |
The C MEX API: the gateway function |
Supported. Build with |
Write a new MATLAB function in Fortran |
The Fortran MEX API and Fortran Matrix API |
Pointer only on this page (see "Fortran MEX"). |
The documentation is blunt about the cost of MEX: MATLAB is a high-productivity environment whose strength is removing low-level programming, so write the program in MATLAB first, and use a MEX function only when the application needs it. Profile first (Profiling and Timing) and try the vectorized form before writing any C or C++. A compiled function also has real costs: a bug can crash the whole MATLAB session instead of raising an error, binaries are specific to a platform and a MATLAB release family, and every collaborator and CI machine needs a compiler (see Build Automation with buildtool for MexTask, which builds MEX files as part of a project, and Continuous Integration).
The heatflow kernel in plain C++
The stencil from Vectorization and Preallocation is
evaluated at the interior points of an nx-by-ny temperature matrix. Writing it as a function of raw pointers has two benefits: it is the same for every route on this page, and it can be compiled and tested with an ordinary C++ compiler, without MATLAB. The one thing to get right is the layout. MATLAB stores arrays column by column: element T(i,j) of an nx-by-ny matrix (one-based, row i, column j) is at the zero-based position (i-1) + (j-1)*nx of the buffer. In the zero-based C++ loops below the element in row i and column j is therefore T[i + j*nx]. A kernel that assumed row-major order, the usual layout in C and C++, would compute the stencil on the transpose.
// heatflow_stencil.hpp - pure C++ kernel, no MATLAB headers.
// Five-point Laplacian on a column-major nx-by-ny grid with spacing dx.
#ifndef HEATFLOW_STENCIL_HPP
#define HEATFLOW_STENCIL_HPP
#include <cstddef>
namespace heatflow {
// T has nx*ny elements and L has (nx-2)*(ny-2) elements, both column-major
// (element (i,j) of an nx-by-ny matrix is at i + j*nx, with zero-based i and j).
inline void laplacian5(const double* T, std::size_t nx, std::size_t ny,
double dx, double* L) {
const std::size_t mx = nx - 2;
for (std::size_t j = 1; j + 1 < ny; ++j) {
for (std::size_t i = 1; i + 1 < nx; ++i) {
L[(i - 1) + (j - 1) * mx] =
(T[(i - 1) + j * nx] + T[(i + 1) + j * nx] +
T[i + (j - 1) * nx] + T[i + (j + 1) * nx] -
4.0 * T[i + j * nx]) / (dx * dx);
}
}
}
} // namespace heatflow
#endif // HEATFLOW_STENCIL_HPP
The loop bounds are written j + 1 < ny so that the unsigned counters never underflow when a grid is smaller than 3-by-3 (the callers below reject such grids before the kernel runs). The inner loop runs over i, the row index, which is the fast index of the buffer, as in the MATLAB loop version.
A test driver that does not include any MATLAB header, with a field T(i,j) = i^3 + j^2 (zero-based i and j) on a 5-by-4 grid with dx = 0.5. The exact Laplacian of this field is 6i + 2 divided by the factor dx^2, so the result changes along the rows and is constant along the columns: a transposed layout would show it.
// Standalone check of the kernel: no MATLAB headers are involved.
#include <cstdio>
#include <vector>
#include "heatflow_stencil.hpp"
int main() {
const std::size_t nx = 5, ny = 4;
const double dx = 0.5;
std::vector<double> T(nx * ny);
for (std::size_t j = 0; j < ny; ++j)
for (std::size_t i = 0; i < nx; ++i)
T[i + j * nx] = static_cast<double>(i * i * i) + static_cast<double>(j * j);
std::vector<double> L((nx - 2) * (ny - 2));
heatflow::laplacian5(T.data(), nx, ny, dx, L.data());
for (std::size_t i = 0; i < nx - 2; ++i) {
for (std::size_t j = 0; j < ny - 2; ++j)
std::printf("%8.2f", L[i + j * (nx - 2)]);
std::printf("\n");
}
return 0;
}
Built and run here with Apple clang, invoked as g++ -std=c++17 -Wall -Wextra -pedantic -O2 test_stencil.cpp -o test_stencil (no warnings); the header also passes clang++ -std=c++11 -Wall -Wextra -fsyntax-only. Output of the compiled program:
32.00 32.00
56.00 56.00
80.00 80.00
The same test field and the same indexed MATLAB expression from the vectorization page, run in Octave 11.3.0 as the reference (T = I.^3 + J.^2 from meshgrid, then the four shifted slices); Octave 11.3.0 output, MATLAB prints the same values with its own layout:
32 32
56 56
80 80
The two agree, in values and in shape (3-by-2: two rows and two columns smaller than the 5-by-4 input). Everything below uses this header, so a stencil result that is wrong in a MEX function is a wrapper problem, not a kernel problem.
Source: MATLAB documentation: Data Types for Passing MEX Function Data (the note on row-major against column-major ordering)
Published interfaces: clibgen and the clib namespace
If you have a compiled C or C++ library that exports functionality, MATLAB can call its classes and functions directly. The person who builds the interface is called the publisher; the people who use it only call functions in the clib namespace. The documentation presents it as a two-sided workflow:
-
Publish: describe the library to MATLAB (headers, optional source and compiled library files), fill in what MATLAB cannot infer, and build an interface library with the same compiler that built the C or C++ library (for a header-only library any supported compiler will do).
-
Use: put the interface folder on the path and call
clib.<interfacename>.<function>, create objects of C++ classes, pass MATLAB arrays that MATLAB converts to the closest C or C++ type.
What MATLAB cannot infer is almost always the size of a pointer argument. A parameter declared const double* T is a pointer to one value or to an array of any length; the publisher says how long it is, usually by naming another argument. Until every construct is complete, MATLAB lists it as incomplete and leaves it out of the interface.
The R2026b workflow: clibgen.api objects
Since R2026b the recommended programmatic workflow is two objects. A clibgen.api.InterfaceConfiguration says what to include and where to find it (header files, include path, name standardization, build options). A clibgen.api.InterfaceDefinition is created from it, parses the headers, and lets you inspect (findClass, findFunction, findMethod), complete (define) and build the interface. Both objects can be saved and loaded again, and the script is meant to be kept in version control and re-run whenever the library changes, including from CI. The alternative is clibPublishInterfaceWorkflow (since R2023a), which opens a live script that walks through generate, define, build and test with a Live Editor task. Before R2026b the two options were that live script or a definition file made with clibgen.generateLibraryDefinition (next section).
For heatflow, a small header-only library with two functions that a program outside MATLAB might also use. The pointer function is the typical "incomplete" case.
// heatflow_stats.hpp - header-only library; plain global functions.
#ifndef HEATFLOW_STATS_HPP
#define HEATFLOW_STATS_HPP
#include <cstddef>
// Largest stable explicit time step, dt <= dx^2 / (4 alpha).
inline double hfStableTimeStep(double dx, double alpha) {
return dx * dx / (4.0 * alpha);
}
// Mean of n temperatures stored contiguously.
inline double hfMeanTemperature(const double* T, std::size_t n) {
double s = 0.0;
for (std::size_t k = 0; k < n; ++k) {
s += T[k];
}
return n > 0 ? s / static_cast<double>(n) : 0.0;
}
#endif // HEATFLOW_STATS_HPP
The publishing script follows the structure of the documentation’s programmatic example (matrixOperations): configure, create the definition, find the incomplete function and define the size of the pointer argument by naming the length argument, build, add the interface folder to the path. Not run here (it needs a supported C++ compiler and MATLAB R2026b).
% publishHeatflowStats.m - publish a MATLAB interface to heatflow_stats.hpp
icfg = clibgen.api.InterfaceConfiguration("heatflowstats");
icfg.HeaderFiles = fullfile(pwd,"heatflow_stats.hpp");
idef = clibgen.api.InterfaceDefinition(icfg);
idef.IncompleteFunctions % hfMeanTemperature: Size of T is missing
fcnDef = idef.findFunction("hfMeanTemperature");
argDef = fcnDef.CPPInputs([fcnDef.CPPInputs.Name] == "T");
argDef.define(Size="n"); % T holds n elements, n is the next argument
build(idef)
addpath(fullfile(idef.OutputFolder,idef.InterfaceName))
Calling the library is ordinary MATLAB. The interface name becomes part of the full name: clib.heatflowstats. Not run here.
dt = clib.heatflowstats.hfStableTimeStep(0.1,1e-4); % 25 (dx^2/(4*alpha))
m = clib.heatflowstats.hfMeanTemperature([290 300 310 320]); % 305
help clib.heatflowstats.hfMeanTemperature
The values in the comments are what the formula gives. They were computed by compiling the same arithmetic as a plain C function in a small C program and running it here (it printed 25 305); the MATLAB calls themselves were not run.
During development, run the interface library out of process so that rebuilding does not require restarting MATLAB, and so that a crash in the library does not take MATLAB down with it. Call clibConfiguration before the first use of the library. Not run here.
cfg = clibConfiguration("heatflowstats",ExecutionMode="outofprocess")
cfg.unload % release the library after a rebuild
In-process execution is the default and the one to use when performance matters. Out-of-process execution also helps when the library needs a different version of a third-party library than the one MATLAB itself ships, and Ctrl+C can interrupt a call (since R2025a). When the library functions use MATLAB arrays, the MATLAB root folder has to be on the system path (see "Set Run-Time Library Path for C/C++ Interface" in the documentation).
The older route: clibgen.generateLibraryDefinition
Before R2026b the programmatic route generated a definition file. clibgen.generateLibraryDefinition(InterfaceGenerationFiles,Name=Value) writes define<libname>.m (file name define<libname>.m since R2022b; earlier releases wrote a Live Script .mlx), where <libname> is the name of the first file unless you pass InterfaceName (called PackageName before R2024a). Constructs it can fully define are written as code; incomplete ones are written as commented-out, partly filled code that you uncomment and complete by hand. Then build on the definition file creates the interface.
% Not run here. Generate the definition file, edit it, then build the interface.
hFile = fullfile(pwd,"heatflow_stats.hpp");
clibgen.generateLibraryDefinition(hFile) % writes defineheatflow_stats.m
edit defineheatflow_stats.m % complete the Size of the pointer argument
build(defineheatflow_stats)
addpath("heatflow_stats")
Two details of this form: the argument can be a header file (.h, .hpp, .hxx) or source file (.c, .cpp, .cxx); a library that is completely defined in the headers needs no Libraries argument; IncludePath names the folders of headers included by the main header; and with CLinkage=true a .h file is treated as C++-compatible C code. R2024a added RootPaths so that definition files can use paths relative to an absolute base and be shared (for example on GitHub) between publishers with the same library layout. clibgen.buildInterface creates an interface without a definition file and is also "not recommended" in R2026b. There are no plans to remove either function, and existing definition files still build; for new work, use the objects.
Types, limits and sharing
The conversion between MATLAB and C or C++ types is automatic and goes from the closest match to the least close one: a MATLAB double array is passed to a double buffer, std::vector<double> or fixed-size array; a MATLAB int32 array to an int32_t one, and so on. The documentation has a full table ("MATLAB Type to C++ Type Mapping"), and separate topics for structures, const and non-const parameters, passing by reference, function pointers, templates, std::vector, opaque objects, and the clib.array objects that wrap native C++ arrays. Not every C++ feature is supported ("Limitations to C/C++ Support" lists them, with workaround examples), and the lifetime rules for C++ objects created from MATLAB have their own topic. A finished interface is distributed as a package file (.mltbx; since R2026b the name for what used to be called a toolbox file), with the run-time libraries copied next to it. Packages are the subject of the page on packages and the MATLAB Package Manager.
loadlibrary and calllib: the older route for C
For a C shared library (.dll, .so or .dylib) and a header file that a C compiler can parse, loadlibrary reads the header, builds a table of the functions, and calllib calls one by name. Functions written in C++ must be declared extern "C" to be visible this way (see C++: Program Structure and Compilation for what extern "C" does). The documentation says in several places that the recommended way to call C and C++ libraries is the published interface of the previous section (since R2022a), and that the C topic is for MATLAB R2021b or earlier; the functions remain documented in R2026b.
loadlibrary(libname,hfile) % library and header
loadlibrary(libname) % header has the same name as the library
loadlibrary(libname,hfile,Name=Value) % addheader, alias, includepath, mfilename, thunkfilename
loadlibrary(libname,@protofile) % prototype file instead of a header
[notfound,warnings] = loadlibrary(___)
[x1,...,xN] = calllib(libname,funcname,arg1,...,argN)
libfunctions(libname,"-full") % function signatures as MATLAB sees them
libfunctionsview(libname) % the same in a window
libisloaded(libname)
unloadlibrary(libname)
A C library for heatflow with a plain header. The two functions are the C versions of the ones above; The length is an int to keep the prototype simple.
/* heatflow_c.h - plain C interface that loadlibrary can parse. */
#ifndef HEATFLOW_C_H
#define HEATFLOW_C_H
/* Largest stable explicit time step, dt <= dx^2 / (4 alpha). */
double hf_stable_dt(double dx, double alpha);
/* Mean of n temperatures. */
double hf_mean(const double *T, int n);
#endif /* HEATFLOW_C_H */
/* heatflow_c.c - build as a shared library, for example:
* gcc -shared -fPIC -o heatflow_c.so heatflow_c.c (Linux)
*/
#include "heatflow_c.h"
double hf_stable_dt(double dx, double alpha)
{
return dx * dx / (4.0 * alpha);
}
double hf_mean(const double *T, int n)
{
double s = 0.0;
int k;
for (k = 0; k < n; ++k) {
s += T[k];
}
return n > 0 ? s / n : 0.0;
}
Compiled here with cc -std=c99 -Wall -Wextra -pedantic -shared -fPIC (no warnings) and, linked directly into a driver, called with hf_stable_dt(0.1,1e-4) and hf_mean of 290, 300, 310 and 320; the program printed 25 305. The MATLAB session below was not run (loadlibrary needs a supported C compiler and Perl, and the library file name must match the libname argument, which is case-sensitive).
% Not run here.
loadlibrary("heatflow_c","heatflow_c.h") % library file heatflow_c.dll / .so / .dylib
libfunctions("heatflow_c","-full") % pointer arguments carry a Ptr suffix
dt = calllib("heatflow_c","hf_stable_dt",0.1,1e-4);
m = calllib("heatflow_c","hf_mean",[290 300 310 320],int32(4));
unloadlibrary("heatflow_c")
Four details decide whether calllib works. MATLAB checks the argument types against the prototype before it calls, so a mismatch fails with an error about data types: look at libfunctions(libname,"-full") and convert the arguments (int32(4) above). Pointer arguments are passed with a libpointer when the function must write through them or when the value is a pointer to a pointer; structures need libstruct. A header that includes other headers needs the addheader option or includepath. And do not call loadlibrary for a library that is already loaded (test with libisloaded), nor leave a library loaded when you rebuild it. The documentation also lists library features that are not supported ("Limitations to Shared Library Support"), and a prototype file (generated with mfilename) lets you rename functions with an alias or fix the signature of a function the header parser misreads.
Source: MATLAB documentation: loadlibrary, calllib and Call C from MATLAB
Setting up the compiler: the mex command
Every MEX function, and every interface built with clibgen, needs a C or C++ compiler that MATLAB supports; the list is on the MathWorks pages "Supported and Compatible Compilers". Select the compiler for each language once:
mex -setup C++ % default compiler for C++ MEX functions (also: cpp, CPP)
mex -setup c % default compiler for C
mex -setup -client engine C++ % compiler for standalone engine and MAT-file applications
Called without a language, mex -setup shows the C compiler only. If you reinstall a compiler, run mex -setup again to refresh the configuration. mex.getCompilerConfigurations returns information about the configured compilers. The documentation recommends a first build of the example arrayProduct.cpp that ships with MATLAB to test the environment (it is copied from fullfile(matlabroot,"extern","examples","cpp_mex")). Not run here.
copyfile(fullfile(matlabroot,"extern","examples","cpp_mex","arrayProduct.cpp"),".")
mex -setup C++
mex arrayProduct.cpp
a = arrayProduct(2,[1 2;3 4]) % the documentation shows [2 4; 6 8]
mex filenames compiles one or more C, C++ or Fortran sources, object files and static libraries into a binary MEX file in the current folder. The first file named gives the MEX file its name unless you override it with -output. The binary has a platform-specific extension (mexext returns it; the documentation lists mexw64 for Windows, mexa64 for Linux and mexmaca64 for macOS on Apple silicon), so a MEX file built on one platform does not run on another. The options used most often:
| Option | Effect |
|---|---|
|
Verbose: prints the fully evaluated compile and link steps. The first thing to try when a build fails. |
|
Prints the commands without running them. |
|
Adds debugging symbols and turns optimization off. Optimization ( |
|
Name of the MEX file; folder for all output files. |
|
Include path; library folder and dynamic library ( |
|
Define or remove a preprocessor macro. |
|
Compile only; produces an object file. |
|
Append compiler or linker flags (Windows with Visual Studio: |
|
Select the release-specific C Matrix API (see below). Not for the C++ MEX API. |
|
Build a standalone engine or MAT-file application instead of a MEX function. |
For the C++ MEX API the headers are mex.hpp and mexAdapter.hpp from matlabroot/extern/include, the same on every platform. The compiler must support C++11. If a project grows beyond one file, mexAdapter.hpp is included once, in the file that defines the MexFunction class. Building the MEX file from a larger C++ project with CMake instead of mex is possible too (the documentation has "Custom Build with MEX Script Options" and engine build topics); the C++ side of that is C++: Build and Tooling, and the C side is C: Build and Tooling.
The C++ MEX API
Anatomy of a C++ MEX function
A C++ MEX function is a class named MexFunction that derives from matlab::mex::Function and overrides the function call operator, operator(), so that its instances are function objects. MATLAB passes the arguments as two matlab::mex::ArgumentList containers: inputs, with one matlab::data::Array per input argument, and outputs, in which the function stores the results. inputs.size() is the number of arguments in the call and outputs.size() the number of requested outputs; ArgumentList supports [], size, empty and iterators, so range-based for loops work. The three namespaces are matlab::mex (the MEX interface), matlab::data (the Data API: arrays) and matlab::engine (the C++ Engine API, to call MATLAB from the function).
Things worth knowing about the lifecycle: MATLAB creates one instance of the class the first time the MEX function is called, and it lives until you run clear mex (or clear the function), so members can hold state, and a constructor and destructor can open and close external resources. The base class provides getEngine() (a std::shared_ptr<matlab::engine::MATLABEngine>), mexLock and mexUnlock (prevent or allow clearing of the MEX file from memory) and getFunctionName. Engine methods such as feval, eval, getVariable, setVariable, getProperty and setProperty must be called on the same thread as the Function object.
mexFunction against the MexFunction class
| C MEX API | C++ MEX API | |
|---|---|---|
Entry point |
A C function |
A class |
Arguments |
Counts |
Two |
Header |
|
|
Memory |
|
Value-like arrays with copy-on-write; smart pointers and iterators; no raw data pointers by default |
Errors |
|
Call the MATLAB |
Complex data |
Separate ( |
Complex arrays are their own type ( |
Works in |
Any release; the only choice for R2017b and earlier |
R2018a and later |
The two APIs cannot be mixed in one MEX file. Use matlab::data types or mxArray functions, never both. The -R2017b and -R2018a options of mex are for the C Matrix API and are not used for C++ code written with the Data API.
Data types and shared copies
Everything is a matlab::data::Array; more specific classes give more functionality: TypedArray<T> (numeric, logical, char16_t, complex std::complex<T>, MATLABString for string arrays, and others; it supports iterators and indexing), CharArray (converters for ASCII and UTF-16), CellArray, StructArray, and plain Array for MATLAB objects. You create arrays with a matlab::data::ArrayFactory:
matlab::data::ArrayFactory factory;
auto a = factory.createScalar(2.5); // 1-by-1 double
auto b = factory.createArray<double>({1, 3}, {1.0, 2.0, 3.0}); // 1-by-3 double
auto c = factory.createCharArray("heatflow"); // char row vector
auto d = factory.createArray<double>({2, 2}, {1.0, 3.0, 2.0, 4.0}); // [1 2; 3 4]
The last line shows the trap from the earlier section: an initializer list is read in the order of the memory layout, column by column, so [1 2; 3 4] is written 1, 3, 2, 4. (The documentation notes the row-major against column-major difference for exactly this reason.) The Data API can also describe a different memory layout with getMemoryLayout (COLUMN_MAJOR or ROW_MAJOR), which makes it easier to adapt row-major C++ data, and createArrayFromBuffer takes the layout as its last argument.
Shared copies and copy-on-write. The Data API follows the same copy-on-write rules as MATLAB code. An input array is a shared copy of the MATLAB variable, and a copy of the data is made only when the function modifies the array. Two idioms keep the copies away:
-
To only read an input, assign it to a
const TypedArray<T>: it stays shared and the data is never copied. -
To modify an input and return it,
std::moveit into a non-constTypedArray<T>after validating it, and do not useinputs[k]again. MATLAB then does not copy the array.
The documentation’s example of the second case is called as arry = removeNegativeNumbers(arry), reassigning the result to the variable that was passed, so that no second copy of the large array stays alive.
No raw pointers. The Data API deliberately hides the data pointer. For TypedArray<T> you get iterators and a zero-based, linear operator[]; release() hands over the buffer as a std::unique_ptr and (since R2024b) first unshares the array, copying the data if it is shared. getReadOnlyElements and getWritableElements return ranges for iteration. To feed a pointer-based kernel such as the stencil above, the choices are to copy the input into a std::vector<double> (one pass over the data, what the listing below does), or to work through iterators. On the output side there is a copy-free way: createBuffer<T>(n) allocates an uninitialized buffer, the kernel writes into it, and createArrayFromBuffer<T>(dims,std::move(buffer)) turns it into an array that MATLAB owns.
The heatflow stencil as a C++ MEX function
The wrapper validates the arguments, adapts the data to the kernel and builds the output. The kernel is the header from the beginning of this page, with no MATLAB dependency, so it compiles into the MEX file unchanged. Errors are raised by calling the MATLAB error function through feval, with a message identifier (here heatflow:laplacianMex:..., names chosen for this page) and a message; the first argument of feval is the function name as a UTF-16 string, then the number of outputs (zero), then a std::vector<matlab::data::Array> of arguments. Not run here.
// laplacianMex.cpp - L = laplacianMex(T, dx): five-point Laplacian of the
// interior of a real double matrix T, with the C++ MEX API.
// Build with: mex laplacianMex.cpp
#include <cstddef>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "mex.hpp"
#include "mexAdapter.hpp"
#include "heatflow_stencil.hpp"
class MexFunction : public matlab::mex::Function {
matlab::data::ArrayFactory factory;
public:
void operator()(matlab::mex::ArgumentList outputs,
matlab::mex::ArgumentList inputs) {
checkArguments(outputs, inputs);
// A const TypedArray stays a shared copy of the MATLAB variable.
const matlab::data::TypedArray<double> T = inputs[0];
const double dx = inputs[1][0];
const matlab::data::ArrayDimensions dims = T.getDimensions();
const std::size_t nx = dims[0];
const std::size_t ny = dims[1];
// The Data API hides the data pointer, so copy once into a vector
// (column-major order, the same order as MATLAB).
std::vector<double> in;
in.reserve(T.getNumberOfElements());
for (const double& v : T) {
in.push_back(v);
}
// Write the result straight into a buffer that MATLAB then owns.
auto buffer = factory.createBuffer<double>((nx - 2) * (ny - 2));
heatflow::laplacian5(in.data(), nx, ny, dx, buffer.get());
outputs[0] = factory.createArrayFromBuffer<double>({nx - 2, ny - 2},
std::move(buffer));
}
private:
void fail(const std::string& id, const std::string& message) {
std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr = getEngine();
matlabPtr->feval(u"error", 0,
std::vector<matlab::data::Array>(
{factory.createCharArray(id),
factory.createCharArray(message)}));
}
void checkArguments(matlab::mex::ArgumentList outputs,
matlab::mex::ArgumentList inputs) {
using matlab::data::ArrayType;
if (inputs.size() != 2) {
fail("heatflow:laplacianMex:nargin", "Two inputs required: T and dx.");
}
if (outputs.size() > 1) {
fail("heatflow:laplacianMex:nargout", "At most one output.");
}
if (inputs[0].getType() != ArrayType::DOUBLE ||
inputs[0].getDimensions().size() != 2) {
fail("heatflow:laplacianMex:type", "T must be a real double matrix.");
}
const matlab::data::ArrayDimensions dims = inputs[0].getDimensions();
if (dims[0] < 3 || dims[1] < 3) {
fail("heatflow:laplacianMex:size", "T must be at least 3-by-3.");
}
if (inputs[1].getType() != ArrayType::DOUBLE ||
inputs[1].getNumberOfElements() != 1) {
fail("heatflow:laplacianMex:dx", "dx must be a real double scalar.");
}
const double dx = inputs[1][0];
if (!(dx > 0.0)) {
fail("heatflow:laplacianMex:dx", "dx must be positive.");
}
}
};
Points to read in the listing. ArrayType::DOUBLE is the real double type; a complex array reports COMPLEX_DOUBLE, so one comparison rejects both integers and complex data. Each fail call ends in a MATLAB error, which leaves the function, so the code after a failed check does not run (the checks are ordered so that later ones can rely on earlier ones: inputs[1] is only touched when two inputs exist). The class has no constructor and no state, which is the right shape for a pure function; factory is a member only because several methods use it. The kernel gets in.data() and the buffer’s pointer, never a MATLAB object. And the output has the right shape because createArrayFromBuffer reads its buffer column-major by default, the same layout the kernel writes.
Build and test from MATLAB. A test that compares with the indexed MATLAB version from the vectorization page is the most useful one, and the five lines are written so that they could go into a function-based test (see Unit Testing with matlab.unittest) in the project. Not run here: the expected result is the shape and values shown for the plain C++ program and for the Octave reference above.
mex laplacianMex.cpp % creates laplacianMex.mexw64 / .mexa64 / .mexmaca64
nx = 5; ny = 4; dx = 0.5;
[J,I] = meshgrid(0:ny-1,0:nx-1);
T = I.^3 + J.^2;
expected = heatflow.laplacianIndexed(T,dx);
actual = laplacianMex(T,dx);
assert(isequal(size(actual),[3 2]))
assert(max(abs(actual - expected),[],"all") < 1e-12)
try
laplacianMex(ones(2),dx) % too small
catch ME
disp(ME.identifier) % heatflow:laplacianMex:size (the identifier set in the source)
end
Because every error comes from the source above, the identifier shown in the last comment is the one written there; MATLAB’s own messages for other mistakes (for example calling with the wrong number of output arguments) were not captured.
The performance of the compiled version against the vectorized one has not been measured on this page, and no number is claimed. The stencil is a memory-bound kernel that vectorized MATLAB code already runs in optimized loops, so a MEX version is worth building only if timeit on your own data says it is. The one thing a MEX version can do that vectorized code cannot is to compute in place, without the temporary arrays that the four shifted slices create.
Out of process, threads and the other topics
C++ MEX functions can run in a separate process with no code change. mh = mexhost creates a MEX host process (a matlab.mex.MexHost object) and feval(mh,"laplacianMex",T,dx) runs the function in it. That isolates a crash in the MEX function from the MATLAB process, lets you use a third-party library that conflicts with MATLAB’s own, lets you attach a debugger to the host, and lets Ctrl+C interrupt the call. Data passed between the processes is limited to 2 GB, and the function runs in the MATLAB current folder. Not run here.
mh = mexhost;
actual = feval(mh,"laplacianMex",T,dx);
The remaining topics of the C++ MEX documentation, each on its own page, are calling MATLAB functions from the MEX function (feval, eval), getting and setting variables of the base and global workspaces, MATLAB objects and properties, MEX functions as class methods, calling MATLAB from user-created threads with the asynchronous Engine API, and catching exceptions. The MEX API is not thread safe ("MEX API Is Not Thread Safe" in the documentation), and a thread that you create yourself may call MATLAB only through the asynchronous methods of the Engine API.
The C MEX API: interleaved complex and typed accessors
The C MEX API is the older one and is still fully supported. It is the choice when the code must run in R2017b or earlier, or when you prefer C. Every C MEX function has one entry point, the gateway mexFunction; MATLAB finds the symbol with that name in the binary and calls it with nlhs, plhs, nrhs and prhs. MATLAB data are mxArray pointers, created with functions such as mxCreateDoubleMatrix and inspected with mxIsDouble, mxIsComplex, mxGetM, mxGetN and mxGetNumberOfDimensions. mexErrMsgIdAndTxt raises an error with an identifier and does not return. You may return a value even when nlhs is 0 (it becomes ans).
Interleaved complex. Before R2018a, the C Matrix API stored the real and imaginary parts of a complex array in two separate arrays. Since R2018a there is also an interleaved complex API, in which each complex number is stored as its real and imaginary parts side by side, the layout of most numerical libraries, so no translation is needed. It brings a typed data access family: mxGetDoubles, mxGetSingles, mxGetInt8s … mxGetUint64s for real data and mxGetComplexDoubles and the like for complex data (and matching mxSet...), which check the class and the complexity of the array. You select the API when building:
| Build option | What you get |
|---|---|
|
The separate complex API, the large-array-handling API (arrays with more than |
|
The interleaved complex API with typed accessors, large arrays, graphics handles as objects. Recommended for new code. For Fortran, an R2018a MEX file needs R2018a Update 3 or later. |
|
The separate complex API with large-array handling. |
|
The separate complex API with the version 7.2 array handling ( |
To write one source file for both complex APIs, the macro MX_HAS_INTERLEAVED_COMPLEX tells the preprocessor which one is in use.
mxGetPr is legacy. mxGetPr (and mxGetPi) return the real part pointer of a double array without checking the class. In R2026b mxGetPr is marked "not recommended": use mxGetDoubles for real arrays of class double and mxGetComplexDoubles for complex ones; both validate the type and the complexity. In a MEX file built with -R2018a, calling mxGetPr on a complex array terminates the MEX function with an error. This is what "old MEX API" means in books of the mxGetPr period.
The heatflow stencil as a C MEX function, using the typed accessor and the interleaved API. mxDouble is the double type of the C Matrix API. The code duplicates the kernel instead of including the C++ header, because C has no namespace, inline in the C++ sense, or std::size_t. Not run here; it also passes a C99 syntax check against a stand-in mex.h (see the introduction).
/* laplacianMexC.c - L = laplacianMexC(T, dx): five-point Laplacian of the
* interior of a real double matrix T. Build with: mex -R2018a laplacianMexC.c */
#include "mex.h"
static void laplacian5(const mxDouble *T, size_t nx, size_t ny,
double dx, mxDouble *L)
{
const size_t mx = nx - 2;
size_t i, j;
for (j = 1; j + 1 < ny; ++j) {
for (i = 1; i + 1 < nx; ++i) {
L[(i - 1) + (j - 1) * mx] =
(T[(i - 1) + j * nx] + T[(i + 1) + j * nx] +
T[i + (j - 1) * nx] + T[i + (j + 1) * nx] -
4.0 * T[i + j * nx]) / (dx * dx);
}
}
}
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
size_t nx, ny;
double dx;
if (nrhs != 2) {
mexErrMsgIdAndTxt("heatflow:laplacianMexC:nargin",
"Two inputs required: T and dx.");
}
if (nlhs > 1) {
mexErrMsgIdAndTxt("heatflow:laplacianMexC:nargout",
"At most one output.");
}
if (!mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) ||
mxGetNumberOfDimensions(prhs[0]) != 2) {
mexErrMsgIdAndTxt("heatflow:laplacianMexC:type",
"T must be a real double matrix.");
}
if (!mxIsDouble(prhs[1]) || mxIsComplex(prhs[1]) ||
mxGetNumberOfElements(prhs[1]) != 1) {
mexErrMsgIdAndTxt("heatflow:laplacianMexC:dx",
"dx must be a real double scalar.");
}
nx = mxGetM(prhs[0]);
ny = mxGetN(prhs[0]);
dx = mxGetScalar(prhs[1]);
if (nx < 3 || ny < 3) {
mexErrMsgIdAndTxt("heatflow:laplacianMexC:size",
"T must be at least 3-by-3.");
}
if (!(dx > 0.0)) {
mexErrMsgIdAndTxt("heatflow:laplacianMexC:dx",
"dx must be positive.");
}
plhs[0] = mxCreateDoubleMatrix((mwSize)(nx - 2), (mwSize)(ny - 2), mxREAL);
laplacian5(mxGetDoubles(prhs[0]), nx, ny, dx, mxGetDoubles(plhs[0]));
}
Build with mex -R2018a laplacianMexC.c and call it exactly like laplacianMex. The C version reads the input in place (no copy), which is the practical advantage of the C API for large numerical arrays; the price is that every pointer is yours to keep valid. The C API also returns memory to MATLAB differently: arrays created with mxCreate... that are assigned to plhs belong to MATLAB, memory from mxMalloc is freed automatically when the MEX function returns unless it is made persistent, and arrays obtained with mexGetVariable or created for temporary use follow their own rules ("Memory Management Issues" in the documentation). The input arrays prhs must not be modified.
Fortran MEX
MEX functions can also be written in Fortran, with the Fortran MEX API and the Fortran Matrix API, which are based on the same mxArray data structure as the C ones and have the same typed accessors (mxGetDoubles and the others) for interleaved complex data. The build is the same mex command with a Fortran source file; mex -largeArrayDims and -R2018a apply to Fortran as well. The MATLAB documentation’s Fortran entry point is the page Write Fortran Functions Callable from MATLAB (MEX Files).
This section is a pointer, not a tutorial. The Fortran Reference of this site covers the Fortran side with the same heatflow scenario, in MATLAB: MEX, Engine and MAT-File APIs (issue #277): the gateway, the Fortran Matrix API, the same stencil as a Fortran MEX function, and the Engine and MAT-file APIs. Read it for Fortran; the build options and the mxArray model on this page apply to it unchanged. For the same ideas from other languages, see FFI & C Interop (Rust) and C: Pointers for the pointer rules a C MEX function depends on.
Pitfalls
| Symptom | Cause | Remedy |
|---|---|---|
MATLAB crashes, sometimes after the MEX function has returned |
A write outside an array or through a null pointer corrupts memory; the failure can appear later than the bug ("MEX File Segmentation Fault") |
Validate sizes first; check pointers before use; run MATLAB under a debugger ( |
The result is the transpose, or the stencil looks wrong at the edges |
The kernel assumed row-major order |
MATLAB is column-major: element |
An initializer list gives the wrong matrix |
|
Write the elements in column order, or pass the layout argument. |
A rebuilt MEX function behaves like the old one |
The C++ |
|
|
The array is complex, and the interleaved API does not allow |
Use |
Compile errors or odd behaviour when two APIs are used together |
|
Choose one API per MEX file. |
The input array of a C MEX function changes in the caller |
The function wrote through |
Never modify inputs; create an output array and write into that. |
An |
The MEX file was built for another platform, another API version or by an incompatible compiler |
Rebuild on the target machine with the same MATLAB release; see the troubleshooting pages "Invalid MEX File Errors", "MEX Version Compatibility" and "MEX Platform Compatibility". |
A published interface cannot be built, or |
The compiler differs from the one that built the library, or a pointer argument has no size |
Use the same compiler for the interface and the library; list |
|
The MATLAB arguments do not match the prototype that |
Inspect |
Where older books differ
The MEX chapters of the older book consulted for this section (Davis, MATLAB Primer, written for R2010a) describe MEX files in C and in Fortran. Their forms are older than every API on this page except the C Matrix API: no C++ Data API (R2018a), no interleaved complex API and typed accessors (mxGetDoubles and the others, R2018a), no clibgen published interface (R2019a) with clibPublishInterfaceWorkflow (R2023a) and the clibgen.api objects (R2026b), no out-of-process execution. Code that uses mxGetPr and mxGetPi for double data still builds with the default -R2017b option, but mxGetPr is "not recommended" in R2026b and stops a function built with -R2018a when it meets a complex array; the typed accessors are the replacement. Code that passed array sizes as int rather than size_t/mwSize predates the large-array API (-largeArrayDims) and needs updating ("Upgrade MEX Files to Use 64-Bit API" in the documentation). loadlibrary and calllib are old enough to be in those books, and still exist, but the published interface is the documented way to call C and C++ libraries.