Io 编程/编写插件
此示例将向您展示如何创建一个用 C 编写的新的插件,该插件可以包含在 Io 源代码包中并与之一起构建。这里展示的示例插件是一个非常小的对象,它只有一个返回自身的方法。我们将它称为TrivialObject。
有关创建 C++ 绑定的介绍,请参阅 将 Io 绑定到 C++。
此框架基于 Steve Dekorte 的 Zlib 插件源代码。
Io 插件由几个部分组成
- build.io,通过克隆 AddonBuilder 并指定系统依赖项或执行其他预构建操作来定义插件。
- depends,一个文本文件,包含一个单行列表,列出了此插件所依赖的其他 Io 插件(如果有)。
- "protos",一个文本文件,包含一个单行列表,列出了此插件提供的原型名称。
- io/,一个目录,包含您要与插件一起包含的 Io 脚本(名为YourAddon.io)。如果您的所有代码都在 C 中,并且您没有其他 Io 方法要定义(如本例所示),您可能不需要在这里放任何东西。
- source/,一个目录,包含要构建到插件库中的 C 和/或 C++ 源代码和头文件。如上所述,如果您的所有代码都是用 Io 编写的,您可能不需要在这里放任何东西。
- tests/,一个目录,包含一组要运行的测试,用于验证此插件。
在 Io 源代码树下使用以下结构创建这些文件和文件夹
- io/
- addons/
- TrivialObject/
- build.io
- depends
- io/
- source/
- IoTrivialObject.h
- IoTrivialObject.c
- tests/
- TrivialObject/
- addons/
现在编写文件内容
AddonBuilder clone do( )
对于如此简单的对象,我们不需要任何依赖项,但是可以使用以下方法指定它们
dependsOnBinding(name)dependsOnHeader(name)dependsOnLib(name)dependsOnFramework(name)dependsOnInclude(name)dependsOnLinkOption(name)dependsOnSysLib(name)dependsOnFrameworkOrLib(frameworkName, libName)
//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
//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;
}
当运行make addon TrivialObject时,首先加载并运行build.io。在本例中它什么也不做,但它可以指定依赖项,执行系统命令,甚至触发外部 make 操作,如 SGML 的情况所示。
接下来,配置源代码树,AddonBuilder 在 source/ 目录中创建一个名为 IoTrivialObjectInit.c 的新文件,作为插件库的入口点。此文件的具体内容取决于source/中找到的文件数量和类型,如下所述。
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));
}
如您所见,它定义了IoTrivialObject_proto(void* state),该函数通过向 VM 上下文添加一个名为插件的槽位来初始化插件,并将该槽位设置为由 IoTrivialobject_proto 创建的全新原型。
AddonBuilder 将插件构建到插件的 _build 目录中。在这个目录中,您可能会发现
- _build/
- dll/ — 动态库在此处构建。
- headers/ — source/ 目录中的所有 *.h 文件都会复制到这里。
- lib/ — 静态库在此处构建
- objs/ — 构建生成的 .o/.obj 文件放置在此处
构建完成后,Io 安装过程会将整个 addons/ 目录复制到其最终目的地(例如 /usr/local/lib/io/addons)。
在一个更复杂的插件中,source/中可能存在多个源文件,用于不同的目的。AddonBuilder 会根据这些文件的文件名格式对它们进行不同的处理。
- source 目录中任何扩展名为 .h 的文件都将复制到 _build/headers。
- 任何以 Io*.c 或 Io*.cpp 为模式命名的源文件,并且不包含下划线,将被视为 Io 原型的源代码。
- 这些文件可能包含一行包含
docDependsOn(Name),以指定源文件依赖于编译名为IoName.c的文件生成的 obj 文件。这些源文件将按照这些依赖关系指定的顺序进行构建。 - 每个原型源文件将在插件的 Init 文件中生成一个相应的条目:
IoObject *IoFileName_proto(void *state)
- 这些文件可能包含一行包含
- 以 Io*.c 或 Io*.cpp 为模式命名,并且包含下划线的文件,例如 IoExtra_source,被视为“额外”源代码,并且将在插件的 Init 文件中生成一个相应的条目,以执行它们的初始化:
void IoExtra_sourceInit(void *context) - 任何名称中包含 "Init" 的源文件都将被忽略,但此插件的自动生成的 Init 文件除外。
- 目录中的所有其他文件都被 AddonBuilder 忽略。
可以使用make testaddons编写并运行插件的验证测试。
要编写一个测试
- 在插件树的tests/correctness/目录中创建一个名为run.io的文件,以查找并运行同一目录中的任何单元测试。
TestSuite clone setPath(System launchPath) run
- 在tests/correctness下创建测试脚本,这些脚本克隆 UnitTest 并使用断言来验证您的条件。