Ada kalanya MATLAB mentok: satu loop panas yang tidak bisa di-vectorize, pustaka C warisan proyek lama yang tidak boleh ditulis ulang, atau driver perangkat yang hanya tersedia dalam C. Untuk tiga situasi tersebut MATLAB menyediakan MEX — MATLAB Executable — yakni fungsi C/C++ yang dikompilasi menjadi berkas pustaka dan dipanggil persis seperti fungsi MATLAB biasa. Chapter ini membedah gerbang mexFunction, tipe data mxArray, kompilasi mex, dan alternatif loadlibrary untuk memanggil shared library C tanpa menulis MEX. Cakupan materinya:

  • struktur gateway mexFunction baris-per-baris pada contoh klasik timestwo.c
  • tipe data mxArray, mxCreateDoubleMatrix, mxGetPr, dan typed data API modern
  • validasi galat dengan mexErrMsgIdAndTxt
  • kompilasi mex timestwo.c dan kompatibilitas API klasik vs modern
  • alternatif loadlibrary + calllib untuk shared library C

Kapan Perlu Integrasi C #

Aturannya: C adalah pilihan terakhir setelah dua jalan MATLAB murni — vectorization dan parfor — gagal memberi kecepatan yang dibutuhkan. Mekanismenya: MEX file berjalan di thread yang sama dengan MATLAB sehingga panggilan per elemen dari C tidak membayar interpreter overhead. Tabel berikut memetakan situasi ke solusinya:

SituasiSolusi pertamaSolusi lanjutan
loop panas per elemenvectorizationMEX C dengan for array
butuh paralelisme dataparforMEX + OpenMP
pakai pustaka C yang sudah adaloadlibrary + calllibMEX wrapper
akses perangkat/driver C—MEX langsung ke driver

Anatomi Gateway mexFunction #

Aturannya: setiap MEX file C wajib punya tepat satu fungsi mexFunction — gateway yang dipanggil MATLAB setiap kali pengguna memanggil nama berkas. Mekanismenya: MATLAB meneruskan seluruh argumen masukan dan keluaran lewat empat parameter, dan kode C kita yang bertugas memvalidasi, menyalin data, memanggil rutin komputasi, lalu mengisi keluaran. Bentuk umumnya:

c
void mexFunction(int nlhs, mxArray *plhs[],
                 int nrhs, const mxArray *prhs[])

Empat parameter tersebut adalah kontrak antara MATLAB dan C: nlhs dan nrhs menghitung jumlah argumen keluaran (left-hand side) dan masukan (right-hand side), plhs adalah array penampung keluaran yang harus kita isi, dan prhs adalah array argumen masukan bertipe mxArray *. Contoh klasik dari dokumentasi MathWorks, timestwo.c, mengalikan satu skalar dengan dua — berkas lengkapnya tersedia sebagai Berkas Sumber chapter ini:

c
#include "mex.h"

void timestwo(double y[], double x[])
{
    y[0] = 2.0 * x[0];
}

void mexFunction(int nlhs, mxArray *plhs[],
                 int nrhs, const mxArray *prhs[])
{
    double *x, *y;
    size_t m, n;

    /* Periksa jumlah argumen masukan dan keluaran. */
    if (nrhs != 1) {
        mexErrMsgIdAndTxt("MATLAB:timestwo:invalidNumInputs",
                "One input required.");
    } else if (nlhs > 1) {
        mexErrMsgIdAndTxt("MATLAB:timestwo:maxlhs",
                "Too many output arguments.");
    }

    /* Masukan harus berupa skalar double nonkompleks. */
    m = mxGetM(prhs[0]);
    n = mxGetN(prhs[0]);
    if (!mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) ||
        (m != 1) || (n != 1)) {
        mexErrMsgIdAndTxt("MATLAB:timestwo:inputNotScalar",
                "Input must be a noncomplex scalar double.");
    }

    /* Buat matriks keluaran (padanan zeros di sisi MATLAB). */
    plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL);

    /* Ambil pointer ke data nyata pada masukan dan keluaran. */
    x = mxGetPr(prhs[0]);
    y = mxGetPr(plhs[0]);

    /* Panggil rutin komputasi timestwo. */
    timestwo(y, x);
}

