From ccfbff9dbb468fc00467059bcad5246036a15d88 Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Wed, 29 Oct 2014 02:32:20 +0200 Subject: [PATCH 1/2] Drafting conventions for C modules and DLLs --- doc/c-module-convention.rst | 114 ++++++++++++++++++++++++++++++++++++ doc/modules.rst | 29 +++++---- 2 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 doc/c-module-convention.rst diff --git a/doc/c-module-convention.rst b/doc/c-module-convention.rst new file mode 100644 index 00000000..4e34d18a --- /dev/null +++ b/doc/c-module-convention.rst @@ -0,0 +1,114 @@ +=========================== +Duktape C module convention +=========================== + +Overview +======== + +This document provides a recommended convention for writing an init function +for a C module. The convention allows a module to be initialized manually +when using static linking, or as part of loading the module from a DLL. +Modules can be initialized either as part of CommonJS module loading or +outside of it. + +The convention is in no way mandatory and it's perfectly fine to use different +module loader conventions. However, modules following this convention will be +easier to share between projects. + +Module init function +==================== + +The init function for a module ``my_module`` should contain an initialization +function of the following form:: + + duk_ret_t dukopen_my_module(duk_context *ctx) { + /* Initialize module in whatever way is most appropriate. + * Called as a Duktape/C function. + * + * Push the module result (e.g. an object with methods) on + * top of the value stack and return 1 to indicate there's + * a return value. Temporary values can be left below the + * return value like in normal Duktape/C functions. + */ + + duk_push_object(ctx); /* module result */ + + duk_put_function_list(ctx, -1, my_module_funcs); + + duk_push_int(ctx, 42); + duk_put_prop_string(ctx, -2, "meaningOfLife"); + + return 1; /* return module value */ + } + +The init function is called as a Duktape/C function. When initializing +the module manually, you should use:: + + duk_push_c_function(ctx, dukopen_my_module, 0 /*nargs*/); + duk_call(ctx, 0); /* or duk_pcall() if you want to catch errors */ + + /* Stack top contains module value */ + +A DLL loader should use the same convention to call the init function +after figuring out the init function name and locating it from the DLL +symbol table. + +DLL name +======== + +When a C module is compiled into a DLL, the DLL filename should include +the module name (``my_module`` in the running example) with any platform +specific prefix and suffix. For example:: + + my_module.so # Linux + my_module.dll # Windows + +A DLL loader should assume that the init function name is ``dukopen_`` +followed by the module name part extracted from the DLL filename (here, +``dukopen_my_module()``). + +Module name +=========== + +To avoid case conversion and special character issues, module names should +have the form:: + + [a-zA-Z_][0-9a-zA-Z_-]* + +This should minimize platform issues. + +Mixed Ecmascript / C modules +============================ + +When a module is being initialized by a CommonJS aware module loader, the +loader should support mixed modules containing both C and Ecmascript code. +For example:: + + my_module.so # C module + my_module.js # Ecmascript module (CommonJS) + +Such a combined module should be loaded as follows: + +* First load the C module normally, yielding a return value RET. + +* If RET is an object, use it to initialize the CommonJS ``exports`` value + before loading the Ecmascript module. The Ecmascript module can then + use whatever symbols the C modules registered, and add further symbols to + the same exports value. + +* If RET is not an object, ignore it and load the Ecmascript module normally. + +**FIXME: at the moment the module loader cannot replace the ``exports`` +value, so it needs copy symbols from RET into ``exports`` one by one.** + +Limitations +=========== + +* This convention may not work on all platforms where Duktape itself ports to. + For instance, a platform might have no DLL support or have filename + restrictions that don't allow DLLs to be named as specified above. + +* The convention is not "CommonJS native": a C module doesn't get an exports + table and cannot load sub-modules (at least relative to its own CommonJS + identifier). This is intentional to keep the C module convention as simple + as possible. diff --git a/doc/modules.rst b/doc/modules.rst index 9a1d17be..4d93aa82 100644 --- a/doc/modules.rst +++ b/doc/modules.rst @@ -19,8 +19,10 @@ built into Duktape: module search function. * C modules, static or DLL-based, can be implemented on top of the module - search function by user code. Because DLL handling is inherently - unportable, there is no built-in DLL support at the moment. + search function by user code. There is no built-in C module support in + the main Duktape library to avoid portability issues for exotic platforms. + However, there is a recommended convention which works on most platforms + and allows both static and DLL loading, see ``c-module-convention.rst``. Using modules from Ecmascript code (require) ============================================ @@ -214,6 +216,21 @@ Duktape doesn't currently support assignment to ``module.exports``. C modules and DLLs ================== +Recommended convention +---------------------- + +``c-module-convention.rst`` describes a recommended convention for defining +an init function for a C module. The convention allows a C module to be +initialized manually when using static linking, or as part of loading the +module from a DLL. + +The recommendation is in no way mandatory and you can easily write a module +loader with your own conventions (see below). However, modules following +the recommended convention will be easier to share between projects. + +Implementing a C module / DLL loader +------------------------------------ + The user provided module search function can be used to implement DLL support. Simply load the DLL based on the module identifier, and call some kind of init function in the DLL to register module symbols into the 'exports' table given @@ -235,10 +252,6 @@ Limitations: (e.g. through a finalizer) is **not** enough because other modules can copy references to individual exported values. -.. note:: At the moment there are no recommended conventions for DLLs or - what a module initialization function should look like. These - conventions will be provided in a future version. See future work. - Background ========== @@ -355,10 +368,6 @@ Several ideas to improve the C module support: * Provide a default DLL loading helper for at least POSIX and Windows. -* Provide suggested module initialization conventions that work both with - static linking and DLLs, and allow Duktape modules to be more easily - exchanged between projects. - Module unloading support ------------------------ From f4f6611d224738c1a13a80c62928d495285b545a Mon Sep 17 00:00:00 2001 From: Sami Vaarala Date: Tue, 11 Nov 2014 21:50:47 +0200 Subject: [PATCH 2/2] Update C module convention doc --- doc/c-module-convention.rst | 78 ++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/doc/c-module-convention.rst b/doc/c-module-convention.rst index 4e34d18a..befeea32 100644 --- a/doc/c-module-convention.rst +++ b/doc/c-module-convention.rst @@ -6,14 +6,13 @@ Overview ======== This document provides a recommended convention for writing an init function -for a C module. The convention allows a module to be initialized manually -when using static linking, or as part of loading the module from a DLL. -Modules can be initialized either as part of CommonJS module loading or -outside of it. +for a C module. The convention allows modules to be used with both static +linking and DLL loading, and either as part of Duktape's CommonJS module +loading or outside of it. -The convention is in no way mandatory and it's perfectly fine to use different -module loader conventions. However, modules following this convention will be -easier to share between projects. +The convention is in no way mandatory and it's perfectly fine to use a +different module loader convention for your project. However, modules +following this convention will be easier to share between projects. Module init function ==================== @@ -25,10 +24,10 @@ function of the following form:: /* Initialize module in whatever way is most appropriate. * Called as a Duktape/C function. * - * Push the module result (e.g. an object with methods) on - * top of the value stack and return 1 to indicate there's - * a return value. Temporary values can be left below the - * return value like in normal Duktape/C functions. + * Push the module result (e.g. an object with exported symbols or + * a function) on top of the value stack and return 1 to indicate + * there's a return value. Temporary values can be left below + * the return value like in normal Duktape/C functions. */ duk_push_object(ctx); /* module result */ @@ -81,34 +80,75 @@ Mixed Ecmascript / C modules ============================ When a module is being initialized by a CommonJS aware module loader, the -loader should support mixed modules containing both C and Ecmascript code. +loader can support mixed modules containing both C and Ecmascript code. For example:: my_module.so # C module my_module.js # Ecmascript module (CommonJS) -Such a combined module should be loaded as follows: +To support mixed modules, a Duktape 1.x ``modSearch()`` function should: + +* First load the C module normally, yielding a return value RET. + +* If RET is an object, copy the own properties of RET into the ``exports`` + value created by Duktape. It should then return the source code of the + Ecmascript module; when executed, further symbols get added to the same + ``exports`` value. + +* If RET is not an object, ignore it and load the Ecmascript module normally. + (Alternatively, write RET to a fixed export name to make it accessible, + e.g. ``exports.value``.) + +The algorithm for Duktape 2.0 is still under design, but at a high level: * First load the C module normally, yielding a return value RET. * If RET is an object, use it to initialize the CommonJS ``exports`` value before loading the Ecmascript module. The Ecmascript module can then use whatever symbols the C modules registered, and add further symbols to - the same exports value. + the same ``exports`` value. * If RET is not an object, ignore it and load the Ecmascript module normally. + (Alternatively, expose RET with a fixed name, e.g. initialize ``exports`` + as ``{ value: RET }``.) + +Duktape 1.x CommonJS notes +========================== -**FIXME: at the moment the module loader cannot replace the ``exports`` -value, so it needs copy symbols from RET into ``exports`` one by one.** +In Duktape 1.x the module ``exports`` value is always an object created by +Duktape, and cannot be replaced by modSearch(). modSearch() can only add +symbols to the pre-created object. This has two implications for +implementing modSearch(): + +- When a C module returns an object, the symbols from the object must be + copied to the pre-created ``exports`` value manually by the modSearch() + function. + +- When a C module returns a non-object, there are several alternatives: + + + The modSearch() function can ignore the module value. This will make + the module value inaccessible (unless the C module init function registered + symbols directly to the global object or similar). + + + The modSearch() function can copy the module value into a fixed name in + the ``exports`` table. Suggested name is ``exports.value``. + +These limitations will most likely be fixed in Duktape 2.0 module loading +rework. Limitations =========== -* This convention may not work on all platforms where Duktape itself ports to. +* The convention may not work on all platforms where Duktape itself ports to. For instance, a platform might have no DLL support or have filename restrictions that don't allow DLLs to be named as specified above. * The convention is not "CommonJS native": a C module doesn't get an exports table and cannot load sub-modules (at least relative to its own CommonJS - identifier). This is intentional to keep the C module convention as simple - as possible. + identifier). This trade-off is intentional to keep the C module convention + as simple as possible. + +* Duktape 1.x CommonJS module loading doesn't support modules with a non-object + return value (i.e. all modules return an ``exports`` table). This module + convention is not limited to object return values so that non-object modules + can be supported in Duktape 2.0.