Nodejs: node-gyp 在 C++ 中链接 C 的文件

Nodejs: node-gyp 在 C++ 中链接 C 的文件

原文链接:http://xcoder.in/2014/12/10/link-c-on-gpp/

原因

由于某些原因,我写了个很搓的内存池(C 版本的)。

然后我想到了把之前写的一个 Node.js 包 thmclrx 的更挫的“伪·内存池”用新写的内存池去替换掉。(❛◡❛✿)

然后问题就来了,我貌似不能控制 node-gyp 去用 G++ 编译 *.c 文件,这样的话所有文件编译好之后链接 *.o 文件会出问题。虽然链接的时候没报错,但是使用的时候就会报这么个错 (;´༎ຶД༎ຶ`):

➜ thmclrx git:(master) ✗ node test/test.js
dyld: lazy symbol binding failed: Symbol not found: __Z16xmem_create_poolj
  Referenced from: /Users/.../code/huaban/thmclrx/build/Release/thmclrx.node
  Expected in: dynamic lookup

dyld: Symbol not found: __Z16xmem_create_poolj Referenced from: /Users/…/code/huaban/thmclrx/build/Release/thmclrx.node Expected in: dynamic lookup

[1] 52501 trace trap node test/test.js

大致意思就是说在我编译好链接好的 thmclrx.node 中找不到 __Z16xmem_create_poolj 这个符号,也就是说 xmempool.o 这个用 C 编译出来的文件并没有正确被链接。

假想方案

假想一

一开始我想找的是“如何在 node-gyp 中手动选择编译器”,即不让机器自动选择 GCC 去编译 *.c 文件。后来无果。ル||☛_☚|リ

假想二

再后来我想开了,于是决定让编译的时候去识别我在跟 C 说话还是跟 C++ 说话。(ノ◕ヮ◕)ノ*:・゚✧

解决方案

于是我找到了这么个帖子:http://grokbase.com/t/gg/nodejs/14amregx72/linking-c-sources-files-in-cc-files

他貌似也遇到了跟我相似的问题。下面这个提问者自己提出了这样的回答:

Nevermind, found my own answer after finally hitting the right google search terms.

Added

#ifdef __cplusplus
extern "C" {
#endif

//… source code here…

#ifdef __cplusplus } #endif

So that the CPP compiler would know I was talking C and not CPP :)

答案的大意就是在你的 C 头文件中添加上面 blahblah 一大段宏,好让 C++ 的编译器知道它是在跟 C 的中间文件交流而不是 C++,这样的话链接的时候就能正常接轨了。于是我在我的新版 xmempool 的头文件里面就已经添加上了这两段话了。

事后烟

其实以前我也老在别的项目里面看到这个 #ifdef __cplusplus 的宏定义,只不过以前不知道什么意思。

今天通过这么一件事情终于知道了它的用途了,新技能 get √。

ε(*´・∀・`)з゙


3 回复

Node.js: 在 C++ 中链接 C 的文件

原文链接

原文链接

原因

由于某些原因,我写了一个简单的 C 语言内存池库(GitHub: xmempool)。然后我想到了把之前写的一个 Node.js 包 thmclrx(GitHub: thmclrx)中的“伪·内存池”用新写的内存池替换掉。

然而,问题来了:我似乎无法控制 node-gyp 使用 G++ 来编译 .c 文件。这导致编译后的文件在链接时出现问题。虽然链接没有报错,但在运行时会报错:

➜ thmclrx git:(master) ✗ node test/test.js
dyld: lazy symbol binding failed: Symbol not found: __Z16xmem_create_poolj
  Referenced from: /Users/.../code/huaban/thmclrx/build/Release/thmclrx.node
  Expected in: dynamic lookup

dyld: Symbol not found: __Z16xmem_create_poolj
  Referenced from: /Users/.../code/huaban/thmclrx/build/Release/thmclrx.node
  Expected in: dynamic lookup

[1]    52501 trace trap  node test/test.js

这意味着在编译好的 thmclrx.node 中找不到 __Z16xmem_create_poolj 符号,也就是说 xmempool.o 这个 C 编译出来的文件没有被正确链接。

假想方案

假想一

