Development Workflow

Background

This document explores making a simple standalone application. This application is a command-line program that finds the location of the nearest volcano to the user:

$ ./NearestVolcano
Nearest volcano is in Highland County, Virginia, United States

To build this sample application, you will need a 14.1+ Wolfram installation and a C/C++ compiler.

Step 1: Planning

When building a standalone application, the first step is to determine what you are using the Wolfram Language for. In this scenario, the application just needs to call the Wolfram Language function GeoNearest. Let’s do so using a simple script called NearestVolcano.wl:

If[
        Length[$CommandLine] != 1,
        Print["Usage: NearestVolcano"];
        Exit[1];
];

result = Quiet[GeoNearest["Volcano", Here][[1]]["AdministrativeDivision"][[1]]["Name"]];
If[
        !StringQ[result],
        Print["Failed to find nearest volcano"];
        Exit[1];
];

Print["Nearest volcano is in " <> result];

Exit[0];

Step 2: Runtime

For this application, the next step is to create an executable that evaluates the above code using Wolfram Language Runtime C functions. For your own application, you can choose between creating an executable or dynamic library as neccessary.

The Wolfram Language Runtime is how standalone applications access the Wolfram Language. See the following page for more information:

The following C file is NearestVolcano.c. It is an example of how runtime C functions can be used to evaluate Wolfram Language code. In this case, the code is the script NearestVolcano.wl. Note that loading a script is a very simple way to access the Wolfram Language, but the expression API allows more complicated access, such as individually calling Wolfram Language functions directly in C.

NearestVolcano.c:

#include <stdio.h>

#include "WolframLanguageRuntimeV1SDK.h"

int main(int argc, char **argv)
{
        wlr_runtime_conf configuration;

        wlr_err_t error;

        /* Set up so that executable's arguments are passed to the runtime */
        wlr_InitializeRuntimeConfiguration(&configuration);
        configuration.arguments = argv;
        configuration.argumentCount = argc;

        /* Start the runtime */
        error =
                WLR_SDK_START_RUNTIME(
                        WLR_EXECUTABLE,
                        WLR_LICENSE_OR_SIGNED_CODE_MODE,
                        "<YOURLAYOUTPATH>",
                        &configuration
                );
        if(error != WLR_SUCCESS)
        {
                printf("Failed to start Wolfram Language Runtime\n");
                return 1;
        }

        /* Allow Print[] statements in NearestVolcano.wl to flow to stdout */
        wlr_AddStdoutHandler(wlr_DefaultStdoutHandler, NULL);

        /* Evaluate the script */
        wlr_Eval(wlr_E(wlr_Symbol("Get"), wlr_String("<YOURAPPLICATIONFILEPATH>")));

        printf("Failed to run Wolfram Langauge script\n");

        return 1;
}

Replace <YOURLAYOUTPATH> with your layout path (i.e., the value of $InstallationDirectory). Example values are the following:

Mac: /Applications/Wolfram.app/Contents

Windows: C:\Program Files\Wolfram Research\Wolfram\14.3

Linux:  /usr/local/Wolfram/Wolfram

Replace <YOURAPPLICATIONFILEPATH> with the path to your NearestVolcano.wl.

Compile this C file into an executable NearestVolcano using a command line invocation such as the following:

# MacOSX-x86-64
cc NearestVolcano.c -o NearestVolcano \
        -I "<YOURLAYOUTPATH>/SystemFiles/Components/StandaloneApplicationsSDK/MacOSX-x86-64" \
        "<YOURLAYOUTPATH>/SystemFiles/Components/StandaloneApplicationsSDK/MacOSX-x86-64/StandaloneApplicationsSDK.a" \
        -lstdc++ -ldl -lpthread
