CMake/MatlabMex: Difference between revisions

From KitwarePublic
Jump to navigationJump to search
(Add explicit preformat markup)
(Remove leading space rectangles from preformatted blocks)
Line 19: Line 19:
MATLAB comes with a MEX "compiler".  It is not a true compiler, but a [http://www.mathworks.com/access/helpdesk/help/techdoc/matlab_external/f24338.html frontend build script] to the compiler of your choice.  On MS Windows it is a perl script, and on Unix-like systems it is a shell script, although they have the same user interface.  Issuing  
MATLAB comes with a MEX "compiler".  It is not a true compiler, but a [http://www.mathworks.com/access/helpdesk/help/techdoc/matlab_external/f24338.html frontend build script] to the compiler of your choice.  On MS Windows it is a perl script, and on Unix-like systems it is a shell script, although they have the same user interface.  Issuing  
<pre>
<pre>
mex -setup
mex -setup
</pre>
</pre>
allows you to setup the configuration file the mex script uses to determine which compiler it will use, the flags it will pass, etc.   
allows you to setup the configuration file the mex script uses to determine which compiler it will use, the flags it will pass, etc.   
<pre>
<pre>
mex -v  
mex -v  
</pre>
</pre>
shows the compiler and settings the build script is using.
shows the compiler and settings the build script is using.
Line 45: Line 45:
If the ''mex'' script is in the system's ''PATH'' environment variable, setting the ''CC'' or ''CXX'' environmental variable is all that is required:
If the ''mex'' script is in the system's ''PATH'' environment variable, setting the ''CC'' or ''CXX'' environmental variable is all that is required:
<pre>
<pre>
CC=mex CXX=mex cmake /path/to/project/source
CC=mex CXX=mex cmake /path/to/project/source
</pre>
</pre>


Line 54: Line 54:
It is still possible to use the  
It is still possible to use the  
<pre>
<pre>
include_directories()
include_directories()
link_directories()
link_directories()
target_link_libraries()
target_link_libraries()
</pre>
</pre>
scripting directives in the project's CMakeLists.txt.
scripting directives in the project's CMakeLists.txt.
Line 62: Line 62:
Since all mexfiles are shared libraries,  
Since all mexfiles are shared libraries,  
<pre>
<pre>
add_executable()
add_executable()
add_library(library_name STATIC sources.cpp)
add_library(library_name STATIC sources.cpp)
</pre>
</pre>
are not valid ways for generating targets.  CMake targets should be made with  
are not valid ways for generating targets.  CMake targets should be made with  
<pre>
<pre>
add_library(library_name SHARED sources.cpp)
add_library(library_name SHARED sources.cpp)
</pre>
</pre>
or
or
<pre>
<pre>
add_library(library_name MODULE sources.cpp)
add_library(library_name MODULE sources.cpp)
</pre>
</pre>


Line 86: Line 86:
Add the following to your CMakeLists.txt file:
Add the following to your CMakeLists.txt file:
<pre>
<pre>
ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME}
ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME}
    POST_BUILD
    POST_BUILD
    COMMAND ./mex_link.sh ${PROJECT_NAME} ${MEX_SAVE_PATH}
    COMMAND ./mex_link.sh ${PROJECT_NAME} ${MEX_SAVE_PATH}
    )
    )
</pre>
</pre>


Line 96: Line 96:
mex_link.sh is a short script that compiles mex_stub.c (or cpp) and then calls mex to link everything together. A suitable bash script would be:
mex_link.sh is a short script that compiles mex_stub.c (or cpp) and then calls mex to link everything together. A suitable bash script would be:
<pre>
<pre>
#!/bin/sh
#!/bin/sh
 
OUTPUT=$1
OUTPUT=$1
STATIC_LIB_NAME=$OUTPUT
STATIC_LIB_NAME=$OUTPUT
MOVE_LOCATION=$2
MOVE_LOCATION=$2
LINKER_FLAGS=''
LINKER_FLAGS=''
 
