<-- Home |--matlab |--cpp

正确使用Matlab的Library Compiler

概述

MATLAB Library Compiler 是 MATLAB 的一个强大工具,它可以将 MATLAB 函数编译成动态链接库(DLL),使得其他编程语言(如 C/C++)可以调用 MATLAB 函数。这为跨语言集成提供了便利,特别适用于需要在高性能应用中集成 MATLAB 算法的场景。

前提条件

在目标计算机上运行编译后的 DLL 之前,必须安装相应版本的 MCR。MCR 是一个独立的运行时环境,不需要完整的 MATLAB 安装。实际上,采用Matla编译到C或者C++的动态链接库文件时,提供会提供MCR的安装包,只需要安装即可。由两种形式,网络安装或者离线安装。

前面一段时间在知乎开玩笑:用Matlab写东西,一句代码也可以跟你搞上一张DVD,简直是诚意满满。

  • 编译器: Visual Studio (Windows) 或 GCC (Linux)
  • CMake: 用于构建系统管理
  • MATLAB: 用于编译 MATLAB 函数为 DLL

步骤

第1步:准备 MATLAB 函数

首先创建要编译的 MATLAB 函数。以下是一个简单的示例:

1function r = easy(a,b)
2r = a + b;
3end

第2步:使用 Library Compiler 编译

在 MATLAB 命令窗口中执行以下命令:

1libraryCompiler

至于这玩意怎么用,请不要来问我,我也不知道。编译成功后会生成以下文件:

  • easy.dll - 动态链接库
  • easy.lib - 链接库
  • easy.h - C语言头文件

编译器会自动生成 C 语言接口的头文件,按照这个接口文件,就可以使用函数easy,只不过名字变成了mlfEasy。这里的mlf大概是Matlab Live Forver的缩写,当然也可能是Matlab Love Fucking的缩写。

 1/*
 2 * MATLAB Compiler: 23.2 (R2023b)
 3 * Date: Sat May 24 09:12:22 2025
 4 * Arguments:
 5 * "-B""macro_default""-W""lib:easy,version=1.0""-T""link:lib""-d""D:\writing\qc
 6 * hen-fdii-cardc.github.io\static\matlab\easy_dll\easy\for_testing""-v""D:\writ
 7 * ing\qchen-fdii-cardc.github.io\static\matlab\easy_dll\easy.m"
 8 */
 9
10#ifndef easy_h
11#define easy_h 1
12
13#if defined(__cplusplus) && !defined(mclmcrrt_h) && defined(__linux__)
14#  pragma implementation "mclmcrrt.h"
15#endif
16#include "mclmcrrt.h"
17#ifdef __cplusplus
18extern "C" { // sbcheck:ok:extern_c
19#endif
20
21/* This symbol is defined in shared libraries. Define it here
22 * (to nothing) in case this isn't a shared library. 
23 */
24#ifndef LIB_easy_C_API 
25#define LIB_easy_C_API /* No special import/export declaration */
26#endif
27
28/* GENERAL LIBRARY FUNCTIONS -- START */
29
30extern LIB_easy_C_API 
31bool MW_CALL_CONV easyInitializeWithHandlers(
32       mclOutputHandlerFcn error_handler, 
33       mclOutputHandlerFcn print_handler);
34
35extern LIB_easy_C_API 
36bool MW_CALL_CONV easyInitialize(void);
37extern LIB_easy_C_API 
38void MW_CALL_CONV easyTerminate(void);
39
40extern LIB_easy_C_API 
41void MW_CALL_CONV easyPrintStackTrace(void);
42
43/* GENERAL LIBRARY FUNCTIONS -- END */
44
45/* C INTERFACE -- MLX WRAPPERS FOR USER-DEFINED MATLAB FUNCTIONS -- START */
46
47extern LIB_easy_C_API 
48bool MW_CALL_CONV mlxEasy(int nlhs, mxArray *plhs[], int nrhs, mxArray *prhs[]);
49
50/* C INTERFACE -- MLX WRAPPERS FOR USER-DEFINED MATLAB FUNCTIONS -- END */
51
52/* C INTERFACE -- MLF WRAPPERS FOR USER-DEFINED MATLAB FUNCTIONS -- START */
53
54extern LIB_easy_C_API bool MW_CALL_CONV mlfEasy(int nargout, mxArray** r, mxArray* a, mxArray* b);
55
56#ifdef __cplusplus
57}
58#endif
59/* C INTERFACE -- MLF WRAPPERS FOR USER-DEFINED MATLAB FUNCTIONS -- END */
60
61#endif

