在前面的實作中,我們是直接對 OP_SEND
放入 printf
的處理將結果印出來,但這樣就表示我們在 Ruby 不論呼叫任何方法都只會將他印出來,這並不是我們預期的效果。
現在我們已經可以取出方法名稱,也就表示我們可以利用這個字串去查詢是否有登記在記憶體裡面,並且找到對應的 C Function 來執行,基本上它是一個很簡單的 Key-Value 結構,不過在這之前我們會需要有一個地方去存放這個字串跟方法的關係表。
在前面的實作中我們直接跳過了 mrb_state
的處理,如果是在正常的 mruby 呼叫中我們會這樣使用
mrb_state* mrb = mrb_open();
mrb_load_string(mrb, "puts 1");
mrb_close(mrb);
基本上所有的 mruby 呼叫都會需要將 mrb_state
傳遞進去,這是因為 mrb_state
基本上紀錄了整個 Ruby VM 運行的狀態,也包括了我們所需要紀錄方法的關係表。
首先,我們先增加一個 src/iron.h
來定義對應的行為。
#ifndef _IRON_H_
#define _IRON_H_
#define IRON_API extern
typedef struct mrb_state {
int exc; /* exception */
} mrb_state;
IRON_API mrb_state* mrb_open(void);
IRON_API void mrb_close(mrb_state* mrb);
#endif
我們需要使用 mrb_open
來產生一個新的 mrb_state
供我們使用,以及 mrb_close
來將使用完畢的 mrb_state
情除掉,除此之外我們還需要將這些方法標記為 extern
才能夠被其他專案呼叫到,像是 Arduino 是 C++ 為基底的,如果沒有 extern
的話很有可能是無法讀取到的。
在 mruby 中我們可以利用使否有定義 MRB_API
來區分是否為可以呼叫的方法,還是 mruby 內部自己使用的方法。
這邊會叫做
IRON
的原因單純是取名困難,也可以用 mruby 的方式叫做MRB_API
之類的,另外extern
的部分還不太清楚實際的使用狀況,後續實作利用 PlatformIO 時即使缺少了也沒有發生錯誤,應該是有其他原因造成的。
接下來加入 src/iron.c
實作對應的行為
#include <stdlib.h>
#include "iron.h"
IRON_API mrb_state*
mrb_open(void) {
static const mrb_state mrb_state_zero = { 0 };
mrb_state* mrb = (mrb_state*)malloc(sizeof(mrb_state));
*mrb = mrb_state_zero;
return mrb;
}
IRON_API void
mrb_close(mrb_state* mrb) {
if(!mrb) return;
free(mrb);
}
這邊我們使用了 malloc
分配了記憶體,也代表不會被自動的回收,因此我們需要利用 mrb_close
來將分配的記憶體回收,除此之外因為 malloc
分配給我們的記憶體可能有殘留其他程式使用過的部分,因此我們製作了一個透過 0
(NULL
) 填滿的資料將他複製到我們申請的記憶體區段中確實的清除掉資料殘留。
跟 Ruby 不同的是 C 語言是不會有 GC 等處理的,正常狀況下大部分變數都會在方法被呼叫完畢後回收。如果不想要被回收,就會利用像是
malloc
這類方式項要求一段記憶體來使用,同時也就需要負起將這段記憶體清除的責任。
最後我們在針對 mrb_exec
等方法加入將 mrb_state*
傳入的修改。
// src/main.c
int main(int argc, char** argv) {
mrb_state* mrb = mrb_open();
mrb_run(mrb, app);
mrb_close(mrb);
return 0;
}
// src/vm.h
#define mrb_run(mrb, irep) mrb_exec(mrb, irep_load(irep))
int mrb_exec(mrb_state* mrb, const uint8_t* irep);
// src/vm.c
int mrb_exec(mrb_state* mrb, const uint8_t* data) {
// ...
}
如此一來我們的 Ruby VM 就開始能儲存一些狀態,接下來就是將一些狀態移動到 mrb_state
裡面。
// src/vm.c
// ...
default:
DEBUG_LOG("Unsupport OP Code: %d", insn);
mrb->exc = 1;
像是原本是用 error = 1
的變數來紀錄是否出錯,現在我們就可以將他存到 mrb_state
裡面。
在 mruby 中
exc
是 Exception 資訊的紀錄,他實際會指向一個 Exception 物件,在發生錯誤的時候讓我們可以從exc
裡面取出錯誤的類型跟資訊並且呈現給使用者。
有了 mrb_state
後我們就可以將對應資料儲存進去,下一篇我們會開始來處理紀錄 Ruby 方法跟 C 語言中方法的對應。
實際上
reg
也會被放到mrb_state
中,不過現階段並不是必要的,在我實驗的實作階段中 feature/6-mrb_state 中有做這樣的修改,但是我們可以等到後面處理變數的時候再一起修改。