Skip to content

Quick Start Guide

Erik McClure edited this page Nov 30, 2019 · 5 revisions

The instructions for installing and setting up the SDK are platform-specific, but once you have everything configured, it's all the same code. This assumes you are adding inNative to an existing project, and you have at least some knowledge about C++. If you just want to compile webassembly, ignore this guide and simply install the runtime and follow the instructions for the inNative Command Line tool.

For each platform, you will need to decide if you want to link a static library, statically link to the shared library, or dynamically link to the shared library. For embedding purposes, it's almost always better to simply link the static library, but if you are sharing the runtime between multiple binaries, it might make sense to statically link the DLL. You should only dynamically link to the runtime if space is a serious concern and multiple programs may be relying on the runtime.

Windows

Either download the windows SDK and extract it or run the installer. Find the folder you extracted or installed it to and open the bin directory. Run .\innative-cmd.exe ..\scripts\test-no-op.wat -f o3 from inside the bin folder on the command line (or in powershell) and verify that you get the following output:

Successfully built ..\scripts\test-no-op.exe

If this fails, the runtime is broken and you should file an issue. Otherwise, before linking the library, you should copy the contents of /include/innative/ into a suitable include directory in your C++ project (usually the root). You should then add the parent /include/ folder to your list of include directories, not the innative folder directly. Don't go any further until you can successfully add #include "innative/export.h" to a file in your project and compile it.

Static library

The static library is innative-s.lib, with an associated innative-s.pdb debug information file (the debug build is innative-s-d.lib and innative-s-d.pdb, respectively). Simply place this library files somewhere in your project (inside a /lib/ directory is suggested) and add them to your Additional Dependencies. Then, you also need to put innative-env.lib (and innative-env-d.lib) in your bin folder, or wherever your project output is. Remember that static libraries can't include other static libraries - if you have part of your project that depends on inNative but is also a static library, whatever DLL or EXE uses this must reference both your static library and inNative's static library. See Static Embedding for more information and details about the behavior of inNative's static embedding. Otherwise, skip down to Setting Up The Environment.

Static Shared Library

