You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
437 lines
18 KiB
437 lines
18 KiB
/****************************************************************************
|
|
Copyright (c) 2018-2022 Xiamen Yaji Software Co., Ltd.
|
|
|
|
http://www.cocos.com
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated engine source code (the "Software"), a limited,
|
|
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
|
|
to use Cocos Creator solely to develop games on your target platforms. You shall
|
|
not use Cocos Creator software for developing other software or tools that's
|
|
used for developing games. You are not granted to publish, distribute,
|
|
sublicense, and/or sell copies of Cocos Creator.
|
|
|
|
The software or tools in this License Agreement are licensed, not sold.
|
|
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "JavaScriptObjCBridge.h"
|
|
#include "cocos/bindings/jswrapper/SeApi.h"
|
|
#include "cocos/bindings/manual/jsb_conversions.h"
|
|
#include "cocos/bindings/manual/jsb_global.h"
|
|
|
|
#include "base/std/container/string.h"
|
|
#include <iostream>
|
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
se::Value static objc_to_seval(id objcVal) {
|
|
se::Value ret;
|
|
if (objcVal == nil)
|
|
return ret;
|
|
|
|
if ([objcVal isKindOfClass:[NSNumber class]]) {
|
|
NSNumber *number = (NSNumber *)objcVal;
|
|
ccstd::string numberType = [number objCType];
|
|
if (numberType == @encode(BOOL) || numberType == @encode(bool)) {
|
|
ret.setBoolean([number boolValue]);
|
|
} else if (numberType == @encode(int) || numberType == @encode(long) || numberType == @encode(short) || numberType == @encode(unsigned int) || numberType == @encode(unsigned long) || numberType == @encode(unsigned short) || numberType == @encode(float) || numberType == @encode(double) || numberType == @encode(char) || numberType == @encode(unsigned char)) {
|
|
ret.setDouble([number doubleValue]);
|
|
} else {
|
|
CC_LOG_ERROR("Unknown number type: %s", numberType.c_str());
|
|
}
|
|
} else if ([objcVal isKindOfClass:[NSString class]]) {
|
|
const char *content = [objcVal cStringUsingEncoding:NSUTF8StringEncoding];
|
|
ret.setString(content);
|
|
} else if ([objcVal isKindOfClass:[NSDictionary class]]) {
|
|
CC_LOG_ERROR("JavaScriptObjCBridge doesn't support to bind NSDictionary!");
|
|
} else {
|
|
const char *content = [[NSString stringWithFormat:@"%@", objcVal] cStringUsingEncoding:NSUTF8StringEncoding];
|
|
ret.setString(content);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#define JSO_ERR_OK (0)
|
|
#define JSO_ERR_TYPE_NOT_SUPPORT (-1)
|
|
#define JSO_ERR_INVALID_ARGUMENTS (-2)
|
|
#define JSO_ERR_METHOD_NOT_FOUND (-3)
|
|
#define JSO_ERR_EXCEPTION_OCCURRED (-4)
|
|
#define JSO_ERR_CLASS_NOT_FOUND (-5)
|
|
#define JSO_ERR_VM_FAILURE (-6)
|
|
|
|
class JavaScriptObjCBridge {
|
|
public:
|
|
class CallInfo {
|
|
public:
|
|
CallInfo(const char *className, const char *methodName)
|
|
: _className(className),
|
|
_methodName(methodName) {}
|
|
|
|
~CallInfo() {}
|
|
|
|
int getErrorCode() const {
|
|
return _error;
|
|
}
|
|
|
|
bool execute(const se::ValueArray &argv, se::Value &rval);
|
|
|
|
private:
|
|
int _error{JSO_ERR_OK};
|
|
ccstd::string _className;
|
|
ccstd::string _methodName;
|
|
};
|
|
};
|
|
|
|
using JsCallback = std::function<void(const ccstd::string &, const ccstd::string &)>;
|
|
|
|
class ScriptNativeBridge {
|
|
public:
|
|
void callByNative(const ccstd::string &arg0, const ccstd::string &arg1);
|
|
inline void setCallback(const JsCallback &cb) {
|
|
_callback = cb;
|
|
}
|
|
static ScriptNativeBridge *bridgeCxxInstance;
|
|
se::Value jsCb;
|
|
|
|
private:
|
|
JsCallback _callback{nullptr}; // NOLINT(readability-identifier-naming)
|
|
};
|
|
ScriptNativeBridge *ScriptNativeBridge::bridgeCxxInstance{nullptr};
|
|
bool JavaScriptObjCBridge::CallInfo::execute(const se::ValueArray &argv, se::Value &rval) {
|
|
NSString *className = [NSString stringWithCString:_className.c_str() encoding:NSUTF8StringEncoding];
|
|
NSString *methodName = [NSString stringWithCString:_methodName.c_str() encoding:NSUTF8StringEncoding];
|
|
|
|
if (!className || !methodName) {
|
|
_error = JSO_ERR_INVALID_ARGUMENTS;
|
|
return false;
|
|
}
|
|
|
|
Class targetClass = NSClassFromString(className);
|
|
if (!targetClass) {
|
|
_error = JSO_ERR_CLASS_NOT_FOUND;
|
|
return false;
|
|
}
|
|
SEL methodSel;
|
|
methodSel = NSSelectorFromString(methodName);
|
|
if (!methodSel) {
|
|
_error = JSO_ERR_METHOD_NOT_FOUND;
|
|
return false;
|
|
}
|
|
methodSel = NSSelectorFromString(methodName);
|
|
NSMethodSignature *methodSig = [targetClass methodSignatureForSelector:(SEL)methodSel];
|
|
if (methodSig == nil) {
|
|
_error = JSO_ERR_METHOD_NOT_FOUND;
|
|
NSLog(@"%@.%@ method isn't found!", className, methodName);
|
|
return false;
|
|
}
|
|
@try {
|
|
int argc = (int)argv.size();
|
|
NSUInteger argumentCount = [methodSig numberOfArguments];
|
|
if (argumentCount != argc) {
|
|
_error = JSO_ERR_INVALID_ARGUMENTS;
|
|
return false;
|
|
}
|
|
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
|
|
[invocation setTarget:targetClass];
|
|
[invocation setSelector:methodSel];
|
|
|
|
for (int i = 2; i < argc; ++i) {
|
|
ccstd::string argumentType = [methodSig getArgumentTypeAtIndex:i];
|
|
const se::Value &arg = argv[i];
|
|
|
|
/* - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
|
|
*
|
|
* Refer to https://developer.apple.com/documentation/foundation/nsinvocation/1437834-setargument?language=objc
|
|
*
|
|
* This method copies the contents of buffer as the argument at index. The number of bytes copied is determined by the argument size.
|
|
* When the argument value is an object, pass a pointer to the variable (or memory) from which the object should be copied:
|
|
*/
|
|
|
|
if (arg.isString()) {
|
|
NSString *str = [NSString stringWithCString:arg.toString().c_str() encoding:NSUTF8StringEncoding];
|
|
[invocation setArgument:&str atIndex:i];
|
|
} else if (arg.isNumber()) {
|
|
if (argumentType == @encode(int)) {
|
|
int val = arg.toInt32();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(long)) {
|
|
long val = static_cast<long>(arg.toDouble());
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(short)) {
|
|
short val = arg.toInt16();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(unsigned int)) {
|
|
unsigned int val = arg.toUint32();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(unsigned long)) {
|
|
unsigned long val = static_cast<unsigned long>(arg.toDouble());
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(unsigned short)) {
|
|
unsigned short val = arg.toUint16();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(float)) {
|
|
float val = arg.toFloat();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(double)) {
|
|
double val = arg.toDouble();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(char)) {
|
|
char val = arg.toInt8();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(unsigned char)) {
|
|
unsigned char val = arg.toUint8();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == "@") { // NSNumber*
|
|
NSNumber *number = [NSNumber numberWithDouble:arg.toDouble()];
|
|
[invocation setArgument:&number atIndex:i];
|
|
} else {
|
|
NSLog(@"Unsupported argument type: %s", argumentType.c_str());
|
|
_error = JSO_ERR_TYPE_NOT_SUPPORT;
|
|
return false;
|
|
}
|
|
} else if (arg.isBoolean()) {
|
|
if (argumentType == @encode(BOOL)) {
|
|
BOOL val = arg.toBoolean() ? YES : NO;
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else if (argumentType == @encode(bool)) {
|
|
bool val = arg.toBoolean();
|
|
[invocation setArgument:&val atIndex:i];
|
|
} else {
|
|
NSLog(@"Unsupported argument type: %s", argumentType.c_str());
|
|
_error = JSO_ERR_TYPE_NOT_SUPPORT;
|
|
return false;
|
|
}
|
|
} else if (arg.isNullOrUndefined()) {
|
|
// Don't call [invocation setArgument] will pass nil to relevant Objective-C argument.
|
|
} else {
|
|
NSLog(@"Unsupported argument type, se::Value::Type: %d", (int)arg.getType());
|
|
_error = JSO_ERR_TYPE_NOT_SUPPORT;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
NSUInteger returnLength = [methodSig methodReturnLength];
|
|
ccstd::string returnType = [methodSig methodReturnType];
|
|
[invocation invoke];
|
|
|
|
if (returnLength > 0) {
|
|
if (returnType == "@") {
|
|
id ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval = objc_to_seval(ret);
|
|
} else if (returnType == @encode(BOOL) || returnType == @encode(bool)) {
|
|
bool ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setBoolean(ret);
|
|
} else if (returnType == @encode(int)) {
|
|
int ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setInt32(ret);
|
|
} else if (returnType == @encode(long)) {
|
|
long ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setDouble(static_cast<long>(ret));
|
|
} else if (returnType == @encode(short)) {
|
|
short ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setInt16(ret);
|
|
} else if (returnType == @encode(unsigned int)) {
|
|
unsigned int ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setUint32(ret);
|
|
} else if (returnType == @encode(unsigned long)) {
|
|
unsigned long ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setDouble(static_cast<double>(ret));
|
|
} else if (returnType == @encode(unsigned short)) {
|
|
unsigned short ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setUint16(ret);
|
|
} else if (returnType == @encode(float)) {
|
|
float ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setFloat(ret);
|
|
} else if (returnType == @encode(double)) {
|
|
double ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setDouble(ret);
|
|
} else if (returnType == @encode(char)) {
|
|
int8_t ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setInt8(ret);
|
|
} else if (returnType == @encode(unsigned char)) {
|
|
uint8_t ret;
|
|
[invocation getReturnValue:&ret];
|
|
rval.setUint8(ret);
|
|
} else {
|
|
_error = JSO_ERR_TYPE_NOT_SUPPORT;
|
|
NSLog(@"not support return type = %s", returnType.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
} @catch (NSException *exception) {
|
|
NSLog(@"EXCEPTION THROW: %@", exception);
|
|
_error = JSO_ERR_EXCEPTION_OCCURRED;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScriptNativeBridge::callByNative(const ccstd::string &arg0, const ccstd::string &arg1) {
|
|
_callback(arg0, arg1);
|
|
}
|
|
|
|
se::Class *__jsb_JavaScriptObjCBridge_class = nullptr;
|
|
|
|
static bool JavaScriptObjCBridge_finalize(se::State &s) {
|
|
return true;
|
|
}
|
|
SE_BIND_FINALIZE_FUNC(JavaScriptObjCBridge_finalize)
|
|
|
|
static bool JavaScriptObjCBridge_constructor(se::State &s) {
|
|
JavaScriptObjCBridge *cobj = ccnew JavaScriptObjCBridge();
|
|
s.thisObject()->setPrivateData(cobj);
|
|
return true;
|
|
}
|
|
SE_BIND_CTOR(JavaScriptObjCBridge_constructor, __jsb_JavaScriptObjCBridge_class, JavaScriptObjCBridge_finalize)
|
|
|
|
static bool JavaScriptObjCBridge_callStaticMethod(se::State &s) {
|
|
const auto &args = s.args();
|
|
int argc = (int)args.size();
|
|
|
|
if (argc >= 2) {
|
|
bool ok = false;
|
|
ccstd::string clsName, methodName;
|
|
ok = sevalue_to_native(args[0], &clsName);
|
|
SE_PRECONDITION2(ok, false, "Converting class name failed!");
|
|
|
|
ok = sevalue_to_native(args[1], &methodName);
|
|
SE_PRECONDITION2(ok, false, "Converting method name failed!");
|
|
|
|
JavaScriptObjCBridge::CallInfo call(clsName.c_str(), methodName.c_str());
|
|
ok = call.execute(args, s.rval());
|
|
if (!ok) {
|
|
s.rval().setUndefined();
|
|
SE_REPORT_ERROR("call (%s.%s) failed, result code: %d", clsName.c_str(), methodName.c_str(), call.getErrorCode());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting >=2", argc);
|
|
return false;
|
|
}
|
|
SE_BIND_FUNC(JavaScriptObjCBridge_callStaticMethod)
|
|
|
|
static bool ScriptNativeBridge_getCallback(se::State &s) {
|
|
ScriptNativeBridge *cobj = (ScriptNativeBridge *)s.nativeThisObject();
|
|
CC_ASSERT_EQ(cobj, ScriptNativeBridge::bridgeCxxInstance);
|
|
s.rval() = cobj->jsCb;
|
|
SE_HOLD_RETURN_VALUE(cobj->jsCb, s.thisObject(), s.rval());
|
|
return true;
|
|
}
|
|
SE_BIND_PROP_GET(ScriptNativeBridge_getCallback)
|
|
|
|
static bool ScriptNativeBridge_setCallback(se::State &s) { //NOLINT(readability-identifier-naming)
|
|
auto *cobj = static_cast<ScriptNativeBridge *>(s.nativeThisObject());
|
|
CC_ASSERT_EQ(cobj, ScriptNativeBridge::bridgeCxxInstance);
|
|
const auto &args = s.args();
|
|
se::Value jsFunc = args[0];
|
|
cobj->jsCb = jsFunc;
|
|
if (jsFunc.isNullOrUndefined()) {
|
|
cobj->setCallback(nullptr);
|
|
} else {
|
|
CC_ASSERT(jsFunc.isObject() && jsFunc.toObject()->isFunction());
|
|
s.thisObject()->attachObject(jsFunc.toObject());
|
|
cobj->setCallback([jsFunc](const ccstd::string &arg0, const ccstd::string &arg1) {
|
|
se::AutoHandleScope hs;
|
|
se::ValueArray args;
|
|
args.push_back(se::Value(arg0));
|
|
if (!arg1.empty()) {
|
|
args.push_back(se::Value(arg1));
|
|
}
|
|
jsFunc.toObject()->call(args, nullptr);
|
|
});
|
|
}
|
|
return true;
|
|
}
|
|
SE_BIND_PROP_SET(ScriptNativeBridge_setCallback)
|
|
|
|
static bool ScriptNativeBridge_sendToNative(se::State &s) { //NOLINT
|
|
const auto &args = s.args();
|
|
int argc = (int)args.size();
|
|
if (argc >= 1 && argc < 3) {
|
|
bool ok = false;
|
|
ccstd::string arg0;
|
|
ok = sevalue_to_native(args[0], &arg0);
|
|
SE_PRECONDITION2(ok, false, "Converting first argument failed!");
|
|
ccstd::string arg1;
|
|
if (argc == 2) {
|
|
ok = sevalue_to_native(args[1], &arg1);
|
|
SE_PRECONDITION2(ok, false, "Converting second argument failed!");
|
|
}
|
|
ok = callPlatformStringMethod(arg0, arg1);
|
|
SE_PRECONDITION2(ok, false, "call platform event failed!");
|
|
return ok;
|
|
}
|
|
SE_REPORT_ERROR("wrong number of arguments: %d, was expecting at least %d and less than %d", argc, 1, 3);
|
|
return false;
|
|
}
|
|
SE_BIND_FUNC(ScriptNativeBridge_sendToNative)
|
|
|
|
bool register_javascript_objc_bridge(se::Object *obj) {
|
|
se::Class *cls = se::Class::create("JavaScriptObjCBridge", obj, nullptr, _SE(JavaScriptObjCBridge_constructor));
|
|
cls->defineFinalizeFunction(_SE(JavaScriptObjCBridge_finalize));
|
|
|
|
cls->defineFunction("callStaticMethod", _SE(JavaScriptObjCBridge_callStaticMethod));
|
|
cls->install();
|
|
__jsb_JavaScriptObjCBridge_class = cls;
|
|
|
|
se::ScriptEngine::getInstance()->clearException();
|
|
|
|
return true;
|
|
}
|
|
|
|
se::Class *__jsb_ScriptNativeBridge_class = nullptr; // NOLINT
|
|
|
|
static bool ScriptNativeBridge_finalize(se::State &s) { //NOLINT(readability-identifier-naming)
|
|
//bridgeCxxInstance is an se object which will be deleted in se holder.
|
|
ScriptNativeBridge::bridgeCxxInstance = nullptr;
|
|
return true;
|
|
}
|
|
SE_BIND_FINALIZE_FUNC(ScriptNativeBridge_finalize)
|
|
|
|
static bool ScriptNativeBridge_constructor(se::State &s) { //NOLINT(readability-identifier-naming)
|
|
auto *cobj = ccnew ScriptNativeBridge();
|
|
s.thisObject()->setPrivateData(cobj);
|
|
ScriptNativeBridge::bridgeCxxInstance = cobj;
|
|
return true;
|
|
}
|
|
SE_BIND_CTOR(ScriptNativeBridge_constructor, __jsb_ScriptNativeBridge_class, ScriptNativeBridge_finalize)
|
|
bool register_script_native_bridge(se::Object *obj) { //NOLINT(readability-identifier-naming)
|
|
se::Class *cls = se::Class::create("ScriptNativeBridge", obj, nullptr, _SE(ScriptNativeBridge_constructor));
|
|
cls->defineFinalizeFunction(_SE(ScriptNativeBridge_finalize));
|
|
cls->defineFunction("sendToNative", _SE(ScriptNativeBridge_sendToNative));
|
|
cls->defineProperty("onNative", _SE(ScriptNativeBridge_getCallback), _SE(ScriptNativeBridge_setCallback));
|
|
|
|
cls->install();
|
|
__jsb_ScriptNativeBridge_class = cls;
|
|
|
|
se::ScriptEngine::getInstance()->clearException();
|
|
|
|
return true;
|
|
}
|
|
void callScript(const ccstd::string &arg0, const ccstd::string &arg1) {
|
|
ScriptNativeBridge::bridgeCxxInstance->callByNative(arg0, arg1);
|
|
}
|
|
|