C 语言集成

以下是调用 MATLAB DLL 的完整 C 程序示例:

  1#include <stdio.h>
  2#include "easy.h"
  3
  4#ifdef _WIN32
  5#include <windows.h>
  6#include <process.h>
  7#endif
  8
  9// Correct function signatures with proper calling convention
 10static int errorHandler(const char *msg)
 11{
 12    printf("MATLAB Error: %s\n", msg);
 13    fflush(stdout);
 14    return 0;
 15}
 16
 17static int printHandler(const char *msg)
 18{
 19    printf("MATLAB Message: %s\n", msg);
 20    fflush(stdout);
 21    return 0;
 22}
 23
 24int main()
 25{
 26    printf("Starting MATLAB runtime initialization...\n");
 27    fflush(stdout);
 28
 29    const char *args[] = {"-singleCompThread"};
 30    if (!mclInitializeApplication(args, 1))
 31    {
 32        printf("Failed to initialize MATLAB runtime\n");
 33        fflush(stdout);
 34        return -1;
 35    }
 36
 37    // Initialize MATLAB runtime with handlers
 38    if (!easyInitializeWithHandlers(errorHandler, printHandler))
 39    {
 40        printf("Failed to initialize MATLAB runtime\n");
 41        fflush(stdout);
 42        return -1;
 43    }
 44    printf("MATLAB runtime initialized successfully\n");
 45    fflush(stdout);
 46
 47    // Create input parameters
 48    mxArray *a = mxCreateDoubleScalar(1.0);
 49    mxArray *b = mxCreateDoubleScalar(2.0);
 50    mxArray *result = NULL;
 51
 52    // Call MATLAB function
 53    if (mlfEasy(1, &result, a, b))
 54    {
 55        // Get result
 56        double *resultData = mxGetPr(result);
 57        printf("Calculation result: %f\n", resultData[0]);
 58        fflush(stdout);
 59
 60        // Free result array
 61        mxDestroyArray(result);
 62    }
 63    else
 64    {
 65        printf("Function call failed\n");
 66        fflush(stdout);
 67    }
 68
 69    // Clean up input parameters
 70    mxDestroyArray(a);
 71    mxDestroyArray(b);
 72
 73    printf("Terminating MATLAB runtime...\n");
 74    fflush(stdout);
 75
 76    // Terminate MATLAB runtime
 77    easyTerminate();
 78
 79    printf("easy library runtime terminated\n");
 80    fflush(stdout);
 81
 82    printf("Attempting graceful exit...\n");
 83    fflush(stdout);
 84
 85    if (!mclTerminateApplication())
 86    {
 87        printf("Failed to terminate MATLAB runtime\n");
 88        fflush(stdout);
 89
 90        // Try multiple termination methods
 91        printf("Using TerminateProcess...\n");
 92        fflush(stdout);
 93
 94#ifdef _WIN32
 95        // Use TerminateProcess instead of ExitProcess
 96        HANDLE hProcess = GetCurrentProcess();
 97        TerminateProcess(hProcess, -1);
 98#endif
 99        return -1;
100    }
101    // If we reach here, TerminateProcess failed
102    printf("mclTerminateApplication succeeded, exit...\n");
103    fflush(stdout);
104    return 0;
105}

关键的步骤和代码:

  • 使用 mxCreateDoubleScalar 创建输入参数
  • 调用 mlfEasy 执行 MATLAB 函数
  • 使用 mxGetPr 获取结果数据

构建配置

CMakeLists.txt 配置