Mari kita bedah baris-baris kode di atas:

  1. Baris 1 (#include "mex.h"): header resmi MEX API — tanpa baris ini, fungsi mx* dan mex* tidak dikenal compiler.
  2. Baris 3-6 (void timestwo(double y[], double x[])): rutin komputasi murni C — logika inti dipisah dari gateway agar mudah diuji di C.
  3. Baris 8-9 (void mexFunction(int nlhs, mxArray *plhs[], ...)): gateway — MATLAB memanggil fungsi ini, bukan timestwo langsung.
  4. Baris 14-21 (if (nrhs != 1) ... else if (nlhs > 1)): validasi jumlah argumen; mexErrMsgIdAndTxt melempar galat ber-identifier ke MATLAB dan menghentikan eksekusi MEX saat itu juga.
  5. Baris 23-30 (mxGetM ... inputNotScalar): validasi tipe dan ukuran masukan — skalar double nonkompleks saja; kombinasi mxIsDouble, mxIsComplex, m, n menolak char, logical, kompleks, dan matriks.
  6. Baris 33 (plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL)): alokasi matriks keluaran mxREAL (data nyata, tanpa bagian imajiner) — padanan zeros(m,n).
  7. Baris 36-37 (x = mxGetPr(prhs[0]); y = mxGetPr(plhs[0]);): mxGetPr mengembalikan double* yang menunjuk data numerik mxArray — C bekerja pada memori MATLAB secara langsung.
  8. Baris 40 (timestwo(y, x)): rutin komputasi dipanggil; hasil tertulis ke memori keluaran.

mxArray: Jembatan Data MATLAB-C #

Aturannya: seluruh data yang melintasi batas MATLAB-C berupa mxArray — tipe opaque yang membungkus array MATLAB beserta tipenya. Mekanismenya: fungsi mx dipakai untuk membaca struktur (ukuran, tipe) dan mengambil pointer ke datanya; dua API tersedia — API klasik dan typed data API R2018a+:

Tipe data MATLABMembuat (mxArray)Akses klasikAkses modern (R2018a+)
double nyatamxCreateDoubleMatrixmxGetPr / mxSetPrmxGetDoubles / mxSetDoubles
double kompleksmxCreateDoubleMatrix + mxCOMPLEXmxGetPr + mxGetPimxGetComplexDoubles
numerik lain (int32, single, …)mxCreateNumericMatrixmxGetDatamxGetInt32s, mxGetSingles, …
charmxCreateStringmxGetChars / mxGetStringsama dengan klasik

Pada API modern, baris x = mxGetPr(prhs[0]); ditulis menjadi:

c
mxDouble *x = mxGetDoubles(prhs[0]);   /* R2018a+ typed data API */

Tipe mxDouble dijamin double presisi penuh, dan API ini juga satu-satunya jalur bersih untuk data kompleks ter-interleave. Untuk kode baru, MathWorks menganjurkan typed data API; contoh timestwo.c di atas tetap memakai mxGetPr karena pola klasik ini masih dikompilasi dan paling banyak dipakai di kode warisan.

Kompilasi dan Kompatibilitas API #

Aturannya: perintah mex mengubah timestwo.c menjadi pustaka binaan lokal — timestwo.mexa64 di Linux, timestwo.mexw64 di Windows, timestwo.maci64 di macOS — yang dipanggil dari MATLAB dengan timestwo(21). Mekanismenya: mex memanggil compiler C/C++ yang terpasang, menautkan pustaka MATLAB, lalu menghasilkan berkas dengan ekstensi platform. Urutan pengerjaannya:

matlab
mex -setup c          % sekali saja: pilih compiler C
mex timestwo.c        % kompilasi menjadi timestwo.mexa64/dll
timestwo(21)          % panggil seperti fungsi MATLAB biasa