echo 'Running godawful mex linking hack...'
echo 'Running godawful mex linking hack...'
# mex_stub.o compiled with:
# mex_stub.o compiled with:
gcc-4.1 -g -Wall -fPIC -std=c99 -pthread -DMX_COMPAT_32 -DMATLAB_MEX_FILE \  
gcc-4.1 -g -Wall -fPIC -std=c99 -pthread -DMX_COMPAT_32 -DMATLAB_MEX_FILE \  
-I/opt/matlab/latest/extern/include -c mex_stub.c -o mex_stub.o
-I/opt/matlab/latest/extern/include -c mex_stub.c -o mex_stub.o
mex -g -cxx CC='gcc-4.1' CXX='gcc-4.1' LD='gcc-4.1' -L./ -lpthread -lgthread-2.0 \  
mex -g -cxx CC='gcc-4.1' CXX='gcc-4.1' LD='gcc-4.1' -L./ -lpthread -lgthread-2.0 \  
-lrt -lglib-2.0 -l$STATIC_LIB_NAME $LINKER_FLAGS -output $OUTPUT mex_stub.o
-lrt -lglib-2.0 -l$STATIC_LIB_NAME $LINKER_FLAGS -output $OUTPUT mex_stub.o
mv $OUTPUT.mexa64 $MOVE_LOCATION
mv $OUTPUT.mexa64 $MOVE_LOCATION
</pre>
</pre>


Line 116: Line 116:
MEX_SAVE_PATH will also need to be defined, often simply as:
MEX_SAVE_PATH will also need to be defined, often simply as:
<pre>
<pre>
SET(MEX_SAVE_PATH "${CMAKE_SOURCE_DIR}")
SET(MEX_SAVE_PATH "${CMAKE_SOURCE_DIR}")
</pre>
</pre>