On Windows, to "statically" link to a DLL, you include it's corresponding .lib file in your project, just like you would for a static library. The DLL's .lib file is innative.lib (and it's debug build is innative-d.lib). Follow all the instructions for a Static Library above by putting the .lib files in a folder and referencing them in your Additional Dependencies. Then, you also need to put innative.dll (and innative-d.dll) in your bin folder, or wherever your project output is. You'll also need to include innative-env.lib in your bin folder (and innative-env-d.lib for debug builds), and put the debug information .pdb files next to the .dll files.

If you are building a DLL that itself depends on inNative, you should consider statically embedding it. Otherwise, you have to ensure that anyone who uses your DLL also includes innative.dll in their project. This can quickly turn into DLL Hell, so be wary. For more information, please see Static Shared Embedding. Otherwise, skip down to Setting Up The Environment.

Dynamic Shared Library

The expected way to use inNative's dynamic shared library is to use innative-stub, which searches the registry for an installed version of the inNative Runtime that most closely matches the version your program uses, then loads it dynamically. The stub is a static library, so add innative-stub.lib (and the debug version, innative-stub-d.lib) to your Additional Dependencies. You do not need to distribute innative-env.lib with your project if you are dynamically linking to the runtime. For alternative ways of dynamically loading the runtime, see Dynamic Shared Embedding. Otherwise, skip down to Setting Up The Environment.

Linux

Download (or install) the POSIX SDK into an empty directory and open the bin folder. Run ./innative-cmd ../scripts/test-no-op.wat -f o3 from inside the bin folder on the command line and verify that you get the following output:

Successfully built ../scripts/test-no-op

If this fails, the runtime is broken and you should file an issue. Otherwise, before linking the library, you should copy the contents of /include/innative/ into a suitable include directory. This either by inside your project or in a system-wide include folder. You should then add the parent /include/ folder to your list of include directories, not the innative folder directly. Don't go any further until you can successfully add #include "innative/export.h" to a file in your project and compile it.

Static library

Simply place the static library innative.a somewhere in your project (inside a /lib/ directory is suggested) and modify your build process to include the static library along with all your object files when linking. Remember that static libraries can't include other static libraries - the top level .so or executable is what must be linked against the static library. Don't use a -l option with innative.a either, that's intended for shared libraries. You will then need to include innative-env.a (and innative-env-d.a) next to your executable, or in another folder that is distributed with your project. See Static Embedding for more information and details about the behavior of inNative's static embedding. Otherwise, skip down to Setting Up The Environment.

Static Shared Library

On Linux, to "statically" link to a shared library, you use the -l option. libinnative.so is named such that you can add -linnative to your project, and the linker should find the .so file either in the same directory, or in /usr/lib/. You will also need to include innative-env.a (and innative-env-d.a) next to your executable, or in another folder that is distributed with your project.

Important: If you do not include libinnative.so alongside your executable, and the user has installed the inNative runtime on their machine, you will end up statically linked to whatever the most recent inNative runtime is on the machine, even if it's an incompatible version. To avoid this, consider renaming the library to the specific version you want before linking, like libinnative.0.1.0.so, then linking to it via -linnative.0.1.0. That way, even if the linker looks in /usr/lib/, it will still only get a compatible version. For more information, see Static Shared Embedding. Otherwise, skip down to Setting Up The Environment.

Dynamic Shared Library

On Linux, it's easy enough to simply link to the semantic version of the library you want without having to dynamically load it. For example, you could link to -linnative0.1 to get the latest v0.1.x runtime that happens to be installed. However, the provided innative-stub static library will first attempt to find an exact match before trying more and more general version lookups, until it falls back to simply loading any version of the runtime on the machine. This has a higher chance of success, but could also crash the program if an incompatible version of the runtime is loaded.

The stub is a static library, so all you have to do is add innative-stub.a to your project. You do not need to distribute innative-env.a with your project if you are dynamically linking to the runtime. For more information, see Dynamic Shared Embedding. Otherwise, skip down to Setting Up The Environment.

Setting up the environment

To begin setting up inNative, first we have to initialize the external API. This will be taken care of by whatever method of linking to the library you chose, so the code won't change even if you switch from a dynamic embedding to a static one.

INExports exports;
innative_runtime(&exports);

This fills exports with all the function pointers we need. We'll be using the exports struct from now on to call our inNative functions, so make sure you keep it available.

Environment* env = (*exports.CreateEnvironment)(1, 0, (!argc ? 0 : argv[0]));

This creates an inNative environment that's expecting 1 module. We pass in argv[0] if it's available, because on POSIX systems this is used to help figure out the current location of the EXE. If you were going to modify the environment settings, this is the place you would do it. For a detailed explanation of what all the settings do, check the bottom of External API.

env->flags |= ENV_LIBRARY;

For our purposes, we only need to add the ENV_LIBRARY flag so we can load our WebAssembly module as a plugin. We could also add ENV_NO_INIT if we wanted more control over when the plugin gets initialized and destroyed, but we'll be ignoring that for now (check inNative Flags for an example on how to use ENV_NO_INIT).

int err;
(*exports.AddModule)(env, "your_script.wasm", 0, "your_script", &err);

This adds our WebAssembly module to the environment, with a given default name. The default name is used if we can't find any module name in the module file itself to use (see Module Naming Resolution. Environments can compile many different modules at the same time, and can also be re-used for multiple compilation stages. For our purposes, we're only adding a single WebAssembly module. Remember that if we were compiling a .wat file, we'd also need the ENV_ENABLE_WAT flag, since we don't compile .wat files by default.

err = (*exports.AddEmbedding)(env, 0, (void*)INNATIVE_DEFAULT_ENVIRONMENT, 0);

This adds the default embedding environment (innative-env.lib on windows and innative-env.a on linux) to our environment. This is what contains all the basic logic necessary to run WebAssembly on the current operating system. If you aren't dynamically linking to the runtime, make sure you've included innative-env.lib or innative-env.a in your project, because that's what INNATIVE_DEFAULT_ENVIRONMENT maps to! You can also rename them and move them around, so long as you call AddEmbedding() to add them to the Environment.

If we wanted to add a custom embedding environment, this is where we would do it, but for now, we're not going to bother.

err = (*exports.FinalizeEnvironment)(env);

After we've added all of our WebAssembly modules and embedding environments, we call the FinalizeEnvironment() function to ensure all modules are loaded into memory and registered. You can still modify the Environment after calling this function, but you must remember to call FinalizeEnvironments() after any changes, before you attempt to compile anything!

err = (*exports.Compile)(env, "your_script.out");

This compiles your environment into a single binary. Depending on your platform, it should probably be named .so or .dll, but it really doesn't matter. This will both validate and compile all the WebAssembly modules, and output errors into env->errors. If an error code is returned, check the env->errors for more details.

(*exports.DestroyEnvironment)(env);

If you only intend to compile things once, you don't need to keep the environment around now that the compilation has completed, so lets destroy it first. If you are going to be compiling more things, don't destroy it, and check Incremental Compilation for more details.

void* assembly = (*exports.LoadAssembly)("your_script.out");

This simply loads your binary into memory and returns a handle to it. It can return NULL if something fails while loading the library.

void (*update_entity)(int) = (void (*)(int))(*exports.LoadFunction)(assembly, "your_module", "update_entity");

Now that we've loaded our WebAssembly module as a C binary, we can load functions from it! Simply provide the module name and function name of any exported WebAssembly function, and this will return a pointer to it. You must cast it to the correct function signature, as C++ has no way of knowing what you exported. If this function is returning NULL, double check that you exported the function in WebAssembly, and you have the correct module name (remember that the name you give in AddModule could be overridden by a name given in the module itself). You can also retrieve the tables, memories, and globals using other functions in the External API.

(*update_entity)(0);

We can now call our functions using C++ to achieve whatever the WebAssembly modules are supposed to do. When calling from C++, it's often useful to be able to access the module linear memories using LoadMemory(), which will allow your C++ code to resolve indexes into WebAssembly linear memory.

(*exports.FreeAssembly)(assembly);

Once we are finished, we free the assembly. Provided we did not specify ENV_NO_INIT, it will automatically clean itself up before being unloaded from memory.

That's it! You are now compiling and loading WebAssembly modules in your program! Check Home for a more complete listing of topics that go into more detail about how the library works, and feel free to contribute to the Wiki if it seems incomplete.