Kompatibilitas API dikendalikan flag -R2017b (API klasik, kompleks terpisah — default lama untuk MEX C) dan -R2018a (API ter-interleave + typed data — standar untuk MEX C++ dan kode baru). Kode real-data seperti timestwo.c dikompilasi benar pada kedua API; tulis flag secara eksplisit (mex -R2018a timestwo.c) bila kode memakai mxGetDoubles dan sejenisnya. Panggilan timestwo(21) lalu mengembalikan hasil sesuai dokumentasi resmi:

text
ans =
    42

Pemanggilan dengan masukan salah — misalnya timestwo('21') — menghentikan eksekusi dan mencetak galat Error using timestwo disusul pesan Input must be a noncomplex scalar double.; perilaku tersebut berasal dari panggilan mexErrMsgIdAndTxt pada gateway.

Alternatif: loadlibrary dan calllib #

Aturannya: bila target berupa shared library C (.dll/.so/.dylib) yang sudah ada beserta header-nya, kita tidak perlu menulis MEX sama sekali. Mekanismenya: loadlibrary memuat pustaka ke memori MATLAB, calllib memanggil satu fungsi C dengan konversi argumen otomatis, dan unloadlibrary melepasnya:

matlab
loadlibrary('libkonversi', 'konversi.h')          % muat pustaka + header
hasil = calllib('libkonversi', 'hitungDiskon', 100000)   % panggil fungsi C
unloadlibrary('libkonversi')                      % lepaskan dari memori

Fungsi C yang bisa dipanggil dibatasi pada tipe primitif dan pointer yang bisa dipetakan ke tipe MATLAB — double* masuk-keluar sebagai argumen pointer bertipe lib.pointer. Dibandingkan MEX, rute ini lebih cepat ditulis karena tidak ada kode C baru; batasnya fleksibilitas: MEX tetap diperlukan bila kita butuh kontrol penuh atas mxArray. Panggilan loadlibrary/calllib adalah fitur MATLAB — runner Octave tidak mengujinya.

Menguji Logika timestwo di MATLAB Murni #

Karena kompilasi MEX tidak tersedia di runner, contoh.m menguji semantik timestwo.c di MATLAB murni: fungsi timestwo di dalamnya memvalidasi masukan dengan aturan yang sama (isa menggantikan mxIsDouble, isreal menggantikan mxIsComplex, isscalar menggantikan cek m/n), lalu melempar galat ber-identifier yang sama:

matlab
    daftarUji = { ...
        'double skalar',   21,     true; ...
        'double negatif',  -3.5,   true; ...
        'nol',             0,      true; ...
        'char ''21''',     '21',   false; ...
        'double kompleks', 2+3i,   false; ...
        'logical',         true,   false; ...
        'matriks 1x2',     [1 2],  false ...
        };
    for k = 1:size(daftarUji, 1)
        label = daftarUji{k, 1};
        nilai = daftarUji{k, 2};
        harusSukses = daftarUji{k, 3};
        try
            y = timestwo(nilai);
            status = sprintf('OK (y = %g)', y);
            sukses = true;
        catch err
            status = sprintf('galat %s', err.identifier);
            sukses = false;
        end
        assert(isequal(sukses, harusSukses), 'uji "%s" gagal', label);
        fprintf('%-18s -> %s\n', label, status);
    end

Bagian ketiga contoh.m membandingkan loop ala C dengan operasi vektoris — menegaskan alasan klasik menulis MEX sekaligus idiom MATLAB yang menggantikannya:

matlab
    x = 1:8;
    y = zeros(size(x));            % padanan mxCreateDoubleMatrix + mxGetPr
    for k = 1:numel(x)
        y(k) = 2 * x(k);           % padanan y[k] = 2.0 * x[k]
    end
    fprintf('loop C-style vs 2*x: identik = %d\n', isequal(y, 2*x));

Output yang Dihasilkan (keseluruhan contoh.m):

