认识soui4js(第3篇):使用C/C++开发扩展模块
首先需要明确:JS代码本身不具备直接调用系统API的能力,JS代码能调用什么功能,都依赖于其它扩展模块提供了什么样的接口。
soui4js模块将soui的界面能力作为一个js模块导出到了js中,使得js可以和C++一样操作GUI。
但是操作GUI只是一个客户端APP的一个需求。一个产品可能会有各种需求是现有模块不能实现的,或者是能性能要求很高不宜使用JS实现的。
这就需要用户自己使用如C/C++这样的语言来扩展js模块。
soui4js的代码中有一个模块utils,它就是一个典型的扩展模块。
在该模块中,我们导出了几个文件操作,编码转换方法。用户要扩展自己的模块,只需要参考utils模块来增加自己的API即可。
utils模块是一个dll项目,编译后生成一个utils.dll
该dll导出两个方法:
1 extern "C" 2 __declspec(dllexport) JSModuleDef* js_init_module(JSContext* ctx, const char* module_name) 3 { 4 qjsbind::Context* context = qjsbind::Context::get(ctx); 5 if(!context) 6 context = new qjsbind::Context(ctx); 7 qjsbind::Module *module = context->NewModule(module_name); 8 Exp_Utils(module); 9 return module->module(); 10 } 11 12 extern "C" 13 __declspec(dllexport) void js_uninit_module(JSContext * ctx) 14 { 15 qjsbind::Context* context = qjsbind::Context::get(ctx); 16 if(context && context->isAttached()) 17 delete context; 18 }
js_init_module是在quickjs加载扩展模块时调用。这个函数的功能就是给JSContext匹配一个qjsbind::Context对象。
对于UI线程来的模块,JSContext中已经有了Context对象,但是worker线程import创建的module,JSContext中是没有qjsbind::Context对象的。此时我们需要new一个新的qjsbind::Context对象出来。
然后调用自己实现的Exp_Utils(module)方法来导出C++方法到JS中。
js_uninit_module是quickjs在释放一个module时调用(注:这是soui4js使用的quickjs扩展的功能,原版qjs没有该逻辑)
这里主要是释放在js_init_module中new的qjsbind::Context对象,防止内存泄漏。
下面我们看一下Exp_Utils的代码
void Exp_Utils(qjsbind::Module* module) { module->ExportFunc("Md5", &Md5); module->ExportFunc("FileMd5", &FileMd5); module->ExportFunc("FileMd5Ex", &FileMd5Ex); module->ExportFunc("DelFile", &fileop::DelFile); module->ExportFunc("DelDir", &fileop::DelDir); module->ExportFunc("SelectFile", &fileop::SelectFile); module->ExportFunc("CopyFile", &fileop::JsCopyFile); module->ExportFunc("CopyDir", &fileop::JsCopyDir); module->ExportFunc("Base64Decode", &Base64Decode); module->ExportFunc("Base64Encode", &Base64Encode); module->ExportFunc("UrlDecode", &urldecode); module->ExportFunc("UrlEncode", &urlencode); module->ExportFunc("PlaySound", &sysapi::JsPlaySound); }
module->ExportFunc("Md5", &Md5); 这一行的意思是将C++函数Md5导出到JS中,名字就是"Md5"。
下面我们看一下Md5函数的原型:
std::string Md5(const char* buf, int length) { unsigned char digest[16]; MD5_CTX context; MD5Init(&context); MD5Update(&context, (const unsigned char*)buf, length); MD5Final(digest, &context); return MDPrint(digest); }
可以看出,这个函数和普通的C函数并没有什么不同。所有输入输出参数和JS的交换都在qjsbind中自动进行了处理。
Exp_Utils方法中,使用qjsbind::Module->ExportFunc方法,非常方便的实现了将C++方法导出到JS中。
qjsbind中对于常见的数据类型都做了处理。但是如果要支持用户自己的特定数据类型,则还是需要用户自己对特定参数类型做模板特化。
如果一个特殊类型的输入输出参数只在一个导出函数里使用,也可以使用手动参数类型转换,这样就不需要做参数的模板特化了。
方法很简单,定义一个形式如下的函数:
1 Value Test(qjsbind::Context *ctx, ArgList& args) { 2 SLOGI2("test") << args.size(); 3 return undefined_value; 4 }
这里函数的参数及返回值都是固定的。ArgList & args里有从js传递过来的参数列表,你可以手动识别并转换成你需要的数据类型。函数执行完成后,再自己返回自己期望的类型即可。
注意:
原本quickjs也提供了开发扩展模块的框架,在原框架中,用户导出一个函数到JS要逐个处理qjs的输入输出参数,使用起来比较麻烦,使用本文的方法,借助qjsbind模块提供的模板方法,可以更简单方便的实现这个过程。