Io Programming/Writing Addons
Writing a C Addon for Io
[edit | edit source]This example will show you how to create a new Addon written in C that can be included in and built along with the Io source package. The example addon shown here is a very minimal object with one method that returns self. We will call it TrivialObject.
For an introduction to creating C++ bindings, see Binding Io to C++.
This skeleton was based on Steve Dekorte's Zlib addon sources.
Addon Structure
[edit | edit source]An Io addon is comprised of a few parts:
- build.io, which defines the addon by cloning AddonBuilder and specifying system dependencies and/or performing other pre-build operations.
- depends, a text file containing a one-line list of other Io addons that this addon depends on, if any.
- "protos", a text file containing a one-line list of the proto names offered by this addon.
- io/, a directory containing your Io script to include with the addon (named YourAddon.io). If all your code is in C and you have no additional Io methods to define, as in this example, you may not need anything here.
- source/, a directory of C and/or C++ sources and headers to build into the addon library. As above, if all your code for this addon is written in Io, you may not need anything here.
- tests/, a directory containing a battery of tests to be run to validate this addon.
Creating the Addon
[edit | edit source]Addon Structure
[edit | edit source]Create these files and folders using this structure under the Io source tree:
- io/
- addons/
- TrivialObject/
- build.io
- depends
- io/
- source/
- IoTrivialObject.h
- IoTrivialObject.c
- tests/
- TrivialObject/
- addons/
Files
[edit | edit source]Now write the file contents:
build.io
[edit | edit source]AddonBuilder clone do( )
For such a simple object, we don't need any dependencies, but they could be specified using:
dependsOnBinding(name)
dependsOnHeader(name)
dependsOnLib(name)
dependsOnFramework(name)
dependsOnInclude(name)
dependsOnLinkOption(name)
dependsOnSysLib(name)
dependsOnFrameworkOrLib(frameworkName, libName)
IoTrivialObject.h
[edit | edit source]//metadoc copyright Your Name Here 2008 // don't forget the macro guard #ifndef IOTrivialObject_DEFINED #define IOTrivialObject_DEFINED 1 #include "IoObject.h" #include "IoSeq.h" // define a macro that can check whether an IoObject is of our type by checking whether it holds a pointer to our clone function #define ISTrivialObject(self) IoObject_hasCloneFunc_(self, (IoTagCloneFunc *)IoTrivialObject_rawClone) // declare a C type for ourselves typedef IoObject IoTrivialObject; // define the requisite functions IoTag *IoTrivialObject_newTag(void *state); IoObject *IoTrivialObject_proto(void *state); IoObject *IoTrivialObject_rawClone(IoTrivialObject *self); IoObject *IoTrivialObject_mark(IoTrivialObject *self); void IoTrivialObject_free(IoTrivialObject *self); // define our custom C functions IoObject *IoTrivialObject_returnSelf(IoTrivialObject *self, IoObject *locals, IoMessage *m); #endif
IoTrivialObject.c
[edit | edit source]//metadoc TrivalObject copyright Your Name Here //metadoc TrivalObject license BSD revised //metadoc TrivalObject category Example /*metadoc TrivalObject description Describe your addon here. */ #include "IoState.h" #include "IoTrivialObject.h" // _tag makes an IoTag for the bookkeeping of names and methods for this proto IoTag *IoTrivialObject_newTag(void *state) { // first allocate a new IoTag IoTag *tag = IoTag_newWithName_("TrivialObject"); // record this tag as belonging to this VM IoTag_state_(tag, state); // give the tag pointers to the _free, _mark and _rawClone functions we'll need to use IoTag_freeFunc_(tag, (IoTagFreeFunc *)IoTrivialObject_free); IoTag_markFunc_(tag, (IoTagMarkFunc *)IoTrivialObject_mark); IoTag_cloneFunc_(tag, (IoTagCloneFunc *)IoTrivialObject_rawClone); return tag; } // _proto creates the first-ever instance of the prototype IoObject *IoTrivialObject_proto(void *state) { // First we allocate a new IoObject IoTrivialObject *self = IoObject_new(state); // Then tag it IoObject_tag_(self, IoTrivialObject_newTag(state)); // then register this proto generator IoState_registerProtoWithFunc_(state, self, IoTrivialObject_proto); // and finally, define the table of methods this proto supports // we just have one method here, returnSelf, then terminate the array // with NULLs { IoMethodTable methodTable[] = { {"returnSelf", IoTrivialObject_returnSelf}, {NULL, NULL}, }; IoObject_addMethodTable_(self, methodTable); } return self; } // _rawClone clones the existing proto passed as the only argument IoObject *IoTrivialObject_rawClone(IoTrivialObject *proto) { IoObject *self = IoObject_rawClonePrimitive(proto); // This is where any object-specific data would be copied return self; } // _new creates a new object from this prototype IoObject *IoTrivialObject_new(void *state) { IoObject *proto = IoState_protoWithInitFunction_(state, IoTrivialObject_proto); return IOCLONE(proto); } // _mark is called when this proto is marked for garbage collection // If this proto kept references to any other IoObjects, it should call their mark() methods as well. IoObject *IoTrivialObject_mark(IoTrivialObject* self) { return self; } // _free defines any cleanup or deallocation code to run when the object gets garbage collected void IoTrivialObject_free(IoTrivialObject *self) { // free dynamically allocated data and do any cleanup } // This is the one method we define, which does nothing but return self. IoObject *IoTrivialObject_returnSelf(IoTrivialObject *self, IoObject *locals, IoMessage *m) { // A method should always return an IoObject* // Per Io style guidelines, it's preferred to return self when possible. return self; }
Building
[edit | edit source]When make addon TrivialObject
is run, first build.io
is loaded and run. In our case it does nothing, but it could specify dependencies, perform system commands, or even trigger external make operations as in the case of SGML.
Next, the source tree is configured and AddonBuilder creates a new file in the source/ directory called IoTrivialObjectInit.c to serve as the entry point to the addon's library. The exact of this file depend on the number and types of files found in source/ as described below.
IoTrivialObjectInit.c
#include "IoState.h" #include "IoObject.h" IoObject *IoTrivialObject_proto(void *state); void IoTrivialObjectInit(IoObject *context) { IoState *self = IoObject_state((IoObject *)context); IoObject_setSlot_to_(context, SIOSYMBOL("TrivialObject"), IoTrivialObject_proto(self)); }
As you can see, it defines IoTrivialObject_proto(void* state)
which initializes the addon by adding a new slot to the VM context named for the addon, and sets that slot to the brand new prototype created by IoTrivialobject_proto.
AddonBuilder builds an addon into the addon's _build directory. Within this directory you may find:
- _build/
- dll/ — Dynamic libraries are built here.
- headers/ — Any *.h files from the source/ directory are copied here.
- lib/ — Static libraries are built here
- objs/ — .o/.obj files produced by the build are placed here
After building, the Io install process then copies the entire addons/ directory to its final destination (e.g. /usr/local/lib/io/addons).
Using Additional Sources
[edit | edit source]In a more complicated addon, there may be multiple source files in source/ that exist for various purposes. These files are treated differently by AddonBuilder based on the format of their filenames.
- Any file in the source directory with the extension .h will be copied to _build/headers.
- Any source file named with the pattern Io*.c or Io*.cpp, and does not contain underscores, will be treated as source code for an Io proto.
- These files may contain a line containing
docDependsOn(Name)
to specify that the source depends on the object file produced by compiling a file calledIoName.c
. The sources will be built in an order specified by these dependencies. - Each proto source files will have a corresponding entry generated in the addon's Init file:
IoObject *IoFileName_proto(void *state)
- These files may contain a line containing
- Files named with the pattern Io*.c or Io*.cpp and containing underscores, e.g., IoExtra_source, are treated as "extra" sources, and will have a corresponding entry generated in the addon's Init file to perform their initializations:
void IoExtra_sourceInit(void *context)
- Any source file that contains "Init" in its name will be ignored, except for the automatically-generated Init file for this addon.
- All other files in the directory are ignored by AddonBuilder.
Testing
[edit | edit source]Validation tests for your addon can be written and then run using make testaddons
.
To write a test:
- Create a file named run.io in the tests/correctness/ directory of your addon tree to find and run any unit tests in the same directory.
TestSuite clone setPath(System launchPath) run
- Create test scripts under tests/correctness which clone UnitTest and use assertions to validate your conditions.