Note: This exercise was tested on Stampede2 and Frontera; details on running on those systems are on the next page, MATLAB at TACC. The exercises should normally run on any MATLAB installation from version 2017a to 2020a, though other versions will likely work as well.

Previously, we showed the basic interface and build process for mex functions. Now we go into additional detail on the use of mexFunctions and the MATLAB API in C, in particular the procedure for extracting information from the passed mxArrays, and putting information computed in C back into mxArrays for eventual use in MATLAB. Readers not familiar with C may want to come back to this section after having completed Intro to C Programming.

This extended exercise computes the row averages of an m*n input matrix and returns them in an m*1 array to the user. Note: writing an averaging function of this sort is not really useful for anything outside of an exercise in learning to write mex files. In general, one should always prefer built-in MATLAB functions (in this case, mean) to perform basic tasks as they are always correct and nearly always as efficient or more than anything user-coded in C.

Although the following code can be copied and pasted, it is recommended to type it out while reading this tutorial to get a feel for writing mex functions, especially if you plan to use your own mex functions soon.

File: avg.c

As in the previous code, our mex function is named mexFunction and is defined in its own C file, in this case avg.c. Also as before, the mexFunction takes four arguments: nlhs, nrhs, plhs, and prhs, and returns void. The function begins by defining several variables which will be required in the computation of the averages. input and output are of type pointer-to-double and will eventually be used to point at the incoming m*n array and the resulting m*1 outgoing result array. i and j are loop variables, n_rows and n_cols are the number of rows and columns, respectively, of the incoming m*n array. Finally, avg is a temporary variable which will be used inside the loop to accumulate row sums and temporarily store the average before it is moved to the output storage.

Next, we perform an essential task for any mex function: checking the input variables. Recall that because MATLAB functions can be called from the interpreter with any number of input (and output) variables, including zero, we must be able to handle unexpected argument combinations in our mex file gracefully. In this particular case, we can handle exactly 1 right-hand side argument (nrhs==1) and 0 or 1 left-hand side arguments: the user may catch the output or may not. Therefore, as shown we print an error message and return immediately if either of these conditions is not met. Accessing an undefined entry in plhs will almost certainly lead to a segmentation fault violation and crash the running MATLAB process, so this input-checking step is essential.

After checking the user input, we need to actually extract the data from the MATLAB mxArray objects for processing. The mxGetPr function takes a pointer to an mxArray object and returns a pointer-to-double which is usable in C. This function, which stands for "get pointer to real," is defined, like all other MATLAB-specific functions, in the mex.h header file included at the beginning of the avg.c source. Note that in R2018a and later, it is suggested to use mxGetDoubles instead of mxGetPr, for better type safety; you may need to use the -R2018a option to mex for this to work. Try it yourself with this exercise if you have a qualifying version; you can access it by including "matrix.h", part of the C Matrix API previously discussed.

In addition to mxGetPr, MATLAB also provides the mxGetPi function which returns a pointer to the complex-valued data (if any) held in an mxArray (in R2018a and later you should instead use mxGetComplexDoubles). We next call the mxGetM and mxGetN functions which return, respectively, the number of rows "M" and columns "N" which are represented by the passed mxArray data object. In each of the mxGetPr, mxGetM, and mxGetN calls we have passed prhs[0], that is, a pointer to the zeroth location of the prhs array. We know this location is safe to access since we have previously checked that nrhs==1. (Recall that C is "zero-based" so array accesses go from 0 to nrhs-1 in contrast to MATLAB which is "one-based".)

Next we need to create storage for the return result, but only if the user has actually tried to "catch" the result, i.e. if nlhs==1. MATLAB's mex API provides a special function to allocate storage in mxArrays called mxCreateDoubleMatrix. The mxCreateDoubleMatrix function must be passed three arguments: the number of rows, the number of columns and the type to be stored in the matrix, in this case mxREAL. The return value of mxCreateDoubleMatrix is a pointer to an mxArray object, and in this rendition we store this returned pointer in plhs[0]. Once storage in the mxArray has been created, we still need to be able to access it from C, and for this purpose we will use the pointer-to-double 'output' declared earlier. We can assign 'output' to point at plhs[0] by once again utilizing the 'mxGetPr' function.

Now that the data arrays have been created and properly set up, we are ready to perform the actual averaging algorithm. The algorithm itself is rather simple, and uses the fact that the input array is stored in so-called "row-major" format. Row-major storage means that a logically two (or more) dimensional array is stored in a one-dimensional storage location, row by row. To access the (i,j) entry of the original two-dimensional data, you must compute the correct offset into the one-dimensional storage, which is given by i*n_cols + j, where n_cols is the number of columns in the two-dimensional array.

The averaging algorithm first loops over each row in the input data and computes the sum of the row entries. This step is followed by division by the number of columns. Note that we explicitly cast the integer variable n_cols to a double before performing the division to ensure that floating-point division is actually performed instead of integer division. Finally, if the user has requested to catch the output, we assign the newly-computed average to the ith entry of the output vector. This concludes the mex function, and the file avg.c.

To compile the mex function, please run the mex command from within the MATLAB interpreter as shown below. You can then try a few values as inputs to see if it is working as expected.


>>> mex avg.c
>> y = avg(rand(3));
>> y
    0.6977
    0.7476
    0.4931

After compiling our function (by running mex avg.c from the MATLAB interpreter) we can test out its function on, for example, a 3*3 matrix of random data given by rand(3). The reader should verify that MATLAB's built-in mean function gives the same result as the hand-coded mex function when passed the same data array.

Finally, let us also attempt an invalid calling sequence with our newly-created mex function. If we ask for two return values, say [y,z] instead of 1, the error message from avg.c should be triggered and the function should return immediately. Try it, and you should get a similar error:


>>> [y,z] = avg(rand(3));
Error! Expecting exactly 1 rhs and up ...
    to 1 lhs argument!
??? One or more output arguments not ...
    assigned during call to "avg".

Note that MATLAB also warns that one of the output arguments was not assigned during the call to avg: this additional error checking happens automatically for all mex functions without any additional input from the user. The same error message would be triggered if e.g. the user had called the 'avg' function without any rhs inputs.

This exercise has only scratched the surface of what is possible in MATLAB mex file programming. For example, we have not discussed working with strings, scalar values, complex data, calling MATLAB functions from mex files, creating cell arrays in mex files, generating MATLAB errors from mex files, or writing FORTRAN-based mex files. Additional mex file examples can be found in the $matlab/extern/examples/mx directory of your MATLAB installation, and a number of useful tutorials can be found on the Internet. Finally, we note that GNU Octave also provides support for writing mex files and therefore you are not limited to using your mex functions only in the official MATLAB product.

 
©   Cornell University  |  Center for Advanced Computing  |  Copyright Statement  |  Inclusivity Statement