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 并使用断言来验证您的条件。