我正在为 macOS 创建一个 Node C++ 插件,因此我将 Objective-C 与 C++ 和 Node Addon API 混合使用。
我想为 Node JS 提供一个函数,该函数接收一个回调,以便稍后在调用 Obj-C 观察者时调用。这就是我试图实现这一目标的方式:
#include <node.h>
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@interface JSCallback:NSObject {
//Instance variables
NSString *testStr;
v8::Local<v8::Context> context;
v8::Local<v8::Function> fn;
v8::Isolate* isolate;
}
@property(retain, nonatomic, readwrite) NSString *testStr;
@property(nonatomic, readwrite) v8::Local<v8::Context> context;
@property(nonatomic, readwrite) v8::Local<v8::Function> fn;
@property(nonatomic, readwrite) v8::Isolate* isolate;
@end
@implementation JSCallback
@synthesize testStr;
@synthesize context;
@synthesize fn;
@synthesize isolate;
@end
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Function;
using v8::Context;
using v8::Value;
static OSStatus audioOutputDeviceChanged(
AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress* inAddresses,
void* __nullable inClientData
) {
JSCallback *jsCb = *((__unsafe_unretained JSCallback **)(&inClientData));
printf("%s", [jsCb.testStr UTF8String]);
jsCb.fn->Call(jsCb.context, Null(jsCb.isolate), 0, {}).ToLocalChecked();
return noErr;
}
void setOnAudioOutputDeviceChange(const FunctionCallbackInfo<Value>& args) {
AudioObjectPropertyAddress address = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster,
};
Isolate* isolate = args.GetIsolate();
Local<Context> context = isolate->GetCurrentContext();
Local<Function> cb = Local<Function>::Cast(args[0]);
// cb->Call(context, Null(isolate), 0, {}).ToLocalChecked();
JSCallback *jsCb = [[JSCallback alloc]init];
jsCb.testStr = @"a test string #002";
jsCb.context = context;
jsCb.fn = cb;
jsCb.isolate = isolate;
OSStatus status = AudioObjectAddPropertyListener(
kAudioObjectSystemObject,
&address,
&audioOutputDeviceChanged,
jsCb
);
if (status != noErr) {
NSException *e = [NSException
exceptionWithName:@"OSStatus Error"
reason:[NSString stringWithFormat:@"OSStatus Error (status: %d)", status]
userInfo:nil];
@throw e;
}
}
void Initialize(Local<Object> exports) {
NODE_SET_METHOD(exports, "setOnAudioOutputDeviceChange", setOnAudioOutputDeviceChange);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
}
我创建了一个JSCallback
类来保存触发 JS 回调函数所需的变量。然后将其作为inClientData
for传递AudioObjectAddPropertyListener
。
然后当audioOutputDeviceChanged
被调用时,我尝试使用我存储的变量来触发 JS 回调。但是,当我这样做时,JS 脚本崩溃,并且只打印以下内容(无堆栈跟踪):
#
# Fatal error in v8::HandleScope::CreateHandle()
# Cannot create a handle without a HandleScope
#
我认为这可能会发生,因为当setOnAudioOutputDeviceChange
返回时,它会释放(或类似的东西)变量(上下文、cb 和隔离)。因此它们在函数之外无法使用。我怎样才能解决这个问题?
如果需要,这是我使用插件的 JS 代码:
const addon = require('./addon/build/Release/addon');
addon.setOnAudioOutputDeviceChange(() => {
console.info('setOnAudioOutputDeviceChange called');
});
setTimeout(() => {
// This keeps the JS script alive for some time
console.log('timedout');
}, 200000);
这是我的binding.gyp
文件,尽管我怀疑它是相关的:
{
"targets": [
{
"target_name": "addon",
"sources": [
"addon.mm",
],
"xcode_settings": {
"OTHER_CFLAGS": [
"-ObjC++",
],
},
"link_settings": {
"libraries": [
"-framework Foundation",
"-framework AVFoundation",
],
},
},
],
}