ObjectScript API, integration with C ++. Part 4: connecting custom classes and functions in C ++

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

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

What is the actual advantage of the 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 receive the correct parameters, and the value returned from C ++ will be correctly converted to an analog on OS.

Part 4: binding of custom classes and functions in C ++


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

Connecting a global function


Suppose we have the following function in C ++:

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 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 many parameters. We connect functions to OS as the 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 they can be used on the 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

C ++ class connection


Here the fun begins. Suppose we have the following C ++ test class that we want to use in OS code:

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

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(os, funcs);

Done, check on 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)

Conclusion:

t.i: 1
t.j: 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 starters, suppose we have a data structure in C ++ and we want it to look on the OS as a container with values.

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

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
{
	// 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

We register functions in C ++ for working 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, everything works! All simple data types (float, int, etc.) are already described on the OS via CtypeValue in the same way. Use CtypeValue if you need to describe a specific data type conversion OS -> C ++ and vice versa.

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

Other relevant articles about ObjectScript:


Also popular now: