node-gtk 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,163 @@
1
+ #!/bin/bash
2
+ #
3
+ # windows-bundle-runtime.sh
4
+ #
5
+ # Make a freshly-built Windows prebuilt self-contained so it can be used WITHOUT
6
+ # MSYS2/MinGW or any compiler on the target machine. We copy the addon's entire
7
+ # transitive MinGW DLL closure next to the .node, plus the GObject-Introspection
8
+ # typelibs and runtime data it needs at runtime.
9
+ #
10
+ # The set of bundled libraries is driven by the curated seed list
11
+ # windows/runtime-libraries.txt (also a user-facing reference). The exact
12
+ # resolved DLL set is written to <binding-dir>/bundled-dlls.txt.
13
+ #
14
+ # Run inside the MINGW64 shell, after building.
15
+ #
16
+ # ./scripts/windows-bundle-runtime.sh lib/binding/node-v127-win32-x64
17
+ #
18
+ set -euo pipefail
19
+
20
+ SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
21
+
22
+ BINDING_DIR="${1:?usage: windows-bundle-runtime.sh <binding-dir> [runtime-libraries.txt]}"
23
+ # The curated seed list lives in a text file so it doubles as user-facing
24
+ # reference (windows/runtime-libraries.txt). Override with $2 if needed.
25
+ LIBS_FILE="${2:-$SCRIPT_DIR/../windows/runtime-libraries.txt}"
26
+ # MinGW bin dir the seed names resolve against.
27
+ MB="${MINGW_BIN:-/mingw64/bin}"
28
+
29
+ NODE_FILE="$BINDING_DIR/node_gtk.node"
30
+
31
+ if [ ! -f "$NODE_FILE" ]; then
32
+ echo "error: $NODE_FILE not found"
33
+ ls -la "$BINDING_DIR" || true
34
+ exit 1
35
+ fi
36
+ if [ ! -f "$LIBS_FILE" ]; then
37
+ echo "error: seed list $LIBS_FILE not found"
38
+ exit 1
39
+ fi
40
+
41
+ # GObject-Introspection loads each namespace's shared library at runtime via
42
+ # g_module_open() when you call gi.require('Gtk', ...). Those libraries
43
+ # (libgio, libgtk, libgdk, libpango, libgdk_pixbuf, ...) are NOT linked by
44
+ # node_gtk.node, so ntldd on the addon alone misses them. We therefore seed the
45
+ # closure from the addon AND from the GTK runtime libraries listed in LIBS_FILE.
46
+ echo "## Seed libraries from $LIBS_FILE"
47
+ ENTRY_LIBS=("$NODE_FILE")
48
+ while IFS= read -r line; do
49
+ # strip comments and surrounding whitespace; skip blanks
50
+ name="${line%%#*}"
51
+ name="$(echo "$name" | tr -d '[:space:]')"
52
+ [ -z "$name" ] && continue
53
+ if [ -f "$MB/$name" ]; then
54
+ ENTRY_LIBS+=("$MB/$name")
55
+ echo " - $name"
56
+ else
57
+ echo " (skip missing seed $name)"
58
+ fi
59
+ done < "$LIBS_FILE"
60
+
61
+ echo "## Computing recursive DLL closure for the addon + GTK runtime"
62
+ # ntldd -R prints every transitive dependency with its resolved Windows path:
63
+ # libgtk-3-0.dll => C:\msys64\mingw64\bin\libgtk-3-0.dll (0x...)
64
+ # Collect the union of every entry's closure, keep only MinGW-provided DLLs
65
+ # (skip C:\Windows\System32 OS DLLs), and copy them next to the .node.
66
+ : > /tmp/dll-closure.txt
67
+ for lib in "${ENTRY_LIBS[@]}"; do
68
+ [ -f "$lib" ] || { echo " (skip missing entry $lib)"; continue; }
69
+ # the entry library itself (when it is one of the GTK runtime DLLs)
70
+ case "$lib" in *mingw64*) echo "$lib" >> /tmp/dll-closure.txt ;; esac
71
+ ntldd -R "$lib" \
72
+ | sed -n 's/.* => \(.*\) (0x.*/\1/p' \
73
+ | while IFS= read -r winpath; do
74
+ [ -z "$winpath" ] && continue
75
+ u=$(cygpath -u "$winpath" 2>/dev/null || echo "$winpath")
76
+ case "$u" in *mingw64*) echo "$u" >> /tmp/dll-closure.txt ;; esac
77
+ done
78
+ done
79
+
80
+ copied=0
81
+ sort -u /tmp/dll-closure.txt | while IFS= read -r u; do
82
+ if [ -f "$u" ]; then
83
+ cp -f "$u" "$BINDING_DIR/"
84
+ echo " + $(basename "$u")"
85
+ copied=$((copied + 1))
86
+ fi
87
+ done
88
+ DLL_COUNT=$(ls "$BINDING_DIR"/*.dll 2>/dev/null | wc -l | tr -d ' ')
89
+ echo "## DLLs bundled into $BINDING_DIR ($DLL_COUNT total)"
90
+
91
+ # Write the exact set that shipped, as a reference manifest (ships in the
92
+ # bundle and the artifact). Generated from the curated seed list above.
93
+ MANIFEST="$BINDING_DIR/bundled-dlls.txt"
94
+ {
95
+ echo "# bundled-dlls.txt — Windows DLLs shipped with this node-gtk prebuilt."
96
+ echo "# GENERATED by scripts/windows-bundle-runtime.sh; do not edit by hand."
97
+ echo "# Edit the curated seed list windows/runtime-libraries.txt instead."
98
+ echo "# $DLL_COUNT DLLs, expanded from the seed list via 'ntldd -R'."
99
+ echo "#"
100
+ ( cd "$BINDING_DIR" && ls -1 *.dll 2>/dev/null | sort )
101
+ } > "$MANIFEST"
102
+ echo "## Wrote manifest $MANIFEST"
103
+
104
+ echo "## Bundling GObject-Introspection typelibs"
105
+ TYPELIB_SRC=$(pkg-config --variable=typelibdir gobject-introspection-1.0 2>/dev/null || true)
106
+ if [ -z "$TYPELIB_SRC" ] || [ ! -d "$TYPELIB_SRC" ]; then
107
+ TYPELIB_SRC=/mingw64/lib/girepository-1.0
108
+ fi
109
+ TYPELIB_DST="$BINDING_DIR/girepository-1.0"
110
+ mkdir -p "$TYPELIB_DST"
111
+ # Copy the full typelib set; it is small and guarantees every transitive
112
+ # namespace dependency (Gdk, Pango, cairo, GdkPixbuf, Graphene, ...) is present.
113
+ cp -f "$TYPELIB_SRC"/*.typelib "$TYPELIB_DST/"
114
+ echo "## Typelibs bundled from $TYPELIB_SRC -> $TYPELIB_DST"
115
+
116
+ # ---------------------------------------------------------------------------
117
+ # Runtime DATA a real GTK4/Adwaita app needs at run time (beyond DLLs/typelibs).
118
+ # These are copied so we can MEASURE the realistic bundle size and ship a
119
+ # self-contained app. cp is best-effort: a missing source is not fatal.
120
+ # ---------------------------------------------------------------------------
121
+ copy_tree() { # src dst
122
+ if [ -d "$1" ]; then mkdir -p "$2"; cp -rf "$1"/. "$2"/ 2>/dev/null || true; fi
123
+ }
124
+
125
+ echo "## Bundling runtime data (loaders, schemas, icons, gtksourceview)"
126
+ # gdk-pixbuf image loaders (PNG/SVG/... — needed for icons/images)
127
+ copy_tree /mingw64/lib/gdk-pixbuf-2.0 "$BINDING_DIR/lib/gdk-pixbuf-2.0"
128
+ # compiled GSettings schemas (GTK4/Adw/GtkSourceView read settings)
129
+ copy_tree /mingw64/share/glib-2.0/schemas "$BINDING_DIR/share/glib-2.0/schemas"
130
+ # icon themes (Adwaita + hicolor fallback) — typically the biggest chunk
131
+ copy_tree /mingw64/share/icons/Adwaita "$BINDING_DIR/share/icons/Adwaita"
132
+ copy_tree /mingw64/share/icons/hicolor "$BINDING_DIR/share/icons/hicolor"
133
+ # GtkSourceView language definitions + style schemes
134
+ copy_tree /mingw64/share/gtksourceview-5 "$BINDING_DIR/share/gtksourceview-5"
135
+
136
+ # Make the gdk-pixbuf loaders cache path-portable: the build machine bakes in
137
+ # absolute paths to each loader DLL, which don't exist on the user's machine.
138
+ # Rewrite each loader path to its bare file name; lib/native.js puts the loaders
139
+ # dir on PATH so g_module_open() resolves the bare name at run time.
140
+ LOADER_CACHE="$BINDING_DIR/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
141
+ if [ -f "$LOADER_CACHE" ]; then
142
+ sed -i -E 's#^"[^"]*[\\/]([^"\\/]+\.dll)"#"\1"#' "$LOADER_CACHE"
143
+ echo "## Rewrote $LOADER_CACHE to portable (bare) loader names"
144
+ fi
145
+
146
+ echo
147
+ echo "## ===== SIZE BREAKDOWN ====="
148
+ size() { # label dir-or-glob
149
+ local label="$1"; shift
150
+ local total
151
+ total=$(du -ch "$@" 2>/dev/null | tail -1 | cut -f1)
152
+ printf ' %-26s %s\n' "$label" "${total:-0}"
153
+ }
154
+ size "DLLs (load-critical)" "$BINDING_DIR"/*.dll
155
+ size "typelibs (load-critical)" "$TYPELIB_DST"
156
+ size " + .node" "$NODE_FILE"
157
+ size "gdk-pixbuf loaders" "$BINDING_DIR/lib/gdk-pixbuf-2.0"
158
+ size "glib schemas" "$BINDING_DIR/share/glib-2.0/schemas"
159
+ size "icons (Adwaita+hicolor)" "$BINDING_DIR/share/icons"
160
+ size "gtksourceview data" "$BINDING_DIR/share/gtksourceview-5"
161
+ echo " --------------------------------------"
162
+ size "TOTAL (uncompressed)" "$BINDING_DIR"
163
+ echo "## =========================="
@@ -0,0 +1,104 @@
1
+ /*
2
+ * windows-smoke-test.js
3
+ *
4
+ * Verifies that a Windows prebuilt + bundled GTK runtime can be loaded and used
5
+ * on a clean machine that has NO MSYS2/MinGW and NO compiler — i.e. exactly what
6
+ * a user gets from `npm install node-gtk`.
7
+ *
8
+ * CRITICAL: this test sets NO environment variables. Everything (DLL search
9
+ * path, GI typelib path, icon/schema/loader data) is wired up automatically by
10
+ * lib/native.js when it loads the bundled prebuilt. The workflow runs this with
11
+ * the runner's own MinGW stripped from PATH, so a pass proves the bundle is
12
+ * fully self-sufficient via the auto-wiring alone.
13
+ */
14
+
15
+ const path = require('path')
16
+ const fs = require('fs')
17
+
18
+ const abi = process.versions.modules
19
+ const bindingDir = path.join(__dirname, '..', 'lib', 'binding', `node-v${abi}-win32-x64`)
20
+ const typelibDir = path.join(bindingDir, 'girepository-1.0')
21
+
22
+ console.log('node:', process.version, '| abi:', abi)
23
+ console.log('binding dir:', bindingDir, '| exists:', fs.existsSync(bindingDir))
24
+ console.log('.node:', fs.existsSync(path.join(bindingDir, 'node_gtk.node')))
25
+ console.log('typelibs:', fs.existsSync(typelibDir))
26
+ if (fs.existsSync(bindingDir)) {
27
+ const dlls = fs.readdirSync(bindingDir).filter(f => f.endsWith('.dll'))
28
+ console.log(`bundled DLLs: ${dlls.length}`)
29
+ }
30
+
31
+ // Require the local package. lib/native.js resolves the prebuilt via
32
+ // node-pre-gyp's binary.find and auto-wires the bundled runtime — NO manual
33
+ // PATH / GI_TYPELIB_PATH / XDG_DATA_DIRS setup here on purpose.
34
+ const gi = require(path.join(__dirname, '..'))
35
+ console.log('OK: require(node-gtk) — prebuilt loaded and runtime auto-wired by native.js')
36
+
37
+ // The full quilx namespace set. Vte (3.91) has no Windows port, so it is
38
+ // expected to be unavailable; everything else must load.
39
+ const REQUIRED = [
40
+ ['GLib', '2.0'], ['GObject', '2.0'], ['Gio', '2.0'],
41
+ ['Pango', '1.0'], ['PangoCairo', '1.0'],
42
+ ['Gdk', '4.0'], ['GdkPixbuf', '2.0'], ['Graphene', '1.0'],
43
+ ['Gtk', '4.0'], ['Adw', '1'], ['GtkSource', '5'],
44
+ ]
45
+ const OPTIONAL = [['Vte', '3.91']]
46
+
47
+ const loaded = {}
48
+ function load(ns, version, optional) {
49
+ try {
50
+ const mod = gi.require(ns, version)
51
+ console.log(`OK: gi.require('${ns}', '${version}')`)
52
+ loaded[ns] = mod
53
+ return mod
54
+ } catch (e) {
55
+ console.log(`${optional ? 'note' : 'FAIL'}: gi.require('${ns}', '${version}') — ${e.message}`)
56
+ if (!optional) throw e
57
+ return null
58
+ }
59
+ }
60
+
61
+ for (const [ns, v] of REQUIRED) load(ns, v, false)
62
+ for (const [ns, v] of OPTIONAL) load(ns, v, true)
63
+
64
+ // Sanity: the typelibs really resolved their symbols.
65
+ if (typeof loaded.GLib.getMonotonicTime !== 'function')
66
+ throw new Error('GLib.getMonotonicTime missing — typelib not really loaded')
67
+ if (typeof loaded.Gtk.Window !== 'function')
68
+ throw new Error('Gtk.Window missing — Gtk4 typelib not really loaded')
69
+ if (typeof loaded.Adw.ApplicationWindow !== 'function')
70
+ throw new Error('Adw.ApplicationWindow missing — libadwaita not really loaded')
71
+
72
+ // Exercise a real GTK4 + Adwaita object graph (needs a display; the runner has one).
73
+ let appOk = false
74
+ try {
75
+ loaded.Gtk.init()
76
+ const win = new loaded.Adw.ApplicationWindow()
77
+ const buffer = new loaded.GtkSource.Buffer()
78
+ const view = new loaded.GtkSource.View()
79
+ view.setBuffer(buffer)
80
+ win.setContent(view)
81
+ console.log('OK: created Adw.ApplicationWindow + GtkSource.View')
82
+ appOk = true
83
+ } catch (e) {
84
+ console.log('note: live widget creation threw (likely no display):', e.message)
85
+ }
86
+ console.log('live GTK4/Adwaita widgets:', appOk ? 'ok' : 'skipped (no display)')
87
+
88
+ // Exercise the bundled gdk-pixbuf image loaders + (portable) loaders.cache by
89
+ // decoding a real PNG. This proves the loader subsystem works from the bundle.
90
+ const PNG_1x1 = Buffer.from(
91
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC',
92
+ 'base64')
93
+ const pngPath = path.join(__dirname, '..', 'smoke-test-pixel.png')
94
+ fs.writeFileSync(pngPath, PNG_1x1)
95
+ try {
96
+ const pixbuf = loaded.GdkPixbuf.Pixbuf.newFromFile(pngPath)
97
+ if (pixbuf.getWidth() !== 1 || pixbuf.getHeight() !== 1)
98
+ throw new Error(`unexpected pixbuf size ${pixbuf.getWidth()}x${pixbuf.getHeight()}`)
99
+ console.log('OK: GdkPixbuf decoded a PNG via the bundled loaders')
100
+ } finally {
101
+ try { fs.unlinkSync(pngPath) } catch (e) { /* ignore */ }
102
+ }
103
+
104
+ console.log('\n=== SMOKE TEST PASSED: GTK4/Adwaita prebuilt usable with NO compiler/MSYS2 ===')
package/src/closure.cc CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  #include "closure.h"
5
5
  #include "error.h"
6
+ #include "gobject.h"
6
7
  #include "macros.h"
7
8
  #include "loop.h"
8
9
  #include "type.h"
@@ -54,9 +55,9 @@ static void LoadGIArgumentFromPointer (GITypeInfo *type_info, GIArgument *arg) {
54
55
  }
55
56
  }
56
57
 
57
- GClosure *Closure::New (Local<Function> function, GICallableInfo* info, guint signalId) {
58
+ GClosure *Closure::New (guint handlerIndex, GICallableInfo* info, guint signalId) {
58
59
  Closure *closure = (Closure *) g_closure_new_simple (sizeof (*closure), GUINT_TO_POINTER(signalId));
59
- closure->persistent.Reset(function);
60
+ closure->handlerIndex = handlerIndex;
60
61
  if (info) {
61
62
  closure->info = g_base_info_ref(info);
62
63
  } else {
@@ -69,11 +70,18 @@ GClosure *Closure::New (Local<Function> function, GICallableInfo* info, guint si
69
70
  }
70
71
 
71
72
  void Closure::Execute(GICallableInfo *info, guint signal_id,
72
- const Nan::Persistent<v8::Function> &persFn,
73
+ GObject *instance, guint handlerIndex,
73
74
  GValue *g_return_value, guint n_param_values,
74
75
  const GValue *param_values) {
75
76
  Nan::HandleScope scope;
76
- auto func = Nan::New<Function>(persFn);
77
+
78
+ /* The handler lives in a JS array on the instance's wrapper (#375). If the
79
+ * wrapper has been collected (the object was dropped from JS) there is
80
+ * nothing to call. */
81
+ Local<Value> handlerValue = GetSignalHandler(instance, handlerIndex);
82
+ if (handlerValue.IsEmpty() || !handlerValue->IsFunction())
83
+ return;
84
+ auto func = handlerValue.As<Function>();
77
85
 
78
86
  GSignalQuery signal_query = { 0, };
79
87
 
@@ -242,16 +250,21 @@ void Closure::Marshal(GClosure *base,
242
250
 
243
251
  auto closure = (Closure *) base;
244
252
  auto signal_id = GPOINTER_TO_UINT(marshal_data);
253
+ auto handlerIndex = closure->handlerIndex;
254
+
255
+ /* param_values[0] is always the instance the signal was emitted on, which
256
+ * is the object whose wrapper holds this handler (#375). */
257
+ GObject *instance = (GObject *) g_value_get_object(&param_values[0]);
245
258
 
246
259
  AsyncCallEnvironment* env = reinterpret_cast<AsyncCallEnvironment *>(AsyncCallEnvironment::asyncHandle.data);
247
260
 
248
261
  if (env->IsSameThread()) {
249
262
  /* Case 1: same thread */
250
- Closure::Execute(closure->info, signal_id, closure->persistent, g_return_value, n_param_values, param_values);
263
+ Closure::Execute(closure->info, signal_id, instance, handlerIndex, g_return_value, n_param_values, param_values);
251
264
  } else {
252
265
  /* Case 2: different thread */
253
266
  env->Call([&]() {
254
- Closure::Execute(closure->info, signal_id, closure->persistent, g_return_value, n_param_values, param_values);
267
+ Closure::Execute(closure->info, signal_id, instance, handlerIndex, g_return_value, n_param_values, param_values);
255
268
  });
256
269
  }
257
270
  }
package/src/closure.h CHANGED
@@ -16,20 +16,24 @@ namespace GNodeJS {
16
16
 
17
17
  struct Closure {
18
18
  GClosure base;
19
- Nan::Persistent<v8::Function> persistent;
19
+ /* The handler function is NOT held here. It lives in a JS array on the
20
+ * wrapper object (reachable only through the wrapper), and we keep just its
21
+ * index. This breaks the wrapper <-> handler reference loop that a strong
22
+ * Nan::Persistent used to create and leak (#375); see
23
+ * doc/signal-handler-gc.md. */
24
+ guint handlerIndex;
20
25
  GICallableInfo* info;
21
26
 
22
27
  ~Closure() {
23
- persistent.Reset();
24
28
  if (info) g_base_info_unref (info);
25
29
  }
26
30
 
27
- static GClosure *New(Local<Function> function,
31
+ static GClosure *New(guint handlerIndex,
28
32
  GICallableInfo* info,
29
33
  guint signalId);
30
34
 
31
35
  static void Execute(GICallableInfo *info, guint signal_id,
32
- const Nan::Persistent<v8::Function> &persFn,
36
+ GObject *instance, guint handlerIndex,
33
37
  GValue *g_return_value, guint n_param_values,
34
38
  const GValue *param_values);
35
39
 
package/src/function.cc CHANGED
@@ -134,6 +134,43 @@ bool IsDestroyNotify (GIBaseInfo *info) {
134
134
  && strcmp(g_base_info_get_namespace(info), "GLib") == 0;
135
135
  }
136
136
 
137
+ /*
138
+ * A transfer-full GObject return value hands node-gtk an *owning* reference. The
139
+ * JS wrapper only needs node-gtk's toggle reference, so that extra reference has
140
+ * to be released — otherwise the refcount never falls back to 1, ToggleNotify
141
+ * never flips the V8 handle to weak, and the GObject (plus its wrapper) leaks for
142
+ * the lifetime of the process. This is the leak reported in #446: objects from
143
+ * GI function returns (`Type.new()`, transfer-full getters, …) were never GC'd,
144
+ * while `new Type()` — which drops its construction ref in GObjectConstructor —
145
+ * was. It is the return-value counterpart of OUT GObject args, which
146
+ * FreeGIArgument already balances; the return value is otherwise never freed on
147
+ * the success path. (The IN counterpart is RefObjectForTransferFullIn, #439.)
148
+ *
149
+ * Must be sampled *before* the value is wrapped: associating the wrapper sinks a
150
+ * floating reference (consuming it), after which a floating incoming ref (nothing
151
+ * extra to drop) is indistinguishable from a real owned one. G_IS_OBJECT excludes
152
+ * GParamSpec (wrapped via ParamSpec::FromGParamSpec) and boxed/fundamental
153
+ * interface types.
154
+ */
155
+ static bool OwnsExtraGObjectReturnRef(GITypeInfo *return_type, GITransfer transfer, gpointer ptr) {
156
+ if (transfer != GI_TRANSFER_EVERYTHING || ptr == NULL)
157
+ return false;
158
+
159
+ if (g_type_info_get_tag(return_type) != GI_TYPE_TAG_INTERFACE)
160
+ return false;
161
+
162
+ GIBaseInfo *iface = g_type_info_get_interface(return_type);
163
+ GIInfoType itype = g_base_info_get_type(iface);
164
+
165
+ bool result =
166
+ (itype == GI_INFO_TYPE_OBJECT || itype == GI_INFO_TYPE_INTERFACE)
167
+ && G_IS_OBJECT(ptr)
168
+ && !g_object_is_floating(ptr);
169
+
170
+ g_base_info_unref(iface);
171
+ return result;
172
+ }
173
+
137
174
 
138
175
  /**
139
176
  * The constructor just stores the GIBaseInfo ref. The rest of the
@@ -549,6 +586,13 @@ Local<Value> FunctionCall (
549
586
 
550
587
  } else if (!use_return_value) {
551
588
 
589
+ // A transfer-full GObject return hands us an extra owning reference on
590
+ // top of the wrapper's toggle ref; drop it once wrapped so the object
591
+ // isn't pinned alive forever (#446). Sampled now, before wrapping sinks
592
+ // any floating reference.
593
+ bool release_return_ref =
594
+ OwnsExtraGObjectReturnRef(&return_type, return_transfer, return_value_stack.v_pointer);
595
+
552
596
  // Value transferred to jsReturnValue
553
597
  jsReturnValue = func->JsReturnValue (
554
598
  info.This(),
@@ -556,6 +600,9 @@ Local<Value> FunctionCall (
556
600
  &return_value_stack,
557
601
  callable_arg_values,
558
602
  return_transfer);
603
+
604
+ if (release_return_ref)
605
+ g_object_unref (return_value_stack.v_pointer);
559
606
  } else {
560
607
 
561
608
  // Value returned in return_value
package/src/gi.cc CHANGED
@@ -405,6 +405,10 @@ NAN_METHOD(GetModuleCache) {
405
405
  info.GetReturnValue().Set(Nan::New<Object>(GNodeJS::moduleCache));
406
406
  }
407
407
 
408
+ NAN_METHOD(SetLazyClassRegister) {
409
+ GNodeJS::ObjectClass::SetLazyClassRegister(info);
410
+ }
411
+
408
412
  NAN_METHOD(RegisterClass) {
409
413
  GNodeJS::ObjectClass::RegisterClass(info);
410
414
  }
@@ -413,6 +417,10 @@ NAN_METHOD(RegisterVFunc) {
413
417
  GNodeJS::ObjectClass::RegisterVFunc(info);
414
418
  }
415
419
 
420
+ NAN_METHOD(CallVFunc) {
421
+ GNodeJS::ObjectClass::CallVFunc(info);
422
+ }
423
+
416
424
  void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
417
425
  GNodeJS::AsyncCallEnvironment::Initialize();
418
426
 
@@ -431,8 +439,10 @@ void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
431
439
  Nan::Export(exports, "StartLoop", StartLoop);
432
440
  Nan::Export(exports, "IsRunningMicrotasks", IsRunningMicrotasks);
433
441
  Nan::Export(exports, "GetLoopStack", GetLoopStack);
442
+ Nan::Export(exports, "SetLazyClassRegister", SetLazyClassRegister);
434
443
  Nan::Export(exports, "RegisterClass", RegisterClass);
435
444
  Nan::Export(exports, "RegisterVFunc", RegisterVFunc);
445
+ Nan::Export(exports, "CallVFunc", CallVFunc);
436
446
 
437
447
  Nan::Set(exports, UTF8("System"), GNodeJS::System::GetModule());
438
448
  Nan::Set(exports, UTF8("Cairo"), GNodeJS::Cairo::GetModule());