# MacOSX-ARM64
cc NearestVolcano.c -o NearestVolcano \
        -I "<YOURLAYOUTPATH>/SystemFiles/Components/StandaloneApplicationsSDK/MacOSX-ARM64" \
        "<YOURLAYOUTPATH>/SystemFiles/Components/StandaloneApplicationsSDK/MacOSX-ARM64/StandaloneApplicationsSDK.a" \
        -lstdc++ -ldl -lpthread
REM Windows-x86-64
cl NearestVolcano.c ^
        -I "<YOURLAYOUTPATH>\SystemFiles\Components\StandaloneApplicationsSDK\Windows-x86-64" ^
        "<YOURLAYOUTPATH>\SystemFiles\Components\StandaloneApplicationsSDK\Windows-x86-64\StandaloneApplicationsSDK.lib" ^
        /link /STACK:8388608
# Linux-x86-64
cc NearestVolcano.c -o NearestVolcano \
        -I "<YOURLAYOUTPATH>/SystemFiles/Components/StandaloneApplicationsSDK/Linux-x86-64" \
        "<YOURLAYOUTPATH>/SystemFiles/Components/StandaloneApplicationsSDK/Linux-x86-64/StandaloneApplicationsSDK.a" \
        -lstdc++ -ldl -lpthread

The compile commands follow a standard pattern:

  • cc or cl: The name of the platform’s C compiler

  • NearestVolcano.c: Your C source file

  • -o NearestVolcano: The name for the output executable (on MacOS and Linux)

  • -I "...": The include path. This tells the compiler where to find the WolframLanguageRuntimeV1SDK.h header file.

  • ".../StandaloneApplicationsSDK.a" or ...\StandaloneApplicationsSDK.lib: The path to the SDK static library itself, which gets linked into your final executable

  • -l... or /link ...: Additional system libraries or options that the runtime depends on

Step 3: Bundling

At this point in the document, you should now have a working standalone application (i.e., the compiled binary from step 2). The next step is to bundle a layout alongside the application so that the application’s users do not have to download and install the Wolfram Language separately.

The important part of this step is looking at the directory structure of your application as it will be distributed. The desired result is to have the layout located at a fixed relative path to the standalone application binary. The following is an example of a possible structure:

└── NearestVolcano/
        ├── NearestVolcano.exe
        ├── ApplicationFiles
        |   └── NearestVolcano.wl
        └── LayoutFiles/
                └── ....

In this example, LayoutFiles is where the layout will be bundled, but it can be anywhere so long as it is at fixed relative path from the binary to the bundled layout. (If you just want to try out the application, create a directory structure like the above. Then, copy your $InstallationDirectory to LayoutFiles. You should have a SystemFiles directory under LayoutFiles.)

Next, the expression API code in the C file needs to be adjusted in order to allow the standalone application to find the bundled layout. In step 2, the following call was used to start the runtime:

WLR_SDK_START_RUNTIME(WLR_EXECUTABLE, WLR_LICENSE_OR_SIGNED_CODE_MODE, "<YOURLAYOUTPATH>", NULL);

The problem is that an absolute path is used for the layout path. Instead, a relative path needs to be used. Accordingly, a call like the following would be correct (if the structure shown above is used):

WLR_SDK_START_RUNTIME(WLR_EXECUTABLE, WLR_LICENSE_OR_SIGNED_CODE_MODE, "LayoutFiles", NULL);

Note the first argument of WLR_SDK_START_RUNTIME. This argument controls what path the layout is taken to be relative to. Use the WLR_EXECUTABLE setting to find the layout relative to the overall program executable.

Finally, the expression API call from step 2 that evaluates the script also needs to be updated:

wlr_Eval(wlr_E(wlr_Symbol("Get"), wlr_String("<YOURAPPLICATIONFILEPATH>")));

This call needs to be able to find the script, regardless of where the application is located on the end user’s filesystem. In order to do so, the value of $InstallationDirectory can be used. This value will be the location of the application’s LayoutFiles if the WLR_SDK_START_RUNTIME call was correctly updated. The script can then be found relative to $InstallationDirectory:

