[UPDATE 02 Feb 2016: While this post discusses Win32 access, here’s an interesting option for UWP access from JXCore that should eventually work with nodejs when the Microsoft PR for Chakra is merged.]
While the node.js ecosystem provides an amazing number of modules covering almost every imaginable use, sometimes you want to work with existing code created in other languages and tool chains. For example, you may have an existing C++ library or perhaps you want to call operating systems APIs not yet available in npm or elsewhere.
When integrating between different language infrastructures you have a choice of which side of the divide to write the required glue code. Glue that provides data marshalling, function calling and event processing. If you want to access code with a C style calling convention then it relatively easy to add code on the C side as node is itself created in C++. This is easily enough done by creating C/C++ addons but often involves reams of boilerplate code. However, if you do choose that option then you’re going to want to use a tool like nan to make your life tolerable. As the nan readme explains:
Thanks to the crazy changes in V8 (and some in Node core), keeping native addons compiling happily across versions, particularly 0.10 to 0.12 to 4.0, is a minor nightmare. The goal of this project is to store all logic necessary to develop native Node.js add-ons without having to inspect
NODE_MODULE_VERSION
and get yourself into a macro-tangle.
If you want to work on the Javascript side of the divide then ref by provides all the facilities you need for marshalling to/from the C world. It does this by extending node’s Buffer class to provide a type system and facilities for:
- Getting the memory address of a Buffer
- Checking the endianness of the processor
- Checking if a Buffer represents the NULL pointer
- Reading and writing “pointers” with Buffers
- Reading and writing C Strings (NULL-terminated)
- Reading and writing JavaScript Object references
- Reading and writing int64_t and uint64_t values
- A “type” convention to define the contents of a Buffer
Further related ref modules support javascript representations of other C/C++ types including arrays, structures and unions.
Building on ref’s facilities is node-fii which provides a foreign function interface (ffi) for loading and calling functions exported by dynamic libraries (dlls on Windows). It is also possible to call functions in the current process, ideal for functions in static libraries.
While this eliminates large amounts of C boilerplate, it does have a significant calling overhead. Accordingly you are unlikely to want to use it for functions called in a tight loop or otherwise time sensitive applications.
Here’s a simple example from the lib-ffi documentation for wrapping libm’s ceil() function which takes a double parameter and returns a double result and also the static atoi() which takes a string and returns an int.
1
2
3
4
5
6
7
8
9
10
11
12
|
var ffi = require('ffi');
var libm = ffi.Library('libm', {
'ceil': [ 'double', [ 'double' ] ]
});
libm.ceil(1.5); // 2
// You can also access just functions in the current process by passing a null
var current = ffi.Library(null, {
'atoi': [ 'int', [ 'string' ] ]
});
current.atoi('1234'); // 1234
|
A more complex example can be seen is some code I wrote for the GPII system for automatic personalisation from preferences. This is perhaps a slightly unusual application of Node.js as it runs on a Windows device in order to launch and configure various Windows’ settings and assistive technology programmes.
The actual code provides a function GetDisplayResolution() that calls the Windows API EnumDisplaySettings() which returns into the fairly complex DEVICEMODE structure. Note that the DEVMODE structure includes nested unions of structures and while the ref modules support these I decided to flattened out the declaration (after testing my assumptions about packing and padding).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
var ffi = require("ffi");
var ref = require("ref");
var Struct = require("ref-struct");
var arrayType = require("ref-array");
/**
* A map between Windows and C types.
* https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751%28v=vs.85%29.aspx
*/
windows.types = {
"BOOL": "int",
"INT": "int",
"UINT": "uint",
"ULONG": "ulong",
"DWORD": "ulong",
"HKL": "void*",
"ULONG_PTR": "ulong",
"LONG": "long",
"HANDLE": "uint32",
"WORD": "uint16",
"TCHAR": "uint16" // assuming unicode. (ASCII is char, UNICODE is WCHAR -> wchar_t -> unsigned short === UINT16 === uint16
};
var t = windows.types;
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd183565(v=vs.85).aspx
var CCHDEVICENAME = 32;
var CCHFORMNAME = 32;
windows.DEVMODEW = new Struct([
[arrayType(t.TCHAR, CCHDEVICENAME), "dmDeviceName"],
[t.WORD, "dmSpecVersion"],
[t.WORD, "dmDriverVersion"],
[t.WORD, "dmSize"],
[t.WORD, "dmDriverExtra"],
[t.DWORD, "dmFields"],
//union { // TODO there is a ref-union npm module - but this technique is OK for now
// struct {
["short", "dmOrientation"],
["short", "dmPaperSize"],
["short", "dmPaperLength"],
["short", "dmPaperWidth"],
["short", "dmScale"],
["short", "dmCopies"],
["short", "dmDefaultSource"],
["short", "dmPrintQuality"],
// };
// struct {
// POINTL dmPosition;
// DWORD dmDisplayOrientation;
// DWORD dmDisplayFixedOutput;
// };
//};
["short", "dmColor"],
["short", "dmDuplex"],
["short", "dmYResolution"],
["short", "dmTTOption"],
["short", "dmCollate"],
[arrayType(t.TCHAR, CCHFORMNAME), "dmFormName"],
[t.WORD, "dmLogPixels"],
[t.DWORD, "dmBitsPerPel"],
[t.DWORD, "dmPelsWidth"],
[t.DWORD, "dmPelsHeight"],
//union {
[t.DWORD,"dmDisplayFlags"],
// DWORD dmNup;
//};
[t.DWORD, "dmDisplayFrequency"],
//#if (WINVER >= 0x0400)
[t.DWORD, "dmICMMethod"],
[t.DWORD, "dmICMIntent"],
[t.DWORD, "dmMediaType"],
[t.DWORD, "dmDitherType"],
[t.DWORD, "dmReserved1"],
[t.DWORD, "dmReserved2"],
//#if (WINVER >= 0x0500) || (_WIN32_WINNT >= 0x0400)
[t.DWORD, "dmPanningWidth"],
[t.DWORD, "dmPanningHeight"]
//#endif
//#endif
]);
windows.user32 = ffi.Library("user32", {
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd162611(v=vs.85).aspx
// LPCWSTR, DWORD, DEVMODE*
"EnumDisplaySettingsW": [
t.BOOL, ["pointer", t.DWORD, "pointer"]
]
});
/**
* Gets the current screen resolution
*
* @return {Object) The width and height of the screen.
*/
windows.getScreenResolution = function () {
var dm = new windows.DEVMODEW();
dm.ref().fill(0);
dm.dmSize = windows.DEVMODEW.size;
if (c.FALSE != windows.user32.EnumDisplaySettingsW(ref.NULL, c.ENUM_CURRENT_SETTINGS, dm.ref()))
{
// note for unknown reason on win 10 the returned dmSize is 188 not expected 220
return { width: dm.dmPelsWidth, height: dm.dmPelsHeight };
}
return { width: 0, height: 0 };
}
|
more details on Scot Frees’ blog http://blog.scottfrees.com/getting-your-c-to-the-web-with-node-js
Hi,
Nice article. I wanted to try getting current caret position, so came to your blog. I tried the following, but it doen’t work.
var ffi = require(‘ffi’);
var ref = require(‘ref’);
var voidPtr = ref.refType(ref.types.void);
var user32 = ffi.Library(‘user32.dll’, {
GetCaretPos:[‘bool’,[voidPtr]]
});
var pbuf = new Buffer(2);
caretpos = user32.GetCaretPos(pbuf);
var cpos =(new Uint8Array(pbuf));
console.log(“>”,cpos ); //Doesn’t work **> Uint8Array [ 0, 0, 0, 0 ]**
Hi,
Great article. I tried your example to GetDisplayResolution but, this error occurred:
if (c.FALSE !== windows.EnumDisplaySettingsW(ref.NULL, c.ENUM_CURRENT_SETTINGS, dm.ref()))
^
ReferenceError: c is not defined
at Object.windows.getScreenResolution
at Function.Module.runMain (module.js:605:10)
at startup (bootstrap_node.js:158:16)
at bootstrap_node.js:575:3
Where is c defined? Can you help me?
Thanks in advance.
Thanks for reporting that type-o I’ll fix it.