It should be noted that the import library as currently implemented is considered to be in a "beta" state as feedback is gathered from the community. If issues or technical considerations are raised over the next few releases the C module interface may be updated. As such, while you are free to develop binary modules against it please keep an ear out for updates that may require existing modules to be adjusted.
Also note that import is currently only available for Windows. If you would like to assist in porting the import library to other platforms please contact me!
Sqrat 0.8 introduces support for the creation an importing of modules. The modules may take one of several forms.
Each format has advantages and disadvantages, but in all cases the ability to import modules allows you to more easily build, use, and distribute reusable code libraries.
Sqrat Import is a standalone package and has no dependancy on the Sqrat Binding library, and can be used independant of it.
In order to access the import functionality in an embedded virtual machine the import library must first be registered. This is done much like the registration of the squirrel standard libraries. First you must link to "sqratimport.lib" (or .so or .a, depending on your system), then include the following in your source:
#include "sqratimport.h" // Create VM, register other libs, etc... sqrat_register_importlib(v);
Import functionality is now available to the registered virtual machine. Also note that the version of "sq" command line project packaged with sqrat registers the import library by default.
Importing a module from squirrel is fairly simple and flexible. At the most basic level a module can be imported into the root table like so:
::import("modulename");
Notice that no extention was listed. Import will search first for a binary module, followed by either a bytecode or script module. In all cases the exact nature of the module being loaded does not need to be known by the script writer and there is no difference in their usage. Binary modules will search the working directory first and then system directories as dictated by the systems standard search order. Other modules will load from the working directory only at this time.
It is possible to provide import with a full path, although the extension should remain excluded. However, please note that doing so may introduce cross-platform incompatibilities to your script.
When a module is imported any tables, classes, instances, and variables it exposes are imported into the target table. In the above example the target table was the root table, since none was specified. As such if the module exposed a class named "Foo", after importing it could be accessed like so:
::import("foomodule"); f <- ::Foo(); // Create a new instance of Foo
To specify a different table to import into, provide the target table as a second parameter. This allows you to encapsulate modules functionality in a "namespace" of your choosing.
foo <- {}; // Create a table to import into ::import("foomodule", foo); f <- foo.Foo(); // Create a new instance of Foo
Alternately, the target table is also returned by the import function, so the following code has the same result.
foo <- ::import("foomodule", {}); // Import into a new table, which will be assigned to foo f <- foo.Foo(); // Create a new instance of Foo
Multiple modules can be imported into a single table, and a single module can be imported into multiple tables if needed.
foo <- {}; // Create a table to import into ::import("foomodule", foo); ::import("barmodule", foo); // foo now contains elements from foomodule and barmodule bar <- ::import("barmodule", {}); // barmodule now is imported into both foo and bar
A script module is just a normal squirrel script. The file should be given the extension of ".nut", and there are some guidelines that should be followed when building a script intended for a module (detailed below) but otherwise it is no different than your average squirrel script. As an example:
// mymodule.nut class MyClass() { a = 1; b = "Hello"; } function AddTwo(a, b) { return a + b; } PI <- 3.1415;
// importtest.nut mod <- ::import("mymodule", {}); instance <- mod.MyClass(); ::print(instance.a); // Will print "1" ::print(instance.b); // Will print "Hello" res <- mod.AddTwo(5, 3); // res == 8 :print(mod.PI); // Will print "3.1415"
Looking at the example above, one might wonder what advantages there are to using import over dofile in this scenario. The benefits are:
Otherwise, this function works identically to dofile for scripts.
Binary modules take a bit more care to get up and running, but can be much more powerful. In a binary module you have access to any C/C++ libraries you want, and the code can execute much faster.
Binary modules must expose a single C function with the following signature:
SQRESULT sqmodule_load(HSQUIRRELVM v, HSQAPI sq);
This function will be called when the module is imported. It is responsible for doing any setup needed for the module. Interaction with squirrel is done via the standard squirrel C API.
The two parameters expose the required interfaces for interacting with squirrel from within the module. The HSQUIRRELVM is the squirrel virtual machine to import into. The HSQAPI is a pointer to a struct of function pointers containing all calls of the core squirrel API. Function names remain the same with the exeption of being stripped of the "sq_" prefix. So a call to sq_newtable() would be made within the module as sq->newtable(). This allows modules to be developed without linking to the squirrel library, which can help decrease module size when built. You will likely want to save this pointer for later use within the module. The pointer will not be deleted while the module is loaded, so it is safe to store the pointer. You should not attempt to delete the or alter the HSQAPI pointer, since this may lead to undefined behavior. HSQAPI is defined in "sqmodule.h"
When the squirrel module is called, the table to import into will be the top item on the stack. You are free to manipulate the stack however you choose while setting up the module, it will be set back to it's original state when sqmodule_load exits.
If the module is able to load successfully you should return SQ_OK, otherwise return SQ_ERROR.
The following is a sample of a very simple C++ module (Windows).
// simplemodule.h #if !defined(_SIMPLEMODULE_H_) #define _SIMPLEMODULE_H_ #include "sqmodule.h" #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) SQRESULT sqmodule_load(HSQUIRRELVM v, HSQAPI api); #ifdef __cplusplus } #endif #endif
// simplemodule.cpp #include "simplemodule.h" HSQAPI sq; SQInteger modhello(HSQUIRRELVM v) { sq->pushstring(v, _SC("Hello Squirrel Module!"), -1); return 1; } // Module registration SQRESULT sqmodule_load(HSQUIRRELVM v, HSQAPI api) { sq = api; sq->pushstring(v, _SC("hello"), -1); sq->newclosure(v, &modhello, 0); sq->newslot(v, -3, 0); return SQ_OK; }
Usage:
// simplemoduletest.nut ::import("simplemodule"); ::print(hello()); // Will print "Hello Squirrel Module!"
A more full example can be seen in the sqratthread project included with squirrel.
When building a module, especially when it is intended for use by other developers, there are several guidelines that should be adhered to to produce predictable behavior.