wlr_Eval(
        wlr_E(
                wlr_Symbol("Get"),
                wlr_E(
                        wlr_Symbol("FileNameJoin"),
                        wlr_Symbol("$InstallationDirectory"),
                        wlr_String(".."),
                        wlr_String("ApplicationFiles"),
                        wlr_String("NearestVolcano.wl")
                )
        )
);

Then, update your binary so that it reflects your C code changes. Your application should now be able to find the bundled layout!

Step 4: License Signatures

Once the application is complete and almost ready for distribution, its Wolfram Language material can be signed. This allows the application to run in the absence of a Wolfram license.

See this document for more information on what needs to be signed and how license signatures work. In short, the material that requires signing include:

  • Any .wl, .m, or .mx files you load (that are not from Wolfram)

  • The string names of any symbols you evaluate directly from your C code (e.g., symbols used within an expression for wlr_Eval)

(For this application, only NearestVolcano.wl needs to be signed.)

Submit your Wolfram Language material to Wolfram using the ccooley@wolfram.com email address. You will receive a C file called a “license signature module” that contains the license signatures for your Wolfram Language material. Add this C file to the compilation command for your application.

For this application, the new compilation command may look something like the following:

# Linux-x86-64
cc NearestVolcano.c CodeSignatureModule1.c -o NearestVolcano \
        -I "<YOURLAYOUTPATH>/SystemFiles/Components/StandaloneApplicationsSDK/Linux-x86-64" \
        "<YOURLAYOUTPATH>/SystemFiles/Components/StandaloneApplicationsSDK/Linux-x86-64/StandaloneApplicationsSDK.a" \
        -lstdc++ -ldl -lpthread

License signature modules will need to be regenerated if the application’s Wolfram Language material is changed.

Additionally, the application can be forced to always use license signatures. This allows applications to avoid accidentally consuming an end user’s license process (if they have any). In order to do so, change WLR_LICENSE_OR_SIGNED_CODE_MODE to WLR_SIGNED_CODE_MODE in the initial call to WLR_SDK_START_RUNTIME:

WLR_SDK_START_RUNTIME(WLR_EXECUTABLE, WLR_SIGNED_CODE_MODE, "LayoutFiles", NULL);

Step 5: Pruning

After the layout is bundled, it can be pruned. See this document for more information.

Pruning may need to be re-attempted if the application’s Wolfram Language material is changed.

Step 6: Packaging

The final step is to package your application for distribution. This means assembling your executable and the bundled layout files into the final format you will provide to your users, such as a .zip archive or a platform-specific application bundle or installer.

A Note on Signing Your Application

This step involves a final, critical signing process that is your responsibility as the application developer. It is important to distinguish this from the Wolfram License Signatures discussed in Step 4:

  • Wolfram License Signatures (from Step 4): Provided by Wolfram, these signatures enable your application’s Wolfram Language components to run without a standard license. They are about functionality.

  • Application Code Signing: This is the standard industry practice of using a developer certificate to sign your executable. This process is what assures an operating system that your application is from a trusted source, satisfying requirements like macOS Gatekeeper and preventing warnings from Microsoft Defender SmartScreen. This is about identity and security.

While Wolfram signs its own products, your standalone application is your own. You must use your own developer identity to sign the final product for distribution.

Next Steps

This workflow covers the fundamental steps of creating a standalone application. To build more complex applications and prepare them for distribution, consider exploring the following topics:

  • Review the full API reference

    The The Standalone Applications SDK document contains detailed descriptions for every function in the expression API, including advanced features not covered in this guide.

  • Learn about advanced topics

    The Topics section of the runtime guide covers critical concepts such as expression memory management, error handling patterns, and memory management.

  • Understand license signatures in depth

    For detailed information on how license signatures work and what assets require signing, read the License Signatures guide.

  • Optimize the application footprint

    When ready to distribute, reduce the size of the bundled layout by following the Pruning guide.