Using Legacy Code with MATLAB Coder
By Anders Sollander and Sarah Drewes, MathWorks
You plan to generate C code for a MATLAB® application using MATLAB Coder™, but the application contains MATLAB functions not supported by MATLAB Coder, as well as legacy C code from a previous project. Will you still be able to generate code for the application?
Fortunately, the answer is yes. Using tic
and toc
and a key value store as examples, this article shows you how. It covers the following topics:
- Implementing functions not supported by MATLAB Coder
- Calling legacy C functions from generated code
- Importing legacy C functionality into MATLAB
- Improving the build process by using helper functions to add information such as include directories and source files
The code used in this article is available for download.
Implementing Functions Not Supported by MATLAB Coder
We can generate code for unsupported functions by providing implementations and wrappers that can be used in MATLAB. We will illustrate this process with a tic
and toc
function.
tic
and toc
measure elapsed time by reading the time, in seconds, that elapses between starting the stopwatch timer (tic
) and stopping it (toc
).
Suppose we have a MATLAB application that involves measuring the elapsed time:
my_alg.m
function [B, t1] = my_alg(n) t0 = mlcutils.tic; A = rand(n,n); B = inv(A'*A); t1 = mlcutils.toc(t0); end
Since functions for measuring real time are target-dependent, there is no direct way for MATLAB Coder to generate code for tic
and toc
that will work everywhere. We therefore have to supply this code ourselves. We will create a package, mlcutils
, with wrapper functions, mlcutils.tic
and mlcutils.toc
, which uses our own C implementations of tic
and toc
.
tictoc_impl.c
#include <time.h> #include "tictoc_impl.h" double MW_tic(void) { return ((double) clock()); } double MW_toc(double start) { return (clock() - start) / CLOCKS_PER_SEC; }
Both the tic
and toc
C methods can be called from MATLAB using the coder.ceval
function.
tic.m
1 function startTime = tic 2 % mlcutils.tic This function works like the built-in tic, but 3 % should be called with the corresponding package. 4 5 if coder.target('MATLAB') 6 % This will call the built-in tic function, since we're not 7 % using the package name 8 startTime = tic; 9 else 10 srcDir = mlcutils.getSrcRoot; 11 coder.updateBuildInfo('addSourcePaths', srcDir); 12 coder.updateBuildInfo('addIncludePaths', srcDir); 13 coder.updateBuildInfo('addSourceFiles', 'tictoc_impl.c'); 14 coder.cinclude('tictoc_impl.h'); 15 16 startTime = coder.nullcopy(double(0)); 17 startTime = coder.ceval('MW_tic'); 18 end 19 end
Before generating code, we must do two things: distinguish code running in MATLAB from C code, and include build information.
Distinguish Code Running in MATLAB from C Code
The coder.ceval
function only works when you are generating C code; it cannot be called when you are running the code in the MATLAB interpreter. For this reason, we use the coder.target
function to distinguish code running in the MATLAB interpreter from code generated by MATLAB Coder.
If the code runs in MATLAB, the standard tic
and toc
MATLAB functions are called (lines 5–8 in the above code sample). If code is generated, the C methods MW_tic
and MW_toc
are called using coder.ceval
(lines 9–17 in the above code sample).
Note that to prevent a recursive call in the if
statement, we included our own tic
function in the package mlcutils
, to distinguish it from the MATLAB built-in tic
function. Alternatively, we could have renamed our function or ensured that the built-in version of tic
is called as follows:
startTime = builtin('tic');
Include Build Information
To enable the code generated by coder.ceval
to build correctly, we must add information to enable the build process to find the source and include paths of the legacy C code (lines 10–13). We must also add an include statement for the relevant header file (line 14) to ensure that the function called (line 17) is known to the compiler. Since coder.ceval
only sees the name of the C function called, we must ensure that the arguments and return values are of the right data type. We will then be able to cast the function’s arguments to the correct type. For the return value, we use coder.nullcopy
to clarify which data type is expected.
Generating the Code
We can now use the adjusted application my_alg.m
for code generation with MATLAB Coder. We can do this either using the MATLAB Coder app or via the MATLAB command line.
codegen -config:lib my_alg -args {int32(0)}
The first argument (-config:lib
) specifies that we want to generate C library code, the second argument is the name of the function (my_alg
), and the last part (-args {int32(0)})
specifies the type of input argument. The generated code will utilize the C methods MW_tic
and MW_toc
in tictoc_impl.c
.
/* mlcutils.tic This function works like the built-in tic, but */ /* should be called with the corresponding package. */ t0 = MW_tic();
Using a Function with No MATLAB Equivalent
The generated code might need to use a function that is not available in MATLAB. To show how this can be done, we’ll use a simple implementation of a key value store.
A key value store (kvs) is an associative container that stores elements formed by a combination of a key value and a mapped value. The key values are used to sort and identify the elements, while the mapped values store the content associated with this key.
Our sample application includes the following MATLAB and C++ functions:
set
: Insert a key value pair, where key is of type integer and value is a string.has
: Check if a certain key is present in the container; return 1 if it is present and 0 if not.get
: Retrieve the value associated with a specified key.
kvs_impl.cpp
#include "kvs_impl.h" #include <map> #include <string> static std::map<int, std::string> gMap; int hasEntry(int key) { return gMap.find(key) != gMap.end(); } void getEntry(int key, char *pEntry) { auto search = gMap.find(key); if (search != gMap.end()) { const char *pStr = search->second.c_str(); size_t N = strlen(pStr); strncpy(pEntry, pStr, N); pEntry[N] = 0; } else { pEntry[0] = 0; } } void setEntry(int key, const char *str) { gMap[key] = std::string(str); }
These functions are called from MATLAB using coder.ceval
.
kvs.m
function [iRet, sRet] = kvs(action, key, val) iRet = int32(0); coder.varsize('sRet',[1, 256], [0, 1]) sRet = char(zeros(1, 256)); if coder.target('MATLAB') % no implementation in MATLAB iRet = 0; sRet = '0'; else srcDir = mlcutils.getSrcRoot; coder.updateBuildInfo('addSourcePaths', srcDir); coder.updateBuildInfo('addIncludePaths', srcDir); coder.updateBuildInfo('addSourceFiles', 'kvs_impl.cpp'); coder.cinclude('kvs_impl.h'); % call the C function when not called from MATLAB switch action case 'has' iRet = coder.ceval('hasEntry', int32(key)); sRet = ''; case 'get' iRet = int32(0); coder.ceval('getEntry', int32(key), coder.ref(sRet)); sRet(sRet==0) = []; % Shorten by discarding 0 elements case 'set' sval = [val, char(0)]; coder.ceval('setEntry', int32(key), sval); iRet = int32(0); sRet = ''; end end end
It is important to specify the data types of inputs and outputs correctly. In the code segment above, the C++ function getEntry
has input value key of type integer and writes the associated output to the second argument pEntry
, a character string. Thus, the first argument is declared as an int32 scalar and the second argument is a character of length up to 256 that is declared as a reference.
Importing Legacy C Functionality into MATLAB
In our first example, both tic
and toc
have an equivalent function in MATLAB that we access when running the application in MATLAB. As our key value store example showed, this might not always be the case. Further, instead of the simple key value store, we might want to use a sophisticated database function for which we have legacy C code but no MATLAB equivalent.
In this case, we can call the C code from MATLAB via a mex interface. We use MATLAB Coder to generate a mex function for each legacy C code method that should be called from MATLAB.
We begin by generating a mex function from kvs.m
.
>> codegen -config:mex -I .\+mlcutils -o .\+mlcutils\kvs_mex kvs ...
-args {'abc', int32(0), coder.typeof('x', [1,256], [0, 1])}
The argument –I
tells the compiler where to find the MATLAB file, and the –o
causes the compiler to put the resulting executable back into the package. The generated mex function kvs_mex.mexw64
can now be called from MATLAB in kvs.m
.
kvs.m
if coder.target('MATLAB') % call the C function via a mex interface [iRet, sRet] = mlcutils.kvs_mex(action, int32(key), val); else % Code generation, not shown right now end
The same implementation in kvs_impl.c
can now be used by both the generated C code and the MATLAB application.
Using Helper Files to Manage the Build Information
As we have seen, a considerable amount of information must be added for the build process to complete. Adding this information to every file can be error-prone and cumbersome. Instead, we consolidate all buildInfo
updates and incorporate them into one helper file.
updateBuildInfo.m
function updateBuildInfo % General information srcDir = mlcutils.getSrcRoot; coder.updateBuildInfo('addSourcePaths', srcDir); coder.updateBuildInfo('addIncludePaths', srcDir); % tic/toc specific coder.updateBuildInfo('addSourceFiles', 'tictoc_imp1.c'); coder.cinclude('tictoc_imp1.h'); % kvs specific coder.updateBuildInfo('addSourceFiles', 'kvs_imp1.cpp'); coder.cinclude('kvs_imp1.h'); end
Note that we only add the source paths and include paths once. We then add each of the source files and include directives as necessary.1
Location Independence
For code that can be maintained and used in different environments, it is often necessary to dynamically determine file locations. The problem, however, is that functions like which
, fileparts
, and fullfile
are not supported for code generation. The solution is to create a static function getSrcRoot
during the installation phase.
getSrcRoot.m
function root = getSrcRoot root = 'C:\projects\ACME-Inc\code\src'; end
The following function creates the correct version of this static function automatically.
genMLCUtilsRoot.m
function getMLCUtilsRoot here = fileparts (mfilename('fullpath')); fcnName = 'getSrcRoot'; fn = fullfile(here, [fcnName, '.m']); fh = fopen(fn, 'wt'); if fh < 0 error('Couldn't open file for write.\n'); end closeAfter = onCleanup(@() fclose(fh)); fprintf(fh, 'function root = %s\n\n', fcnName); fprintf(fh, 'root = ''%s'';\n\n', fullfile(fileparts(here), 'src')); fprintf(fh, 'end\n\n'); end
If you create an install script for your library, that sets the necessary MATLAB paths, you can also call this function in your setup script to ensure that the right source directory can always be found during code generation.
Summary
In this article, we showed how you can integrate MATLAB functions not supported by MATLAB Coder and call existing legacy functions from the generated code. We imported C-code functionality into MATLAB with minimal effort, reusing the existing C code and our MATLAB Coder configuration. Finally, we showed how the build process can be maintained in an easy and location-independent fashion when working with MATLAB Coder projects.
1 With large libraries, this step might unnecessarily increase the build times if the project uses only a small part of the library. In this case you may prefer to keep the build information in the respective applications.
Published 2015 - 92309v00