没苦硬吃警告

 1cmake_minimum_required(VERSION 3.10)
 2project(matlab_dll_demo C)
 3
 4# 设置C标准
 5set(CMAKE_C_STANDARD 11)
 6set(CMAKE_C_STANDARD_REQUIRED ON)
 7
 8# 设置MATLAB安装路径
 9set(MATLAB_ROOT "C:/Program Files/MATLAB/R2023b")
10set(MATLAB_INCLUDE_DIRS 
11    "${MATLAB_ROOT}/extern/include"
12    "${MATLAB_ROOT}/sys/opengl/include"
13    "${MATLAB_ROOT}/sys/os/glnxa64"
14)
15set(MATLAB_LIBRARY_DIRS "${MATLAB_ROOT}/extern/lib/win64/microsoft")
16
17# 设置Release版本的编译选项
18if(MSVC)
19    set(CMAKE_C_FLAGS_RELEASE "/O2 /DNDEBUG")
20else()
21    set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG")
22endif()
23
24# 添加可执行文件
25add_executable(${PROJECT_NAME} main.c easy.lib)
26
27# 添加头文件目录
28target_include_directories(${PROJECT_NAME} PRIVATE 
29    ${CMAKE_CURRENT_SOURCE_DIR}
30    ${MATLAB_INCLUDE_DIRS}
31)
32
33# 添加库文件目录
34link_directories(${MATLAB_LIBRARY_DIRS})
35
36# 链接MATLAB DLL和运行时库
37target_link_libraries(${PROJECT_NAME} PRIVATE 
38    ${CMAKE_CURRENT_SOURCE_DIR}/easy.lib
39    "${MATLAB_LIBRARY_DIRS}/libmex.lib"
40    "${MATLAB_LIBRARY_DIRS}/libmx.lib"
41    "${MATLAB_LIBRARY_DIRS}/libmat.lib"
42    "${MATLAB_LIBRARY_DIRS}/mclmcrrt.lib"
43)
44
45# 复制DLL到输出目录
46add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
47    COMMAND ${CMAKE_COMMAND} -E copy_if_different
48    ${CMAKE_CURRENT_SOURCE_DIR}/easy.dll
49    $<TARGET_FILE_DIR:${PROJECT_NAME}>) 

唯一需要注意的就是替换Matlab的安装路径。然后就是一气呵成的

1cmake -B build
2cmake --build build --config Release
3build\Release\matlab_dll_demo.exe

完美。

员工通道

实际上,我是故意要把什么CMake写在前面凑字数。Matlab中提供非常简单的工具产生exe文件。

1mbuild main.c easy.lib

这是windows下,linux下是

1mbuild main.c -L. -leasy

当然这两个前提都是把easy.lib/easy.dll和easy.h放在当前目录下。这里面Linux那个-L.就是告诉编译器,动态链接库在当前目录下。

我就不这么办,我既要让AI帮我写一个CMakeLists.txt。

一个小坑

当然,在使用Matlab编译的DLL时,会有两个小坑,说是1个自然就是2个,四大猛男当然是5个人。

  • 一个问题就是必须配对使用两个函数:
    • mclInitializeApplication
    • mclTerminateApplication
  • 另外一个问题就是如果在Matlab代码中使用了多线程或者类似玩意,可能会有坑。所以在上面那个函数调用是可以加上-singleCompThread参数。

这个问题在undocumentedmatlab.com有详细说明。

实际上,我作为一个狠人,我还准备了Windows下面的狠活。

1// 使用强制终止
2#ifdef _WIN32
3HANDLE hProcess = GetCurrentProcess();
4TerminateProcess(hProcess, 0);
5#endif

总结

MATLAB Library Compiler 为 MATLAB 算法的跨语言集成提供了强大的解决方案。

不要干什么把M文件翻译成dll,然后又在Matlab中用loadlibrary加载的事情。

那个功能是pcode,不是这个,下回再说吧。看过我以前帖子的朋友自然会help pcode, doc pcode一套连招。


文章标签

|-->matlab |-->cpp |-->library_compiler |-->mcr


GitHub