存储一个JS回调,以便稍后在Node C ++插件中调用

问题描述

我正在为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和隔离)。因此,它们在功能之外是不可用的。我该如何解决

如果需要,这是我使用插件的JS代码

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释放对象

文档herehere中描述了桥接强制转换,您可以通过搜索Stack Overflow和其他地方找到更多示例。

或者,您可以手动构造一个CFBridgingRelease对象,而不是手动保留/释放Obj-C对象,而是可以重组代码,使Obj-C对象与整个Node模块一样长。初始化一次(例如,在您的static函数中)或在Initialize中为“ context-aware addon”创建它。然后,您可以在将此对象作为NODE_MODULE_INITIALIZER传递时使用__bridge,而不必担心保留和释放。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...