Loren on the Art of MATLABTurn ideas into MATLAB

Note

Loren on the Art of MATLAB has been archived and will not be updated.

Writing Deployable Code

I'm pleased to introduce Peter Webb as this week's guest blogger. Peter has worked on the MATLAB Compiler since 1995. This is the first in a series of posts about how to make your M-files compile more easily.

Contents

The MATLAB Compiler and the deployment tools create deployable applications by "freezing" a collection of MATLAB functions and state information into a portable package. Deployed applications run using the MATLAB Component Runtime (MCR), which "thaws" the functions and state back into executable form. The Compiler creates either standalone executables or shared libraries (DLLs). Many of the differences between MATLAB and the MCR arise because a single user-written application may make use of multiple Compiler-generated shared libraries; in this case the shared libraries share the MCR's global data.

The MCR differs from MATLAB in five ways:

• The MCR has no desktop.
• The MCR only executes encrypted M-files.
• Global MCR resources, like the Handle Graphics root object and the random number seed, are shared between multiple libraries working together.
• The M-File and Java paths are fixed and cannot be changed.
• Certain MATLAB and toolbox features are not available to deployed applications.

Because the MCR differs from MATLAB, M-Files written to run using the MCR may sometimes differ from M-Files that run only in MATLAB. Generally speaking, the differences between MATLAB and the MCR fall into four categories:

• Finding functions at compile time
• Path management at runtime
• Non-deployable features
• Differences in behavior

This article describes some general guidelines and then focuses on the problem of finding functions at compile time; I will address the other three types of differences in subsequent posts.

General Guidelines

The process of compiling and deploying an application is more efficient if the application follows these guidelines.

• Don't create or use non-constant static state anywhere (in M, C++ or Java code). Code that uses global variables, for example, will likely yield unexpected results if other applications share the MCR's global resources. Don't do this:
    global data;
y = data + x;
data = x;

• Instead, create a MATLAB handle object to store the shared data, and pass that object to each function that needs access to the shared data.

• Compiled applications cannot read, parse, create or manipulate M-files at runtime. The MCR will only execute encrypted M-Files; as a result, all of the M-files in a deployed application are encrypted. MATLAB functions like fopen and help will read encrypted M-Files as unintelligible binary data. Deployed applications cannot, for example, create and then execute M-files at runtime. This will compile, but won't run:
    fp = fopen('Compute.m', 'w');
fprintf(fp, 'function x = Compute(y)\n');
fprintf(fp, '    x = y + 17;\n');
fclose(fp);
rehash;
z = Compute(10);

• This behavior is by design. You must ensure that your application contains all the M-files it needs at compile time.

• Don't rely on the current directory (cd) or changes to the MATLAB path (addpath) to control the execution of M-files. The path of a deployed application is determined at compiled time, and remains forever fixed. The current directory is never on the path in a deployed application. In the code below, cd-ing to the stringFcns directory does not make stringFcns/add shadow mathFcns/add. The order of these functions at runtime depends on what their order was at compile time. This causes errors:
    cd mathFcns
cd ../stringFcns
s = add(s1, s2);

• Avoid this problem by either using different function names, e.g., addstring and addnum or MATLAB Objects.

• Use the isdeployed function (available in M, C++ and Java) to execute deployment specific code paths, or to protect MATLAB-only code (~isdeployed). isdeployed is true when run in the MCR, and false when run in MATLAB. For example, deployed applications must use deployprint, rather than print, to send data to the printer:
    if ~isdeployed
print
else
deployprint
end
• Provide graceful degradation for applications that rely on non-deployable functions. Typically, this means using isdeployed to provide deployable alternatives to non-deployable functions, or at least to issue a precise warning message.
    switch (userInput)
% There's no editor available to deployed applications;
% print a warning message.
case 'EditMFile'
if isdeployed
warning('MyApp:NonDeployable', ...
['The editor is not available ', ...
'in compiled applications.']);
else
edit(mfile);
end
% Deployed applications can't call DOC. But they can
%  redirect a help query to The MathWorks help site.
case 'Help'
if ~isdeployed
doc(mfile);
else
web('https://www.mathworks.com/support.html');
end
end

Finding Functions at Compile Time

In order to produce a deployable package that is smaller than a MATLAB installation, the MATLAB Compiler attempts to narrow the set of deployed functions to only those required by the application. The Compiler calls the MATLAB function depfun to compute the dependent functions of each M-File it compiles. This process differs from the mechanism by which MATLAB determines which functions to call.

MATLAB searches for the best match for a function at runtime, when the types of the inputs and the contents of the MATLAB path are known precisely. depfun analyzes M-files at compile-time, when much less information is available; as a result it may omit functions that your application requires. Functions may be omitted because they are non-deployable or invoked only as callbacks or by eval.

Non-deployable Functions

Licensing restrictions prevent most design time functions from being compiled or deployed. A design time function is one that changes or augments the basic functionality of a program. Design time functions include the MATLAB editor, most toolbox GUI-based tools, like the Image Processing Toolbox function imtool, and functions that create M-Files, like the Fuzzy Logic Toolbox function anfis. This code will compile, but won't run:

    [fis,error,stepsize] = anfis(trainingData);

Attempting to execute non-deployable functions generates MATLAB:UndefinedFunction errors at runtime. Check your application's code to make sure it uses only deployable functions. mcc generates a list of excluded functions into a file called mccExcludedFunctions.log. Search this file for the string "because of compilability rules" to find the non-deployable functions depfun excluded from compilation. Because of the conservative nature of depfun's compile-time analysis, mccExcludedFiles.log may be quite large, and may list functions that your application does not require.

Detecting Dependencies on Callback Functions

The MATLAB Compiler's dependency analysis statically analyzes M-file functions to determine what other M-files they depend on. This analysis cannot determine the types of any variables or examine the contents of any strings. As a result, callback functions, which are often mentioned only in strings, can be overlooked; the Compiler will produce an application that appears to work but which will generate runtime errors when it attempts to invoke the missing callbacks.

For example:

set(gca, 'ButtonDownFcn', 'LogButtonPress');

If LogButtonPress is not included in the deployed package, this program will fail. Include LogButtonPress by explicitly specifying it on the mcc command line, or inserting a %#function pragma in the M-file that uses LogButtonPress.

Specifying a callback using mcc:

mcc [other options] -a dir1/dir2/LogButtonPress.m

Using a %#function pragma

%#function LogButtonPress

Including Functions Called by eval

MATLAB's eval function poses the same kind of problem for the Compiler's dependency analysis that callbacks do: the Compiler cannot examine text strings to determine if they contain function calls. Applications that use eval fail at runtime if the functions invoked by eval are not included in the deployed package.

For example, this code that uses eval to create dynamically named MATLAB variables requires the function getdata:

for n = 1:count
eval(sprintf('x%d = getdata(%d);', n, n));
end

The solutions to problems created by eval are the same as those for callbacks: use the %#function pragma, or explicitly deploy the function in question by listing it as an argument to your mcc command.