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 並使用斷言來驗證您的條件。