MATLAB: MEX, Engine and MAT-File APIs
|
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. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1) and the MathWorks documentation for MATLAB
R2026b, both read on 2026-10-19. No MATLAB is installed in the toolchain used for this page, so no MEX file, Engine
program or MAT-file program was built or run against MATLAB. The MATLAB behaviour described here is taken from the
MathWorks reference pages named in the references, and every MATLAB statement is "checked against the docs, not run". What
was done is narrower and is stated for each listing: the Fortran source was compiled warning-free with
gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all (GNU Fortran 16.2) against a stand-in
fintrf.h that is part of this page, and with LLVM Flang 23. The gateway was also run against a small mock of the
dozen API entry points it uses (a C file that imitates the gfortran calling convention; it is not MATLAB, and it only proves
that the Fortran side, the argument contract and the algorithm behave as intended). The deliberately wrong listings show real
compiler diagnostics. A future MATLAB section of this site (issue #248) is the place for MATLAB-side usage; this page is the Fortran side.
The running example is the explicit FTCS kernel of heatflow, ftcs_sweep from heatflow_kernels, exposed to MATLAB as
[unew, residual] = heatflow_mex(u, steps). For the C view of the same kernel see
Interoperability with C and
Calling Fortran from C, C++ and Python; the
iso_c_binding background is there too. Array order (both languages are column-major) is in
Arrays Fundamentals, kinds in
Intrinsic Types and Kinds, pointers in
Pointers and Targets, and interfaces in
Interfaces, Generics and Operator Overloading.
Three ways to connect Fortran and MATLAB
MathWorks documents Fortran in four pieces: the MEX API, the Matrix API, the Engine API and the MAT-file API. They differ in who starts whom.
| Interface | Direction | Typical use |
|---|---|---|
MEX file ( |
MATLAB calls your Fortran. The compiled library ( |
speed up a kernel, reuse a Fortran library from MATLAB |
Engine API ( |
your standalone Fortran program starts MATLAB and sends it commands |
use MATLAB plotting or toolboxes from a simulation |
MAT-file API ( |
your standalone Fortran program reads and writes |
exchange data with MATLAB users |
The Matrix API (mx* routines) is shared by all three: it creates, inspects and destroys the mxArray objects that carry
MATLAB data. Source: MathWorks: Fortran with MATLAB (R2026b) and
Write Fortran Functions Callable from MATLAB (MEX Files).
The path of one MEX call, from the MATLAB command line to the Fortran kernel and back:
The gateway: mexFunction
A Fortran MEX file is one subroutine with a fixed name and four arguments. It replaces the main program:
subroutine mexFunction(nlhs, plhs, nrhs, prhs)
integer nlhs, nrhs ! numbers of outputs and inputs the caller asked for
mwPointer plhs(*), prhs(*) ! pointers to the mxArray objects
end subroutine mexFunction
-
nlhsandnrhsare the numbers of left-hand-side (output) and right-hand-side (input) arguments of the MATLAB call. -
prhsholdsnrhspointers to the inputs. They are read only: do not modify them. To return a modified input, copy it withmxDuplicateArrayand return the copy. -
plhsreceives the outputs; the gateway stores the pointer to each output array there. -
The name of the source file sets the name of the MATLAB function, and the file that holds
mexFunctionmust be the first one listed on themexcommand line. PutmexFunctionafter the computational routines in the file. -
A common design is a thin gateway that validates the inputs and converts them, and a separate computational routine that knows nothing about MATLAB. In heatflow that routine is
ftcs_sweep, which the C API and the Python wrapper also use.
Source: MathWorks: Components of Fortran MEX File and mexFunction.
fintrf.h and the pointer and size types
MATLAB values are mxArray objects that Fortran cannot see into, so the API passes pointers as integers. Fortran has no
portable pointer-sized integer, so MathWorks supplies a header, fintrf.h, of preprocessor macros, and the source file must
therefore be preprocessed (an upper-case .F or .F90 extension in most compilers, see the next section).
| Macro | Meaning in the documentation | Notes |
|---|---|---|
|
a pointer to an |
introduced in R2006a; use it for every pointer function, argument and variable |
|
array sizes such as dimensions; documented as unsigned, |
a size is never a default |
|
index values, |
used for sparse index arrays |
|
a signed integer type for sizes, |
introduced in R2009a |
Source: mwPointer, mwSize, mwIndex and mwSignedIndex.
Two consequences matter in practice. First, mixing a default integer (4 bytes) and a mwSize (8 bytes on a 64-bit
platform) is the classic MEX bug, and it is silent with implicit interfaces (an example follows below). Second, INTEGER*8
is not standard Fortran: the standard only knows integer(kind=k) (7.4.3.2) and the kind numbers are processor dependent, so a
file that includes the real fintrf.h cannot be compiled with -std=f2023. That is why a MEX build is done by mex,
which supplies the compiler flags, and why this page uses a stand-in header for its strict checks:
! Stand-in for MathWorks' fintrf.h, for compile checks and mock runs only.
! The real header (from matlabroot/extern/include) maps these macros to INTEGER*8 or INTEGER*4.
#define mwPointer integer(8)
#define mwSize integer(8)
#define mwIndex integer(8)
#define mwSignedIndex integer(8)
The stand-in is not the MathWorks header, which is installed with MATLAB (under matlabroot/extern/include) and whose
contents are not reproduced here. It only gives the macros the shape the documentation describes, with a 64-bit pointer.
Building: the mex command
mex compiles and links a MEX file with the Fortran compiler it finds (mex -setup fortran selects or shows it). The options
that matter for Fortran, from the R2026b mex reference:
| Option | Effect |
|---|---|
|
the default API: separate real and imaginary arrays for complex data, large-array handling. The page says the default will change to |
|
the interleaved complex API, including the typed accessors such as |
|
separate complex data, large arrays (the |
|
the old API limited to 231-1 elements; do not use it with BLAS or LAPACK calls |
|
put the output files in a folder |
|
name the MEX file explicitly instead of after the first source file |
|
compile to an object only, then pass the object to a later link command |
|
change the default Fortran compiler for MEX files ( |
-
Source form. The upper-case
.Fextension is recommended for platform independence, because a lower-case.ffile is assumed to be already preprocessed. The R2026b page says that as of R2026amexbuilds fixed-form and free-form Fortran, including.F90. Older releases are documented for fixed-form.Fonly. -
Several sources.
mex -R2018a heatflow_mex.F90 heatflow_kinds.f90 ...lists the gateway first; the first source names the MEX file. -
Modules. A module must be compiled before the files that
useit, exactly as in Modules and Submodules, so list the module files before the gateway. A practical layout builds the Fortran library with the project’s own build system and links it into the MEX file. -
Rebuild after upgrades. Since R2025a the constant
mxMAXNAMis 2048 instead of 63, and MathWorks tells you to recompile MEX, Engine and MAT-file applications that use it.
Supported compilers are listed on the MathWorks page MATLAB supported compilers,
per platform and release. The Windows page read for this document lists MinGW 8.1 Fortran, Intel oneAPI 2025 for Fortran
and Intel oneAPI 2024 for Fortran (the last needs Microsoft Visual Studio too), but it did not name the MATLAB release it
applied to and the Linux and macOS lists are on other pages, so check the page for your release and platform. The mex
reference lists support for Intel ifx from R2024b, MinGW Fortran from R2024a and NAG on Apple silicon from R2023b. LLVM Flang
and gfortran are not named as supported Fortran compilers on the pages read here; a MEX file must be built with a compiler
MATLAB supports, whatever else is used to develop the kernel.
Source: MathWorks: mex.
A modern gateway for the heatflow kernel
The MathWorks examples declare the API functions with a type statement and external, so the compiler cannot check any call.
A better pattern is a module with interface blocks for the routines in use. The module below is ours, not MathWorks': the
documentation ships headers only. Each interface follows the Fortran syntax of the reference page for that routine (for
example mwPointer mxCreateDoubleMatrix(m, n, ComplexFlag) with mwSize sizes and an integer*4 flag, and subroutine
mxDestroyArray(pm)), and replaces real*8 and integer*4 with real64 and int32 from iso_fortran_env:
#include "fintrf.h"
! Explicit interfaces for the part of the MATLAB Fortran API used by the heatflow gateway.
! MathWorks ships no Fortran module: the names below are the documented ones, the interfaces are ours.
module matlab_mex_api
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
private
public :: mxGetM, mxGetN, mxGetDoubles, mxGetScalar, mxIsDouble, mxIsComplex
public :: mxCreateDoubleMatrix, mxCreateDoubleScalar, mxDestroyArray
public :: mxCopyPtrToReal8, mxCopyReal8ToPtr, mexErrMsgIdAndTxt, mexPrintf
interface
function mxGetM(pm) result(m)
mwPointer, intent(in) :: pm
mwPointer :: m ! documented return type; the value is a row count
end function mxGetM
function mxGetN(pm) result(n)
mwPointer, intent(in) :: pm
mwPointer :: n
end function mxGetN
function mxGetDoubles(pm) result(p)
mwPointer, intent(in) :: pm
mwPointer :: p
end function mxGetDoubles
function mxGetScalar(pm) result(v)
import :: real64
mwPointer, intent(in) :: pm
real(real64) :: v
end function mxGetScalar
function mxIsDouble(pm) result(r)
import :: int32
mwPointer, intent(in) :: pm
integer(int32) :: r
end function mxIsDouble
function mxIsComplex(pm) result(r)
import :: int32
mwPointer, intent(in) :: pm
integer(int32) :: r
end function mxIsComplex
function mxCreateDoubleMatrix(m, n, complex_flag) result(pm)
import :: int32
mwSize, intent(in) :: m, n
integer(int32), intent(in) :: complex_flag
mwPointer :: pm
end function mxCreateDoubleMatrix
function mxCreateDoubleScalar(value) result(pm)
import :: real64
real(real64), intent(in) :: value
mwPointer :: pm
end function mxCreateDoubleScalar
subroutine mxDestroyArray(pm)
mwPointer, intent(in) :: pm
end subroutine mxDestroyArray
subroutine mxCopyPtrToReal8(px, y, n)
import :: real64
mwPointer, intent(in) :: px
real(real64), intent(out) :: y(*)
mwSize, intent(in) :: n
end subroutine mxCopyPtrToReal8
subroutine mxCopyReal8ToPtr(y, px, n)
import :: real64
real(real64), intent(in) :: y(*)
mwPointer, intent(in) :: px
mwSize, intent(in) :: n
end subroutine mxCopyReal8ToPtr
subroutine mexErrMsgIdAndTxt(errorid, errormsg)
character(len=*), intent(in) :: errorid, errormsg
end subroutine mexErrMsgIdAndTxt
function mexPrintf(message) result(nprinted)
import :: int32
character(len=*), intent(in) :: message
integer(int32) :: nprinted
end function mexPrintf
end interface
end module matlab_mex_api
Source: MathWorks: Fortran Matrix API, mxCreateDoubleMatrix (Fortran), mxDestroyArray (Fortran) and J3/23-007r1: 15.4.2.2 Explicit interface
The return type of mxGetM and mxGetN is documented as mwPointer, although the value is a count; the page explains that
Fortran has no size_t equivalent. The interfaces keep the documented type and the gateway assigns the result to mwSize.
mxIsDouble is documented to return integer*4, 1 for double data; the interface for mxIsComplex assumes the same
convention, and the gateway only compares it with zero.
The gateway follows the flow every MEX file has: validate, size, create the output, get the data, compute, and signal errors.
#include "fintrf.h"
! MEX gateway: [unew, residual] = heatflow_mex(u, steps) -- free form, preprocessed for fintrf.h
subroutine mexFunction(nlhs, plhs, nrhs, prhs)
use, intrinsic :: iso_fortran_env, only: int32, real64
use matlab_mex_api
use heatflow_kernels, only: ftcs_sweep
implicit none
integer(int32), intent(in) :: nlhs, nrhs
mwPointer, intent(out) :: plhs(*)
mwPointer, intent(in) :: prhs(*)
real(real64), parameter :: alpha = 1.0_real64, dt = 0.1_real64, dx = 1.0_real64, dy = 1.0_real64
real(real64), allocatable :: u(:, :), unew(:, :)
real(real64) :: residual, steps_value
mwSize :: nx, ny
integer :: k, steps
integer(int32) :: nprinted
! 1. validate the call
if (nrhs /= 2) call mexErrMsgIdAndTxt('heatflow:nrhs', 'Two inputs required: u and steps.')
if (nlhs > 2) call mexErrMsgIdAndTxt('heatflow:nlhs', 'At most two outputs.')
if (mxIsDouble(prhs(1)) == 0 .or. mxIsComplex(prhs(1)) /= 0) &
call mexErrMsgIdAndTxt('heatflow:type', 'u must be a real double matrix.')
if (mxGetM(prhs(2))*mxGetN(prhs(2)) /= 1 .or. mxIsDouble(prhs(2)) == 0) &
call mexErrMsgIdAndTxt('heatflow:steps', 'steps must be a real double scalar.')
! 2. size
nx = mxGetM(prhs(1))
ny = mxGetN(prhs(1))
if (nx < 3 .or. ny < 3) call mexErrMsgIdAndTxt('heatflow:size', 'u must be at least 3-by-3.')
steps_value = mxGetScalar(prhs(2))
if (steps_value < 0.0_real64 .or. abs(steps_value - anint(steps_value)) > epsilon(steps_value)) &
call mexErrMsgIdAndTxt('heatflow:steps', 'steps must be a nonnegative integer.')
steps = nint(steps_value)
! 3. copy the input into Fortran storage (column-major in both languages: no transpose)
allocate (u(nx, ny), unew(nx, ny))
call mxCopyPtrToReal8(mxGetDoubles(prhs(1)), u, nx*ny)
! 4. compute with the same kernel the Fortran program uses
unew = u
residual = 0.0_real64
do k = 1, steps
call ftcs_sweep(u, unew, alpha, dt, dx, dy, residual)
u = unew
end do
! 5. create the outputs and copy the results out
plhs(1) = mxCreateDoubleMatrix(nx, ny, 0_int32)
call mxCopyReal8ToPtr(u, mxGetDoubles(plhs(1)), nx*ny)
if (nlhs >= 2) plhs(2) = mxCreateDoubleScalar(residual)
nprinted = mexPrintf('heatflow_mex done'//achar(10))
end subroutine mexFunction
Compiled with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all -c (module and kernel files first) and with
Flang 23, both without diagnostics. The mex build line would be mex -R2018a 01_gateway.F90 plus the sources of the
heatflow_* modules, with the interface module listed before the gateway.
How the pieces work:
-
nrhs /= 2andnlhs > 2are the argument-count checks.mexErrMsgIdAndTxtprints the message with its identifier and, per the reference page, terminates the MEX file and returns control to the MATLAB prompt: it does not return, so the statements after it run only if the test was false. It does not clear the MEX file and does not call amexAtExitroutine, and memory allocated withmxCallocor themxCreate*routines is freed automatically. -
mxIsDoubleandmxIsComplexcheck the class. Without them,mxGetDoubleson asingleor a complex array would end the MEX file in MATLAB, or return 0 in a standalone program. -
The data come as a pointer.
mxCopyPtrToReal8copiesnvalues into a Fortran array; it is the supported route when the compiler cannot pass a pointer value as an array (see%VALbelow). Both languages store matrices column-major, sou(nx, ny)receives the MATLAB matrix without a transpose. -
The outputs are created by
mxCreateDoubleMatrix(ComplexFlag 0 for real) andmxCreateDoubleScalar. The caller owns an array it creates, but an array stored inplhsis handed to MATLAB: do not callmxDestroyArrayon an output returned inplhs. -
The kernel is the same
ftcs_sweepused by the program, so the MEX file and the Fortran executable produce identical numbers for the same grid.
Source: mexErrMsgIdAndTxt, mxCreateDoubleMatrix (Fortran), mxDestroyArray (Fortran) and mxCopyPtrToReal8.
Running the gateway against a mock
To exercise the Fortran side, the gateway was linked with a mock written for this page: a C file that implements mxGetM,
mxGetN, mxGetDoubles, mxGetScalar, mxIsDouble, mxIsComplex, mxCreateDoubleMatrix, mxCreateDoubleScalar,
mxDestroyArray, mxCopyPtrToReal8, mxCopyReal8ToPtr, mexErrMsgIdAndTxt (which prints and jumps back, imitating
the return to the prompt) and mexPrintf. It uses the gfortran convention of by-reference arguments, a lower-case name
with a trailing underscore and hidden string lengths. A Fortran driver stands in for the MATLAB command line: it builds a 5-by-5
matrix with a hot centre and calls the gateway through a bind(c) wrapper:
#include "fintrf.h"
! Stands in for MATLAB: builds mxArrays with the mock, calls the gateway, prints the result.
program driver
use, intrinsic :: iso_c_binding, only: c_int, c_int64_t
use, intrinsic :: iso_fortran_env, only: real64
use matlab_mex_api
implicit none
interface
function mock_call(nlhs, plhs, nrhs, prhs) bind(c, name='mock_call') result(failed)
import :: c_int, c_int64_t
integer(c_int), value :: nlhs, nrhs
integer(c_int64_t), intent(inout) :: plhs(*), prhs(*)
integer(c_int) :: failed
end function mock_call
end interface
integer(c_int64_t) :: plhs(2), prhs(2)
real(real64) :: u(5, 5), out(5, 5)
integer :: failed
integer(8) :: n
n = 25
u = 0.0_real64
u(3, 3) = 100.0_real64
prhs(1) = mxCreateDoubleMatrix(5_8, 5_8, 0)
call mxCopyReal8ToPtr(u, mxGetDoubles(prhs(1)), n)
prhs(2) = mxCreateDoubleScalar(1.0_real64)
plhs = 0
flush (6)
failed = mock_call(2_c_int, plhs, 2_c_int, prhs)
print '(a, i0)', 'gateway failed = ', failed
call mxCopyPtrToReal8(mxGetDoubles(plhs(1)), out, n)
print '(a, f8.3)', 'center after 1 step = ', out(3, 3)
print '(a, f8.3)', 'neighbour = ', out(3, 2)
print '(a, f8.3)', 'residual = ', mxGetScalar(plhs(2))
! wrong call: only one input
flush (6)
failed = mock_call(1_c_int, plhs, 1_c_int, prhs)
print '(a, i0)', 'gateway failed = ', failed
call mxDestroyArray(prhs(1)); call mxDestroyArray(prhs(2))
call mxDestroyArray(plhs(1)); call mxDestroyArray(plhs(2))
end program driver
heatflow_mex done
gateway failed = 0
center after 1 step = 60.000
neighbour = 10.000
residual = 40.000
MEX error [heatflow:nrhs]: Two inputs required: u and steps.
gateway failed = 1
Source: MathWorks: Components of Fortran MEX File and mexFunction
The numbers are what the kernel gives for alpha*dt/dx**2 = 0.1: the centre 100 becomes 100 + 0.1*(-400) = 60 and each of its four
neighbours becomes 10, with a largest change of 40. The second call, with one input, takes the error path. The same program
built with Flang 23 prints the same values (only the interleaving of the C and Fortran output streams differs). This shows
the Fortran code is consistent; it says nothing about MATLAB itself. In MATLAB the intended call is:
u = zeros(5, 5);
u(3, 3) = 100;
[unew, residual] = heatflow_mex(u, 1); % not run here: no MATLAB available
The Fortran Matrix API
The R2026b Fortran Matrix API is grouped in five pages: data types, create or delete, validate, access and convert. The tables list the routines by group, taken from those pages.
| Group | Routines |
|---|---|
Types and constants |
|
Create numeric |
|
Create other |
|
Memory |
|
Validate |
|
Access attributes |
|
Access data |
|
Structures, cells, sparse, objects |
|
Convert ( |
|
Source: Fortran Matrix API, Create or Delete Fortran Array, Validate Fortran Data, Access Fortran Data and Convert Fortran Arrays.
Rules that apply across the API:
-
Ownership. An array made by an
mxCreate*routine,mxDuplicateArray,matGetVariableorengGetVariablebelongs to the caller until it is handed to MATLAB throughplhs,mexPutVariable,matPutVariableorengPutVariable. Release it withmxDestroyArray(which does nothing when given 0). Never destroy an array returned in the outputs ofmexFunction, one returned bymxGetField,mxGetFieldByNumberormxGetCell. -
Raw memory.
mxMalloc,mxCallocandmxReallocallocate through the MATLAB memory manager, andmxFreereleases them; in a MEX file the memory is freed automatically when the MEX file returns unless it is made persistent withmexMakeMemoryPersistent. Fortranallocatememory is the program’s own and is not freed by MATLAB; Fortran allocatables are deallocated automatically at the end of the gateway subroutine, which is another reason to prefer them. -
Failure. Creation routines that run out of memory terminate the MEX file; in a standalone application they return 0, so check the pointer.
-
Types. Validate before you access.
mxGetDoubleson a non-double array ends the MEX file, or returns 0 outside MEX;mxGetScalaris for a nonempty numeric, logical or character array and is declaredreal*8; forint64oruint64the value can lose precision beyondflintmax.
Complex data: interleaved and separate
MATLAB changed how complex arrays are stored. The two APIs are selected at build time and are not interchangeable:
| API | Build option | Storage and accessors |
|---|---|---|
Separate (older) |
|
real parts and imaginary parts in two arrays, reached by |
Interleaved |
|
real and imaginary parts alternate; typed accessors |
-
mxGetPrandmxGetPiare marked not recommended (since R2018b).mxGetPrworks only for real double arrays, does not check the type, and with-R2018aends the MEX file when called on a complex array. The replacements aremxGetDoublesfor real data andmxGetComplexDoublesfor complex data; callmxIsDoubleandmxIsComplexfirst. -
The typed accessors were introduced in R2018b. The
mexpage notes that R2018a did not support the interleaved API for Fortran, that R2018b added it, and that to run such a file in R2018a you need Update 3. -
The gateway above uses
mxGetDoubles, so it needsmex -R2018a. The same code withmxGetPrbuilds with the default options but breaks for complex input. -
mxCopyPtrToComplex16andmxCopyComplex16ToPtrmovecomplex(real64)data in and out; with the interleaved API the pointer comes frommxGetComplexDoubles.
Source: mxGetPr (Fortran) and mxGetDoubles (Fortran).
Getting at the data: mxCopy routines, %VAL and c_f_pointer
mxGetDoubles returns an address held in an mwPointer, which is an integer. Fortran cannot index through an integer
address, so the API offers three ways:
-
mxCopy*routines (used above): copy between the MATLAB array and a Fortran array. Portable and simple. The reference page formxCopyPtrToReal8says to use it with compilers that do not support%VAL. The cost is a copy. -
%VAL: pass the address by value, so the callee sees it as an array dummy. This is how the MathWorks examples pass data to a computational routine without copying: -
c_ptrandc_f_pointer: convert the address to a standardtype(c_ptr)and associate a Fortran pointer with it.
The %VAL form, which is a vendor extension and not in the standard:
#include "fintrf.h"
! The %VAL style of the MathWorks examples: the computational routine receives the data address by value.
subroutine mexFunction(nlhs, plhs, nrhs, prhs)
use, intrinsic :: iso_fortran_env, only: int32
use matlab_mex_api
implicit none
integer(int32), intent(in) :: nlhs, nrhs
mwPointer, intent(out) :: plhs(*)
mwPointer, intent(in) :: prhs(*)
mwSize :: m, n
m = mxGetM(prhs(1)); n = mxGetN(prhs(1))
plhs(1) = mxCreateDoubleMatrix(m, n, 0_int32)
call timestwo(%val(mxGetDoubles(plhs(1))), %val(mxGetDoubles(prhs(1))), m*n)
end subroutine mexFunction
07_val.F90:13:22:
13 | call timestwo(%val(mxGetDoubles(plhs(1))), %val(mxGetDoubles(prhs(1))), m*n)
| 1
Error: GNU Extension: argument list function at (1)
That is gfortran 16.2 with -std=f2023; with -std=gnu it compiles, and Flang 23 compiled it without a diagnostic too. The
file does not define timestwo (it would be a separate routine whose dummies are real(real64) :: y(n), x(n)), so it was compiled but not linked. %VAL is a
compiler extension of the same family as Cray pointers: standard code should use c_f_pointer or the mxCopy* routines.
The standard route is the second listing. It converts the integer address with transfer and c_f_pointer
(18.2.3.3) and works on the MATLAB data in place, with no copy:
#include "fintrf.h"
! MEX gateway y = timestwo(x): works on the MATLAB data in place through c_f_pointer, no mxCopy* calls.
subroutine mexFunction(nlhs, plhs, nrhs, prhs)
use, intrinsic :: iso_c_binding, only: c_ptr, c_f_pointer
use, intrinsic :: iso_fortran_env, only: int32, real64
use matlab_mex_api
implicit none
integer(int32), intent(in) :: nlhs, nrhs
mwPointer, intent(out) :: plhs(*)
mwPointer, intent(in) :: prhs(*)
type(c_ptr) :: cp
real(real64), pointer :: x(:), y(:)
mwSize :: m, n
if (nrhs /= 1 .or. nlhs > 1) call mexErrMsgIdAndTxt('timestwo:args', 'One input and one output.')
if (mxIsDouble(prhs(1)) == 0 .or. mxIsComplex(prhs(1)) /= 0) &
call mexErrMsgIdAndTxt('timestwo:type', 'x must be a real double array.')
m = mxGetM(prhs(1))
n = mxGetN(prhs(1))
plhs(1) = mxCreateDoubleMatrix(m, n, 0_int32)
! mwPointer holds an address; transfer turns it into a c_ptr (processor dependent: fine on the
! 64-bit targets MATLAB supports, but not guaranteed by the standard)
cp = transfer(mxGetDoubles(prhs(1)), cp)
call c_f_pointer(cp, x, [m*n])
cp = transfer(mxGetDoubles(plhs(1)), cp)
call c_f_pointer(cp, y, [m*n])
y = 2.0_real64*x
end subroutine mexFunction
transfer(mxGetDoubles(prhs(1)), cp) copies the bit pattern of the 64-bit integer into a c_ptr (16.9.212): that works because both are
one machine address, but the standard leaves the physical representation of c_ptr to the processor, so it is a portable idiom
only across the 64-bit compilers MATLAB supports. With the mock and a 2-by-2 input the program prints (gfortran 16.2 and Flang 23
alike):
failed = 0
2.0 6.0
4.0 8.0
The input matrix was [1 3; 2 4], so each element is doubled. Writing through the pointer to the output is
allowed because that array was created by the gateway. Never write through a pointer to an input array. Both listings are
labelled as not run in MATLAB.
MEX routines
The routines in the Fortran MEX API (mex-library-fortran.html lists 18 functions) talk to the MATLAB environment:
| Routine | Purpose |
|---|---|
|
the entry point described above |
|
name of the running MEX function |
|
call a MATLAB function, operator (quoted, for example |
|
same, but returns an |
|
run a command in the caller’s workspace, the second returns error information |
|
copy a variable out of a workspace, read it by read-only pointer, or write one into a workspace |
|
print to the MATLAB command window; returns the number of characters printed |
|
raise an error with an identifier and return to the prompt |
|
raise a warning with an identifier |
|
register one routine to run when the MEX file is cleared or MATLAB exits |
|
prevent or allow clearing of the MEX file from memory |
|
keep an array or MATLAB-allocated memory after the call returns |
Source: Fortran MEX API.
Details worth knowing:
-
Calling back into MATLAB. The arrays that
mexCallMATLABwrites into its ownplhsare temporary: MATLAB frees them when the MEX file exits, and you may callmxDestroyArrayearlier if memory is tight. Thatplhsis not the gateway’splhs. If the called function errors, MATLAB terminates the MEX file; usemexCallMATLABWithTrapto catch the error. A trap-based call looks like this (a sketch, not run):mwPointer :: args(1), results(1), me ! args(1) holds an mxArray created earlier me = mexCallMATLABWithTrap(1, results, 1, args, 'sqrt') if (me /= 0) then ! me points to an MException object: report or recover, then continue call mxDestroyArray(me) end ifIn this sketch the integer literals are default integers, matching the documented
integer*4 nlhs, nrhs. The page does not say that the Fortran no-error result is 0 (it saysNULL); the sketch assumes the usual convention, which thematOpen,engOpenandmxCreate*pages state explicitly for Fortran. -
Errors.
mexSetTrapFlagis removed (the page dates it from R2018a; the banner says R2026b): usemexCallMATLABWithTrap. Existing MEX files that call it still run. -
Printing.
mexPrintftakes onecharacter(len=*)argument in Fortran. The percent sign is special in the C documentation (%%for a literal one); the Fortran page does not say whether the same applies, so avoid%in messages or test it. The page does not describe line ends: the usual practice is to appendachar(10), as the gateway above does, but that is common practice rather than something the page states, so check it on your release. UsemexPrintffor text meant for the MATLAB command window. -
Persistence. A MEX file is loaded once and kept until
clearor MATLAB exits, so Fortransavevariables and module variables persist between calls. UsemexAtExitto release them: declare the routineexternalwhere it is referenced and pass it by name. Only one exit routine is active, a later call replaces it, and a locked MEX file does not run it. Module variables in a MEX file are shared state: see Modules and Submodules. -
Identifiers.
mexErrMsgIdAndTxttakes a message identifier in thecomponent:mnemonicstyle (heatflow:nrhs), which MATLAB code can test withMException.identifier; the reference page defers the format to theMExceptiondocumentation.
Source: mexCallMATLAB (Fortran), mexCallMATLABWithTrap, mexPrintf, mexAtExit and mexSetTrapFlag.
Explicit interfaces catch the integer-size bug
Because the interface module declares mwSize arguments, passing default integer literals is rejected by the compiler. A
gateway written like the ordinary examples, plhs(1) = mxCreateDoubleMatrix(3, 3, 0), with the interface module in use:
#include "fintrf.h"
! Deliberately wrong: default-integer literals where the API wants mwSize (64-bit here).
subroutine mexFunction(nlhs, plhs, nrhs, prhs)
use, intrinsic :: iso_fortran_env, only: int32
use matlab_mex_api
implicit none
integer(int32), intent(in) :: nlhs, nrhs
mwPointer, intent(out) :: plhs(*)
mwPointer, intent(in) :: prhs(*)
plhs(1) = mxCreateDoubleMatrix(3, 3, 0)
end subroutine mexFunction
05_implicit_bug.F90:10:12:
10 | plhs(1) = mxCreateDoubleMatrix(3, 3, 0)
| 1
Error: Type mismatch in argument 'm' at (1); passed INTEGER(4) to INTEGER(8)
05_implicit_bug.F90:10:12:
10 | plhs(1) = mxCreateDoubleMatrix(3, 3, 0)
| 1
Error: Type mismatch in argument 'n' at (1); passed INTEGER(4) to INTEGER(8)
(gfortran 16.2.) The same call with the external style, in which the function has an implicit interface (15.4.3.8) and the
compiler knows nothing about its arguments:
#include "fintrf.h"
! Same mistake written the way the MathWorks examples declare things: external, implicit interface.
subroutine mexFunction(nlhs, plhs, nrhs, prhs)
implicit none
integer, intent(in) :: nlhs, nrhs
mwPointer, intent(out) :: plhs(*)
mwPointer, intent(in) :: prhs(*)
mwPointer, external :: mxCreateDoubleMatrix
plhs(1) = mxCreateDoubleMatrix(3, 3, 0)
end subroutine mexFunction
gfortran 16.2 and Flang 23 compile it, gfortran with nothing but unused-argument warnings. The 4-byte literals are passed
by reference where the callee reads 8 bytes, so on a 64-bit platform the sizes it sees are undefined: the
compiler accepted a bug. Writing 3_8 or declaring integer(8), parameter :: three = 3 avoids it, and the
explicit interface (15.4.2.2) makes the compiler check it. The older style was written for compilers that could not check.
Engine API
The Engine API lets a standalone Fortran program start a MATLAB session, send it variables and commands, and read results back. It needs an installed MATLAB: the engine does not run on a machine that only has the MATLAB Runtime.
| Routine (Fortran) | Contract in the reference pages |
|---|---|
|
starts MATLAB. On Windows |
|
evaluates an expression. Returns 1 if the session is not running or the pointer is invalid, otherwise 0 even if MATLAB could not evaluate the command |
|
writes an |
|
copies a variable into a new |
|
sets a |
|
sends a quit command and closes; 0 on success, 1 on failure |
One note from the pages: for engGetVariable the Description says it returns 0 on success and 1 on error, but the output
description says it returns the pointer or NULL. Test the pointer against 0.
The reference pages show #include "engine.h" and #include "mat.h" in their Fortran syntax blocks; the sketches here include only fintrf.h, the header documented for the mwPointer macro, so adapt the includes to the MathWorks example you start from. A complete sketch with the Matrix API calls from this page (syntax-checked against the stand-in header with gfortran 16.2 and Flang 23; not linked and not run, because there is no
MATLAB engine library):
#include "fintrf.h"
! Engine API sketch: checked for Fortran syntax only (stand-in header, no MATLAB).
program engine_demo
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
mwPointer, external :: engOpen, engGetVariable, mxCreateDoubleMatrix, mxGetDoubles
integer(int32), external :: engEvalString, engPutVariable, engClose, engOutputBuffer
mwPointer :: ep, x_mx, y_mx
real(real64) :: x(3), y(3)
character(len=256) :: buffer
integer(int32) :: status
ep = engOpen('') ! empty start command: MATLAB on this host (Linux, macOS)
if (ep == 0) stop 'engOpen failed'
status = engOutputBuffer(ep, buffer) ! capture what MATLAB would print
x = [1.0_real64, 2.0_real64, 3.0_real64]
x_mx = mxCreateDoubleMatrix(3_8, 1_8, 0)
call mxCopyReal8ToPtr(x, mxGetDoubles(x_mx), 3_8)
status = engPutVariable(ep, 'x', x_mx) ! MATLAB gets a copy; we still own x_mx
if (status /= 0) stop 'engPutVariable failed'
status = engEvalString(ep, 'y = cumsum(x);') ! 0 even if MATLAB could not run the text
y_mx = engGetVariable(ep, 'y') ! a new mxArray that we must destroy
if (y_mx == 0) stop 'engGetVariable failed'
call mxCopyPtrToReal8(mxGetDoubles(y_mx), y, 3_8)
print '(3f6.1)', y
call mxDestroyArray(x_mx)
call mxDestroyArray(y_mx)
status = engClose(ep)
end program engine_demo
To build a standalone engine program use mex -client engine, for example mex -v -client engine fengdemo.F for the MathWorks
example fengdemo.F from matlabroot/extern/examples/eng_mat. On Linux the page asks you to put matlabroot/bin/glnxa64 and
matlabroot/sys/os/glnxa64 on LD_LIBRARY_PATH and matlabroot/bin on PATH; macOS and Windows have their own pages. The
engine library on Windows talks to MATLAB over COM, on UNIX over pipes.
MAT-file API
The MAT-file API reads and writes .mat files from a Fortran program without MATLAB running. It is the least invasive
interface: the program needs the MathWorks libraries but not a MATLAB session.
| Routine (Fortran) | Contract in the reference pages |
|---|---|
|
returns a file handle, 0 if the open fails |
|
copies a variable into a new |
|
writes an array; appends it if the name is new, rewrites the file if it replaces one; 0 on success, otherwise a code to examine with |
|
returns a pointer to an array of pointers to the variable names, with their count in |
|
closes the file; 0 on success, -1 for a write error |
Also available: matGetVariableInfo, matGetNextVariable, matGetNextVariableInfo, matPutVariableAsGlobal, matDeleteVariable,
matGetErrno and matGetFp. The mode string selects what matOpen does:
| Mode | Effect |
|---|---|
|
read only, keeps the version of the existing file |
|
read and write, does not create a missing file |
|
write only, deletes previous contents |
|
MATLAB version 4 file |
|
version 5 or earlier file; |
|
version 7.0 or earlier, compressed |
|
HDF5-based file that can store objects larger than 2 GB |
Without wL, MATLAB writes character data as Unicode. Since R2025a variable names can be up to mxMAXNAM = 2048 characters. The MathWorks examples matdemo1.F
(creates matdemo.mat with a numeric matrix, a global matrix and a string) and matdemo2.F (reads and describes it) are in
matlabroot/extern/examples/eng_mat. A sketch with the heatflow data (syntax-checked as above, not linked and not run):
#include "fintrf.h"
! MAT-file API sketch: checked for Fortran syntax only (stand-in header, no MATLAB).
program mat_demo
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
mwPointer, external :: matOpen, matGetVariable, matGetDir, mxCreateDoubleMatrix, mxGetDoubles
integer(int32), external :: matPutVariable, matClose
mwPointer :: mfp, pm, dir_ptr
integer(int32) :: status, ndir
real(real64) :: a(2, 2)
a = reshape([1.0_real64, 2.0_real64, 3.0_real64, 4.0_real64], [2, 2])
mfp = matOpen('heatflow.mat', 'w7.3') ! HDF5-based file; 'w' writes the default version
if (mfp == 0) stop 'matOpen failed'
pm = mxCreateDoubleMatrix(2_8, 2_8, 0)
call mxCopyReal8ToPtr(a, mxGetDoubles(pm), 4_8)
status = matPutVariable(mfp, 'a', pm) ! nonzero on error
if (status /= 0) stop 'matPutVariable failed'
call mxDestroyArray(pm)
status = matClose(mfp)
mfp = matOpen('heatflow.mat', 'r')
dir_ptr = matGetDir(mfp, ndir) ! ndir < 0 on error, 0 if the file is empty
print '(a, i0)', 'variables: ', ndir
call mxFree(dir_ptr) ! the name table is ours to free
pm = matGetVariable(mfp, 'a') ! a new mxArray
call mxDestroyArray(pm)
status = matClose(mfp)
end program mat_demo
Note the two ownership rules: the array from matGetVariable goes through mxDestroyArray, and the name table of matGetDir through
mxFree. The names in that table are null-terminated C strings addressed by pointers; copying one into a Fortran
character variable is done with the mxCopyPtrToPtrArray and mxCopyPtrToCharacter routines of the Matrix API.
Modernization: legacy MATLAB Fortran style versus current practice
The MathWorks API was designed for Fortran 77 compilers and the style persists in vendor examples. The API itself has no module and the headers need a preprocessor, but the code around the calls can be modern.
| Legacy style in MATLAB examples | Current practice | Why |
|---|---|---|
Fixed-form |
free-form |
longer lines, no continuation column; see Program Structure and Source Form |
|
|
the star sizes are not standard; a kind says what is meant. |
|
an interface module (above) |
the compiler checks argument count and types, catching the integer-size bug |
|
|
|
|
|
the copies cost memory and time |
|
|
type-checked, interleaved storage, the documented direction |
pointer-sized integers by hand ( |
the |
64-bit and 32-bit builds use the same source |
|
|
removed |
|
module variables released by |
Pitfalls
-
Default
integerfor sizes. A literal or a defaultintegerpassed asmwSizeis 4 bytes where 8 are read. Use an interface module,mwSizevariables and_8or a kind constant. -
Forgetting
-R2018a.mxGetDoublesand the complex accessors need the interleaved API. Without the option the build fails or behaves as the separate API. -
Destroying
plhs. An array stored inplhsbelongs to MATLAB. Destroying it crashes MATLAB. -
Writing to
prhs. The inputs are shared with the workspace. Duplicate before changing. -
Not validating the class.
mxGetDoublesormxGetScalaron the wrong class ends the MEX file or returns garbage. CheckmxIsDouble,mxIsComplex,mxIsEmptyand the sizes first. -
Leaking
engGetVariableandmatGetVariableresults in a standalone program. Standalone programs get no automatic cleanup; MEX files do. -
Trusting the return value of
engEvalString. It is 0 even when MATLAB rejected the command; evaluate with atry/catchwrapper in MATLAB and read the status variable, or inspect the output buffer. -
A non-terminated output buffer.
engOutputBufferfills a buffer without a terminating null, so blank-fill the buffer before each call and trim it yourself. -
Rows against columns. Both languages are column-major:
u(i, j)is MATLAB’su(i, j). The mistakes come from C code being row-major, not from Fortran. -
Heap corruption by a wrong kind. A
real(4)where the API expectsreal*8copies half the bytes. Usereal64. -
Module state across calls. A MEX file stays loaded, so module variables keep values between calls and between MATLAB scripts; initialize explicitly or release in
mexAtExit. -
Running the strict flags on the real header.
-std=f2023rejectsinteger*8in the realfintrf.h. Letmexchoose the flags, and use strict flags on the kernel and on a stand-in header as done here. -
Compiler support. The compiler
mexuses must be one MATLAB supports; gfortran or Flang can compile and test the kernel, but not necessarily the MEX file. -
Mixing Fortran compilers. A MEX file built with one Fortran compiler and a library built with another can end up with two Fortran runtimes (for example
libgfortranand the Intel runtime) in one process; build the pieces with the same compiler.
See also Common Gotchas and Pitfalls.
Where the references are outdated
The 2023 MATLAB API reference (R2023a) and older MEX tutorials are still the most common text on the subject, and parts of them no longer match R2026b:
| Older material says | R2026b documentation (read 2026-10-19) |
|---|---|
build with fixed-form |
|
use |
marked not recommended since R2018b; use the typed accessors with |
the separate complex API is the default |
the default is still |
|
removed; use |
names up to 63 characters ( |
2048 characters since R2025a, and affected programs must be recompiled |
Fortran compilers of that era |
ifx supported from R2024b, MinGW Fortran from R2024a, NAG on Apple silicon from R2023b, oneAPI 2025 on Windows |
References
-
MathWorks: Write Fortran Functions Callable from MATLAB (MEX Files) and Components of Fortran MEX File
-
MathWorks: mex (build options,
-R2018a,-R2017b,-setup) and MATLAB supported compilers -
MathWorks: Write Fortran Programs to Read MAT File Data (MAT-file API)
-
MathWorks: MATLAB C/C++, Fortran, Java, and Python API Reference (PDF, R2023a)