📜 ⬆️ ⬇️

ObjectScript API, integration with C ++. Part 4: Connecting Custom Classes and Functions in C ++

ObjectScript is a new, open source, embeddable object-oriented programming language. ObjectScript extends the capabilities of languages ​​such as JavaScript, Lua and PHP.

According to the results of previous articles, there were many questions about how to connect your classes and functions in C ++ to ObjectScript. The way of connection available in the first OS assemblies hardly met the needs and I decided to make a more powerful and convenient binding, which now comes bundled with the OS by default.

What is the real advantage of a new binding: now you can connect any function, with any parameters, any return value without additional wrappers. Immediately connect the function that you have and everything is ready. And be sure that when you call a C ++ function from a script on OS, it will get the correct parameters, and the value returned from C ++ will be correctly converted to an analog on OS.
')

Part 4: Binding User Classes and Functions in C ++


In part 3 , the low-level connection method was described, it is preserved. The new method implements magic on connecting functions with any parameters, any return value. So let's go!

Connecting the global function


Let us in C ++ have the following function:

std::string getcwdString() { const int PATH_MAX = 1024; char buf[PATH_MAX]; getcwd(buf, PATH_MAX); return buf; } 

To connect it to the global namespace on the OS, you need to run the code:

 os->setGlobal(def("getcwd", getcwdString)); 

Call the function from ObjectScript:

 print "getcwd: "..getcwd() 

Conclusion:

 getcwd: C:\Sources\OS\proj.win32\osbind 

Connecting a module with functions


Suppose we have the following functions in C ++ (note that the functions accept and return completely different data types):

 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; } 

Of course, user-defined functions can take multiple parameters. We connect functions to the OS as a my module:

 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(); 

Done, now you can use them in 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) 

Conclusion:

 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 

Class connection in C ++


This is where the fun begins. Suppose we have the following test class in C ++, which we want to use in the code on 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); } }; 

We connect to 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); 

Done, check in OS:

 var t = TestClass(1, 0.25) print "ti: "..ti print "tj: "..tj var t2 = TestClass(2, 0.5) t2.i = t2.i + tj print "t2" t2.print() print "t.doSomething(10, 100.001, 1000.1, t2): "..t.doSomething(10, 100.001, 1000.1, t2) 

Conclusion:

 ti: 1 tj: 0.25 t2 test class: 2, 0.500000 t.doSomething(10, 100.001, 1000.1, t2): 1113.8509994506835 

Works! In the source code for this article, you will also find how to clone a custom class and overload mathematical operators.

Connecting a custom data type in C ++


Well, for a snack, suppose we have a data structure in C ++ and we want it to look like a container with values ​​in the 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", pa, pb); } TestStruct changeTestStruct(const TestStruct& p) { return TestStruct(pa*10, pb*100); } 

Let's teach the OS to work with our structure (pass it as a parameter and return the result):

 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(pa); os->setProperty("a"); os->pushStackValue(); os->pushNumber(pb); os->setProperty("b"); } }; } // namespace ObjectScript 

Register functions in C ++ to work with TestStruct in the global OS namespace:

 os->setGlobal(def("printTestStruct", printTestStruct)); os->setGlobal(def("changeTestStruct", changeTestStruct)); 

Check in OS:

 var data = {a=10 b=20} printTestStruct(data) data = changeTestStruct(data) printTestStruct(data) print data 

Conclusion:

 TestStruct: 10.000000 20.000000 TestStruct: 100.000000 2000.000000 {"a":100,"b":2000} 

Great, it works! All simple data types (float, int, etc.) are already described in OS via CtypeValue in the same way. Use CtypeValue if you need to describe a specific conversion of the OS -> C ++ data type and vice versa.

You can download the ObjectScript sources and an example from this article at this link , open proj.win32 \ examples.sln , the osbind project.

Other relevant ObjectScript articles:

Source: https://habr.com/ru/post/154221/


All Articles