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 (mexFunction)

MATLAB calls your Fortran. The compiled library (mexext prints its platform-specific extension) is called like a function

speed up a kernel, reuse a Fortran library from MATLAB

Engine API (engOpen and friends)

your standalone Fortran program starts MATLAB and sends it commands

use MATLAB plotting or toolboxes from a simulation

MAT-file API (matOpen and friends)

your standalone Fortran program reads and writes .mat files, no MATLAB process needed at run time

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:

sequenceDiagram participant M as MATLAB participant G as mexFunction (gateway) participant K as ftcs_sweep (kernel) M->>G: unew = heatflow_mex(u, steps) Note over M,G: nlhs, plhs, nrhs, prhs G->>G: validate nrhs, nlhs, class, size G->>M: mxGetM, mxGetN, mxGetDoubles G->>G: allocate and copy u (mxCopyPtrToReal8) loop steps times G->>K: ftcs_sweep(u, unew, ...) K-->>G: unew, residual end G->>M: mxCreateDoubleMatrix, mxCopyReal8ToPtr G-->>M: return, plhs(1) holds unew Note over G,M: on any failure mexErrMsgIdAndTxt returns control to the MATLAB prompt

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
  • nlhs and nrhs are the numbers of left-hand-side (output) and right-hand-side (input) arguments of the MATLAB call.

  • prhs holds nrhs pointers to the inputs. They are read only: do not modify them. To return a modified input, copy it with mxDuplicateArray and return the copy.

  • plhs receives 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 mexFunction must be the first one listed on the mex command line. Put mexFunction after 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.

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

mwPointer

a pointer to an mxArray, expands to INTEGER*8 on 64-bit platforms and INTEGER*4 on 32-bit platforms

introduced in R2006a; use it for every pointer function, argument and variable

mwSize

array sizes such as dimensions; documented as unsigned, INTEGER*4 or INTEGER*8 depending on platform and compilation flags

a size is never a default integer

mwIndex

index values, INTEGER*4 or INTEGER*8 depending on platform and compilation flags

used for sparse index arrays

mwSignedIndex

a signed integer type for sizes, INTEGER*4 or INTEGER*8 depending on platform and compilation flags

introduced in R2009a

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

-R2017b

the default API: separate real and imaginary arrays for complex data, large-array handling. The page says the default will change to -R2018a in a future release and recommends building with the interleaved API now

-R2018a

the interleaved complex API, including the typed accessors such as mxGetDoubles

-largeArrayDims

separate complex data, large arrays (the -R2017b behaviour)

-compatibleArrayDims

the old API limited to 231-1 elements; do not use it with BLAS or LAPACK calls

-outdir dirname

put the output files in a folder

-output mexname

name the MEX file explicitly instead of after the first source file

-c

compile to an object only, then pass the object to a later link command

-setup fortran

change the default Fortran compiler for MEX files (-setup -client engine fortran does the same for Engine and MAT-file applications)

  • Source form. The upper-case .F extension is recommended for platform independence, because a lower-case .f file is assumed to be already preprocessed. The R2026b page says that as of R2026a mex builds fixed-form and free-form Fortran, including .F90. Older releases are documented for fixed-form .F only.

  • 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 use it, 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 mxMAXNAM is 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

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 /= 2 and nlhs > 2 are the argument-count checks. mexErrMsgIdAndTxt prints 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 a mexAtExit routine, and memory allocated with mxCalloc or the mxCreate* routines is freed automatically.

  • mxIsDouble and mxIsComplex check the class. Without them, mxGetDoubles on a single or a complex array would end the MEX file in MATLAB, or return 0 in a standalone program.

  • The data come as a pointer. mxCopyPtrToReal8 copies n values into a Fortran array; it is the supported route when the compiler cannot pass a pointer value as an array (see %VAL below). Both languages store matrices column-major, so u(nx, ny) receives the MATLAB matrix without a transpose.

  • The outputs are created by mxCreateDoubleMatrix (ComplexFlag 0 for real) and mxCreateDoubleScalar. The caller owns an array it creates, but an array stored in plhs is handed to MATLAB: do not call mxDestroyArray on an output returned in plhs.

  • The kernel is the same ftcs_sweep used by the program, so the MEX file and the Fortran executable produce identical numbers for the same grid.

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

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