Line 124: Line 124:
The following flags should be added as compile flags -fPIC -DMX_COMPAT_32 -DMATLAB_MEX_FILE (the last 2 just being defines) using (I can't remember if the -std=c99 is optional or not):
The following flags should be added as compile flags -fPIC -DMX_COMPAT_32 -DMATLAB_MEX_FILE (the last 2 just being defines) using (I can't remember if the -std=c99 is optional or not):
<pre>
<pre>
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -std=c99 -DMX_COMPAT_32 -DMATLAB_MEX_FILE")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -std=c99 -DMX_COMPAT_32 -DMATLAB_MEX_FILE")
</pre>
</pre>


Adding something like the following to your CMakeLists.txt file will cause make clean to work properly:
Adding something like the following to your CMakeLists.txt file will cause make clean to work properly:
<pre>
<pre>
SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${MEX_SAVE_PATH}/${PROJECT_NAME}.mexa64;mex_stub.o")
SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${MEX_SAVE_PATH}/${PROJECT_NAME}.mexa64;mex_stub.o")
</pre>
</pre>


Line 140: Line 140:
To compile this example, unpack the source, then
To compile this example, unpack the source, then
<pre>
<pre>
mkdir hello_b
mkdir hello_b
cd hello_b
cd hello_b
CC=/path/to/mex CXX=/path/to/mex /path/to/cmake /path/to/source
CC=/path/to/mex CXX=/path/to/mex /path/to/cmake /path/to/source
make
make
</pre>
</pre>


Next, at a MATLAB prompt,
Next, at a MATLAB prompt,
<pre>
<pre>
cd /path/to/hello_b/tests/
cd /path/to/hello_b/tests/
hello
hello
</pre>
</pre>



Revision as of 18:33, 24 April 2018

What are MATLAB MEX files?

MEX files are functions written in C or C++ that are callable in the MATLAB scripting language in a MATLAB interpretor. MATLAB is a 'Matrix Laboratory', a general purpose scientific scripting language owned by The MathWorks company.

Details of writing MEX files can be found in the MATLAB documentation.

Before you begin

MATLAB is a language with good syntax for working with matrices, a variety of functions, excellent documentation, and it is ubiquitous throughout the academic, scientific community. On the other hand, it is extremely slow, has poor memory utilization, is prohibitively expensive, sees little use outside academia, has a language syntax that is only good for simple procedural scripting, is closed source, and has numerous bugs. Writing MEX files is often an attempt to address some of these limitations -- speed and memory usage. However, it is a difficult and not very elegant solution. Instead, researchers should consider using superior products, such as SciPy, SciLab, Sage, or Octave.

Why would you want to use CMake for creating MEX files?

If you have a very small project that consists of only a single source code file that does not link to other libraries, then simply using the MEX compiler at the command line is all that you need. However, if your project is non-trivial, there are many advantages to using CMake. A make system allows you to recompile and link only the files that change during development and to perform parallel builds. CMake provides a configuration system for finding libraries on systems. The are many advantages to organizing and controlling the build system in a simple and powerful way, which CMake can do, among other things.

How do you use CMake with MATLAB?

Background on the MATLAB MEX "compiler"

MATLAB comes with a MEX "compiler". It is not a true compiler, but a frontend build script to the compiler of your choice. On MS Windows it is a perl script, and on Unix-like systems it is a shell script, although they have the same user interface. Issuing

mex -setup

allows you to setup the configuration file the mex script uses to determine which compiler it will use, the flags it will pass, etc.

mex -v 

shows the compiler and settings the build script is using.

The MEX compiler can process C, C++, or Fortran code. The code must contain a gateway function with specific syntax that MATLAB hooks into. Primary MATLAB itself is written is C, and a variety of functions are available in the mex.h header for interrogating MATLAB data structures, allocating memory in a way that it is controlled by MATLAB, etc. The result of this process is a shared library (dll) mexfile that is loaded by by the MATLAB interpretor when the basename is found in MATLAB's path.

How can CMake fit into this process?

There are three basic approaches to utilize CMake for the process of generating mexfiles.

Try to emulate the logic built into the mex build script in CMakeLists.txt

Since mexfiles are simply native shared libraries with some special functions, etc., it is possible to bypass the mex build script completely. The logic built into the mex built script can be replaced by appropriate scripting of the CMakeLists.txt.

Unfortunately, this approach requires significant effort, and it is difficult to maintain. In addition to the mex gateway function, a MEX file requires specific preprocessor definitions, compiler flags, link scripts, linked libraries, etc. These must be reproduced in the CMakeLists.txt. The options vary across compilers and across platforms. Furthermore, MathWorks produces new MATLAB versions twice a year, and these options change across versions. Configuration must be maintained for every MATLAB version that is desired, and new releases of MATLAB likely break the build process.

Tell CMake to treat the mex build script as the project's compiler

The other option is to tell CMake to treat the mex build script as the project's compiler. This approach dulls the power of CMake in some cases due to the limited capabilities of the mex script, but it makes project much more maintainable. This article describes this approach.

If the mex script is in the system's PATH environment variable, setting the CC or CXX environmental variable is all that is required:

CC=mex CXX=mex cmake /path/to/project/source

This method requires a patched version of CMake. The patch can be obtained here.

There are some differences from normal CMake usage when using the mex build script as the system compiler. Instead of configuring additional compiler flags from within CMake, compiler flags for the underlying compiler must be set in the mexopts configuration file. Compiler flags configured in CMake should be options sent to the mex compiler.

It is still possible to use the

include_directories()
link_directories()
target_link_libraries()

scripting directives in the project's CMakeLists.txt.

Since all mexfiles are shared libraries,

add_executable()
add_library(library_name STATIC sources.cpp)

are not valid ways for generating targets. CMake targets should be made with

add_library(library_name SHARED sources.cpp)

or

add_library(library_name MODULE sources.cpp)

Build your own static library using CMake, and then linking at the final stage

This is a fairly neat workaround for the poorness of MEX. In principle, it allows the user to build an abitrary static library using the usual build toolchain, and then only invoke the mex compiler at the final stage, linking everything together into a MEX file. It sidesteps the logical checks that MEX thinks it needs to carry out.

The method goes something like this: Replace your mexFunction() and mexAtExit() and other routines that MATLAB would expect to find with some placeholder. e.g. __mexFunction__() and __at_exit__().

Compile a static library using a suitably defined CMakeLists.txt that uses the usual build tools, e.g. gcc.

Create a stub function that wraps your __mexFunction__() etc with the MATLAB defined functions mexFunction() etc. I.e. it simply enters the function and calls the alternative version. An example is File:Mex stub.cpp.

Add the following to your CMakeLists.txt file:

ADD_CUSTOM_COMMAND(TARGET ${PROJECT_NAME}
    POST_BUILD
    COMMAND ./mex_link.sh ${PROJECT_NAME} ${MEX_SAVE_PATH}
    )

This instructs CMake to call ./mex_link.sh when the static library has been built.

mex_link.sh is a short script that compiles mex_stub.c (or cpp) and then calls mex to link everything together. A suitable bash script would be:

#!/bin/sh

OUTPUT=$1
STATIC_LIB_NAME=$OUTPUT
MOVE_LOCATION=$2
LINKER_FLAGS=''

echo 'Running godawful mex linking hack...'
# mex_stub.o compiled with:
gcc-4.1 -g -Wall -fPIC -std=c99 -pthread -DMX_COMPAT_32 -DMATLAB_MEX_FILE \ 
-I/opt/matlab/latest/extern/include -c mex_stub.c -o mex_stub.o
mex -g -cxx CC='gcc-4.1' CXX='gcc-4.1' LD='gcc-4.1' -L./ -lpthread -lgthread-2.0 \ 
-lrt -lglib-2.0 -l$STATIC_LIB_NAME $LINKER_FLAGS -output $OUTPUT mex_stub.o
mv $OUTPUT.mexa64 $MOVE_LOCATION

The onus is on the user to get all the linker flags correct. Certainly, "-lpthread -lgthread-2.0 -lrt -lglib-2.0" are all project dependent flags included as an example.

MEX_SAVE_PATH will also need to be defined, often simply as:

SET(MEX_SAVE_PATH "${CMAKE_SOURCE_DIR}")

The result of the make should be a built MEX file that works fine with MATLAB.

Notes: The following flags should be added as compile flags -fPIC -DMX_COMPAT_32 -DMATLAB_MEX_FILE (the last 2 just being defines) using (I can't remember if the -std=c99 is optional or not):

SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -std=c99 -DMX_COMPAT_32 -DMATLAB_MEX_FILE")

Adding something like the following to your CMakeLists.txt file will cause make clean to work properly:

SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${MEX_SAVE_PATH}/${PROJECT_NAME}.mexa64;mex_stub.o")

This technique has only been tested with GCC under linux. Not sure if it will work anywhere else.

Hello World Example

The source code for a "hello world" project can be downloaded or examined.

To compile this example, unpack the source, then

mkdir hello_b
cd hello_b
CC=/path/to/mex CXX=/path/to/mex /path/to/cmake /path/to/source
make

Next, at a MATLAB prompt,

cd /path/to/hello_b/tests/
hello

This project has successfully been tested on the following setups:

  • 32-bit Linux/MATLAB R2007a/gcc-4.1.1
  • 64-bit Linux/MATLAB R2007a/gcc-4.1.1
  • 64-bit Linux/MATLAB R2009a/gcc-4.1.2
  • 32-bit WindowsXP/MATLAB R2008a/gnumex-MSYS-MinGW
  • 32-bit WindowsXP/MATLAB R2008a/gnumex-Cygwin-MinGW

More complex example: ITK based image reader

Here is a more complex project that demonstrates finding external projects, adding additional include directories, and linking to libraries.

This project creates an ITK based image reader that supports all the image formats accessible to ITK .

Platform specific issues

Windows

The mex build script in Windows is set up to only look for static libraries when linking, not dll's.

Gnumex

Gnumex is a utility designed to help setup the mexopts.bat mex configuration file to use GNU tools on Windows. Unfortunately, the mex build script does not look for static libraries made with the GNU toolchains correctly. Apply the following patch to ${MATLAB_ROOT}/bin/mex.pl so that the script can see those libraries.

--- mex.pl.backup       2009-07-03 10:46:30.095025600 -0500
+++ mex.pl      2009-07-04 19:55:04.241083200 -0500
@@ -1103,6 +1103,7 @@
         my $lib_found = 0;
         foreach my $lib_dir (@IMPLICIT_LIB_DIRS) {
             my $win_name = mexCatfile($lib_dir, $1 . ".lib");
+            my $mingwwin_name = mexCatfile($lib_dir, "lib" . $1 . ".a");
             my $unx_name = mexCatfile($lib_dir, "lib" . $1 . ".lib");
             if (-e $win_name) {
                 $IMPLICIT_LIBS .= " " . smart_quote($win_name);
@@ -1112,6 +1113,10 @@
                 $IMPLICIT_LIBS .= " " . smart_quote($unx_name);
                 $lib_found = 1;
                 last;
+            } elsif (-e $mingwwin_name) {
+                $IMPLICIT_LIBS .= " " . smart_quote($mingwwin_name);
+                $lib_found = 1;
+                last;
             }
         }
         

MSYS

In order to run the mex build script in MSYS, a script such as the following is required:

#!/bin/sh
MATLAB_DIR='/c/Program Files/MATLAB/R2008a'
"${MATLAB_DIR}/sys/perl/win32/bin/perl.exe" "${MATLAB_DIR}/bin/mex.pl" "$@"

Place it in a location such as /usr/local/bin/mex.

Use the CMake Generator (-G switch to cmake.exe) MSYS Makefiles.

Cygwin

In order to run the mex build script in Cygwin, a script such as the following is required:

#!/bin/sh
MATLAB_DIR='C:\Program Files/MATLAB/R2008a'
"${MATLAB_DIR}/sys/perl/win32/bin/perl.exe" "${MATLAB_DIR}/bin/mex.pl" "$@"

Place it in a location such as /usr/local/bin/mex

Use the make.exe found here.

Use the CMake Generator (-G switch to cmake.exe) Unix Makefiles. [[File:Example.jpg]]



CMake: [Welcome | Site Map]