ObjectScript — новый встраиваемый объектно-ориентированный язык программирования с открытым исходным кодом. ObjectScript расширяет возможности таких языков, как JavaScript, Lua и PHP.
По результатам предыдущих статей было много вопросов о том, как подключать свои классы и функции на C++ к ObjectScript. Имеющийся в первых сборках OS способ подключения едва ли удовлетворял потребности и я решил сделать более мощный и удобный биндинг, который теперь идет в комплекте с OS поумолчанию.
В чем собственно преимущество нового биндинга: теперь можно подключать любую функцию, с любыми параметрами, любым возвращаемым значением без дополнительных оберток. Сразу подключаете ту функцию, которая у вас есть и все, готово. И будьте уверены, что при вызове C++ функции из скрипта на OS она получит правильные параметры, а возвращаемое из C++ значение правильно преобразуется в аналог на OS.
В части 3 был описан низкоуровневый способ подключения, он сохранился. Новый способ реализует магию по подключению функций с любыми параметрами, любым возвращаемым значением. Итак поехали!
Пусть у нас на С++ есть следующая функция:
Чтобы подключить ее в глобальное пространство имен на OS, нужно выполнить код:
Вызываем фунцию из ObjectScript:
Вывод:
Пусть у нас есть следующие функции на С++ (обратите внимание на то, что функции принимают и возвращают совершено разные типы данных):
Конечно, пользовательские функции могут принимать множество параметров. Подключаем функции к OS как модуль my:
Готово, теперь их можно использовать в OS:
Вывод:
Тут начинается самое интересное. Предполжим, у нас есть следующий тестовый класс на C++, который мы хотим использовать в коде на OS:
Подключаем к OS:
Готово, проверяем в OS:
Вывод:
Работает! В исходниках к данной статье вы также найдете, как клонировать пользовательский класс и перегружать математические операторы.
Ну и на закуску, предположим у нас в C++ есть структура данных и мы хотим, чтобы она выглядела в OS, как контейнер со значениями.
Давайте научим OS работать с нашей структурой (передавать ее в качестве параметра и возвращать результат):
Регистрируем функции на C++ для работы c TestStruct в глобальном пространстве имен OS:
Проверяем в OS:
Вывод:
Отлично, все работает! Все простые типы данных (float, int и т.п.) уже описаны в OS через CtypeValue аналогичным образом. Используйте CtypeValue, если вам нужно описать специфическую конвертацию типа данных OS -> C++ и наоборот.
Вы можете скачать исходники ObjectScript и пример из данной статьи по этой ссылке, открыть proj.win32\examples.sln, проект osbind.
Другие релевантные статьи об ObjectScript:
По результатам предыдущих статей было много вопросов о том, как подключать свои классы и функции на C++ к ObjectScript. Имеющийся в первых сборках OS способ подключения едва ли удовлетворял потребности и я решил сделать более мощный и удобный биндинг, который теперь идет в комплекте с OS поумолчанию.
В чем собственно преимущество нового биндинга: теперь можно подключать любую функцию, с любыми параметрами, любым возвращаемым значением без дополнительных оберток. Сразу подключаете ту функцию, которая у вас есть и все, готово. И будьте уверены, что при вызове C++ функции из скрипта на OS она получит правильные параметры, а возвращаемое из C++ значение правильно преобразуется в аналог на OS.
Часть 4: биндинг пользовательских классов и функций на C++
В части 3 был описан низкоуровневый способ подключения, он сохранился. Новый способ реализует магию по подключению функций с любыми параметрами, любым возвращаемым значением. Итак поехали!
Подключение глобальной функции
Пусть у нас на С++ есть следующая функция:
std::string getcwdString() { const int PATH_MAX = 1024; char buf[PATH_MAX]; getcwd(buf, PATH_MAX); return buf; }
Чтобы подключить ее в глобальное пространство имен на OS, нужно выполнить код:
os->setGlobal(def("getcwd", getcwdString));
Вызываем фунцию из ObjectScript:
print "getcwd: "..getcwd()
Вывод:
getcwd: C:\Sources\OS\proj.win32\osbind
Подключение модуля с функциями
Пусть у нас есть следующие функции на С++ (обратите внимание на то, что функции принимают и возвращают совершено разные типы данных):
bool my_isdigit(const OS::String& str) { int len = str.getLen(); for(int i = 0; i < len; i++){ if(!isdigit(str[i])){ return false; } } return len > 0; } std::string my_hash(const char * str) { int i, len = strlen(str), hash = 5381; for(i = 0; i < len; i++){ hash = ((hash << 5) + hash) + str[i]; } hash &= 0x7fffffff; char buf[16]; for(i = 0; hash > 0; hash >>= 4){ buf[i++] = "0123456789abcdef"[hash & 0xf]; } buf[i] = 0; return buf; } void my_print_num(int i) { printf("my_print_num: %d\n", i); } void my_print_void(void) { printf("my_print_void\n"); } long double my_fabs(long double a) { return a >= 0 ? a : -a; }
Конечно, пользовательские функции могут принимать множество параметров. Подключаем функции к OS как модуль my:
OS::FuncDef funcs[] = { def("isdigit", my_isdigit), def("hash", my_hash), def("print_num", my_print_num), def("print_void", my_print_void), def("abs", my_fabs), {} }; os->getModule("my"); os->setFuncs(funcs); os->pop();
Готово, теперь их можно использовать в OS:
print "isdigit(123): "..my.isdigit("123") print "isdigit(123q): "..my.isdigit("123q") print "my.hash(123): "..my.hash(123) print "call my.print_num(123.5)" my.print_num(123.5) print "call my.print_void()" my.print_void() print "my.abs(-12): "..my.abs(-12) print "my.fabs(-123.5): "..my.fabs(-123.5)
Вывод:
isdigit(123): true isdigit(123q): false my.hash(123): bf9878b call my.print_num(123.5) my_print_num: 123 call my.print_void() my_print_void my.abs(-12): 12 my.fabs(-123.5): 123.5
Подключение класса на C++
Тут начинается самое интересное. Предполжим, у нас есть следующий тестовый класс на C++, который мы хотим использовать в коде на OS:
class TestClass { public: int i; float j; TestClass(int _i, float _j){ i = _i; j = _j; } int getI() const { return i; } void setI(int _i){ i = _i; } float getJ() const { return j; } void setJ(float _j){ j = _j; } double doSomething(int a, float b, double c, TestClass * pb) { return i + j + a + b + c + pb->i + pb->j; } void print() { printf("test class: %d, %f\n", i, j); } };
Подключаем к OS:
// 1. нужно объявить класс в пространстве имен ObjectScript // OS_DECL_USER_CLASS - это макрос, в котором объявляются несколько // служебных функций для правильной типизации класса на C++ namespace ObjectScript { OS_DECL_USER_CLASS(TestClass); } // 2. нужно сделать функцию, которая будет создавать экземпляр класса TestClass * __constructTestClass(int i, float j){ return new TestClass(i, j); } // 3. описать протопит класса и зарегистрировать его в OS OS::FuncDef funcs[] = { def("__construct", __constructTestClass), def("__get@i", &TestClass::getI), def("__set@i", &TestClass::setI), def("__get@j", &TestClass::getJ), def("__set@j", &TestClass::setJ), def("doSomething", &TestClass::doSomething), def("print", &TestClass::print), {} }; registerUserClass<TestClass>(os, funcs);
Готово, проверяем в OS:
var t = TestClass(1, 0.25) print "t.i: "..t.i print "t.j: "..t.j var t2 = TestClass(2, 0.5) t2.i = t2.i + t.j print "t2" t2.print() print "t.doSomething(10, 100.001, 1000.1, t2): "..t.doSomething(10, 100.001, 1000.1, t2)
Вывод:
t.i: 1 t.j: 0.25 t2 test class: 2, 0.500000 t.doSomething(10, 100.001, 1000.1, t2): 1113.8509994506835
Работает! В исходниках к данной статье вы также найдете, как клонировать пользовательский класс и перегружать математические операторы.
Подключение пользовательского типа данных на C++
Ну и на закуску, предположим у нас в C++ есть структура данных и мы хотим, чтобы она выглядела в OS, как контейнер со значениями.
struct TestStruct { float a, b; TestStruct(){ a = b = 0; } TestStruct(float _a, float _b){ a = _a; b = _b; } }; void printTestStruct(const TestStruct& p) { printf("TestStruct: %f %f\n", p.a, p.b); } TestStruct changeTestStruct(const TestStruct& p) { return TestStruct(p.a*10, p.b*100); }
Давайте научим OS работать с нашей структурой (передавать ее в качестве параметра и возвращать результат):
namespace ObjectScript { OS_DECL_USER_CLASS(TestStruct); template <> struct CtypeValue<TestStruct> { // type используется внутри OS typedef TestStruct type; // возвращает true, если функция на C++ может работать с полученным значением static bool isValid(const TestStruct&){ return true; } // если параметр не был передан из OS, то возвращается def static TestStruct def(ObjectScript::OS * os){ return TestStruct(0, 0); } // считывание параметра из стека OS static TestStruct getArg(ObjectScript::OS * os, int offs) { if(os->isObject(offs)){ os->getProperty(offs, "a"); // required float a = os->popFloat(); os->getProperty(offs, "b"); // required float b = os->popFloat(); return TestStruct(a, b); } os->triggerError(OS_E_ERROR, "TestStruct expected"); return TestStruct(0, 0); } // учим OS пушить в стек значения типа TestStruct static void push(ObjectScript::OS * os, const TestStruct& p) { os->newObject(); os->pushStackValue(); os->pushNumber(p.a); os->setProperty("a"); os->pushStackValue(); os->pushNumber(p.b); os->setProperty("b"); } }; } // namespace ObjectScript
Регистрируем функции на C++ для работы c TestStruct в глобальном пространстве имен OS:
os->setGlobal(def("printTestStruct", printTestStruct)); os->setGlobal(def("changeTestStruct", changeTestStruct));
Проверяем в OS:
var data = {a=10 b=20} printTestStruct(data) data = changeTestStruct(data) printTestStruct(data) print data
Вывод:
TestStruct: 10.000000 20.000000 TestStruct: 100.000000 2000.000000 {"a":100,"b":2000}
Отлично, все работает! Все простые типы данных (float, int и т.п.) уже описаны в OS через CtypeValue аналогичным образом. Используйте CtypeValue, если вам нужно описать специфическую конвертацию типа данных OS -> C++ и наоборот.
Вы можете скачать исходники ObjectScript и пример из данной статьи по этой ссылке, открыть proj.win32\examples.sln, проект osbind.
Другие релевантные статьи об ObjectScript:
- ObjectScript API, интеграция с C++. Част�� 3: подключение модуля с функциями на C++
- ObjectScript API, интеграция с C++. Часть 2: выполнение скрипта на OS из C++
- ObjectScript API, интеграция с C++. Часть 1: работа со стеком, вызов функций OS из C++
- ObjectScript — новый язык программирования, быстрее чем PHP и JS
- ObjectScript — новый язык программирования
