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.

About the Author

Sarah Drewes is a senior MathWorks consultant who helps customers develop MATLAB based algorithms to meet their business goals. Sarah is a specialist in mathematical modeling and optimization who has experience in solving complex optimization problems in a variety of application areas such as communications, finance, image processing, and automotive. She holds a Ph.D. in mathematics from the Technische Universität Darmstadt and a diploma in mathematics from the Technische Universität München.

Anders Sollander is a principal MathWorks consultant who assists customers with many aspects of Model-Based Design, including automatic code generation, interfacing software and hardware, implementing protocols and compilers, and optimizing design and architecture. Anders holds an M.S. in technical physics from Uppsala University, Sweden.

Published 2015 - 92309v00


View Articles for Related Capabilities