text
== Bagian 1: hitung y = 2*x ==
timestwo(21) = 42
timestwo(-3.5) = -7
timestwo(0) = 0
timestwo(1e+10) = 2e+10
== Bagian 2: kontrak argumen gateway ==
double skalar      -> OK (y = 42)
double negatif     -> OK (y = -7)
nol                -> OK (y = 0)
char '21'          -> galat MATLAB:timestwo:inputNotScalar
double kompleks    -> galat MATLAB:timestwo:inputNotScalar
logical            -> galat MATLAB:timestwo:inputNotScalar
matriks 1x2        -> galat MATLAB:timestwo:inputNotScalar
== Bagian 3: loop C vs vektoris ==
loop C-style vs 2*x: identik = 1
kontrak & logika timestwo: 12 uji lulus, PASS

Tujuh baris kontrak tersebut adalah tabel kebenaran gateway versi MATLAB: hanya skalar double nonkompleks yang lolos, lima masukan lain ditolak dengan identifier MATLAB:timestwo:inputNotScalar — persis cabang galat pada kode C.

Ringkasan #

MEX menghubungkan MATLAB dan C lewat satu gateway mexFunction(nlhs, plhs, nrhs, prhs): validasi argumen lebih dulu (mxIsDouble, mxIsComplex, ukuran), lalu alokasi keluaran (mxCreateDoubleMatrix), pengambilan pointer (mxGetPr, atau mxGetDoubles di API R2018a+), dan pemanggilan rutin komputasi; galat validasi dilempar lewat mexErrMsgIdAndTxt. Kompilasi mex timestwo.c menghasilkan pustaka platform (.mexa64/.mexw64), sedangkan loadlibrary+calllib menjadi jalur tanpa-MEX untuk shared library yang sudah ada. Skrip contoh.m membuktikan kontrak dan logika times-two di MATLAB murni: 12 uji lulus termasuk lima cabang galat, dan loop C-style terbukti identik dengan 2*x.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

timestwo.c

Unduh berkas
c
/*=====================================================================
 * timestwo.c - contoh klasik dari dokumentasi MathWorks
 *
 * Mengalikan satu masukan skalar dengan 2.
 *
 * Kompilasi (MATLAB dengan C compiler terpasang):
 *   mex timestwo.c
 *
 * Lalu panggil dari Command Window:
 *   timestwo(21)     -> 42
 *
 * Sumber: MathWorks, "Tables of MEX Function Source Code Examples"
 * https://www.mathworks.com/help/matlab/matlab_external/
 * table-of-mex-source-code-examples.html
 *===================================================================*/
#include "mex.h"

void timestwo(double y[], double x[])
{
    y[0] = 2.0 * x[0];
}

void mexFunction(int nlhs, mxArray *plhs[],
                 int nrhs, const mxArray *prhs[])
{
    double *x, *y;
    size_t m, n;

    /* Periksa jumlah argumen masukan dan keluaran. */
    if (nrhs != 1) {
        mexErrMsgIdAndTxt("MATLAB:timestwo:invalidNumInputs",
                "One input required.");
    } else if (nlhs > 1) {
        mexErrMsgIdAndTxt("MATLAB:timestwo:maxlhs",
                "Too many output arguments.");
    }

    /* Masukan harus berupa skalar double nonkompleks. */
    m = mxGetM(prhs[0]);
    n = mxGetN(prhs[0]);
    if (!mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) ||
        (m != 1) || (n != 1)) {
        mexErrMsgIdAndTxt("MATLAB:timestwo:inputNotScalar",
                "Input must be a noncomplex scalar double.");
    }

    /* Buat matriks keluaran (padanan zeros di sisi MATLAB). */
    plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL);

    /* Ambil pointer ke data nyata pada masukan dan keluaran. */
    x = mxGetPr(prhs[0]);
    y = mxGetPr(plhs[0]);

    /* Panggil rutin komputasi timestwo. */
    timestwo(y, x);
}