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 √。
ε(*´・∀・`)з゙
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 语言的方式进行调用的,从而避免名称修饰带来的问题。
以下是具体的步骤和示例代码:
步骤
-
在 C 头文件中添加
extern "C"
:- 打开你的 C 头文件(例如
xmempool.h
),并在包含函数声明的地方加上extern "C"
。
#ifdef __cplusplus extern "C" { #endif // 函数声明 void xmem_create_pool(size_t size); #ifdef __cplusplus } #endif
- 打开你的 C 头文件(例如
-
确保 C++ 文件中包含头文件:
- 确保你的 C++ 文件(例如
my_node_module.cpp
)包含了这个头文件,并且可以正确地调用 C 函数。
#include "xmempool.h" // C++ 文件中的其他代码 void some_cpp_function() { xmem_create_pool(1024); // 调用 C 函数 }
- 确保你的 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 模块集成在一起,并避免名称修饰带来的链接错误。