问题描述
我正在为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),{}).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),{}).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
的{{1}}传递。
然后在调用AudioObjectAddPropertyListener
时,我尝试使用存储的变量来触发JS回调。但是,这样做时,JS脚本会崩溃,并且仅显示以下内容(无堆栈跟踪):
audioOutputDeviceChanged
我认为这可能是因为#
# Fatal error in v8::HandleScope::CreateHandle()
# Cannot create a handle without a HandleScope
#
返回时,它会释放(或某种形式的)变量(上下文,cb和隔离)。因此,它们在功能之外是不可用的。我该如何解决?
setonAudioOutputDeviceChange
这是我的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
解决方法
您的诊断对我而言似乎是合理的:“当setOnAudioOutputDeviceChange
返回时,它会取消分配(或某种形式)变量(上下文,cb和隔离)。因此,它们在函数外部不可用。”
这些变量由jsCb
对象保存,但是您将其作为AudioObjectAddPropertyListener()
的{{1}}传递给context
,因此没有任何东西可以保留该对象。 相反,尝试将其作为void*
或(__bridge_retained void*)jsCb
传递,这将在添加回调之前增加引用计数。然后,您可以在audioOutputDeviceChanged函数中将其检索为CFBridgingRetain(jsCb)
,它将既不保留也不释放它。最后,在删除音频属性侦听器时,应通过JSCallback *jsCb = (__bridge JSCallback *)inClientData;
或__bridge_transfer
/ CFRelease
释放对象。
文档here和here中描述了桥接强制转换,您可以通过搜索Stack Overflow和其他地方找到更多示例。
或者,您可以手动构造一个CFBridgingRelease
对象,而不是手动保留/释放Obj-C对象,而是可以重组代码,使Obj-C对象与整个Node模块一样长。初始化一次(例如,在您的static
函数中)或在Initialize
中为“ context-aware addon”创建它。然后,您可以在将此对象作为NODE_MODULE_INITIALIZER
传递时使用__bridge
,而不必担心保留和释放。