Using MATLAB objects from custom C++ libraries to be used by MEX functions

92 Views Asked by At

My current MEX file is properly compiling, however, I would like to create let's say several MEX files:

  • mex_a.cpp
  • mex_b.cpp
  • mex_c.cpp

At this moment, mex_a.cpp contains the typical function definition:

class MexFunction : public matlab::mex::Function {

private:
    // Get pointer to engine
    std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr = getEngine();

    // Get array factory
    std::shared_ptr<matlab::data::ArrayFactory> factoryPtr;

private:
 void operator()(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) override
    {
        try
        {
            // Parse inputs..
            mex_aux->mex_common_function(...)
            // Parse outputs..
        }
        catch (int)
        {
            std::fprintf(stdout, "ERROR: Something unexpected happened...");
        }
    }

In mex_aux, we can find the definition and implementation of mex_common_function. The purpose of this object is to be used by mex_a.cpp, mex_b.cpp and mex_c.cpp, basically what a common library do.

My findings are:

  • If mex_common_function is static, everything works.
  • Otherwise, it doesn't work while using matlab objects.

The .hpp file looks like:

#pragma once

// Include system libraries
# include <string>
# include <utility>

// MATLAB stuff
#include "mex.hpp"

class mex_aux
{
public:
    // Constructor
    mex_aux() = default;

    // Destructor
    ~mex_aux() = default;

private:
    // Get pointer to engine
    std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr;

    // Get array factory
    std::shared_ptr<matlab::data::ArrayFactory> factoryPtr;

public:
    // Setters:
    void set_matlab_ptr(std::shared_ptr<matlab::engine::MATLABEngine>& matlab_ptr) {this->matlabPtr = matlab_ptr; }
    void set_factory_ptr(std::shared_ptr<matlab::data::ArrayFactory>& factory_ptr) {this->factoryPtr = factory_ptr; }

public:
    // The used function, if this one is set as static (hence not using members of the class) it works.
    void mex_common_function();

What I am doing now, and hence getting the following error in MATLAB (not during building time) is:

  • Setting matlabPtr as an internal pointer in mex_aux.
  • Same for factoryPtr.

This both from the main mex function file (i.e., mex_a.cpp) after building mex_aux, the setters are used.

Error in MATLAB:

Invalid MEX-file
'/path/to/mex/mexfunction.mexa64':
/path/to/external/libextern.so:
undefined symbol:
_ZN6matlab6engine12MATLABEngine5fevalERKNSt7__cxx1112basic_stringIDsSt11char_traitsIDsESaIDsEEEiRKSt6vectorINS_4data5ArrayESaISC_EERKSt10shared_ptrISt15basic_streambufIDsS5_EESM_

Error in mex_vsod (line 50)
    b(:,:,i) = mexfunction(...);

My CMakeLists.txt:

# MEX AUX Library
set(LIBRARY_MEX_AUX "mex_aux")

add_library(${LIBRARY_MEX_AUX} SHARED
        src/core/tools/mex_aux.cpp)

target_link_libraries(${LIBRARY_MEX_AUX}
        ${Matlab_MEX_LIBRARY}
        ${Matlab_MX_LIBRARY})

set_target_properties(${LIBRARY_MEX_AUX} PROPERTIES
        COMPILE_FLAGS "-fPIC"
        LINK_FLAGS "-Wl,-rpath,./") # To use relative paths in shared libs


# MEX FILES
if (BUILD_WITH_MATLAB)
    # MEX functions files
    matlab_add_mex(NAME mex_vsaod
            SRC src/main/mex_cpp/mex_a.cpp
            LINK_TO ${LIBRARY_target1} ... ${LIBRARY_targetN} ${LIBRARY_MEX_AUX})
endif ()

I guess something wrong is happening within the memory due to a pointer from mex_aux pointing possibly to an un-accessible place in memory (only scoped to the mex file -mex_a.cpp?-)...

Can someone give me a clue? I would really appreciate it.

1

There are 1 best solutions below

1
Cris Luengo On

I have used a shared library in MEX-files where multiple MEX-files need access to the same C++ object. You then create the object in the shared library, and can use it in all the MEX-files that link to it.

But if the shared library needs to call MATLAB functions, it becomes much more complicated to build. I would keep the MATLAB interaction outside the shared library.

Given your use case, "they are mostly conversion functions, like std::vector<doubles> to matlab::arrays", I would either make this a header-only library or a static library. You don't need to share a C++ object across MEX-files, you don't really need this to be a shared library. And the conversion functions would typically not be large, complex code that unnecessarily bloats the MEX-files by linking them statically.

However, if you really want to build a shared library that calls MATLAB, then you need to build it like you would a MEX-file, except it doesn't have the standard MEX-file entry point or the MEX-file extension. But you do need to link all the same MATLAB libraries, and set all the same preprocessor defines.

When you call matlab_add_mex in your CMake file, you run this CMake code. This shows the complexity of building a MEX-file. If you want to write a CMake script that works for all versions of MATLAB and for all platforms, you'll have to basically copy over most of what that function does. But you're using the C++ API, so follow only the paths that apply to MATLAB 9.4 (R2018a) and newer. This includes all the bits that are run when Matlab_HAS_CPP_API and Matlab_DATAARRAY_LIBRARY are set.

It would look something like this (not tested!):

add_library(${LIBRARY_MEX_AUX} SHARED
    src/core/tools/mex_aux.cpp)
    "${Matlab_ROOT_DIR}/extern/version/cpp_mexapi_version.cpp"
)
target_include_directories(${LIBRARY_MEX_AUX} SYSTEM PRIVATE ${Matlab_INCLUDE_DIRS})
target_link_libraries(${LIBRARY_MEX_AUX}
    ${Matlab_ENGINE_LIBRARY}
    ${Matlab_DATAARRAY_LIBRARY}
    ${Matlab_MEX_LIBRARY}
    ${Matlab_MX_LIBRARY}
)
target_compile_definitions(${LIBRARY_MEX_AUX} PRIVATE
    "MATLAB_DEFAULT_RELEASE=R2018a"
    MATLAB_MEX_FILE
)
if(WIN32)
  set_property(TARGET ${LIBRARY_MEX_AUX} PROPERTY
      DEFINE_SYMBOL "DLL_EXPORT_SYM=__declspec(dllexport)")
  # (this is the same as `target_compile_definitions`, not sure why the CMake script uses this syntax)
else()
  target_compile_options(${LIBRARY_MEX_AUX} PRIVATE "-fvisibility=default")
  if(NOT APPLE) # Linux
    # not sure if this is necessary?
    target_compile_options(${LIBRARY_MEX_AUX} PRIVATE "-pthread")
  endif()
endif()

Note that this code might change for future versions of MATLAB, they keep changing how one builds MEX-files. So it really is much more convenient to link your conversion functions statically.


The -fPIC option you add is already handled by CMake when you build a SHARED library. But you'd have to explicitly add it to a static library that you intend to link into a shared library, at least on Linux.