从 Node.js 拦截 C++ 模块“stdout”

问题描述 投票:0回答:1

我目前正在使用 Noble Node.js 模块进行蓝牙 (BLE) 连接。 Noble 模块偶尔会通过 C++ 的

printf
函数将错误直接打印到控制台。 Noble 没有可用于侦听此输出的 Node.js 绑定,这意味着我需要一种方法来手动拦截输出。

我尝试过覆盖 Node 的

console.log
process.std...
,但由于 Noble 使用的是 C++ 的
printf
(并直接打印到控制台),因此这种方法不起作用。

我不想直接修改 C++,因为我必须更改

printf
的每个实例,使得这个解决方案将来难以维护。所有拦截都需要在 Node.js 端完成。从子进程通过管道输出控制台输出也不太理想。

有没有简单的方法可以做到这一点?

c++ node.js printf stdout console.log
1个回答
0
投票

我想我已经使用

spawn
提出了自己的解决方案。这是
lib.js
,包含我自己的库代码的文件:

// ============================================ IMPORTS ============================================

import events from "node:events";
import { spawn } from "node:child_process";

// ============================================ GLOBALS ============================================

const emitter = new events.EventEmitter();
let childProcess;
let id = 0;

//  ======================================== INITIALIZATION ========================================

function initParentProcess() {
  // Create a child process of this file using spawn:
  childProcess = spawn("node", ["."], {
    stdio: ["pipe", "pipe", "pipe", "ipc"],
    env: { IS_CHILD: "true" },
  });

  // Listen for stdout/stderr output from the child process and forward it to the importing file;
  // Note that ".slice(0, -1)" removes ending newlines; you may or may not want this:
  childProcess.stdout?.on("data", (data) => {
    emitter.emit("stdout", data.toString().slice(0, -1));
  });
  childProcess.stderr?.on("data", (data) => {
    emitter.emit("stderr", data.toString().slice(0, -1));
  });

  // Once the child process is ready, emit the "ready" event:
  childProcess.once("message", (state) => {
    if (state === "ready") emitter.emit("ready");
  });
}

function initChildProcess() {
  // Satisfy the ESLint overlords:
  if (!process.send) return;

  // Handle incoming requests from the parent process:
  process.on("message", async ({ id, func, args: args }) => {
    // Satisfy the ESLint overlords:
    if (!process.send) return;

    // Handle each function and their arguments:
    switch (func) {
      case "example":
        process.send({ id, res: await _example(...args) });
        break;
      // Other functions should be handled here
    }
  });

  // Notify the parent process that the child process is ready:
  process.send("ready");
}

if (!process.env.IS_CHILD) {
  // If not a child process, this module must have been imported from another file:
  initParentProcess();
} else {
  // Otherwise, this instance is the child process:
  initChildProcess();
}

// ======================================= ABSTRACTION LAYER =======================================

// The handle function will forward work to the child process, where it will be completed.
// Upon completion, the handle function's Promise will result with the result of the work.
function handle(func, ...args) {
  // Handle setup:
  const handleID = id++;
  const listener = (resolve, id, res) => {
    // Only handle the message if the ID matches the handle ID:
    if (id !== handleID) return;

    // Remove the listener and resolve with the result:
    process.removeListener("message", listener);
    resolve(res);
  };

  // Return a Promise that resolves once we recieve a response from the child process:
  return new Promise((resolve) => {
    childProcess.send({ id: handleID, func, args });
    childProcess.on("message", ({ id, res }) => listener(resolve, id, res));
  });
}

// ================================= CHILD PROCESS IMPLEMENTATIONS =================================

// This is the actual implementation of "example":
async function _example(sampleArg) {
  return `Example output; sampleArg=${sampleArg}`;
}

//  ================================ PARENT PROCESS IMPLEMENTATIONS ================================

// This is the module export for the importing file to use:
export async function example(sampleArg) {
  return await handle("example", sampleArg);
}

//  ====================================== SUPPORTING EXPORTS ======================================

// Event emitter exports so the importing file knows when this module is ready:
export function on(event, callback) {
  emitter.on(event, callback);
}
export function once(event, callback) {
  emitter.once(event, callback);
}

export function close() {
  childProcess.kill();
}

这是

main.js

import { once, example, close } from "./lib.js";

once("ready", async () => {
  console.log("Ready!");
  const result1 = await example("test");
  const result2 = await example("hello world");
  console.log("Result 1:", result1);
  console.log("Result 2:", result2);
  close();
});

运行

node main.js
将打印:

Ready!
Result 1: Example output; sampleArg=test
Result 2: Example output; sampleArg=hello world

此外,我现在可以收听来自

stdout
stderr
main.js
,如下所示:

import { on } from "./lib.js";

on("stdout", (data) => {
  console.log(data);
});
on("stderr", (data) => {
  console.log(data);
});

其工作方式分为以下步骤:

  • 首先,我们导入
    lib.js
    ;然后创建一个子进程。
  • 一旦子进程设置并准备就绪,模块将发出
    "ready"
    事件,我们使用
    main.js
    侦听器在
    once
    中侦听该事件。
  • 然后我们从
    example
    致电
    main.js
  • 这将调用
    example
    中导出的
    lib.js
    函数,该函数又调用
    handle
  • handle
    函数将我们的请求转发给子进程。
  • 子进程处理数据,并将结果返回给父进程。
  • handle
    函数获取此结果,并解决承诺,有效地将其返回到我们导出的
    example
    函数。
  • 结果会在导出的
    example
    函数中返回,返回到
    main.js
  • 我们重复此操作以获得
    result1
    result2
    。然后我们将结果打印到控制台。

对于我想要解决的问题来说,这似乎有点复杂,但如果它有效,它就有效。请告诉我是否有任何方法可以简化此操作,而又不会增加使用

main.js
中的模块的难度。

© www.soinside.com 2019 - 2024. All rights reserved.