写c++扩展的时候,怎么样让node-gyp找到需要的头文件
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了写c++扩展的时候,怎么样让node-gyp找到需要的头文件相关的知识,希望对你有一定的参考价值。
一、编写Node.js原生扩展Node.js是一个强大的平台,理想状态下一切都都可以用javascript写成。然而,你可能还会用到许多遗留的库和系统,这样的话使用c++编写Node.JS扩展会是一个不错的注意。
以下所有例子的源代码可在node扩展示例中找到 。
编写Node.js C + +扩展很大程度上就像是写V8的扩展; Node.js增加了一些接口,但大部分时间你都是在使原始的V8数据类型和方法,为了理解以下的代码,你必须首先阅读V8引擎嵌入指南。
Javascript版本的Hello World
在讲解C++版本的例子之前,先让我们来看看在Node.js中用Javascript编写的等价模块是什么样子。这是一个最简单的Hello World,也不是通过HTTP,但它展示了node模块的结构,而其接口也和大多数C++扩展要提供的接口差不多:
HelloWorldJs = function()
this.m_count = 0;
;
HelloWorldJs.prototype.hello = function()
this.m_count++;
return “Hello World”;
;
exports.HelloWorldJs = HelloWorldJs;
正如你所看到的,它使用prototype为HelloWorldJs类创建了一个新的方法。请注意,上述代码通过将HelloWorldJS添加到exports变量来暴露构造函数。
要在其他地方使用该模块,请使用如下代码:
var helloworld = require(‘helloworld_js’);
var hi = new helloworld.HelloWorldJs();
console.log(hi.hello()); // prints “Hello World” to stdout
C++版本的Hello World
要开始编写C++扩展,首先要能够编译Node.js(请注意,我们使用的是Node.js 2.0版本)。本文所讲内容应该兼容所有未来的0.2.x版本。一旦编译安装完node,编译模块就不在需要额外的东西了。
完整的源代码可以在这里找到 。在使用Node.js或V8之前,我们需要包括相关的头文件:
#include <v8.h>
#include <node.h>
using namespace node;
using namespace v8;
在本例子中我直接使用了V8和node的命名空间,使代码更易于阅读。虽然这种用法和谷歌的自己的C++编程风格指南相悖,但由于你需要不停的使用V8定义的类型,所以目前为止的大多数node的扩展仍然使用了V8的命名空间。
接下来,声明HelloWorld类。它继承自node::ObjectWrap类 ,这个类提供了几个如引用计数、在V8内部传递contex等的实用功能。一般来说,所有对象应该继承ObjectWrap:
class HelloWorld: ObjectWrap
private:
int m_count;
public:
声明类之后,我们定义了一个静态成员函数,用来初始化对象并将其导入Node.js提供的target对象中。设个函数基本上是告诉Node.js和V8你的类是如何创建的,和它将包含什么方法:
static Persistent<FunctionTemplate> s_ct;
static void Init(Handle<Object> target)
HandleScope scope;
Local<FunctionTemplate> t = FunctionTemplate::New(New);
s_ct = Persistent<FunctionTemplate>::New(t);
s_ct->InstanceTemplate()->SetInternalFieldCount(1);
s_ct->SetClassName(String::NewSymbol(“HelloWorld”));
NODE_SET_PROTOTYPE_METHOD(s_ct, “hello”, Hello);
target->Set(String::NewSymbol(“HelloWorld”),
s_ct->GetFunction());
在上面这个函数中target参数将是模块对象,即你的扩展将要载入的地方。(译著:这个函数将你的对象及其方法连接到
这个模块对象,以便外界可以访问)首先我们为New方法创建一个FunctionTemplate,将于稍后解释。我们还为该对象添加一个内部字段,并命
名为HelloWorld。然后使用NODE_SET_PROTOTYPE_METHOD宏将hello方法绑定到该对象。最后,一旦我们建立好这个函数模板后,将他分配给target对象的HelloWorld属性,将类暴露给用户。
接下来的部分是一个标准的C++构造函数:
HelloWorld() :
m_count(0)
~HelloWorld()
接下来,在::New 方法中V8引擎将调用这个简单的C++构造函数:
static Handle<Value> New(const Arguments& args)
HandleScope scope;
HelloWorld* hw = new HelloWorld();
hw->Wrap(args.This());
return args.This();
此段代码相当于上面Javascript代码中使用的构造函数。它调用new HelloWorld
创造了一个普通的C++对象,然后调用从ObjectWrap继承的Wrap方法,
它将一个C++HelloWorld类的引用保存到args.This()的值中。在包装完成后返回args.This(),整个函数的行为和
javascript中的new运算符类似,返回this指向的对象。
现在我们已经建立了对象,下面介绍在Init函数中被绑定到hello的函数:
static Handle<Value> Hello(const Arguments& args)
HandleScope scope;
HelloWorld* hw = ObjectWrap::Unwrap<HelloWorld>(args.This());
hw->m_count++;
Local<String> result = String::New(“Hello World”);
return scope.Close(result);
函数中首先使用ObjectWrap模板的方法提取出指向HelloWorld类的指针,然后和javascript版本的HelloWorld一样递增计数器。我们新建一个内容为“HelloWorld”的v8字符串对象,然后在关闭本地作用域的时候返回这个字符串。
上面的代码实际上只是针对v8的接口,最终我们还需要让Node.js知道如何动态加载我们的代码。为了使Node.js的扩展可以在执行时从动态链接库加载,需要有一个dlsym函数可以识别的符号,所以执行编写如下代码:
extern “C”
static void init (Handle<Object> target)
HelloWorld::Init(target);
NODE_MODULE(helloworld, init);
由于c++的符号命名规则,我们使用extern
C,以便该符号可以被dysym识别。init方法是Node.js加载模块后第一个调用的函数,如果你有多个类型,请全部在这里初始化。
NODE_MODULE宏用来填充一个用于存储模块信息的结构体,存储的信息如模块使用的API版本。这些信息可以用来防止未来因API不兼容导致的崩
溃。
到此,我们已经完成了一个可用的C++ NodeJS扩展。
Node.js也提供了一个用于构建模块的简单工具:
node-waf首先编写一个包含扩展编译方法的wscript文件,然后执行node-waf configure &&
node-waf build完成模块的编译和链接工作。对于这个helloworld的例子来说,wscript内容如下:
def set_options(opt):
opt.tool_options(“compiler_cxx”)
def configure(conf):
conf.check_tool(“compiler_cxx”)
conf.check_tool(“node_addon”)
def build(bld):
obj = bld.new_task_gen(“cxx”, “shlib”, “node_addon”)
obj.cxxflags = [“-g”, “-D_FILE_OFFSET_BITS=64”, “-D_LARGEFILE_SOURCE”, “-Wall”]
obj.target = “helloworld”
obj.source = “helloworld.cc”
异步IO的HelloWorld
对于实际的应用来说,HelloWorld的示例太过简单了一些,Node.js主要的优势是提供异步IO。
Node.js内部通过libeio将会产生阻塞的操作全都放入线程池中执行。如果需要和遗留的c库交互,通常需要使用异步IO来为javascript
代码提供回调接口。
通常的模式是提供一个回调,在异步操作完成时被调用——你可以在整个Node.js的API中看到这种模式。
Node.js的filesystem模块提供了一个很好的例子,其中大多数的函数都在操作完成后通过调用回调函数来传递数据。和许多传统的GUI框架一
样,Node.js只在主线程中执行JavaScript,因此主线程以外的任何操作都不应该直接和V8或Javascript交互。
同样helloworld_eio.cc源代码在GitHub上。我只强调和原来HelloWorld之间的差异,其中大部分代码保持不变,变化集中在Hello方法中:
static Handle<Value> Hello(const Arguments& args)
HandleScope scope;
REQ_FUN_ARG(0, cb);
HelloWorldEio* hw = ObjectWrap::Unwrap<HelloWorldEio>(args.This());
在Hello函数的入口处 ,我们使用宏从参数列表的第一个位置获取回调函数,在下一节中将详细介绍。然后,我们使用相同的Unwarp方法提取指向类对象的指针。
hello_baton_t *baton = new hello_baton_t();
baton->hw = hw;
baton->increment_by = 2;
baton->sleep_for = 1;
baton->cb = Persistent<Function>::New(cb);
这里我们创建一个baton结构,并将各种参数保存在里面。请注意,我们为回调函数创建了一个永久引用,因为我们想要在超出当前函数作用域的地方使用它。如果不这么做,在本函数结束后将无法再调用回调函数。
hw->Ref();
eio_custom(EIO_Hello, EIO_PRI_DEFAULT, EIO_AfterHello, baton);
ev_ref(EV_DEFAULT_UC);
return Undefined();
如下代码是真正的重点。首先,我们增加HelloWorld对象的引用计数,这样在其他线程执行的时候他就不会被回收。
函数eio_custom接受两个函数指针作为参数。EIO_Hello函数将在线程池中执行,然后EIO_AfterHello函数将回到在“主线程”
中执行。我们的baton结构也被传递进各函数,这些函数可以使用baton结构中的数据完成相关的操作。同时,我们也增加event
loop的引用。这很重要,因为如果event
loop无事可做,Node.js就会退出。最终,函数返回Undefined,因为真正的工作将在其他线程中完成。
static int EIO_Hello(eio_req *req)
hello_baton_t *baton = static_cast<hello_baton_t *>(req->data);
sleep(baton->sleep_for);
baton->hw->m_count += baton->increment_by;
return 0;
这个回调函数将在libeio管理的线程中执行。首先,解析出baton结构,这样可以访问之前设置的各种参数。然后
sheep
baton->sleep_for秒,这么做是安全的,因为这个函数运行在独立的线程中并不会阻塞主线程中javascript的执行。然后我们的
增计数器,在实际的系统中,这些操作通常需要使用Lock/Mutex进行同步。
当上述方法返回后,libeio将会通知主线程它需要在主线成上执行代码,此时EIO_AfterHello将会被调用。
static int EIO_AfterHello(eio_req *req)
HandleScope scope;
hello_baton_t *baton = static_cast<hello_baton_t *>(req->data);
ev_unref(EV_DEFAULT_UC);
baton->hw->Unref();
进度此函数时,我们提取出baton结构,删除事件循环的引用,并减少HelloWorld对象的引用。
Local<Value> argv[1];
argv[0] = String::New(“Hello World”);
TryCatch try_catch;
baton->cb->Call(Context::GetCurrent()->Global(), 1, argv);
if (try_catch.HasCaught())
FatalException(try_catch);
新建要传递给回调函数的字符串参数,并放入字符串数组中。然后我们调用回调传递一个参数,并检测可能抛出的异常。
baton->cb.Dispose();
delete baton;
return 0;
在执行过回调之后,应该销毁持久引用,然后删除之前创建的baton结构。
最后,你可以使用如下形式在Javascript中使用该模块:
var helloeio = require(‘./helloworld_eio’);
hi = new helloeio.HelloWorldEio();
hi.hello(function(data)
console.log(data);
);
参数传递与解析
除了HelloWorld之外,你还需要理解最后一个问题:参数的处理。在helloWorld EIO例子中,我们使用一个REQ_FUN_ARG宏,然我们看看这个宏到底都做些什么。
#define REQ_FUN_ARG(I, VAR) \
if (args.Length() <= (I) || !args[I]->IsFunction()) \
return ThrowException(Exception::TypeError( \
String::New(“Argument ” #I ” must be a function”))); \
Local<Function> VAR = Local<Function>::Cast(args[I]);
就像Javascript中的argument变量,v8使用数组传递所有的参数。由于没有严格的类型限制,所以传递给函数的参数数目可能和期待的不同。为了对用户友好,使用如下的宏检测一下参数数组的长度并判断参数是否是正确的类型。如果传递了错误的参数类型,该宏将会抛出TypeError异常。为简化参数的解析,目前为止大多数的Node.js扩展都有一些本地作用域内的宏,用于特定类型参数的检测。
二、揭秘node.js事件
要使用NodeJS,你需要知道一个重要的东西:事件(events)。Node中有很多对象都可以触发事件,Node
的文档中有很多示例。但文档也许并不能清晰的讲解如何编写自定义事件以及监听函数。对于一些简单的程序你可以不使用自定义事件,但这样很难应对复杂的应
用。那么如何编写自定义事件?首先需要了解的是在node.js中的’events’模块。
快速概览
要访问此模块,只需使用如下语句:
require(‘events’)
requires(‘events’).EventEmitter
特别说明,node中所有能触发事件的对象基本上都是后者的实例。让我们创建一个简单的演示程序Dummy:
dummy.js
view plaincopy to clipboardprint?
// basic imports
var events = require(‘events’);
// for us to do a require later
module.exports = Dummy;
function Dummy()
events.EventEmitter.call(this);
10.
11. // inherit events.EventEmitter
12. Dummy.super_ = events.EventEmitter;
13. Dummy.prototype = Object.create(events.EventEmitter.prototype,
14. constructor:
15. value: Dummy,
16. enumerable: false
17.
18. );
// basic imports
var events = require(‘events’);
// for us to do a require later
module.exports = Dummy;
function Dummy()
events.EventEmitter.call(this);
// inherit events.EventEmitter
Dummy.super_ = events.EventEmitter;
Dummy.prototype = Object.create(events.EventEmitter.prototype,
constructor:
value: Dummy,
enumerable: false
);
上述代码中重点展示如何使用EventEmitter扩充对象,并从中继承所有的原型对象,方法…等等。
现在,我们假设Dummy有一个cooking()的方法,一旦把食物做熟之后它会触发’cooked’事件,并调用一个名为’eat’的回调函数。
dummy-cooking.js
view plaincopy to clipboardprint?
Dummy.prototype.cooking = function(chicken)
var self = this;
self.chicken = chicken;
self.cook = cook(); // assume dummy function that’ll do the cooking
self.cook(chicken, function(cooked_chicken)
self.chicken = cooked_chicken;
self.emit(‘cooked’, self.chicken);
);
10. return self;
11.
Dummy.prototype.cooking = function(chicken)
var self = this;
self.chicken = chicken;
self.cook = cook(); // assume dummy function that’ll do the cooking
self.cook(chicken, function(cooked_chicken)
self.chicken = cooked_chicken;
self.emit(‘cooked’, self.chicken);
);
return self;
参考技术A 你没装node-gyp吧。用npm可以装。
另外,在win8装node-gyp你需要先有python2.7和visual studio C++ 2012
如何让 node-gyp 在 Windows 7 平台上工作
【中文标题】如何让 node-gyp 在 Windows 7 平台上工作【英文标题】:How do I get node-gyp to work on Windows 7 platform 【发布时间】:2014-02-07 15:42:28 【问题描述】:在尝试编译标准“Hello World”示例 [2] 时,我尝试在 Windows 7 + Node.js 平台上使用 node-gyp 失败了 [3]。注意:node-gyp 在尝试 npm install contextify
或不带“-g”时以类似的方式失败 [3],因此这些可能是相关问题。
配置:
节点-gyp 0.12.2 Windows 7 x64 SP1 Python 2.7 Node.js 0.10.24 Visual Studio 2010 per [1](也在 2012 年尝试过) VS SDK 7.1 per [1](尝试 32 位和 64 位版本) 从标准 Windows 命令提示符或 SDK7.1 cmd 提示符运行 node-gyp参考: [1]https://github.com/TooTallNate/node-gyp/wiki/Visual-Studio-2010-Setup
[2]https://github.com/joyent/node/tree/master/test/addons/hello-world
[3] “hello world”[2] 项目上的“node-gyp rebuild”产生以下 2 个错误:
....node-gyp\0.10.24\deps\uv\include\win.h(8738): 错误 C2371: 'SYSTEM_POWER_STATUS': 重新定义;不同的基本类型 [...\build\test.vcxproj]
....node-gyp\0.10.24\deps\uv\include\mswsock.h(27): 致命错误 C 1083:无法打开包含文件:'_mingw.h':没有这样的文件或目录 [...\build\test.vcxproj]
以及以下 2 个警告:
....node-gyp\0.10.24\deps\uv\include\win.h(13513): 警告 C4005: 'UNALIGNED' : 宏重新定义 [...\build\test.vcxproj]
....node-gyp\0.10.24\deps\uv\include\mswsock.h(26):警告 C4068 : 未知的编译指示 [...\build\test.vcxproj]
完整的跟踪如下:
C:\sigma\node_modules\x>node-gyp rebuild
gyp info it worked if it ends with ok
gyp info using node-gyp@0.12.2
gyp info using node@0.10.24 | win32 | x64
gyp info spawn python
gyp info spawn args [ 'C:\\Users\\Anybody\\AppData\\Roaming\\npm\\node_modules\\node-gyp\\gyp\\gyp_main.py',
gyp info spawn args 'binding.gyp',
gyp info spawn args '-f',
gyp info spawn args 'msvs',
gyp info spawn args '-G',
gyp info spawn args 'msvs_version=auto',
gyp info spawn args '-I',
gyp info spawn args 'C:\\sigma\\node_modules\\x\\build\\config.gypi',
gyp info spawn args '-I',
gyp info spawn args 'C:\\Users\\Anybody\\AppData\\Roaming\\npm\\node_modules\\node-gyp\\addon.gypi',
gyp info spawn args '-I',
gyp info spawn args 'C:\\Users\\Anybody\\.node-gyp\\0.10.24\\common.gypi',
gyp info spawn args '-Dlibrary=shared_library',
gyp info spawn args '-Dvisibility=default',
gyp info spawn args '-Dnode_root_dir=C:\\Users\\Anybody\\.node-gyp\\0.10.24',
gyp info spawn args '-Dmodule_root_dir=C:\\sigma\\node_modules\\x',
gyp info spawn args '--depth=.',
gyp info spawn args '--generator-output',
gyp info spawn args 'C:\\sigma\\node_modules\\x\\build',
gyp info spawn args '-Goutput_dir=.' ]
gyp info spawn msbuild
gyp info spawn args [ 'build/binding.sln',
gyp info spawn args '/clp:Verbosity=minimal',
gyp info spawn args '/nologo',
gyp info spawn args '/p:Configuration=Release;Platform=x64' ]
Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch. test.cpp
C:\Users\Anybody\.node-gyp\0.10.24\deps\uv\include\win.h(8738): error C2371: 'SYSTEM_POWER_STATUS' : redefinition; different basic types [C:\sigma\node_modules\x\build\test.vcxproj] C:\Users\Anybody\.node-gyp\0.10.24\deps\uv\include\win.h(8737) : see declaration of 'SYSTEM_POWER_STATUS'
C:\Users\Anybody\.node-gyp\0.10.24\deps\uv\include\win.h(13513): warning C4005: 'UNALIGNED' : macro redefinition [C:\sigma\node_modules\x\build\test.vcxproj] C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\crtdef s.h(502) : see previous definition of 'UNALIGNED'
C:\Users\Anybody\.node-gyp\0.10.24\deps\uv\include\mswsock.h(26): warning C4068 : unknown pragma [C:\sigma\node_modules\x\build\test.vcxproj] C:\Users\Anybody\.node-gyp\0.10.24\deps\uv\include\_mingw.h(33): warning C4068: unknown pragma [C:\sigma\node_modules\x\build\test.vcxproj]
C:\Users\Anybody\.node-gyp\0.10.24\deps\uv\include\_mingw.h(51): fatal error C1 189: #error : ERROR: You must use a GNU Compiler. [C:\sigma\node_modules\x\build\test.vcxproj]
gyp ERR! build error
gyp ERR! stack Error: msbuild failed with exit code: 1
gyp ERR! stack at ChildProcess.onExit (C:\Users\Anybody\AppData\Roaming\npm\
node_modules\node-gyp\lib\build.js:267:23)
gyp ERR! stack at ChildProcess.EventEmitter.emit (events.js:98:17)
gyp ERR! stack at Process.ChildProcess._handle.onexit (child_process.js:789:12)
gyp ERR! System Windows_NT 6.1.7601
gyp ERR! command "node" "C:\\Users\\Anybody\\AppData\\Roaming\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js" "rebuild"
gyp ERR! cwd C:\sigma\node_modules\x
gyp ERR! node -v v0.10.24
gyp ERR! node-gyp -v v0.12.2
gyp ERR! not ok
【问题讨论】:
我也遇到了同样的问题~我按照[Refs[1]](github.com/TooTallNate/node-gyp/wiki/Visual-Studio-2010-Setup)的所有步骤,你解决了吗?如果是,请分享。 请将您的用户体验添加到此问题中:github.com/TooTallNate/node-gyp/issues/662 如果更多人加入,他们可能会添加适当的安装程序。 【参考方案1】:虽然这个问题是一个月前的问题,但我最终在网上搜索,这是搜索结果的一部分。
所以这是一个不应该做但可行的答案。
首先,我不熟悉 node-gyp
或整个 Visual Studio 编译之类的东西,我真的为依赖于 python 的 node-gyp 感到抱歉(一个非常沉默的 WTF ) p>
所以不知道为什么 nodejs 版本早于 0.10.22(我现在正在运行它,所以我假设它从那里开始)
如果您在系统中搜索文件“uv.h”,您可能会得到一些结果如果您以前安装过 nodejs 版本,否则您可以继续阅读。
我之前的 0.10.3 nodejs 版本有 uv.h
进入 node-gyp 一些类似
<DRIVERLETTER>:\Users\<USERNAME>\.node-gyp\0.10.3\deps\uv
因此,如果您阅读 npm 文档,您会在 npm install
上找到参数
--nodedir=/path/to/node/source 参数将允许 npm 找到 node 源代码,以便 npm 可以编译原生模块。
所以最后的命令应该是
npm install <package> --nodedir="<DRIVERLETTER>:\Users\<USERNAME\.node-gyp\0.10.3"
您可能会收到一些警告,但它应该会成功完成。
【讨论】:
我没有0.10.3
版本,但我确实在0.10.21
版本中找到了uv.h
文件——尝试安装jsdom
模块——仍然是同样的错误。
WTF +1。 Windows7 上的 Node-gyp 是最大的耻辱,它给节点可用性本身带来了很大的阴影。
--nodedir
应该指定到\dep\uv
例如--nodedir="<DRIVERLETTER>:\Users\<USERNAME\.node-gyp\0.10.3\dep\uv"
??。
必须同意@setec。另外,Windows 很糟糕。
这可能对任何试图解决此问题的人有所帮助:robertkehoe.com/2015/03/fix-node-gyp-rebuild-error-on-windows【参考方案2】:
这就是帮助我解决它的原因:
出于某种原因,我在两个不同的目录中有 node-gyp。
目录 1:
C:\Users\Imran Bughio\.node-gyp\0.10.21\
目录 2:
C:\Users\Imran Bughio\Documents\.node-gyp\0.10.21\
在第二个目录中,我在deps
文件夹中有一个uv
文件夹,但该文件夹不在第一个目录中。
这是路径——注意里面有uv.h
文件。
C:\Users\Imran Bughio\.node-gyp\0.10.21\deps\uv
解决方案:
我只是将 uv 和所有其他额外文件夹从目录 2 移动到目录 1。
@Phoenix 的回答给了我一点提示,最终帮助我解决了这个问题,谢谢 凤凰。
【讨论】:
【参考方案3】:在你做任何事情之前,通过从节点网站安装来更新节点和 NPM(通过 npm 更新节点和 npm 似乎在 Windows 上被破坏了。我不确定那些删除并重新安装一切的顺序说明是最新的,但那肯定是一个主要的 PITA,考虑到那个帖子在节点年代有点旧,这激发了我寻找替代品。
最终,这为我解决了一个问题,将 node-gyp 作为 pg 模块的依赖项,并且它与 vs 2013 一起运行,我相信(或者至少在它开始工作之前它似乎正在寻找它)。
【讨论】:
我在多台机器上为此苦苦挣扎了好几天,安装 VS2013 最终解决了这个问题。以上是关于写c++扩展的时候,怎么样让node-gyp找到需要的头文件的主要内容,如果未能解决你的问题,请参考以下文章