最初的想法是找到一种方法,可以让 node-gyp 手动选择编译器,而不是让系统自动选择 GCC 来编译 .c 文件。但最终未能实现。

假想二

后来,我决定让编译器能够区分我在跟 C 语言还是 C++ 语言对话。

解决方案

我找到了一个解决方案,该方案来自一个类似的讨论帖(链接):

在你的 C 头文件中添加以下宏定义:

#ifdef __cplusplus
extern "C" {
#endif

//... your C code here ...

#ifdef __cplusplus
}
#endif

这样做的目的是让 C++ 编译器知道它正在与 C 语言的源文件进行交互,而不是 C++ 源文件。这将确保链接时一切正常。

示例代码

假设你有一个名为 xmempool.h 的头文件:

// xmempool.h
#ifndef XMEMPPOOL_H
#define XMEMPPOOL_H

#ifdef __cplusplus
extern "C" {
#endif

void* xmem_create_pool(size_t size);
void xmem_destroy_pool(void* pool);

#ifdef __cplusplus
}
#endif

#endif // XMEMPPOOL_H

相应的 C 代码 xmempool.c

// xmempool.c
#include "xmempool.h"

struct XMemPool {
    char data[1024];
};

void* xmem_create_pool(size_t size) {
    return malloc(size);
}

void xmem_destroy_pool(void* pool) {
    free(pool);
}

在 C++ 代码中使用这些函数:

// main.cpp
#include <iostream>
#include "xmempool.h"

int main() {
    void* pool = xmem_create_pool(1024);
    if (pool != nullptr) {
        std::cout << "Memory pool created successfully!" << std::endl;
    }
    xmem_destroy_pool(pool);
    return 0;
}

事后烟

以前我也经常在其他项目中看到 #ifdef __cplusplus 宏定义,但一直不清楚其用途。通过这次经历,我终于明白了它的作用,并学会了如何正确地在 C 和 C++ 之间进行接口设计。


前几天我去福地找个朋友,看到了花瓣的招牌,不过没有上去…

要解决这个问题,关键在于让 C++ 编译器能够正确地理解和处理 C 语言的代码。通过使用 extern "C",可以告诉 C++ 编译器这些函数是以 C 语言的方式进行调用的,从而避免名称修饰带来的问题。

以下是具体的步骤和示例代码:

步骤

  1. 在 C 头文件中添加 extern "C"

    • 打开你的 C 头文件(例如 xmempool.h),并在包含函数声明的地方加上 extern "C"
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    // 函数声明
    void xmem_create_pool(size_t size);
    
    #ifdef __cplusplus
    }
    #endif
    
  2. 确保 C++ 文件中包含头文件

    • 确保你的 C++ 文件(例如 my_node_module.cpp)包含了这个头文件,并且可以正确地调用 C 函数。
    #include "xmempool.h"
    
    // C++ 文件中的其他代码
    void some_cpp_function() {
        xmem_create_pool(1024);  // 调用 C 函数
    }
    

示例代码

假设你有一个简单的 C 库 xmempool.c 和对应的头文件 xmempool.h

xmempool.h

#ifndef XMEMPPOOL_H
#define XMEMPPOOL_H

#ifdef __cplusplus
extern "C" {
#endif

void xmem_create_pool(size_t size);

#ifdef __cplusplus
}
#endif

#endif  // XMEMPPOOL_H

xmempool.c

#include "xmempool.h"
#include <stdlib.h>

void xmem_create_pool(size_t size) {
    // 创建内存池的实现
    void* pool = malloc(size);
    if (pool) {
        // 初始化内存池
    }
}

my_node_module.cpp

#include <node.h>
#include "xmempool.h"

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    xmem_create_pool(1024);  // 调用 C 函数
    args.GetReturnValue().Set(String::NewFromUtf8(isolate, "Hello from Node.js!"));
}

void Initialize(Local<Object> exports) {
    NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

node-gyp 配置

确保你的 binding.gyp 文件配置正确,以便正确编译 C 和 C++ 文件:

{
  "targets": [
    {
      "target_name": "thmclrx",
      "sources": [ "my_node_module.cpp", "xmempool.c" ]
    }
  ]
}

通过以上步骤,你应该能够正确地将 C 代码与 Node.js 模块集成在一起,并避免名称修饰带来的链接错误。

回到顶部