mwPointer, mwSize, mwIndex, mwSignedIndex, mxClassIDFromClassName, mxGetEps, mxGetInf, mxGetNaN

Create numeric

mxCreateDoubleMatrix, mxCreateDoubleScalar, mxCreateNumericMatrix, mxCreateNumericArray, mxMakeArrayReal, mxMakeArrayComplex

Create other

mxCreateString, mxCreateCharMatrixFromStrings, mxCreateCharArray, mxCreateSparse, mxCreateStructMatrix, mxCreateStructArray, mxCreateCellMatrix, mxCreateCellArray

Memory

mxDestroyArray, mxDuplicateArray, mxCalloc, mxMalloc, mxRealloc, mxFree

Validate

mxIsDouble, mxIsSingle, mxIsComplex, mxIsNumeric, mxIsInt8 to mxIsUint64, mxIsChar, mxIsLogical, mxIsSparse, mxIsStruct, mxIsCell, mxIsClass, mxIsInf, mxIsFinite, mxIsNaN, mxIsEmpty, mxIsFromGlobalWS

Access attributes

mxGetM, mxGetN, mxSetM, mxSetN, mxGetNumberOfDimensions, mxGetDimensions, mxSetDimensions, mxGetNumberOfElements, mxGetElementSize, mxCalcSingleSubscript, mxGetClassID, mxGetClassName

Access data

mxGetScalar, typed accessors mxGetDoubles, mxGetSingles, mxGetInt8s to mxGetUint64s and their mxSet* partners, complex accessors mxGetComplexDoubles and relatives, mxGetData, mxGetImagData, mxGetPr, mxGetPi

Structures, cells, sparse, objects

mxGetField, mxSetField, mxGetNumberOfFields, mxGetFieldNameByNumber, mxAddField, mxRemoveField, mxGetCell, mxSetCell, mxGetIr, mxGetJc, mxGetNzmax, mxGetProperty, mxSetProperty

Convert (mxCopy*)

mxCopyReal8ToPtr and mxCopyPtrToReal8, the same pair for Real4, Complex16, Complex8, Integer4, Integer2, Integer1 and Character, plus mxCopyPtrToPtrArray; mxGetString converts an mxChar array to a Fortran character variable

Rules that apply across the API:

  • Ownership. An array made by an mxCreate* routine, mxDuplicateArray, matGetVariable or engGetVariable belongs to the caller until it is handed to MATLAB through plhs, mexPutVariable, matPutVariable or engPutVariable. Release it with mxDestroyArray (which does nothing when given 0). Never destroy an array returned in the outputs of mexFunction, one returned by mxGetField, mxGetFieldByNumber or mxGetCell.

  • Raw memory. mxMalloc, mxCalloc and mxRealloc allocate through the MATLAB memory manager, and mxFree releases them; in a MEX file the memory is freed automatically when the MEX file returns unless it is made persistent with mexMakeMemoryPersistent. Fortran allocate memory 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. mxGetDoubles on a non-double array ends the MEX file, or returns 0 outside MEX; mxGetScalar is for a nonempty numeric, logical or character array and is declared real*8; for int64 or uint64 the value can lose precision beyond flintmax.

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)

mex -R2017b (the default in R2026b), -largeArrayDims

real parts and imaginary parts in two arrays, reached by mxGetPr and mxGetPi or mxGetData and mxGetImagData

Interleaved

mex -R2018a

