Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend shared library interface #19005

Closed
wants to merge 66 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
32932e4
Squash commit of all our previous commits
msoechting Feb 7, 2018
99b9de7
Fix errors introduced by merge
EmberFlare Feb 7, 2018
7f6a8d5
Further merge fixes
EmberFlare Feb 7, 2018
208db30
Update our init- and deinitialize methods with new master code
luminosuslight Feb 7, 2018
1ca52b3
replay central changes from refactor-start branch on new dev
justus-hildebrand Feb 12, 2018
c77d2a1
Added code from initial PR (#62)
Feb 12, 2018
e51dc56
replay changes from fix-repl branch on new dev
justus-hildebrand Feb 12, 2018
9bd425b
fix linter error
justus-hildebrand Feb 12, 2018
bae6b4d
fix small code smells
justus-hildebrand Feb 13, 2018
e2e25de
fix linter
justus-hildebrand Feb 13, 2018
b2a8f79
Merge pull request #67 from hpicgs/deinitialization
Feb 15, 2018
0abe5a4
Add if around eval script
luminosuslight Feb 15, 2018
d133ec8
Clean code
luminosuslight Feb 15, 2018
be93498
Fix documentation
luminosuslight Feb 15, 2018
aad189a
Merge pull request #69 from hpicgs/new-fix-repl
luminosuslight Feb 15, 2018
1959f89
Merge pull request #68 from hpicgs/new-refactor-start
luminosuslight Feb 15, 2018
40b20d8
Fix REPL spawning
cmfcmf Feb 19, 2018
cdce802
Add missing function declaration
cmfcmf Feb 19, 2018
e7f40c1
Fix typo
cmfcmf Feb 19, 2018
c52a994
Correctly deinitialize context
cmfcmf Feb 19, 2018
6d53a55
Fix style, remove debug output
cmfcmf Feb 19, 2018
efc78d6
Fix require not available in shared mode
luminosuslight Feb 21, 2018
ab7db6c
Merge pull request #77 from hpicgs/deini-fix
msoechting Feb 21, 2018
883795c
Merge pull request #78 from hpicgs/fix-allow-repl
msoechting Feb 21, 2018
b7621f0
Merge branch 'dev' into repl-fix
cmfcmf Feb 25, 2018
8fb4446
Merge branch 'master' into dev
cmfcmf Feb 25, 2018
a8dc3fa
Bring comment in bootstrap_node.js back
luminosuslight Feb 25, 2018
35ff063
Re-add error handling for initialize
cmfcmf Feb 26, 2018
984c3ea
Improved usage documentation (#72)
msoechting Feb 26, 2018
1e9c2f7
Remove deinitialize code
cmfcmf Feb 26, 2018
2d2227d
Remove some comments, adjust TODOs (#79)
cmfcmf Feb 26, 2018
6036ee2
Merge branch 'dev' into error-handling
cmfcmf Feb 26, 2018
737c015
Fix cs
cmfcmf Feb 26, 2018
873f871
Fix indent
cmfcmf Feb 26, 2018
b60ad85
Rename parameter to evaluate_stdin
cmfcmf Feb 26, 2018
8826721
Merge branch 'dev' into repl-fix
cmfcmf Feb 26, 2018
e751664
Merge pull request #80 from hpicgs/error-handling
cmfcmf Feb 26, 2018
2b67156
Whoopsidoopsi
cmfcmf Feb 26, 2018
a5f4250
removed travis.yaml in preparation for PR
Feb 26, 2018
695a8db
Merge branch 'dev' into repl-fix
cmfcmf Feb 26, 2018
ad630a5
fixed linter errors
Feb 26, 2018
3d0865e
Merge branch 'dev' into repl-fix
cmfcmf Feb 26, 2018
3aad7e8
Fix doc comment
cmfcmf Feb 26, 2018
9811298
Merge branch 'repl-fix' of github.com:hpicgs/node into repl-fix
cmfcmf Feb 26, 2018
385de54
Merge pull request #75 from hpicgs/repl-fix
Feb 26, 2018
f23feec
removed trailling whitespaces
Feb 26, 2018
9213545
Fix line length
luminosuslight Feb 26, 2018
e916467
Merge branch 'dev' into node-lib-interface
luminosuslight Feb 26, 2018
0f63853
POC of environments in all library methods
cmfcmf Mar 13, 2018
f9060d3
Add missing environment to Call()
cmfcmf Mar 13, 2018
81e9d6a
Remove Isolate::CreateParams, Isollate::Scope and Context::Scope as g…
cmfcmf Mar 13, 2018
c70d931
Remove _isolate variable (unsure about this)
cmfcmf Mar 13, 2018
3aeb883
Fix cs
cmfcmf Mar 13, 2018
c357218
Remove initializer_list methods
cmfcmf Mar 13, 2018
a686c96
Merge pull request #84 from hpicgs/remove-initializer-lists
luminosuslight Mar 13, 2018
646388a
Fix ignored return value of uv_setup_args
luminosuslight Mar 13, 2018
319f9ef
Merge pull request #86 from hpicgs/fix-argv-process-title
cmfcmf Mar 13, 2018
5e2fe3b
Remove outdated TODO
cmfcmf Mar 13, 2018
05e8b99
Merge remote-tracking branch 'origin/dev' into remove-globals
cmfcmf Mar 13, 2018
c2febfb
Merge pull request #83 from hpicgs/remove-globals
cmfcmf Mar 13, 2018
568191e
Add documentation and remove initializer_list call methods
luminosuslight Mar 13, 2018
d493e9a
Merge remote-tracking branch 'origin/dev' into add-environment
cmfcmf Mar 13, 2018
1fbcc75
Remove implementation of init_list methods, too, and fix namespace
luminosuslight Mar 13, 2018
f1196ca
Merge branch 'add-environment' of github.com:hpicgs/node into add-env…
cmfcmf Mar 13, 2018
9d6e944
Merge pull request #82 from hpicgs/add-environment
cmfcmf Mar 17, 2018
cfae579
Merge pull request #87 from hpicgs/dev
cmfcmf Mar 17, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 315 additions & 0 deletions LIB_USAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
# How to use Node.js as a shared library
## Limitations
* It is only possible to access Node.js from the same thread (no multi-threading).
* There can only be one Node.js per process at the same time.

## Handling the Node.js event loop
There are two different ways of handling the Node.js event loop.
### C++ keeps control over thread
By calling `node::ProcessEvents()`, the Node.js event loop will be run once, handling the next pending event. The return value of the call specifies whether there are more events in the queue.

### C++ gives control of the thread to Node.js
By calling `node::RunEventLoop(callback)`, the C++ host program gives up the control of the thread and allows the Node.js event loop to run until no more events are in the queue or `node::StopEventLoop()` is called. The `callback` parameter in the `RunEventLoop` function is called once per iteration of the event loop. This allows the C++ programmer to react to changes in the Node.js state and e.g. terminate Node.js preemptively.

## Examples
In the following, a few examples demonstrate the usage of Node.js as a library. For more complex examples, including handling of the event loop, see the [node-embed](https://github.com/hpicgs/node-embed) repository.

### (1) Evaluating in-line JavaScript code
This example evaluates multiple lines of JavaScript code in the global Node.js context. The result of `console.log` is piped to stdout.

```C++
node::Initialize();
node::Evaluate("var helloMessage = 'Hello from Node.js!';");
node::Evaluate("console.log(helloMessage);");
```

### (2) Running a JavaScript file
This example evaluates a JavaScript file and lets Node handle all pending events until the event loop is empty.

```C++
node::Initialize();
node::Run("cli.js");
while (node::ProcessEvents()) { }
```

### (3) Including an NPM Module
This example uses the [fs](https://nodejs.org/api/fs.html) module to check whether a specific file exists.
```C++
node::Initialize();
auto fs = node::IncludeModule("fs");
v8::Isolate *isolate = node::internal::isolate();

// Check if file cli.js exists in the current working directory.
auto result = node::Call(fs, "existsSync", {v8::String::NewFromUtf8(isolate, "file.txt")});

auto file_exists = v8::Local<v8::Boolean>::Cast(result)->BooleanValue();
std::cout << (file_exists ? "file.txt exists in cwd" : "file.txt does NOT exist in cwd") << std::endl;

```

### (4) Advanced: Combining a Qt GUI with Node.js
This example, which is borrowed from the examples repository [node-embed](https://github.com/hpicgs/node-embed), fetches a RSS feed from the BBC and displays it in a Qt GUI. For this, the `feedparser` and `request` modules from NPM are utilized.

#### main.cpp
```C++
#include <chrono>
#include <iostream>
#include <thread>

#include <QDebug>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

#include <cpplocate/cpplocate.h>

#include "node.h"
#include "node_lib.h"

#include "RssFeed.h"

int main(int argc, char* argv[]) {
// Locate the JavaScript file we want to embed:
const std::string js_file = "data/node-lib-qt-rss.js";
const std::string data_path = cpplocate::locatePath(js_file);
const std::string js_path = data_path + "/" + js_file;

// Initialize Qt:
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;

// to be able to access the public slots of the RssFeed instance
// we inject a pointer to it in the QML context:
engine.rootContext()->setContextProperty("rssFeed", &RssFeed::getInstance());

engine.load(QUrl(QLatin1String("qrc:/main.qml")));

// Initialize Node.js engine:
node::Initialize();

// Register C++ methods to be used within JavaScript
// The third parameter binds the C++ module to that name,
// allowing the functions to be called like this: "cppQtGui.clearFeed(...)"
node::RegisterModule("cpp-qt-gui", {
{"addFeedItem", RssFeed::addFeedItem},
{"clearFeed", RssFeed::clearFeed},
{"redraw", RssFeed::redrawGUI},
}, "cppQtGui");

// Evaluate the JavaScript file once:
node::Run(js_path);

// Load intial RSS feed to display it:
RssFeed::refreshFeed();

// Run the Qt application:
app.exec();

// After we are done, deinitialize the Node.js engine:
node::Deinitialize();
}
```

#### RssFeed.h
```C++
#pragma once

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

/**
* @brief The RssFeed class retrieves an RSS feed from the Internet and
* provides its entries.
*/
class RssFeed : public QObject {

Q_OBJECT

Q_PROPERTY(QStringList entries READ getEntries NOTIFY entriesChanged)

private:
/**
* @brief Creates a new RssFeed with a given QObject as its parent.
*
* @param parent The parent object.
*/
explicit RssFeed(QObject* parent=nullptr);

public:
/**
* @brief Returns the singleton instance for this class.
*
* @return The singlton instance for this class.
*/
static RssFeed& getInstance();

/**
* @brief This method is called from the embedded JavaScript.
* It is used for deleting all items from this RSS feed.
*
* @param args The arguments passed from the embedded JavaScript.
* Hint: This method does not expect any arguments.
*/
static void clearFeed(const v8::FunctionCallbackInfo<v8::Value>& args);

/**
* @brief This method is called from the embedded JavaScript.
* It is used to add an entry to this RSS feed.
*
* @param args The arguments passed from the embedded JavaScript.
* Hint: This method expects an object, which contains the RSS feed item.
*/
static void addFeedItem(const v8::FunctionCallbackInfo<v8::Value>& args);

/**
* @brief This method is called from the embedded JavaScript.
* It is used to refresh the GUI, after all retrieved feed items have been
* appended.
*
* @param args The arguments passed from the embedded JavaScript.
* Hint: This method does not expect any arguments.
*/
static void redrawGUI(const v8::FunctionCallbackInfo<v8::Value>& args);

/**
* @brief This method updates the feed by calling a JS function
* and running the Node.js main loop until the whole feed was received.
*/
Q_INVOKABLE static void refreshFeed();

private:
static RssFeed* instance; /*!< The singleton instance for this class. */
QStringList entries; /*!< The list of RSS feeds to display. */

signals:
void entriesChanged(); /*!< Emitted after entries were added or removed. */

public slots:
/**
* @brief getEntries returns the entries of the RSS feed
*
* @return a list of text entries
*/
QStringList getEntries() const;
};
```

#### RssFeed.cpp
```C++
#include "RssFeed.h"

#include <iostream>
#include <QGuiApplication>
#include "node_lib.h"

RssFeed* RssFeed::instance = nullptr;

RssFeed::RssFeed(QObject* parent)
: QObject(parent)
{

}

RssFeed& RssFeed::getInstance(){
if (instance == nullptr){
instance = new RssFeed();
}
return *instance;
}

QStringList RssFeed::getEntries() const {
return entries;
}

void RssFeed::clearFeed(const v8::FunctionCallbackInfo<v8::Value>& args) {
getInstance().entries.clear();
}

void RssFeed::redrawGUI(const v8::FunctionCallbackInfo<v8::Value>& args) {
emit getInstance().entriesChanged();
}

void RssFeed::refreshFeed() {
// invoke the embedded JavaScript in order to fetch new RSS feeds:
node::Evaluate("emitRequest()");

// wait for the embedded JavaScript to finish its execution
// meanwhile, process any QT events
node::RunEventLoop([](){ QGuiApplication::processEvents(); });
}

void RssFeed::addFeedItem(const v8::FunctionCallbackInfo<v8::Value>& args) {
// check, whether this method was called as expected
// therefore, we need to make sure, that the first argument exists
// and that it is an object
v8::Isolate* isolate = args.GetIsolate();
if (args.Length() < 1 || !args[0]->IsObject()) {
isolate->ThrowException(v8::Exception::TypeError(
v8::String::NewFromUtf8(isolate, "Error: One object expected")));
return;
}

v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Object> obj = args[0]->ToObject(context).ToLocalChecked();

// we want to get all properties of our input object:
v8::Local<v8::Array> props = obj->GetOwnPropertyNames(context).ToLocalChecked();

for (int i = 0, l = props->Length(); i < l; i++) {
v8::Local<v8::Value> localKey = props->Get(i);
v8::Local<v8::Value> localVal = obj->Get(context, localKey).ToLocalChecked();
std::string key = *v8::String::Utf8Value(localKey);
std::string val = *v8::String::Utf8Value(localVal);

// append the RSS feed body to the list of RSS feeds:
getInstance().entries << QString::fromStdString(val);
}
}
```

#### node-lib-qt-rss.js
```JS
var FeedParser = require('feedparser');
var request = require('request'); // for fetching the feed

var emitRequest = function () {
console.log("Refreshing feeds...")
var feedparser = new FeedParser([]);
var req = request('http://feeds.bbci.co.uk/news/world/rss.xml')

req.on('error', function (error) {
// catch all request errors but don't handle them in this demo
});

req.on('response', function (res) {
var stream = this; // `this` is `req`, which is a stream

if (res.statusCode !== 200) {
this.emit('error', new Error('Bad status code'));
}
else {
cppQtGui.clearFeed();
stream.pipe(feedparser);
}
});

feedparser.on('error', function (error) {
// catch all parser errors but don't handle them in this demo
});

feedparser.on('readable', function () {
var stream = this; // `this` is `feedparser`, which is a stream
var item = stream.read();

if (item) {
var itemString = item['title'] + '\n' + item['description'];
cppQtGui.addFeedItem( { item: itemString } );
}
});

feedparser.on('end', function (){
cppQtGui.redraw();
});
}
```
11 changes: 9 additions & 2 deletions lib/internal/bootstrap_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,15 @@
preloadModules();
perf.markMilestone(
NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_END);
// If -i or --interactive were passed, or stdin is a TTY.
if (process._forceRepl || NativeModule.require('tty').isatty(0)) {
if (!process._evaluateStdin) {
// Calling evalScript is necessary, because it sets
// globals.require to the require function.
// Otherwise, require doesn't work.
// TODO(cmfcmf): Find a cleaner way to get require to work.
evalScript('[eval]');
} else if (process._forceRepl ||
NativeModule.require('tty').isatty(0)) {
// If -i or --interactive were passed, or stdin is a TTY
// REPL
const cliRepl = NativeModule.require('internal/repl');
cliRepl.createInternalRepl(process.env, function(err, repl) {
Expand Down
1 change: 1 addition & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@
'src/node_http2_state.h',
'src/node_internals.h',
'src/node_javascript.h',
'src/node_lib.h',
'src/node_mutex.h',
'src/node_perf.h',
'src/node_perf_common.h',
Expand Down
Loading