real and imaginary parts alternate; typed accessors mxGetDoubles, mxGetComplexDoubles and the others

  • mxGetPr and mxGetPi are marked not recommended (since R2018b). mxGetPr works only for real double arrays, does not check the type, and with -R2018a ends the MEX file when called on a complex array. The replacements are mxGetDoubles for real data and mxGetComplexDoubles for complex data; call mxIsDouble and mxIsComplex first.

  • The typed accessors were introduced in R2018b. The mex page 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 needs mex -R2018a. The same code with mxGetPr builds with the default options but breaks for complex input.

  • mxCopyPtrToComplex16 and mxCopyComplex16ToPtr move complex(real64) data in and out; with the interleaved API the pointer comes from mxGetComplexDoubles.

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:

  1. mxCopy* routines (used above): copy between the MATLAB array and a Fortran array. Portable and simple. The reference page for mxCopyPtrToReal8 says to use it with compilers that do not support %VAL. The cost is a copy.

  2. %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:

  3. c_ptr and c_f_pointer: convert the address to a standard type(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

mexFunction

the entry point described above

mexFunctionName

name of the running MEX function

mexCallMATLAB(nlhs, plhs, nrhs, prhs, name)

call a MATLAB function, operator (quoted, for example '+'), user-defined function or MEX file; returns 0 on success; at most 50 inputs or outputs

mexCallMATLABWithTrap

same, but returns an MException pointer if MATLAB reports an error, and control continues on the next line

mexEvalString, mexEvalStringWithTrap

run a command in the caller’s workspace, the second returns error information

mexGetVariable, mexGetVariablePtr, mexPutVariable

copy a variable out of a workspace, read it by read-only pointer, or write one into a workspace

mexPrintf(message)

print to the MATLAB command window; returns the number of characters printed

mexErrMsgIdAndTxt(id, message)

raise an error with an identifier and return to the prompt

mexWarnMsgIdAndTxt

raise a warning with an identifier

mexAtExit(ExitFcn)

register one routine to run when the MEX file is cleared or MATLAB exits

mexLock, mexUnlock, mexIsLocked

prevent or allow clearing of the MEX file from memory

mexMakeArrayPersistent, mexMakeMemoryPersistent

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 mexCallMATLAB writes into its own plhs are temporary: MATLAB frees them when the MEX file exits, and you may call mxDestroyArray earlier if memory is tight. That plhs is not the gateway’s plhs. If the called function errors, MATLAB terminates the MEX file; use mexCallMATLABWithTrap to 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 if

    In 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 says NULL); the sketch assumes the usual convention, which the matOpen, engOpen and mxCreate* pages state explicitly for Fortran.

  • Errors. mexSetTrapFlag is removed (the page dates it from R2018a; the banner says R2026b): use mexCallMATLABWithTrap. Existing MEX files that call it still run.

  • Printing. mexPrintf takes one character(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 append achar(10), as the gateway above does, but that is common practice rather than something the page states, so check it on your release. Use mexPrintf for text meant for the MATLAB command window.

  • Persistence. A MEX file is loaded once and kept until clear or MATLAB exits, so Fortran save variables and module variables persist between calls. Use mexAtExit to release them: declare the routine external where 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. mexErrMsgIdAndTxt takes a message identifier in the component:mnemonic style (heatflow:nrhs), which MATLAB code can test with MException.identifier; the reference page defers the format to the MException documentation.

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

mwPointer engOpen(startcmd)

starts MATLAB. On Windows startcmd must be NULL; on UNIX an empty string starts MATLAB on the current host, a host name starts it remotely, any other string is run literally. Returns 0 on failure

integer*4 engEvalString(ep, string)

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

integer*4 engPutVariable(ep, name, pm)

writes an mxArray into the engine workspace (create or replace). Copies the data: the program still owns and must free pm. Limit 2 GB

mwPointer engGetVariable(ep, name)

copies a variable into a new mxArray that you destroy with mxDestroyArray. Fails if the variable does not exist

integer*4 engOutputBuffer(ep, p)

sets a character*n buffer that receives what MATLAB would print. The buffer is not null-terminated. engOutputBuffer(ep, '') turns buffering off

integer*4 engClose(ep)

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

mwPointer matOpen(filename, mode)

returns a file handle, 0 if the open fails

mwPointer matGetVariable(mfp, name)

copies a variable into a new mxArray; 0 on failure; destroy it with mxDestroyArray

integer*4 matPutVariable(mfp, name, pm)

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 matGetErrno

mwPointer matGetDir(mfp, num)

returns a pointer to an array of pointers to the variable names, with their count in num (negative on failure, 0 for an empty file); the table was allocated with mxCalloc, free it with mxFree

integer*4 matClose(mfp)

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

r

read only, keeps the version of the existing file

u

read and write, does not create a missing file

w

write only, deletes previous contents

w4

MATLAB version 4 file

w6 or wL

version 5 or earlier file; wL writes characters with the system default character set

w7 or wz

version 7.0 or earlier, compressed

w7.3

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 .F, columns 7-72

free-form .F90; mex accepts free-form since R2026a

longer lines, no continuation column; see Program Structure and Source Form

integer*4, real*8, complex*16

integer(int32), real(real64), complex(real64) from iso_fortran_env

the star sizes are not standard; a kind says what is meant. mwPointer/mwSize remain macros from the header

external plus a type statement for each API function

an interface module (above)

the compiler checks argument count and types, catching the integer-size bug

%VAL(pointer) to hand a MATLAB array to a routine

c_f_pointer on a c_ptr, or mxCopy*

%VAL is a vendor extension, rejected by -std=f2023

mxCopy* everywhere

mxCopy* where a copy is acceptable, pointers for large data

the copies cost memory and time

mxGetPr, mxGetPi, separate complex

mxGetDoubles, mxGetComplexDoubles, mex -R2018a

type-checked, interleaved storage, the documented direction

pointer-sized integers by hand (integer*8 or integer*4)

the mwPointer macro, transfer to c_ptr when you need a Fortran pointer

64-bit and 32-bit builds use the same source

mexSetTrapFlag

mexCallMATLABWithTrap

removed

common blocks for state between calls

module variables released by mexAtExit

see Legacy Features and Modernization

Pitfalls

  • Default integer for sizes. A literal or a default integer passed as mwSize is 4 bytes where 8 are read. Use an interface module, mwSize variables and _8 or a kind constant.

  • Forgetting -R2018a. mxGetDoubles and the complex accessors need the interleaved API. Without the option the build fails or behaves as the separate API.

  • Destroying plhs. An array stored in plhs belongs to MATLAB. Destroying it crashes MATLAB.

  • Writing to prhs. The inputs are shared with the workspace. Duplicate before changing.

  • Not validating the class. mxGetDoubles or mxGetScalar on the wrong class ends the MEX file or returns garbage. Check mxIsDouble, mxIsComplex, mxIsEmpty and the sizes first.

  • Leaking engGetVariable and matGetVariable results 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 a try/catch wrapper in MATLAB and read the status variable, or inspect the output buffer.

  • A non-terminated output buffer. engOutputBuffer fills 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’s u(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 expects real*8 copies half the bytes. Use real64.

  • 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=f2023 rejects integer*8 in the real fintrf.h. Let mex choose the flags, and use strict flags on the kernel and on a stand-in header as done here.

  • Compiler support. The compiler mex uses 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 libgfortran and the Intel runtime) in one process; build the pieces with the same compiler.

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 .F only

mex builds fixed-form and free-form Fortran, including .F90, as of R2026a

use mxGetPr and mxGetPi

marked not recommended since R2018b; use the typed accessors with mex -R2018a

the separate complex API is the default

the default is still -R2017b in R2026b, with a note that it will change to -R2018a

mexSetTrapFlag

removed; use mexCallMATLABWithTrap

names up to 63 characters (mxMAXNAM)

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