├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── NodeWScript.js ├── README.md ├── activex.js ├── binding.gyp ├── data └── test.xltm ├── examples ├── ado.js ├── event.js └── wbem.js ├── include └── node_activex.h ├── index.d.ts ├── index.js ├── lib_binding.gyp ├── package-lock.json ├── package.json ├── src ├── disp.cpp ├── disp.h ├── main.cpp ├── stdafx.h ├── utils.cpp └── utils.h └── test ├── ado.js ├── excel.js ├── variant.js ├── wscript.js └── wscript ├── README.md ├── arguments.js ├── enumerator.js ├── mixed.js ├── nodewscript_test_cli.cmd └── wmi.js /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-nodejs 2 | 3 | name: ci 4 | on: [push, pull_request] 5 | jobs: 6 | ci: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | node-version: ['18.x', '20.x', '22.x', '23.x'] 11 | os: [windows-latest] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - run: npm ci 19 | - run: npm run build --if-present 20 | # https://community.chocolatey.org/packages?q=Excel 21 | - name: "Installing Office 365 takes about 5 minutes..." 22 | run: choco install office365proplus 23 | - run: | 24 | npm install -g mocha 25 | # mocha --expose-gc test # Three tests will fail! 26 | - run: npm test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | node_modules 4 | build 5 | npm-debug.log 6 | test.js 7 | /data/PERSONS.DBF 8 | /debug* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Yuri Dursin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NodeWScript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var g_winax = require('./activex'); 3 | var path = require('path'); 4 | 5 | if(process.argv.length<3) 6 | { 7 | console.error("Missing argument. Usage:\nnodewscript SomeJS.js ") 8 | } 9 | 10 | // For the case we want to use 'require' or 'module' - we need to make it available through global. 11 | global.require = require; 12 | global.exports = exports; 13 | global.module = module; 14 | 15 | global.SeSIncludeFile = function (includePath, isLocal, ctx) { 16 | 17 | var vm = require("vm"); 18 | 19 | var fs = require("fs"); 20 | var path = require("path"); 21 | 22 | var absolutePath = path.resolve(includePath); 23 | var data = fs.readFileSync(absolutePath); 24 | var script = new vm.Script(data, { filename: absolutePath, displayErrors: true }); 25 | if (isLocal && ctx) { 26 | if (!ctx.__runctx__) ctx.__runctx__ = vm.createContext(ctx); 27 | script.runInContext(ctx.__runctx__); 28 | } else { 29 | script.runInThisContext(); 30 | } 31 | 32 | return ''; 33 | }; 34 | 35 | global.GetObject = function GetObject(strMoniker) { 36 | return new g_winax.Object(strMoniker, {getobject:true}); 37 | } 38 | 39 | var WinAXActiveXObject = ActiveXObject; 40 | 41 | function ActiveXObjectCreate(progId, pfx) 42 | { 43 | var res = new WinAXActiveXObject(progId); 44 | if(progId=="SeSHelper") 45 | { 46 | // SeSHelper implements Include that we want to override 47 | res = new Proxy({helper:res}, { 48 | get(target, propKey, receiver) { 49 | return function (...args) { 50 | var helper = target.helper; 51 | var ovr = 52 | { 53 | IncludeLocal(/**Object*/fName, ctx) /**Object*/ 54 | { 55 | return this.Include(fName, true, ctx); 56 | }, 57 | 58 | Include(/**Object*/fName, local, ctx) /**Object*/ 59 | { 60 | var includePath = helper.ResolvePath(fName); 61 | if(!includePath) 62 | { 63 | var err = "Error trying to include file. File not found:"+fName; 64 | return "console.error("+JSON.stringify(err)+")"; 65 | } 66 | 67 | if(!local) 68 | { 69 | helper.SetAlreadyIncluded(includePath); 70 | } 71 | 72 | return global.SeSIncludeFile(includePath, local, ctx); 73 | 74 | }, 75 | 76 | IncludeOnce(/**Object*/fName) /**Object*/ 77 | { 78 | if(!helper.IsAlreadyIncluded(fName)) 79 | { 80 | return this.Include(fName); 81 | } 82 | return ""; 83 | } 84 | } 85 | 86 | if(propKey in ovr) 87 | { 88 | return ovr[propKey](...args); 89 | } else { 90 | let result = target.helper[propKey](...args); 91 | return result; 92 | } 93 | 94 | }; 95 | } 96 | }); 97 | } 98 | 99 | if(res&&pfx) 100 | { 101 | WScript.ConnectObject(res, pfx); 102 | } 103 | 104 | return res; 105 | } 106 | 107 | global.ActiveXObject=new Proxy(ActiveXObject, { 108 | construct(target,args) 109 | { 110 | //console.log("Creating ActiveXObject: "+args); 111 | return ActiveXObjectCreate(...args); 112 | } 113 | }); 114 | 115 | global.Enumerator = function (arr) { 116 | this.arr = arr; 117 | this.enum = arr._NewEnum; 118 | this.nextItem = null; 119 | this.atEnd = function () { return this.nextItem===undefined; } 120 | this.moveNext = function () { this.nextItem = this.enum.Next(1); return this.atEnd(); } 121 | this.moveFirst = function () { this.enum.Reset(); this.nextItem=null; return this.moveNext(); } 122 | this.item = function () { return this.nextItem; } 123 | 124 | this.moveNext(); 125 | } 126 | 127 | // See interface here 128 | // https://www.devguru.com/content/technologies/wsh/objects-wscript.html 129 | 130 | var WScript = { 131 | // Properties 132 | Application : null, 133 | BuildVersion : "6.0", 134 | FullName : null, 135 | Interactive : true, 136 | Name : "Windows / Node Script Host", 137 | Path : __filename, 138 | ScriptFullName : null, 139 | ScriptName : null, 140 | StdErr: { 141 | Write(txt) { console.log(txt); } 142 | }, 143 | StdIn: { 144 | ReadLine() { 145 | var readline = require('readline'); 146 | var gotLine = false; 147 | var line = undefined; 148 | 149 | var rl = readline.createInterface({ 150 | input: process.stdin, 151 | output: process.stdout, 152 | terminal: false 153 | }); 154 | 155 | rl.on('line', function (cmd) { 156 | line = cmd; 157 | rl.close(); 158 | }); 159 | while(!gotLine) 160 | { 161 | WScript.Sleep(10); 162 | } 163 | return line 164 | } 165 | }, 166 | StdOut: { 167 | Write(txt) { console.err(txt); } 168 | }, 169 | Timeout : -1, 170 | Version : 'NODE.WIN32', 171 | 172 | // Methods 173 | 174 | Echo : console.log, 175 | 176 | __Connected : [], 177 | 178 | ConnectObject(obj, pfx) 179 | { 180 | var connectionPoints = g_winax.getConnectionPoints(obj); 181 | var connectionPoint = connectionPoints[0]; 182 | var allMethods = connectionPoint.getMethods(); 183 | 184 | var advobj={}; 185 | var found = false; 186 | for(var i=0;i0) 232 | { 233 | WScript.DisconnectObject(this.__Connected[0].obj); 234 | } 235 | WScript.Sleep(60); 236 | process.exit(exitCode); 237 | }, 238 | 239 | Sleep (ms) { 240 | var begin = (new Date()).valueOf(); 241 | MessageLoop(); 242 | var dt = (new Date()).valueOf()-begin; 243 | 244 | while(dt{WScript.Quit(0);},timeOut*1000) 300 | } else if(arg.toLowerCase()=="//x"||arg.toLowerCase()=="//d") { 301 | this.__Debug = true; 302 | } 303 | } else { 304 | this.__Args.push(arg); 305 | } 306 | } 307 | } 308 | 309 | // The trick starts here 310 | // We want WScript.Arguments to be accessible in all of the following ways: 311 | // WScript.Arguments(i) 312 | // WScript.Arguments.item(i) 313 | // WScript.Arguments.length 314 | // Since it is not directly possible, we use a number of tricks. 315 | // Function.length is a number of expected arguments 316 | // And we make this 'fake' number 317 | var argArr=[]; 318 | for(var ai=0;ai") 351 | } 352 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Name 2 | 3 | Windows C++ Node.JS addon, that implements COM IDispatch object wrapper, analog ActiveXObject on cscript.exe 4 | 5 | # Features 6 | 7 | * Using *ITypeInfo* for conflict resolution between property and method 8 | (for example !rs.EOF not working without type information, becose need define EOF as an object) 9 | 10 | * Using optional parameters on constructor call 11 | ``` js 12 | var con = new ActiveXObject("Object.Name", { 13 | activate: false, // Allow activate existance object instance (through CoGetObject), false by default 14 | getobject: false, // Allow using name of the file in the ROT (through GetAccessibleObject), false by default 15 | type: true // Allow using type information, true by default 16 | }); 17 | ``` 18 | 19 | * Create COM object from JS object and may be send as argument (for example send to Excel procedure) 20 | ``` js 21 | var com_obj = new ActiveXObject({ 22 | text: test_value, 23 | obj: { params: test_value }, 24 | arr: [ test_value, test_value, test_value ], 25 | func: function(v) { return v*2; } 26 | }); 27 | ``` 28 | 29 | * Additional COM variant types support: 30 | - **int** - VT_INT 31 | - **uint** - VT_UINT 32 | - **int8**, **char** - VT_I1 33 | - **uint8**, **uchar**, **byte** - VT_UI1 34 | - **int16**, **short** - VT_I2 35 | - **uint16**, **ushort** - VT_UI2 36 | - **int32** - VT_I4 37 | - **uint32** - VT_UI4 38 | - **int64**, **long** - VT_I8 39 | - **uint64**, **ulong** - VT_UI8 40 | - **currency** - VT_CY 41 | - **float** - VT_R4 42 | - **double** - VT_R8 43 | - **string** - VT_BSTR 44 | - **date** - VT_DATE 45 | - **decimal** - VT_DECIMAL 46 | - **variant** - VT_VARIANT 47 | - **null** - VT_NULL 48 | - **empty** - VT_EMPTY 49 | - **byref** - VT_BYREF or use prefix **'p'** to indicate reference to the current type 50 | 51 | ``` js 52 | var winax = require('winax'); 53 | var Variant = winax.Variant; 54 | 55 | // create variant instance 56 | var v_short = new Variant(17, 'short'); 57 | var v_short_byref = new Variant(17, 'pshort'); 58 | var v_int_byref = new Variant(17, 'byref'); 59 | var v_byref = new Variant(v_short, 'byref'); 60 | 61 | // create variant arrays 62 | var v_array_of_variant = new Variant([1,'2',3]); 63 | var v_array_of_short = new Variant([1,'2',3], 'short'); 64 | var v_array_of_string = new Variant([1,'2',3], 'string'); 65 | 66 | // change variant content 67 | var v_test = new Variant(); 68 | v_test.assign(17); 69 | v_test.cast('string'); 70 | v_test.clear(); 71 | 72 | // also may be used cast function 73 | var v_short_from_cast = winax.cast(17, 'short'); 74 | ``` 75 | 76 | * Additional dignostic propeties: 77 | - **__id** - dispatch identity, for exmplae: ADODB.Connection.@Execute.Fields 78 | - **__value** - value of dispatch object, equiles valueOf() 79 | - **__type** - array all member items with their properties 80 | - **__methods** - list member mathods by names (ITypeInfo::GetFuncDesc) 81 | - **__vars** - list member variables by names (ITypeInfo::GetVarDesc) 82 | 83 | * Full WScript Emulation Support through nodewscript 84 | - **ActiveXObject** type 85 | - **WScript.CreateObject** 86 | - **WScript.ConnectObject** 87 | - **WScript.DisconnectObject** 88 | - **WScript.Sleep** 89 | - **WScript.Arguments** 90 | - **WScript.Version** 91 | - **GetObject** 92 | - **Enumerator** 93 | 94 | # Usage example 95 | 96 | Install package throw NPM (see below **Building** for details) 97 | ``` 98 | npm install winax 99 | npm install winax --msvs_version=2015 100 | npm install winax --msvs_version=2017 101 | ``` 102 | 103 | Create ADO Connection throw global function 104 | ``` js 105 | require('winax'); 106 | var con = new ActiveXObject('ADODB.Connection'); 107 | ``` 108 | Or using Object prototype 109 | ``` js 110 | var winax = require('winax'); 111 | var con = new winax.Object('ADODB.Connection'); 112 | ``` 113 | Open connection and create simple table 114 | ``` js 115 | con.Open('Provider=Microsoft.ACE.OLEDB.12.0;Data Source=c:\tmp;Extended Properties="DBASE IV;"', '', ''); 116 | con.Execute("Create Table persons.dbf (Name char(50), City char(50), Phone char(20), Zip decimal(5))"); 117 | con.Execute("Insert into persons.dbf Values('John', 'London','123-45-67','14589')"); 118 | con.Execute("Insert into persons.dbf Values('Andrew', 'Paris','333-44-55','38215')"); 119 | con.Execute("Insert into persons.dbf Values('Romeo', 'Rom','222-33-44','54323')"); 120 | ``` 121 | Select query and return RecordSet object 122 | ``` js 123 | var rs = con.Execute("Select * from persons.dbf"); 124 | var reccnt = rs.RecordCount; 125 | ``` 126 | Inspect RecordSet fields 127 | ``` js 128 | var fields = rs.Fields; 129 | var fldcnt = fields.Count; 130 | ``` 131 | Process records 132 | ``` js 133 | rs.MoveFirst(); 134 | while (!rs.EOF) { 135 | var name = fields("Name").value; 136 | var town = fields("City").value; 137 | var phone = fields("Phone").value; 138 | var zip = fields("Zip").value; 139 | console.log("> Person: " + name + " from " + town + " phone: " + phone + " zip: " + zip); 140 | rs.MoveNext(); 141 | } 142 | ``` 143 | Or using fields by index 144 | ``` js 145 | rs.MoveFirst(); 146 | while (rs.EOF != false) { 147 | var name = fields[0].value; 148 | var town = fields[1].value; 149 | var phone = fields[2].value; 150 | var zip = fields[3].value; 151 | console.log("> Person: " + name + " from " + town + " phone: " + phone + " zip: " + zip); 152 | rs.MoveNext(); 153 | } 154 | ``` 155 | Release COM objects (but other temporary objects may be keep references too) 156 | ``` js 157 | winax.release(con, rs, fields) 158 | ``` 159 | Working with Excel ranges using two dimension arrays (from 1.18.0 version) 160 | * The second dimension is only deduced from the first array. 161 | * If data is missing at the time of SafeArrayPutElement, VT_EMPTY is used. 162 | * Best way to explicitly pass VT_EMPTY is to use null 163 | * If the SAFEARRAY dims are smaller than those of the range, the missing cells are emptied. 164 | * Exception to 4! Excel may process the SAFEARRAY as it does when processing a copy/paste operation 165 | ``` js 166 | var excel = new winax.Object("Excel.Application", { activate: true }); 167 | var wbk = excel.Workbooks.Add(template_filename); 168 | var wsh = wbk.Worksheets.Item(1); 169 | wsh.Range("C3:E4").Value = [ ["C3", "D3", "E3" ], ["C4", "D4", "E4" ] ]; 170 | wsh.Range("C3:E4").Value = [ ["C3", "D3", "E3" ], "C4" ]; // will let D4 and E4 empty 171 | wsh.Range("C3:E4").Value = [ [null, "D3", "E3" ], "C4" ]; // will let C3, D4 and E4 empty 172 | wsh.Range("C3:F4").Value = [ [100], [200] ]; // will duplicate the two rows in colums C, D, E, and F 173 | wsh.Range("C3:F4").Value = [ [100, 200, 300, 400] ]; // will duplicate the for cols in rows 3 and 4 174 | wsh.Range("C3:F4").Value = [ [100, 200] ]; // Will correctly duplicate the first two cols, but col E and F with contains "#N/A" 175 | const data = wsh.Range("C3:E4").Value.valueOf(); 176 | console.log("Cell E4 value is", data[1][2]); 177 | ``` 178 | 179 | ## TypeScript 180 | Your `tsconfig.json` needs at least the following configuration: 181 | 182 | 1. [`moduleResolution`](https://www.typescriptlang.org/tsconfig/#moduleResolution) needs to be something other than `classic`. Depending on your `target` and `module` this might be the default. For example: 183 | ```json 184 | { 185 | "compilerOptions": { 186 | "target": "esnext", 187 | "module": "nodenext" 188 | } 189 | } 190 | ``` 191 | 192 | 2. [`lib`](https://www.typescriptlang.org/tsconfig/#lib) needs to _**exclude**_ `ScriptHost`. It contains a conflicting global `ActiveXObject` type and is included by default. For example: 193 | ```json 194 | { 195 | "compilerOptions": { 196 | "lib": [ "ESNext" ] 197 | } 198 | } 199 | ``` 200 | 201 | # Tutorial and Examples 202 | 203 | - [examples/ado.js](https://github.com/durs/node-activex/blob/master/examples/ado.js) 204 | 205 | # Usage as a library 206 | 207 | The repo allows to re-use some of its code as a library for your own native node addon. 208 | 209 | To include it, install it as a normal node-dependency and then add it 210 | to the `"dependencies"` section of your `binding.gyp` file like this: 211 | ``` 212 | "dependencies": [ 213 | " 220 | ``` 221 | 222 | This makes functions like `Variant2Value` or `Value2Variant` that translate between COM VARIANT and node types 223 | available in your code. 224 | Note that providing this library functionality is not the core target of this repo however, 225 | so importing it eg. currently declares 226 | all methods in the global namespace and opens the namespaces `v8` and `node`. 227 | 228 | Check the source code [`src/disp.h`](src/disp.h) and [`src/utils.h`](src/utils.h) for details. 229 | 230 | # Building 231 | 232 | This project uses Visual C++ 2013 (or later versions then support C++11 standard). 233 | Bulding also requires node-gyp and python 2.6 (or later) to be installed. 234 | Supported NodeJS Versions (x86 or x64): 10, 11, 12, 13, 14, 15 235 | You can do this with npm: 236 | ``` 237 | npm install --global --production windows-build-tools 238 | ``` 239 | To obtain and build use console commands: 240 | ``` 241 | git clone git://github.com/durs/node-axtivex.git 242 | cd node-activex 243 | npm install 244 | ``` 245 | or debug version 246 | ``` 247 | npm install --debug 248 | ``` 249 | or using node-gyp directly 250 | ``` 251 | node-gyp configure 252 | node-gyp build 253 | ``` 254 | 255 | For Electron users, need rebuild with a different V8 version: 256 | ``` 257 | npm rebuild winax --runtime=electron --target=12.22.0 --dist-url=https://electronjs.org/headers --build-from-source 258 | ``` 259 | Change --target value to your electron version. 260 | See also Electron Documentation: [Using Native Node Modules](https://electron.atom.io/docs/tutorial/using-native-node-modules/). 261 | 262 | # WScript 263 | 264 | [WScript](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/wscript) is designed as a runtime, so WSH scripts should be executed from command line. WScript emulation mode allows one to use standard **WScript** features and mix them with nodejs and ES6 parts. 265 | 266 | There is one significant difference between WScript and NodeJS in general. NodeJS is designed to be [non-blocking](https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/) and execution is done when last statement is done AND all pending promises and callbacks are resolved. WScript runtime is linear. It is done when last statement is executed. 267 | 268 | So NodeJS is more convenient for writing things like async web services. Also with NodeJS you get access to invaluable collection of **npm** modules. 269 | 270 | WScript has impressive collection of Windows-specific APIs available through ActiveX (such as `WScript.Shell`, `FileSystemObject` etc). Also it supports application events to implement two way communication with external apps and processes. 271 | 272 | This module allows taking the best from the two worlds - you may now use **npm** modules in your WScript scripts. Or, if you have bunch of legacy JScript sources, you may now execute them using this **NodeJS**-based runtime. 273 | 274 | If you install this package with -g key, you will have `nodewscript` command. 275 | 276 | Usage: 277 | ``` 278 | nodewscript [options] 279 | ``` 280 | 281 | Its direct analog in the windows scripting host is `cscript.exe` (it it outputs to the console, and `WScript.Echo` writes a line instead of showing a popup window). 282 | 283 | Where `Filename.js` is a file designed for windows scripting host. 284 | 285 | ## WScript Limitations 286 | 287 | ### Implicit Properties 288 | 289 | This is a key drawback of v8 engine compared to MS. Consider an example: 290 | 291 | ```javascript 292 | var a = WScript.CreateObject(...) 293 | if( a.Prop ) 294 | { 295 | // If 'Prop' is a dynamic property (i.e. it is not defined in TypeInfo and 296 | // not marked explicitly as a property, then execution never gets here. Even if a.Prop is null or false. 297 | } 298 | ``` 299 | 300 | ### Function Setters 301 | 302 | ```javascript 303 | var WshShell = new ActiveXObject("WScript.Shell"); 304 | var processEnv = WshShell.Environment("PROCESS"); 305 | processEnv("ENV_VAR") = "CUSTOM_VALUE"; // This syntax is valid for JScript, but will throw syntax error on v8 306 | ``` 307 | ### Events & Sleep 308 | 309 | Default WScript engine checks for events every time you do some sync operation or Sleep. In this implementation we check for events when `WScript.Sleep` is executed. 310 | 311 | ### 32 vs 64 Bit 312 | 313 | It is using the same bitness as installed version of the nodejs. So if your JScript files are designed for 32 bit, make sure to have nodejs x86 version installed before installing the **winax**. 314 | 315 | 316 | # Tests 317 | 318 | [mocha](https://github.com/visionmedia/mocha) is required to run unit tests. 319 | ``` 320 | npm install -g mocha 321 | mocha --expose-gc test 322 | ``` 323 | 324 | # Contributors 325 | 326 | * [durs](https://github.com/durs) 327 | * [somanuell](https://github.com/somanuell) 328 | * [Daniel-Userlane](https://github.com/Daniel-Userlane) 329 | * [alexeygrinevich](https://github.com/alexeygrinevich) 330 | -------------------------------------------------------------------------------- /activex.js: -------------------------------------------------------------------------------- 1 | var ActiveX = module.exports = require('./build/Release/node_activex.node'); 2 | 3 | global.ActiveXObject = function(id, opt) { 4 | return new ActiveX.Object(id, opt); 5 | }; 6 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'node_activex', 5 | 'conditions': [ 6 | ['OS=="win"', { 7 | 'sources': [ 8 | 'src/main.cpp', 9 | 'src/utils.cpp', 10 | 'src/disp.cpp' 11 | ], 12 | 'libraries': [], 13 | 'dependencies': [] 14 | }] 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /data/test.xltm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/durs/node-activex/d7a13a0b030af1783779be3b39f4672d1f26569c/data/test.xltm -------------------------------------------------------------------------------- /examples/ado.js: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------------------------- 2 | // Project: node-activex 3 | // Author: Yuri Dursin 4 | // Description: Example of using ActiveX addon with ADO 5 | //------------------------------------------------------------------------------------------------------- 6 | 7 | //require('winax'); 8 | require('../activex'); 9 | 10 | var path = require('path'); 11 | var data_path = path.join(__dirname, '../data/'); 12 | var filename = "persons.dbf"; 13 | var constr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + data_path + ";Extended Properties=\"DBASE IV;\""; 14 | 15 | console.log("==> Preapre directory and delete DBF file on exists"); 16 | var fso = new ActiveXObject("Scripting.FileSystemObject"); 17 | if (!fso.FolderExists(data_path)) fso.CreateFolder(data_path); 18 | if (fso.FileExists(data_path + filename)) fso.DeleteFile(data_path + filename); 19 | 20 | console.log("==> Open connection"); 21 | var con = new ActiveXObject("ADODB.Connection"); 22 | console.log("ADO version: " + con.Version); 23 | con.Open(constr, "", ""); 24 | 25 | console.log("==> Create new DBF file") 26 | con.Execute("create Table " + filename + " (Name char(50), City char(50), Phone char(20), Zip decimal(5))"); 27 | 28 | console.log("==> Insert records to DBF") 29 | con.Execute("insert into " + filename + " values('John', 'London','123-45-67','14589')"); 30 | con.Execute("insert into " + filename + " values('Andrew', 'Paris','333-44-55','38215')"); 31 | con.Execute("insert into " + filename + " values('Romeo', 'Rom','222-33-44','54323')"); 32 | 33 | console.log("==> Select records from DBF") 34 | var rs = con.Execute("Select * from " + filename); 35 | var fields = rs.Fields; 36 | console.log("Result field count: " + fields.Count); 37 | console.log("Result record count: " + rs.RecordCount); 38 | 39 | rs.MoveFirst(); 40 | while (!rs.EOF) { 41 | // Access as property by string key 42 | var name = fields["Name"].Value; 43 | 44 | // Access as method with string argument 45 | var town = fields("City").value; 46 | 47 | // Access as indexed array 48 | var phone = fields[2].value; 49 | var zip = fields[3].value; 50 | 51 | console.log("> Person: "+name+" from " + town + " phone: " + phone + " zip: " + zip); 52 | rs.MoveNext(); 53 | } 54 | 55 | con.Close(); 56 | -------------------------------------------------------------------------------- /examples/event.js: -------------------------------------------------------------------------------- 1 | var winax = require('..'); 2 | var interval = setInterval(function () { 3 | winax.peekAndDispatchMessages(); // allows ActiveX event to be dispatched 4 | }, 50); 5 | 6 | var application = new ActiveXObject('Excel.Application'); 7 | 8 | var connectionPoints = winax.getConnectionPoints(application); 9 | var connectionPoint = connectionPoints[0]; 10 | 11 | // Excel Application events: https://docs.microsoft.com/en-us/office/vba/api/excel.application(object)#events 12 | connectionPoint.advise({ 13 | WindowActivate: function (workbook, win) { 14 | console.log('WindowActivate event triggerred'); 15 | }, 16 | WindowDeactivate: function (workbook, win) { 17 | console.log('WindowDeactivate event triggerred'); 18 | }, 19 | WindowResize: function (workbook, win) { 20 | console.log('WindowResize event triggerred'); 21 | }, 22 | NewWorkbook: function (workbook) { 23 | console.log('NewWorkbook event triggerred'); 24 | var worksheet = workbook.Worksheets(1); 25 | if (worksheet) { 26 | var cell = worksheet.Cells(1, 1); 27 | if (cell) { 28 | cell.value = "Hello world"; 29 | } 30 | } 31 | }, 32 | SheetActivate: function (worksheet) { 33 | console.log('SheetActivate event triggerred'); 34 | }, 35 | SheetDeactivate: function (worksheet) { 36 | console.log('SheetDeactivate event triggerred'); 37 | }, 38 | SheetSelectionChange: function (worksheet, range) { 39 | //var worksheet1 = range; // it's a bug now: args in reversal order 40 | //var range = worksheet; 41 | console.log('SheetSelectionChange event triggerred', range.Address); 42 | }, 43 | SheetChange: function (worksheet, range) { 44 | //var worksheet1 = range; // it's a bug now: args in reversal order 45 | //var range = worksheet; 46 | console.log('SheetChange event triggerred', range.Address, range.Value); 47 | }, 48 | }); 49 | 50 | application.Visible = true; 51 | console.log('Excel application has been open.'); 52 | console.log('--> Please create a new workbook to trigger event "NewWorkbook"'); 53 | 54 | setTimeout(function () { 55 | clearInterval(interval); 56 | application.Quit(); 57 | winax.release(application); 58 | }, 1000 * 60); 59 | 60 | -------------------------------------------------------------------------------- /examples/wbem.js: -------------------------------------------------------------------------------- 1 | var ActiveX = require('../activex'); 2 | var conn = new ActiveX.Object('WbemScripting.SWbemLocator'); 3 | var svr = conn.ConnectServer('.', '\\root\\cimv2'); 4 | const resp = svr.ExecQuery('SELECT ProcessorId FROM Win32_Processor'); 5 | for (let i = 0; i < resp.Count; i += 1) { 6 | const properties = resp.ItemIndex(i).Properties_; 7 | let count = properties.Count; 8 | const propEnum = properties._NewEnum; 9 | while (count--) { 10 | const prop = propEnum.Next(); 11 | if (prop) console.log(prop.Name + '=' + prop.Value); 12 | } 13 | } -------------------------------------------------------------------------------- /include/node_activex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Include all the relevant definitions for use of the library code in a dependency. 4 | // Note that this does use the namespaces v8 and node in the current configuration 5 | 6 | #include "../src/stdafx.h" 7 | #include "../src/disp.h" 8 | #include "../src/utils.h" 9 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export class Object { 2 | constructor(id: string, options?: ActiveXObjectOptions); 3 | 4 | // Define properties typically found on COM objects 5 | __id?: string; 6 | __value?: any; 7 | __type?: any[]; 8 | __methods?: string[]; 9 | __vars?: string[]; 10 | 11 | // Define general method signatures if any known 12 | [key: string]: any; 13 | } 14 | 15 | /** @deprecated Use `ActiveXObjectOptions` instead. */ 16 | export interface ActiveXOptions extends ActiveXObjectOptions {} 17 | 18 | export class Variant { 19 | constructor(value?: any, type?: VariantType); 20 | assign(value: any): void; 21 | cast(type: VariantType): void; 22 | clear(): void; 23 | valueOf(): any; 24 | } 25 | 26 | export type VariantType = 27 | | 'int' | 'uint' | 'int8' | 'char' | 'uint8' | 'uchar' | 'byte' 28 | | 'int16' | 'short' | 'uint16' | 'ushort' 29 | | 'int32' | 'uint32' 30 | | 'int64' | 'long' | 'uint64' | 'ulong' 31 | | 'currency' | 'float' | 'double' | 'string' 32 | | 'date' | 'decimal' | 'variant' | 'null' | 'empty' 33 | | 'byref' | 'pbyref'; 34 | 35 | export function cast(value: any, type: VariantType): any; 36 | 37 | // Utility function to release COM objects 38 | export function release(...objects: any[]): void; 39 | 40 | declare global { 41 | function ActiveXObject(id: string, options?: ActiveXObjectOptions): any; 42 | function ActiveXObject(obj: Record): any; 43 | 44 | interface ActiveXObjectOptions { 45 | /** Allow activating existing object instance. */ 46 | activate?: boolean; 47 | 48 | /** Allow using the name of the file in the ROT. **/ 49 | getobject?: boolean; 50 | 51 | /** Allow using type information. */ 52 | type?: boolean; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./activex'); -------------------------------------------------------------------------------- /lib_binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'lib_node_activex', 5 | 'type': 'static_library', 6 | 'sources': [ 7 | 'src/utils.cpp', 8 | 'src/disp.cpp' 9 | ], 10 | 'defines': [ 11 | 'BUILDING_NODE_EXTENSION', 12 | ], 13 | 'direct_dependent_settings': { 14 | 'include_dirs': ['include'] 15 | }, 16 | 'dependencies': [ 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "winax", 3 | "version": "3.5.3", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "winax", 9 | "version": "3.5.3", 10 | "license": "MIT", 11 | "bin": { 12 | "nodewscript": "NodeWScript.js" 13 | }, 14 | "devDependencies": {}, 15 | "engines": { 16 | "node": ">= 10.0.0" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "winax", 3 | "version": "3.6.1", 4 | "description": "Windows COM bindings", 5 | "homepage": "https://github.com/durs/node-activex", 6 | "keywords": [ 7 | "OLE", 8 | "COM", 9 | "ActiveX", 10 | "ActiveXObject", 11 | "CreateObject", 12 | "Variant", 13 | "Dispatch", 14 | "WSH", 15 | "WMI", 16 | "Excel", 17 | "Word", 18 | "WScript", 19 | "ConnectObject", 20 | "DisconnectObject" 21 | ], 22 | "author": { 23 | "name": "Yuri Dursin", 24 | "url": "https://github.com/durs", 25 | "email": "yuri.dursin@gmail.com" 26 | }, 27 | "bin": { 28 | "nodewscript": "./NodeWScript.js" 29 | }, 30 | "contributors": [ 31 | "Yuri Dursin ", 32 | "Somanuell", 33 | "Daniel-Userlane", 34 | "Alexey Grinevich " 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/durs/node-activex.git" 39 | }, 40 | "dependencies": {}, 41 | "devDependencies": {}, 42 | "engines": { 43 | "node": ">= 10.0.0" 44 | }, 45 | "scripts": { 46 | "test": "mocha test" 47 | }, 48 | "license": "MIT", 49 | "main": "index.js" 50 | } 51 | -------------------------------------------------------------------------------- /src/disp.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------------------------- 2 | // Project: NodeActiveX 3 | // Author: Yuri Dursin 4 | // Description: DispObject class implementations 5 | //------------------------------------------------------------------------------------------------------- 6 | 7 | #include "stdafx.h" 8 | #include "disp.h" 9 | 10 | Persistent DispObject::inst_template; 11 | Persistent DispObject::clazz_template; 12 | NodeMethods DispObject::clazz_methods; 13 | 14 | Persistent VariantObject::inst_template; 15 | Persistent VariantObject::clazz_template; 16 | NodeMethods VariantObject::clazz_methods; 17 | 18 | Persistent ConnectionPointObject::inst_template; 19 | Persistent ConnectionPointObject::clazz_template; 20 | 21 | //------------------------------------------------------------------------------------------------------- 22 | // DispObject implemetation 23 | 24 | DispObject::DispObject(const DispInfoPtr &ptr, const std::wstring &nm, DISPID id, LONG indx, int opt) 25 | : disp(ptr), options((ptr->options & option_mask) | opt), name(nm), dispid(id), index(indx) 26 | { 27 | if (dispid == DISPID_UNKNOWN) { 28 | dispid = DISPID_VALUE; 29 | options |= option_prepared; 30 | } 31 | else options |= option_owned; 32 | NODE_DEBUG_FMT("DispObject '%S' constructor", name.c_str()); 33 | } 34 | 35 | DispObject::~DispObject() { 36 | items.Reset(); 37 | methods.Reset(); 38 | vars.Reset(); 39 | NODE_DEBUG_FMT("DispObject '%S' destructor", name.c_str()); 40 | } 41 | 42 | HRESULT DispObject::prepare() { 43 | CComVariant value; 44 | HRESULT hrcode = disp ? disp->GetProperty(dispid, index, &value) : E_UNEXPECTED; 45 | 46 | // Init dispatch interface 47 | options |= option_prepared; 48 | CComPtr ptr; 49 | if (VariantDispGet(&value, &ptr)) { 50 | disp.reset(new DispInfo(ptr, name, options, &disp)); 51 | dispid = DISPID_VALUE; 52 | } 53 | else if ((value.vt & VT_ARRAY) != 0) { 54 | 55 | } 56 | return hrcode; 57 | } 58 | 59 | bool DispObject::release() { 60 | if (!disp) return false; 61 | NODE_DEBUG_FMT("DispObject '%S' release", name.c_str()); 62 | disp.reset(); 63 | items.Reset(); 64 | methods.Reset(); 65 | vars.Reset(); 66 | return true; 67 | } 68 | 69 | bool DispObject::get(LPOLESTR tag, LONG index, const PropertyCallbackInfoGetter &args) { 70 | Isolate *isolate = args.GetIsolate(); 71 | if (!is_prepared()) prepare(); 72 | if (!disp) { 73 | isolate->ThrowException(DispErrorNull(isolate)); 74 | return false; 75 | } 76 | 77 | // Search dispid 78 | HRESULT hrcode; 79 | DISPID propid; 80 | bool prop_by_key = false; 81 | bool this_prop = false; 82 | if (!tag || !*tag) { 83 | tag = (LPOLESTR)name.c_str(); 84 | propid = dispid; 85 | this_prop = true; 86 | } 87 | else { 88 | hrcode = disp->FindProperty(tag, &propid); 89 | if (SUCCEEDED(hrcode) && propid == DISPID_UNKNOWN) hrcode = E_INVALIDARG; 90 | if FAILED(hrcode) { 91 | prop_by_key = (options & option_property) != 0; 92 | if (!prop_by_key) { 93 | //isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyFind", tag)); 94 | args.GetReturnValue().SetUndefined(); 95 | return false; 96 | } 97 | propid = dispid; 98 | } 99 | } 100 | 101 | // Check type info 102 | int opt = 0; 103 | bool is_property_simple = false; 104 | if (prop_by_key) { 105 | is_property_simple = true; 106 | opt |= option_property; 107 | } 108 | else { 109 | DispInfo::type_ptr disp_info; 110 | if (disp->GetTypeInfo(propid, disp_info)) { 111 | if (disp_info->is_function_simple()) opt |= option_function_simple; 112 | else { 113 | if (disp_info->is_property()) opt |= option_property; 114 | is_property_simple = disp_info->is_property_simple(); 115 | 116 | if (disp->bManaged && tag && *tag && wcscmp(tag, L"ToString")==0) 117 | { 118 | // .NET ToString is reported as a property while we normally use it via .ToString() - i.e. as a method. 119 | is_property_simple = false; 120 | } 121 | 122 | } 123 | } 124 | else if ( disp->bManaged && tag && *tag && wcscmp(tag, L"length") == 0) { 125 | DISPID lenprop; 126 | if SUCCEEDED(disp->FindProperty((LPOLESTR)L"length", &lenprop)) { 127 | 128 | // If we have 'IReflect' and '.length' - assume it is .NET JS Array or JS Object 129 | is_property_simple = true; 130 | } 131 | } 132 | else if (disp->bManaged && tag && *tag && index>=0 ) { 133 | // jsarray[x] 134 | is_property_simple = true; 135 | } 136 | } 137 | 138 | // Return as property value 139 | if (is_property_simple) { 140 | CComException except; 141 | CComVariant value; 142 | VarArguments vargs; 143 | if (prop_by_key) vargs.items.push_back(CComVariant(tag)); 144 | if (index >= 0) vargs.items.push_back(CComVariant(index)); 145 | LONG argcnt = (LONG)vargs.items.size(); 146 | VARIANT *pargs = (argcnt > 0) ? &vargs.items.front() : 0; 147 | hrcode = disp->GetProperty(propid, argcnt, pargs, &value, &except); 148 | if (FAILED(hrcode) && dispid != DISPID_VALUE){ 149 | isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyGet", tag, &except)); 150 | return false; 151 | } 152 | CComPtr ptr; 153 | if (VariantDispGet(&value, &ptr)) { 154 | DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp)); 155 | Local result = DispObject::NodeCreate(isolate, args.This(), disp_result, tag, DISPID_UNKNOWN, -1, opt); 156 | args.GetReturnValue().Set(result); 157 | } 158 | else { 159 | args.GetReturnValue().Set(Variant2Value(isolate, value)); 160 | } 161 | } 162 | 163 | // Return as dispatch object 164 | else { 165 | Local result = DispObject::NodeCreate(isolate, args.This(), disp, tag, propid, index, opt); 166 | args.GetReturnValue().Set(result); 167 | } 168 | return true; 169 | } 170 | 171 | bool DispObject::set(LPOLESTR tag, LONG index, const Local &value, const PropertyCallbackInfoSetter &args) { 172 | Isolate *isolate = args.GetIsolate(); 173 | if (!is_prepared()) prepare(); 174 | if (!disp) { 175 | isolate->ThrowException(DispErrorNull(isolate)); 176 | return false; 177 | } 178 | 179 | // Search dispid 180 | HRESULT hrcode; 181 | DISPID propid; 182 | if (!tag || !*tag) { 183 | tag = (LPOLESTR)name.c_str(); 184 | propid = dispid; 185 | } 186 | else { 187 | hrcode = disp->FindProperty(tag, &propid); 188 | if (SUCCEEDED(hrcode) && propid == DISPID_UNKNOWN) hrcode = E_INVALIDARG; 189 | if FAILED(hrcode) { 190 | isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyFind", tag)); 191 | return false; 192 | } 193 | } 194 | 195 | // Set value using dispatch 196 | CComException except; 197 | CComVariant ret; 198 | VarArguments vargs(isolate, value); 199 | if (index >= 0) vargs.items.push_back(CComVariant(index)); 200 | LONG argcnt = (LONG)vargs.items.size(); 201 | VARIANT *pargs = (argcnt > 0) ? &vargs.items.front() : 0; 202 | hrcode = disp->SetProperty(propid, argcnt, pargs, &ret, &except); 203 | if FAILED(hrcode) { 204 | isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyPut", tag, &except)); 205 | return false; 206 | } 207 | 208 | // Send result 209 | CComPtr ptr; 210 | if (VariantDispGet(&ret, &ptr)) { 211 | std::wstring rtag; 212 | rtag.reserve(32); 213 | rtag += L"@"; 214 | rtag += tag; 215 | DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp)); 216 | Local result = DispObject::NodeCreate(isolate, args.This(), disp_result, rtag); 217 | args.GetReturnValue().Set(result); 218 | } 219 | else { 220 | args.GetReturnValue().Set(Variant2Value(isolate, ret)); 221 | } 222 | return true; 223 | } 224 | 225 | void DispObject::call(Isolate *isolate, const FunctionCallbackInfo &args) { 226 | if (!disp) { 227 | isolate->ThrowException(DispErrorNull(isolate)); 228 | return; 229 | } 230 | 231 | CComException except; 232 | CComVariant ret; 233 | VarArguments vargs(isolate, args); 234 | LONG argcnt = (LONG)vargs.items.size(); 235 | VARIANT *pargs = (argcnt > 0) ? &vargs.items.front() : 0; 236 | HRESULT hrcode; 237 | 238 | if (vargs.IsDefault()) { 239 | hrcode = valueOf(isolate, ret, true); 240 | } 241 | else if ((options & option_property) == 0) { 242 | hrcode = disp->ExecuteMethod(dispid, argcnt, pargs, &ret, &except); 243 | } 244 | else { 245 | DispInfo::type_ptr disp_info; 246 | disp->GetTypeInfo(dispid, disp_info); 247 | 248 | if(disp_info->is_property_advanced() && argcnt > 1) { 249 | hrcode = disp->SetProperty(dispid, argcnt, pargs, &ret, &except); 250 | } 251 | else { 252 | hrcode = disp->GetProperty(dispid, argcnt, pargs, &ret, &except); 253 | } 254 | } 255 | if FAILED(hrcode) { 256 | isolate->ThrowException(DispError(isolate, hrcode, L"DispInvoke", name.c_str(), &except)); 257 | return; 258 | } 259 | 260 | // Prepare result 261 | Local result; 262 | CComPtr ptr; 263 | if (VariantDispGet(&ret, &ptr)) { 264 | std::wstring tag; 265 | tag.reserve(32); 266 | tag += L"@"; 267 | tag += name; 268 | DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp)); 269 | result = DispObject::NodeCreate(isolate, args.This(), disp_result, tag); 270 | } 271 | else { 272 | result = Variant2Value(isolate, ret, true); 273 | } 274 | args.GetReturnValue().Set(result); 275 | } 276 | 277 | HRESULT DispObject::valueOf(Isolate *isolate, VARIANT &value, bool simple) { 278 | if (!is_prepared()) prepare(); 279 | HRESULT hrcode; 280 | if (!disp) hrcode = E_UNEXPECTED; 281 | 282 | // simple function without arguments 283 | else if ((options & option_function_simple) != 0) { 284 | hrcode = disp->ExecuteMethod(dispid, 0, 0, &value); 285 | } 286 | 287 | // property or array element 288 | else if (dispid != DISPID_VALUE || index >= 0 || simple) { 289 | hrcode = disp->GetProperty(dispid, index, &value); 290 | } 291 | 292 | // self dispatch object 293 | else /*if (is_object())*/ { 294 | value.vt = VT_DISPATCH; 295 | value.pdispVal = (IDispatch*)disp->ptr; 296 | if (value.pdispVal) value.pdispVal->AddRef(); 297 | hrcode = S_OK; 298 | } 299 | return hrcode; 300 | } 301 | 302 | HRESULT DispObject::valueOf(Isolate *isolate, const Local &self, Local &value) { 303 | if (!is_prepared()) prepare(); 304 | HRESULT hrcode; 305 | if (!disp) hrcode = E_UNEXPECTED; 306 | else { 307 | CComVariant val; 308 | 309 | // simple function without arguments 310 | 311 | if ((options & option_function_simple) != 0) { 312 | hrcode = disp->ExecuteMethod(dispid, 0, 0, &val); 313 | } 314 | 315 | // self value, property or array element 316 | else { 317 | hrcode = disp->GetProperty(dispid, index, &val); 318 | // Try to get some primitive value 319 | if FAILED(hrcode) { 320 | hrcode = disp->ExecuteMethod(dispid, 0, 0, &val); 321 | } 322 | } 323 | 324 | // convert result to v8 value 325 | if SUCCEEDED(hrcode) { 326 | value = Variant2Value(isolate, val); 327 | } 328 | 329 | // or return self as object 330 | else { 331 | value = self; 332 | hrcode = S_OK; 333 | } 334 | } 335 | return hrcode; 336 | } 337 | 338 | void DispObject::toString(const FunctionCallbackInfo &args) { 339 | Isolate *isolate = args.GetIsolate(); 340 | CComVariant val; 341 | HRESULT hrcode = valueOf(isolate, val, true); 342 | if FAILED(hrcode) { 343 | isolate->ThrowException(Win32Error(isolate, hrcode, L"DispToString")); 344 | return; 345 | } 346 | args.GetReturnValue().Set(Variant2String(isolate, val)); 347 | } 348 | 349 | Local DispObject::getIdentity(Isolate *isolate) { 350 | //wchar_t buf[64]; 351 | std::wstring id; 352 | id.reserve(128); 353 | id += name; 354 | DispInfoPtr ptr = disp; 355 | if (ptr && ptr->name == id) 356 | ptr = ptr->parent.lock(); 357 | while (ptr) { 358 | id.insert(0, L"."); 359 | id.insert(0, ptr->name); 360 | /* 361 | if (ptr->index >= 0) { 362 | swprintf_s(buf, L"[%ld]", index); 363 | id += buf; 364 | } 365 | */ 366 | ptr = ptr->parent.lock(); 367 | } 368 | return v8str(isolate, id.c_str()); 369 | } 370 | 371 | void DispObject::initTypeInfo(Isolate *isolate) { 372 | if ((options & option_type) == 0 || !disp) { 373 | return; 374 | } 375 | uint32_t index = 0; 376 | Local _items = v8::Array::New(isolate); 377 | Local _methods = v8::Object::New(isolate); 378 | Local _vars = v8::Object::New(isolate); 379 | disp->Enumerate(1+2/*functions and variables*/, nullptr, [isolate, &_items, &_vars, &_methods, &index](ITypeInfo *info, FUNCDESC *func, VARDESC *var) { 380 | Local ctx = isolate->GetCurrentContext(); 381 | CComBSTR name; 382 | MEMBERID memid = func != nullptr ? func->memid : var->memid; 383 | TypeInfoGetName(info, memid, &name); 384 | Local item(Object::New(isolate)); 385 | Local vname; 386 | if (name) { 387 | vname = v8str(isolate, (BSTR)name); 388 | item->Set(ctx, v8str(isolate, "name"), vname); 389 | } 390 | item->Set(ctx, v8str(isolate, "dispid"), Int32::New(isolate, memid)); 391 | if (func != nullptr) { 392 | item->Set(ctx, v8str(isolate, "invkind"), Int32::New(isolate, (int32_t)func->invkind)); 393 | item->Set(ctx, v8str(isolate, "flags"), Int32::New(isolate, (int32_t)func->wFuncFlags)); 394 | item->Set(ctx, v8str(isolate, "argcnt"), Int32::New(isolate, (int32_t)func->cParams)); 395 | _methods->Set(ctx, vname, item); 396 | } else { 397 | item->Set(ctx, v8str(isolate, "varkind"), Int32::New(isolate, (int32_t)var->varkind)); 398 | item->Set(ctx, v8str(isolate, "flags"), Int32::New(isolate, (int32_t)var->wVarFlags)); 399 | if (var->varkind == VAR_CONST && var->lpvarValue != nullptr) { 400 | v8::Local value = Variant2Value(isolate, *var->lpvarValue, false); 401 | item->Set(ctx, v8str(isolate, "value"), value); 402 | } 403 | _vars->Set(ctx, vname, item); 404 | } 405 | _items->Set(ctx, index++, item); 406 | }); 407 | items.Reset(isolate, _items); 408 | methods.Reset(isolate, _methods); 409 | vars.Reset(isolate, _vars); 410 | } 411 | 412 | //----------------------------------------------------------------------------------- 413 | // Static Node JS callbacks 414 | 415 | void DispObject::NodeInit(const Local &target, Isolate* isolate, Local &ctx) { 416 | 417 | // Prepare constructor template 418 | Local clazz = FunctionTemplate::New(isolate, NodeCreate); 419 | clazz->SetClassName(v8str(isolate, "Dispatch")); 420 | 421 | clazz_methods.add(isolate, clazz, "toString", NodeToString); 422 | clazz_methods.add(isolate, clazz, "valueOf", NodeValueOf); 423 | 424 | Local inst = clazz->InstanceTemplate(); 425 | inst->SetInternalFieldCount(1); 426 | 427 | #ifdef NODE_INTERCEPTED 428 | inst->SetHandler(NamedPropertyHandlerConfiguration(InterceptedNodeGet, InterceptedNodeSet)); 429 | inst->SetHandler(IndexedPropertyHandlerConfiguration(InterceptedNodeGetByIndex, InterceptedNodeSetByIndex)); 430 | #else 431 | inst->SetHandler(NamedPropertyHandlerConfiguration(NodeGet, NodeSet)); 432 | inst->SetHandler(IndexedPropertyHandlerConfiguration(NodeGetByIndex, NodeSetByIndex)); 433 | #endif 434 | 435 | inst->SetCallAsFunctionHandler(NodeCall); 436 | inst->SetNativeDataProperty(v8str(isolate, "__id"), NodeGet); 437 | inst->SetNativeDataProperty(v8str(isolate, "__value"), NodeGet); 438 | //inst->SetLazyDataProperty(v8str(isolate, "__type"), NodeGet, Local(), ReadOnly); 439 | //inst->SetLazyDataProperty(v8str(isolate, "__methods"), NodeGet, Local(), ReadOnly); 440 | //inst->SetLazyDataProperty(v8str(isolate, "__vars"), NodeGet, Local(), ReadOnly); 441 | inst->SetNativeDataProperty(v8str(isolate, "__type"), NodeGet); 442 | inst->SetNativeDataProperty(v8str(isolate, "__methods"), NodeGet); 443 | inst->SetNativeDataProperty(v8str(isolate, "__vars"), NodeGet); 444 | 445 | inst_template.Reset(isolate, inst); 446 | clazz_template.Reset(isolate, clazz); 447 | target->Set(ctx, v8str(isolate, "Object"), clazz->GetFunction(ctx).ToLocalChecked()); 448 | target->Set(ctx, v8str(isolate, "cast"), FunctionTemplate::New(isolate, NodeCast)->GetFunction(ctx).ToLocalChecked()); 449 | target->Set(ctx, v8str(isolate, "release"), FunctionTemplate::New(isolate, NodeRelease)->GetFunction(ctx).ToLocalChecked()); 450 | 451 | target->Set(ctx, v8str(isolate, "getConnectionPoints"), FunctionTemplate::New(isolate, NodeConnectionPoints)->GetFunction(ctx).ToLocalChecked()); 452 | target->Set(ctx, v8str(isolate, "peekAndDispatchMessages"), FunctionTemplate::New(isolate, PeakAndDispatchMessages)->GetFunction(ctx).ToLocalChecked()); 453 | 454 | //Context::GetCurrent()->Global()->Set(v8str(isolate, "ActiveXObject"), t->GetFunction()); 455 | NODE_DEBUG_MSG("DispObject initialized"); 456 | } 457 | 458 | Local DispObject::NodeCreate(Isolate *isolate, const Local &parent, const DispInfoPtr &ptr, const std::wstring &name, DISPID id, LONG index, int opt) { 459 | Local self; 460 | if (!inst_template.IsEmpty()) { 461 | if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) { 462 | (new DispObject(ptr, name, id, index, opt))->Wrap(self); 463 | //Local prop_id(v8str(isolate, "_identity")); 464 | //self->Set(prop_id, v8str(isolate, name.c_str())); 465 | } 466 | } 467 | return self; 468 | } 469 | 470 | void DispObject::NodeCreate(const FunctionCallbackInfo &args) { 471 | Isolate *isolate = args.GetIsolate(); 472 | Local ctx = isolate->GetCurrentContext(); 473 | bool isGetObject = false; 474 | bool isGetAccessibleObject = false; 475 | int argcnt = args.Length(); 476 | if (argcnt < 1) { 477 | isolate->ThrowException(InvalidArgumentsError(isolate)); 478 | return; 479 | } 480 | int options = (option_async | option_type); 481 | if (argcnt > 1) { 482 | Local val, argopt = args[1]; 483 | bool isEmpty = argopt.IsEmpty(); 484 | bool isObject = argopt->IsObject(); 485 | if (!isEmpty && isObject) { 486 | auto opt = Local::Cast(argopt); 487 | if (opt->Get(ctx, v8str(isolate, "async")).ToLocal(&val)) { 488 | if (!v8val2bool(isolate, val, true)) options &= ~option_async; 489 | } 490 | if (opt->Get(ctx, v8str(isolate, "type")).ToLocal(&val)) { 491 | if (!v8val2bool(isolate, val, true)) options &= ~option_type; 492 | } 493 | if (opt->Get(ctx, v8str(isolate, "activate")).ToLocal(&val)) { 494 | if (v8val2bool(isolate, val, false)) options |= option_activate; 495 | } 496 | if (opt->Get(ctx, v8str(isolate, "getobject")).ToLocal(&val)) { 497 | if (v8val2bool(isolate, val, false)) isGetObject = true; 498 | } 499 | if (opt->Get(ctx, v8str(isolate, "getaccessibleobject")).ToLocal(&val)) { 500 | if (v8val2bool(isolate, val, false)) isGetAccessibleObject = true; 501 | } 502 | } 503 | } 504 | 505 | // Invoked as plain function 506 | if (!args.IsConstructCall()) { 507 | Local clazz = clazz_template.Get(isolate); 508 | if (clazz.IsEmpty()) { 509 | isolate->ThrowException(TypeError(isolate, "FunctionTemplateIsEmpty")); 510 | return; 511 | } 512 | const int argc = 1; 513 | Local argv[argc] = { args[0] }; 514 | Local cons; 515 | Local context = isolate->GetCurrentContext(); 516 | if (clazz->GetFunction(context).ToLocal(&cons)) { 517 | Local self; 518 | if (cons->NewInstance(context, argc, argv).ToLocal(&self)) { 519 | args.GetReturnValue().Set(self); 520 | } 521 | } 522 | return; 523 | } 524 | 525 | // Create dispatch object from ProgId 526 | HRESULT hrcode; 527 | std::wstring name; 528 | CComPtr disp; 529 | if (args[0]->IsString()) { 530 | 531 | // Prepare arguments 532 | String::Value vname(isolate, args[0]); 533 | if (vname.length() <= 0) hrcode = E_INVALIDARG; 534 | else { 535 | name.assign((LPOLESTR)*vname, vname.length()); 536 | 537 | CComPtr unk; 538 | if (isGetObject) 539 | { 540 | hrcode = CoGetObject(name.c_str(), NULL, IID_IUnknown, (void**)&unk); 541 | if SUCCEEDED(hrcode) hrcode = unk->QueryInterface(&disp); 542 | } else { 543 | if (isGetAccessibleObject) 544 | { 545 | hrcode = GetAccessibleObject(name.c_str(), unk); 546 | if SUCCEEDED(hrcode) hrcode = unk->QueryInterface(&disp); 547 | } else { 548 | CLSID clsid; 549 | hrcode = CLSIDFromProgID(name.c_str(), &clsid); 550 | if SUCCEEDED(hrcode) { 551 | if ((options & option_activate) == 0) hrcode = E_FAIL; 552 | else { 553 | hrcode = GetActiveObject(clsid, NULL, &unk); 554 | if SUCCEEDED(hrcode) hrcode = unk->QueryInterface(&disp); 555 | } 556 | if FAILED(hrcode) { 557 | hrcode = disp.CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER); 558 | } 559 | } 560 | } 561 | } 562 | } 563 | } 564 | 565 | // Use supplied dispatch pointer 566 | else if (args[0]->IsUint8Array()) { 567 | size_t len = node::Buffer::Length(args[0]); 568 | void *data = node::Buffer::Data(args[0]); 569 | IDispatch *p = (len == sizeof(INT_PTR)) ? (IDispatch *) *(static_cast(data)) : nullptr; 570 | if (!p) { 571 | isolate->ThrowException(InvalidArgumentsError(isolate)); 572 | return; 573 | } 574 | disp.Attach(p); 575 | hrcode = S_OK; 576 | } 577 | 578 | // Create dispatch object from javascript object 579 | else if (args[0]->IsObject()) { 580 | name = L"#"; 581 | disp = new DispObjectImpl(Local::Cast(args[0])); 582 | hrcode = S_OK; 583 | } 584 | 585 | // Other 586 | else { 587 | hrcode = E_INVALIDARG; 588 | } 589 | 590 | // Prepare result 591 | if FAILED(hrcode) { 592 | isolate->ThrowException(DispError(isolate, hrcode, L"CreateInstance", name.c_str())); 593 | } 594 | else { 595 | Local self = args.This(); 596 | DispInfoPtr ptr(new DispInfo(disp, name, options)); 597 | (new DispObject(ptr, name))->Wrap(self); 598 | args.GetReturnValue().Set(self); 599 | } 600 | } 601 | 602 | void DispObject::NodeGet(Local name, const PropertyCallbackInfoGetter& args) { 603 | Isolate *isolate = args.GetIsolate(); 604 | DispObject *self = DispObject::Unwrap(args.This()); 605 | if (!self) { 606 | isolate->ThrowException(DispErrorInvalid(isolate)); 607 | return; 608 | } 609 | String::Value vname(isolate, name); 610 | LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L"valueOf"; 611 | NODE_DEBUG_FMT2("DispObject '%S.%S' get", self->name.c_str(), id); 612 | if (_wcsicmp(id, L"__value") == 0) { 613 | Local result; 614 | HRESULT hrcode = self->valueOf(isolate, args.This(), result); 615 | if FAILED(hrcode) isolate->ThrowException(Win32Error(isolate, hrcode, L"DispValueOf")); 616 | else args.GetReturnValue().Set(result); 617 | } 618 | else if (_wcsicmp(id, L"__id") == 0) { 619 | args.GetReturnValue().Set(self->getIdentity(isolate)); 620 | } 621 | else if (_wcsicmp(id, L"__type") == 0) { 622 | if (self->items.IsEmpty()) { 623 | self->initTypeInfo(isolate); 624 | } 625 | Local result = self->items.Get(isolate); 626 | args.GetReturnValue().Set(result); 627 | } 628 | else if (_wcsicmp(id, L"__methods") == 0) { 629 | if (self->methods.IsEmpty()) { 630 | self->initTypeInfo(isolate); 631 | } 632 | Local result = self->methods.Get(isolate); 633 | args.GetReturnValue().Set(result); 634 | } 635 | else if (_wcsicmp(id, L"__vars") == 0) { 636 | if (self->vars.IsEmpty()) { 637 | self->initTypeInfo(isolate); 638 | } 639 | Local result = self->vars.Get(isolate); 640 | args.GetReturnValue().Set(result); 641 | } 642 | else if (_wcsicmp(id, L"__proto__") == 0) { 643 | Local func; 644 | Local clazz = clazz_template.Get(isolate); 645 | Local ctx = isolate->GetCurrentContext(); 646 | if (!clazz.IsEmpty() && clazz->GetFunction(ctx).ToLocal(&func)) { 647 | args.GetReturnValue().Set(func); 648 | } 649 | else { 650 | args.GetReturnValue().SetNull(); 651 | } 652 | } 653 | else { 654 | Local func; 655 | if (clazz_methods.get(isolate, id, &func)) { 656 | args.GetReturnValue().Set(func); 657 | } 658 | 659 | else if (!self->get(id, -1, args)) { 660 | Local result; 661 | HRESULT hrcode = self->valueOf(isolate, args.This(), result); 662 | if FAILED(hrcode) isolate->ThrowException(Win32Error(isolate, hrcode, L"Unable to Get Value")); 663 | 664 | Local ctx = isolate->GetCurrentContext(); 665 | MaybeLocal localObj = result->ToObject(ctx); 666 | if (localObj.IsEmpty()) { 667 | args.GetReturnValue().SetUndefined(); 668 | return; 669 | } 670 | 671 | Local obj = localObj.ToLocalChecked(); 672 | MaybeLocal realProp = obj->GetRealNamedPropertyInPrototypeChain(ctx, v8str(isolate, id)); 673 | if (realProp.IsEmpty()) { 674 | // We may call non-existing property for an object to check its existence 675 | // So we should return undefined in this case 676 | args.GetReturnValue().SetUndefined(); 677 | } 678 | else { 679 | Local ownProp = realProp.ToLocalChecked(); 680 | if (ownProp->IsFunction()) { 681 | Local func = Local::Cast(ownProp); 682 | if (func.IsEmpty()) return; 683 | args.GetReturnValue().Set(func); 684 | return; 685 | } 686 | } 687 | } 688 | } 689 | } 690 | 691 | void DispObject::NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter& args) { 692 | Isolate *isolate = args.GetIsolate(); 693 | DispObject *self = DispObject::Unwrap(args.This()); 694 | if (!self) { 695 | isolate->ThrowException(DispErrorInvalid(isolate)); 696 | return; 697 | } 698 | NODE_DEBUG_FMT2("DispObject '%S[%u]' get", self->name.c_str(), index); 699 | self->get(0, index, args); 700 | } 701 | 702 | void DispObject::NodeSet(Local name, Local value, const PropertyCallbackInfoSetter& args) { 703 | Isolate *isolate = args.GetIsolate(); 704 | DispObject *self = DispObject::Unwrap(args.This()); 705 | if (!self) { 706 | isolate->ThrowException(DispErrorInvalid(isolate)); 707 | return; 708 | } 709 | String::Value vname(isolate, name); 710 | LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L""; 711 | NODE_DEBUG_FMT2("DispObject '%S.%S' set", self->name.c_str(), id); 712 | self->set(id, -1, value, args); 713 | } 714 | 715 | void DispObject::NodeSetByIndex(uint32_t index, Local value, const PropertyCallbackInfoSetter& args) { 716 | Isolate *isolate = args.GetIsolate(); 717 | DispObject *self = DispObject::Unwrap(args.This()); 718 | if (!self) { 719 | isolate->ThrowException(DispErrorInvalid(isolate)); 720 | return; 721 | } 722 | NODE_DEBUG_FMT2("DispObject '%S[%u]' set", self->name.c_str(), index); 723 | self->set(0, index, value, args); 724 | } 725 | 726 | void DispObject::NodeCall(const FunctionCallbackInfo &args) { 727 | Isolate *isolate = args.GetIsolate(); 728 | DispObject *self = DispObject::Unwrap(args.This()); 729 | if (!self) { 730 | isolate->ThrowException(DispErrorInvalid(isolate)); 731 | return; 732 | } 733 | NODE_DEBUG_FMT("DispObject '%S' call", self->name.c_str()); 734 | self->call(isolate, args); 735 | } 736 | 737 | void DispObject::NodeValueOf(const FunctionCallbackInfo& args) { 738 | Isolate *isolate = args.GetIsolate(); 739 | DispObject *self = DispObject::Unwrap(args.This()); 740 | if (!self) { 741 | isolate->ThrowException(DispErrorInvalid(isolate)); 742 | return; 743 | } 744 | Local result; 745 | HRESULT hrcode = self->valueOf(isolate, args.This(), result); 746 | if FAILED(hrcode) { 747 | isolate->ThrowException(Win32Error(isolate, hrcode, L"DispValueOf")); 748 | return; 749 | } 750 | args.GetReturnValue().Set(result); 751 | } 752 | 753 | void DispObject::NodeToString(const FunctionCallbackInfo& args) { 754 | Isolate *isolate = args.GetIsolate(); 755 | DispObject *self = DispObject::Unwrap(args.This()); 756 | if (!self) { 757 | isolate->ThrowException(DispErrorInvalid(isolate)); 758 | return; 759 | } 760 | self->toString(args); 761 | } 762 | 763 | void DispObject::NodeRelease(const FunctionCallbackInfo& args) { 764 | Isolate *isolate = args.GetIsolate(); 765 | int rcnt = 0, argcnt = args.Length(); 766 | for (int argi = 0; argi < argcnt; argi++) { 767 | auto obj = args[argi]; 768 | if (obj->IsObject()) { 769 | auto disp_obj = Local::Cast(obj); 770 | DispObject *disp = DispObject::Unwrap(disp_obj); 771 | if (disp && disp->release()) 772 | rcnt ++; 773 | } 774 | } 775 | args.GetReturnValue().Set(rcnt); 776 | } 777 | 778 | void DispObject::NodeCast(const FunctionCallbackInfo& args) { 779 | Local inst = VariantObject::NodeCreateInstance(args); 780 | args.GetReturnValue().Set(inst); 781 | } 782 | 783 | void DispObject::NodeConnectionPoints(const FunctionCallbackInfo& args) { 784 | Isolate *isolate = args.GetIsolate(); 785 | Local ctx = isolate->GetCurrentContext(); 786 | Local items = Array::New(isolate); 787 | CComPtr ptr; 788 | CComPtr cp_cont; 789 | CComPtr cp_enum; 790 | 791 | // prepare connecton points from arguments 792 | int argcnt = args.Length(); 793 | if (argcnt >= 1) { 794 | auto arg = args[0]; 795 | if (Value2Unknown(isolate, arg, (IUnknown**)&ptr)) { 796 | if SUCCEEDED(ptr->QueryInterface(&cp_cont)) { 797 | cp_cont->EnumConnectionPoints(&cp_enum); 798 | } 799 | } 800 | } 801 | 802 | // enumerate connection points 803 | if (cp_enum) { 804 | ULONG cnt_fetched; 805 | CComPtr cp_ptr; 806 | uint32_t cnt = 0; 807 | while (SUCCEEDED(cp_enum->Next(1, &cp_ptr, &cnt_fetched)) && cnt_fetched == 1) { 808 | items->Set(ctx, cnt++, ConnectionPointObject::NodeCreateInstance(isolate, cp_ptr, ptr)); 809 | cp_ptr.Release(); 810 | } 811 | } 812 | 813 | // return array of connection points 814 | args.GetReturnValue().Set(items); 815 | } 816 | void DispObject::PeakAndDispatchMessages(const FunctionCallbackInfo& args) { 817 | MSG msg; 818 | while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { 819 | DispatchMessage(&msg); 820 | } 821 | } 822 | 823 | //------------------------------------------------------------------------------------------------------- 824 | 825 | class vtypes_t { 826 | public: 827 | inline vtypes_t(std::initializer_list> recs) { 828 | for (auto &rec : recs) { 829 | str2vt.emplace(rec.first, rec.second); 830 | vt2str.emplace(rec.second, rec.first); 831 | } 832 | } 833 | inline bool find(VARTYPE vt, std::wstring &name) { 834 | auto it = vt2str.find(vt); 835 | if (it == vt2str.end()) return false; 836 | name = it->second; 837 | return true; 838 | } 839 | inline VARTYPE find(const std::wstring &name) { 840 | auto it = str2vt.find(name); 841 | if (it == str2vt.end()) return VT_EMPTY; 842 | return it->second; 843 | } 844 | private: 845 | std::map str2vt; 846 | std::map vt2str; 847 | }; 848 | 849 | static vtypes_t vtypes({ 850 | { L"char", VT_I1 }, 851 | { L"uchar", VT_UI1 }, 852 | { L"byte", VT_UI1 }, 853 | { L"short", VT_I2 }, 854 | { L"ushort", VT_UI2 }, 855 | { L"int", VT_INT }, 856 | { L"uint", VT_UINT }, 857 | { L"long", VT_I8 }, 858 | { L"ulong", VT_UI8 }, 859 | 860 | { L"int8", VT_I1 }, 861 | { L"uint8", VT_UI1 }, 862 | { L"int16", VT_I2 }, 863 | { L"uint16", VT_UI2 }, 864 | { L"int32", VT_I4 }, 865 | { L"uint32", VT_UI4 }, 866 | { L"int64", VT_I8 }, 867 | { L"uint64", VT_UI8 }, 868 | { L"currency", VT_CY }, 869 | 870 | { L"float", VT_R4 }, 871 | { L"double", VT_R8 }, 872 | { L"date", VT_DATE }, 873 | { L"decimal", VT_DECIMAL }, 874 | 875 | { L"string", VT_BSTR }, 876 | { L"empty", VT_EMPTY }, 877 | { L"variant", VT_VARIANT }, 878 | { L"null", VT_NULL }, 879 | { L"byref", VT_BYREF } 880 | }); 881 | 882 | bool VariantObject::assign(Isolate *isolate, Local &val, Local &type) { 883 | VARTYPE vt = VT_EMPTY; 884 | if (!type.IsEmpty()) { 885 | if (type->IsString()) { 886 | String::Value vtstr(isolate, type); 887 | const wchar_t *pvtstr = (const wchar_t *)*vtstr; 888 | int vtstr_len = vtstr.length(); 889 | if (vtstr_len > 1) { 890 | if (pvtstr[0] == 'p') { 891 | vt |= VT_BYREF; 892 | vtstr_len--; 893 | pvtstr++; 894 | } 895 | else if (pvtstr[vtstr_len - 1] == '*') { 896 | vt |= VT_BYREF; 897 | vtstr_len--; 898 | } 899 | else if (pvtstr[vtstr_len - 2] == '[' || pvtstr[vtstr_len - 1] == ']') { 900 | vt |= VT_ARRAY; 901 | vtstr_len -= 2; 902 | } 903 | } 904 | if (vtstr_len > 0) { 905 | std::wstring type(pvtstr, vtstr_len); 906 | vt |= vtypes.find(type); 907 | } 908 | } 909 | else if (type->IsInt32()) { 910 | vt |= type->Int32Value(isolate->GetCurrentContext()).FromMaybe(0); 911 | } 912 | } 913 | 914 | if (val.IsEmpty()) { 915 | if FAILED(value.ChangeType(vt)) return false; 916 | if ((value.vt & VT_BYREF) == 0) pvalue.Clear(); 917 | return true; 918 | } 919 | 920 | value.Clear(); 921 | pvalue.Clear(); 922 | if ((vt & VT_ARRAY) != 0) { 923 | Value2SafeArray(isolate, val, value, vt & ~VT_ARRAY); 924 | } 925 | else if ((vt & VT_BYREF) == 0) { 926 | Value2Variant(isolate, val, value, vt); 927 | } 928 | else { 929 | VARIANT *refvalue = nullptr; 930 | VARTYPE vt_noref = vt & ~VT_BYREF; 931 | VariantObject *ref = (!val.IsEmpty() && val->IsObject()) ? GetInstanceOf(isolate, Local::Cast(val)) : nullptr; 932 | if (ref) { 933 | if ((ref->value.vt & VT_BYREF) != 0) value = ref->value; 934 | else refvalue = &ref->value; 935 | } 936 | else { 937 | Value2Variant(isolate, val, pvalue, vt_noref); 938 | refvalue = &pvalue; 939 | } 940 | if (refvalue) { 941 | if (vt_noref == 0 || vt_noref == VT_VARIANT || refvalue->vt == VT_EMPTY) { 942 | value.vt = VT_VARIANT | VT_BYREF; 943 | value.pvarVal = refvalue; 944 | } 945 | else { 946 | value.vt = refvalue->vt | VT_BYREF; 947 | value.byref = &refvalue->intVal; 948 | } 949 | } 950 | } 951 | return true; 952 | } 953 | 954 | VariantObject::VariantObject(const FunctionCallbackInfo &args) { 955 | Local val, type; 956 | int argcnt = args.Length(); 957 | if (argcnt > 0) val = args[0]; 958 | if (argcnt > 1) type = args[1]; 959 | assign(args.GetIsolate(), val, type); 960 | } 961 | 962 | void VariantObject::NodeInit(const Local &target, Isolate* isolate, Local &ctx) { 963 | 964 | // Prepare constructor template 965 | Local clazz = FunctionTemplate::New(isolate, NodeCreate); 966 | clazz->SetClassName(v8str(isolate, "Variant")); 967 | clazz_methods.add(isolate, clazz, "clear", NodeClear); 968 | clazz_methods.add(isolate, clazz, "assign", NodeAssign); 969 | clazz_methods.add(isolate, clazz, "cast", NodeCast); 970 | clazz_methods.add(isolate, clazz, "toString", NodeToString); 971 | clazz_methods.add(isolate, clazz, "valueOf", NodeValueOf); 972 | 973 | Local inst = clazz->InstanceTemplate(); 974 | inst->SetInternalFieldCount(1); 975 | 976 | #ifdef NODE_INTERCEPTED 977 | inst->SetHandler(NamedPropertyHandlerConfiguration(InterceptedNodeGet, InterceptedNodeSet)); 978 | inst->SetHandler(IndexedPropertyHandlerConfiguration(InterceptedNodeGetByIndex, InterceptedNodeSetByIndex)); 979 | #else 980 | inst->SetHandler(NamedPropertyHandlerConfiguration(NodeGet, NodeSet)); 981 | inst->SetHandler(IndexedPropertyHandlerConfiguration(NodeGetByIndex, NodeSetByIndex)); 982 | #endif 983 | //inst->SetCallAsFunctionHandler(NodeCall); 984 | //inst->SetNativeDataProperty(v8str(isolate, "__id"), NodeGet); 985 | 986 | inst->SetNativeDataProperty(v8str(isolate, "__value"), NodeGet); 987 | //inst->SetLazyDataProperty(v8str(isolate, "__type"), NodeGet, Local(), ReadOnly); 988 | inst->SetNativeDataProperty(v8str(isolate, "__type"), NodeGet); 989 | 990 | inst_template.Reset(isolate, inst); 991 | clazz_template.Reset(isolate, clazz); 992 | Local func; 993 | if (clazz->GetFunction(ctx).ToLocal(&func)) { 994 | target->Set(ctx, v8str(isolate, "Variant"), func); 995 | } 996 | NODE_DEBUG_MSG("VariantObject initialized"); 997 | } 998 | 999 | Local VariantObject::NodeCreateInstance(const FunctionCallbackInfo &args) { 1000 | Local self; 1001 | Isolate *isolate = args.GetIsolate(); 1002 | if (!inst_template.IsEmpty()) { 1003 | if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) { 1004 | (new VariantObject(args))->Wrap(self); 1005 | } 1006 | } 1007 | return self; 1008 | } 1009 | 1010 | void VariantObject::NodeCreate(const FunctionCallbackInfo &args) { 1011 | Isolate *isolate = args.GetIsolate(); 1012 | Local self = args.This(); 1013 | (new VariantObject(args))->Wrap(self); 1014 | args.GetReturnValue().Set(self); 1015 | } 1016 | 1017 | void VariantObject::NodeClear(const FunctionCallbackInfo& args) { 1018 | Isolate *isolate = args.GetIsolate(); 1019 | VariantObject *self = VariantObject::Unwrap(args.This()); 1020 | if (!self) { 1021 | isolate->ThrowException(DispErrorInvalid(isolate)); 1022 | return; 1023 | } 1024 | self->value.Clear(); 1025 | self->pvalue.Clear(); 1026 | } 1027 | 1028 | void VariantObject::NodeAssign(const FunctionCallbackInfo& args) { 1029 | Isolate *isolate = args.GetIsolate(); 1030 | VariantObject *self = VariantObject::Unwrap(args.This()); 1031 | if (!self) { 1032 | isolate->ThrowException(DispErrorInvalid(isolate)); 1033 | return; 1034 | } 1035 | Local val, type; 1036 | int argcnt = args.Length(); 1037 | if (argcnt > 0) val = args[0]; 1038 | if (argcnt > 1) type = args[1]; 1039 | self->assign(isolate, val, type); 1040 | } 1041 | 1042 | void VariantObject::NodeCast(const FunctionCallbackInfo& args) { 1043 | Isolate *isolate = args.GetIsolate(); 1044 | VariantObject *self = VariantObject::Unwrap(args.This()); 1045 | if (!self) { 1046 | isolate->ThrowException(DispErrorInvalid(isolate)); 1047 | return; 1048 | } 1049 | Local val, type; 1050 | int argcnt = args.Length(); 1051 | if (argcnt > 0) type = args[0]; 1052 | self->assign(isolate, val, type); 1053 | } 1054 | 1055 | void VariantObject::NodeValueOf(const FunctionCallbackInfo& args) { 1056 | Isolate *isolate = args.GetIsolate(); 1057 | VariantObject *self = VariantObject::Unwrap(args.This()); 1058 | if (!self) { 1059 | isolate->ThrowException(DispErrorInvalid(isolate)); 1060 | return; 1061 | } 1062 | // Last parameter false because valueOf should return primitive value 1063 | Local result = Variant2Value(isolate, self->value, false); 1064 | args.GetReturnValue().Set(result); 1065 | } 1066 | 1067 | void VariantObject::NodeToString(const FunctionCallbackInfo& args) { 1068 | Isolate *isolate = args.GetIsolate(); 1069 | VariantObject *self = VariantObject::Unwrap(args.This()); 1070 | if (!self) { 1071 | isolate->ThrowException(DispErrorInvalid(isolate)); 1072 | return; 1073 | } 1074 | Local result = Variant2String(isolate, self->value); 1075 | args.GetReturnValue().Set(result); 1076 | } 1077 | 1078 | void VariantObject::NodeGet(Local name, const PropertyCallbackInfoGetter& args) { 1079 | Isolate *isolate = args.GetIsolate(); 1080 | VariantObject *self = VariantObject::Unwrap(args.This()); 1081 | if (!self) { 1082 | isolate->ThrowException(DispErrorInvalid(isolate)); 1083 | return; 1084 | } 1085 | 1086 | String::Value vname(isolate, name); 1087 | LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L"valueOf"; 1088 | if (_wcsicmp(id, L"__value") == 0) { 1089 | Local result = Variant2Value(isolate, self->value); 1090 | args.GetReturnValue().Set(result); 1091 | } 1092 | else if (_wcsicmp(id, L"__type") == 0) { 1093 | std::wstring type, name; 1094 | if (self->value.vt & VT_BYREF) type += L"byref:"; 1095 | if (self->value.vt & VT_ARRAY) type = L"array:"; 1096 | if (vtypes.find(self->value.vt & VT_TYPEMASK, name)) type += name; 1097 | else type += std::to_wstring(self->value.vt & VT_TYPEMASK); 1098 | Local text = v8str(isolate, type.c_str()); 1099 | } 1100 | else if (_wcsicmp(id, L"__proto__") == 0) { 1101 | Local func; 1102 | Local clazz = clazz_template.Get(isolate); 1103 | Local ctx = isolate->GetCurrentContext(); 1104 | if (!clazz.IsEmpty() && clazz_template.Get(isolate)->GetFunction(ctx).ToLocal(&func)) { 1105 | args.GetReturnValue().Set(func); 1106 | } 1107 | else { 1108 | args.GetReturnValue().SetNull(); 1109 | } 1110 | } 1111 | else if (_wcsicmp(id, L"length") == 0) { 1112 | if ((self->value.vt & VT_ARRAY) != 0) { 1113 | args.GetReturnValue().Set((uint32_t)self->value.ArrayLength()); 1114 | } 1115 | } 1116 | else { 1117 | Local func; 1118 | if (clazz_methods.get(isolate, id, &func)) { 1119 | args.GetReturnValue().Set(func); 1120 | } 1121 | } 1122 | } 1123 | 1124 | void VariantObject::NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter& args) { 1125 | Isolate *isolate = args.GetIsolate(); 1126 | VariantObject *self = VariantObject::Unwrap(args.This()); 1127 | if (!self) { 1128 | isolate->ThrowException(DispErrorInvalid(isolate)); 1129 | return; 1130 | } 1131 | Local result; 1132 | if ((self->value.vt & VT_ARRAY) == 0) { 1133 | result = Variant2Value(isolate, self->value); 1134 | } 1135 | else { 1136 | CComVariant value; 1137 | if SUCCEEDED(self->value.ArrayGet((LONG)index, value)) { 1138 | result = Variant2Value(isolate, value); 1139 | } 1140 | } 1141 | args.GetReturnValue().Set(result); 1142 | } 1143 | 1144 | void VariantObject::NodeSet(Local name, Local val, const PropertyCallbackInfoSetter& args) { 1145 | Isolate *isolate = args.GetIsolate(); 1146 | VariantObject *self = VariantObject::Unwrap(args.This()); 1147 | if (!self) { 1148 | isolate->ThrowException(DispErrorInvalid(isolate)); 1149 | return; 1150 | } 1151 | isolate->ThrowException(DispError(isolate, E_NOTIMPL)); 1152 | } 1153 | 1154 | void VariantObject::NodeSetByIndex(uint32_t index, Local value, const PropertyCallbackInfoSetter& args) { 1155 | Isolate *isolate = args.GetIsolate(); 1156 | VariantObject *self = VariantObject::Unwrap(args.This()); 1157 | if (!self) { 1158 | isolate->ThrowException(DispErrorInvalid(isolate)); 1159 | return; 1160 | } 1161 | isolate->ThrowException(DispError(isolate, E_NOTIMPL)); 1162 | } 1163 | 1164 | Local VariantObject::NodeCreate(Isolate* isolate, const VARIANT& var) { 1165 | Local self; 1166 | if (!inst_template.IsEmpty()) { 1167 | if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) { 1168 | (new VariantObject(var))->Wrap(self); 1169 | } 1170 | } 1171 | return self; 1172 | } 1173 | 1174 | //------------------------------------------------------------------------------------------------------- 1175 | 1176 | ConnectionPointObject::ConnectionPointObject(IConnectionPoint *p, IDispatch *d) 1177 | : ptr(p), disp(d) { 1178 | InitIndex(); 1179 | } 1180 | 1181 | bool ConnectionPointObject::InitIndex() { 1182 | if (!ptr || !disp) { 1183 | return false; 1184 | } 1185 | UINT typeindex = 0; 1186 | CComPtr typeinfo; 1187 | if FAILED(disp->GetTypeInfo(typeindex, LOCALE_USER_DEFAULT, &typeinfo)) { 1188 | return false; 1189 | } 1190 | 1191 | CComPtr typelib; 1192 | if FAILED(typeinfo->GetContainingTypeLib(&typelib, &typeindex)) { 1193 | return false; 1194 | } 1195 | 1196 | IID conniid; 1197 | if FAILED(ptr->GetConnectionInterface(&conniid)) { 1198 | return false; 1199 | } 1200 | 1201 | CComPtr conninfo; 1202 | if FAILED(typelib->GetTypeInfoOfGuid(conniid, &conninfo)) { 1203 | return false; 1204 | } 1205 | 1206 | TYPEATTR *typeattr = nullptr; 1207 | if FAILED(conninfo->GetTypeAttr(&typeattr)) { 1208 | return false; 1209 | } 1210 | 1211 | if (typeattr->typekind != TKIND_DISPATCH) { 1212 | conninfo->ReleaseTypeAttr(typeattr); 1213 | return false; 1214 | } 1215 | 1216 | for (UINT fd = 0; fd < typeattr->cFuncs; ++fd) { 1217 | FUNCDESC *funcdesc; 1218 | if FAILED(conninfo->GetFuncDesc(fd, &funcdesc)) { 1219 | continue; 1220 | } 1221 | if (!funcdesc) { 1222 | break; 1223 | } 1224 | 1225 | if (funcdesc->invkind != INVOKE_FUNC || funcdesc->funckind != FUNC_DISPATCH) { 1226 | conninfo->ReleaseFuncDesc(funcdesc); 1227 | continue; 1228 | } 1229 | 1230 | // const size_t nameSize = 256; 1231 | const size_t nameSize = 1; // only event function name required 1232 | BSTR bstrNames[nameSize]; 1233 | UINT maxNames = nameSize; 1234 | UINT maxNamesOut = 0; 1235 | if SUCCEEDED(conninfo->GetNames(funcdesc->memid, reinterpret_cast(&bstrNames), maxNames, &maxNamesOut)) { 1236 | DISPID id = funcdesc->memid; 1237 | std::wstring funcname(bstrNames[0]); 1238 | index.insert(std::pair(id, new DispObjectImpl::name_t(id, funcname))); 1239 | 1240 | for (size_t i = 0; i < maxNamesOut; i++) { 1241 | SysFreeString(bstrNames[i]); 1242 | } 1243 | } 1244 | 1245 | conninfo->ReleaseFuncDesc(funcdesc); 1246 | } 1247 | 1248 | conninfo->ReleaseTypeAttr(typeattr); 1249 | 1250 | return true; 1251 | } 1252 | 1253 | Local ConnectionPointObject::NodeCreateInstance(Isolate *isolate, IConnectionPoint *p, IDispatch* d) { 1254 | Local self; 1255 | if (!inst_template.IsEmpty()) { 1256 | if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) { 1257 | (new ConnectionPointObject(p, d))->Wrap(self); 1258 | } 1259 | } 1260 | return self; 1261 | } 1262 | 1263 | void ConnectionPointObject::NodeInit(const Local &target, Isolate* isolate, Local &ctx) { 1264 | 1265 | // Prepare constructor template 1266 | Local clazz = FunctionTemplate::New(isolate, NodeCreate); 1267 | clazz->SetClassName(v8str(isolate, "ConnectionPoint")); 1268 | 1269 | NODE_SET_PROTOTYPE_METHOD(clazz, "advise", NodeAdvise); 1270 | NODE_SET_PROTOTYPE_METHOD(clazz, "unadvise", NodeUnadvise); 1271 | NODE_SET_PROTOTYPE_METHOD(clazz, "getMethods", NodeConnectionPointMethods); 1272 | 1273 | Local inst = clazz->InstanceTemplate(); 1274 | inst->SetInternalFieldCount(1); 1275 | 1276 | inst_template.Reset(isolate, inst); 1277 | clazz_template.Reset(isolate, clazz); 1278 | //target->Set(v8str(isolate, "ConnectionPoint"), clazz->GetFunction()); 1279 | NODE_DEBUG_MSG("ConnectionPointObject initialized"); 1280 | } 1281 | 1282 | void ConnectionPointObject::NodeCreate(const FunctionCallbackInfo &args) { 1283 | Isolate *isolate = args.GetIsolate(); 1284 | Local self = args.This(); 1285 | (new ConnectionPointObject(args))->Wrap(self); 1286 | args.GetReturnValue().Set(self); 1287 | } 1288 | 1289 | void ConnectionPointObject::NodeAdvise(const FunctionCallbackInfo &args) { 1290 | Isolate *isolate = args.GetIsolate(); 1291 | ConnectionPointObject *self = ConnectionPointObject::Unwrap(args.This()); 1292 | if (!self || !self->ptr) { 1293 | isolate->ThrowException(DispErrorInvalid(isolate)); 1294 | return; 1295 | } 1296 | CComPtr unk; 1297 | int argcnt = args.Length(); 1298 | if (argcnt > 0) { 1299 | Local val = args[0]; 1300 | if (!Value2Unknown(isolate, val, &unk)) { 1301 | Local obj; 1302 | if (!val.IsEmpty() && val->IsObject() && val->ToObject(isolate->GetCurrentContext()).ToLocal(&obj)) { 1303 | 1304 | // .NET Connection Points require to implement specific interface 1305 | // So we need to remember its IID for the case when Container does QueryInterface for it 1306 | IID connif; 1307 | self->ptr->GetConnectionInterface(&connif); 1308 | DispObjectImpl *impl = new DispObjectImpl(obj, false, connif); 1309 | // It requires reversed arguments 1310 | impl->reverse_arguments = true; 1311 | impl->index = self->index; 1312 | if (self->index.size()) { 1313 | impl->dispid_next = self->index.rbegin()->first + 1; 1314 | } 1315 | unk.Attach(impl); 1316 | } 1317 | } 1318 | } 1319 | if (!unk) { 1320 | isolate->ThrowException(InvalidArgumentsError(isolate)); 1321 | return; 1322 | } 1323 | DWORD dwCookie; 1324 | HRESULT hrcode = self->ptr->Advise(unk, &dwCookie); 1325 | if FAILED(hrcode) { 1326 | isolate->ThrowException(DispError(isolate, hrcode)); 1327 | return; 1328 | } 1329 | self->cookies.insert(dwCookie); 1330 | args.GetReturnValue().Set(v8::Integer::New(isolate, (uint32_t)dwCookie)); 1331 | } 1332 | 1333 | void ConnectionPointObject::NodeUnadvise(const FunctionCallbackInfo &args) { 1334 | Isolate *isolate = args.GetIsolate(); 1335 | Local ctx = isolate->GetCurrentContext(); 1336 | ConnectionPointObject *self = ConnectionPointObject::Unwrap(args.This()); 1337 | if (!self || !self->ptr) { 1338 | isolate->ThrowException(DispErrorInvalid(isolate)); 1339 | return; 1340 | } 1341 | 1342 | if (args.Length()==0 || !args[0]->IsUint32()) { 1343 | isolate->ThrowException(InvalidArgumentsError(isolate)); 1344 | return; 1345 | } 1346 | DWORD dwCookie = (args[0]->Uint32Value(ctx)).FromMaybe(0); 1347 | if (dwCookie == 0 || self->cookies.find(dwCookie) == self->cookies.end()) { 1348 | isolate->ThrowException(InvalidArgumentsError(isolate)); 1349 | return; 1350 | } 1351 | self->cookies.erase(dwCookie); 1352 | HRESULT hrcode = self->ptr->Unadvise(dwCookie); 1353 | if FAILED(hrcode) { 1354 | isolate->ThrowException(DispError(isolate, hrcode)); 1355 | return; 1356 | } 1357 | } 1358 | 1359 | void ConnectionPointObject::NodeConnectionPointMethods(const FunctionCallbackInfo& args) { 1360 | Isolate* isolate = args.GetIsolate(); 1361 | Local ctx = isolate->GetCurrentContext(); 1362 | Local items = Array::New(isolate); 1363 | 1364 | ConnectionPointObject* self = ConnectionPointObject::Unwrap(args.This()); 1365 | 1366 | DispObjectImpl::index_t::iterator it; 1367 | uint32_t cnt = 0; 1368 | 1369 | for (it = self->index.begin(); it != self->index.end(); it++) 1370 | { 1371 | items->Set(ctx, cnt++, v8str(isolate, it->second->name.c_str())); 1372 | } 1373 | 1374 | args.GetReturnValue().Set(items); 1375 | } 1376 | 1377 | //------------------------------------------------------------------------------------------------------- 1378 | -------------------------------------------------------------------------------- /src/disp.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------------------------- 2 | // Project: NodeActiveX 3 | // Author: Yuri Dursin 4 | // Description: DispObject class declarations. This class incapsulates COM IDispatch interface to Node JS Object 5 | //------------------------------------------------------------------------------------------------------- 6 | 7 | #pragma once 8 | 9 | #include "utils.h" 10 | 11 | enum options_t { 12 | option_none = 0, 13 | option_async = 0x0001, 14 | option_type = 0x0002, 15 | option_activate = 0x0004, 16 | option_prepared = 0x0100, 17 | option_owned = 0x0200, 18 | option_property = 0x0400, 19 | option_function_simple = 0x0800, 20 | option_mask = 0x00FF, 21 | option_auto = (option_async | option_type) 22 | }; 23 | 24 | inline bool TypeInfoGetName(ITypeInfo *info, DISPID dispid, BSTR *name) { 25 | HRESULT hrcode = info->GetDocumentation(dispid, name, NULL, NULL, NULL); 26 | if SUCCEEDED(hrcode) return true; 27 | UINT cnt_ret; 28 | return info->GetNames(dispid, name, 1, &cnt_ret) == S_OK && cnt_ret > 0; 29 | } 30 | 31 | template 32 | bool TypeInfoPrepareFunc(ITypeInfo *info, UINT n, T process) { 33 | FUNCDESC *desc; 34 | if (info->GetFuncDesc(n, &desc) != S_OK) return false; 35 | process(info, desc, nullptr); 36 | info->ReleaseFuncDesc(desc); 37 | return true; 38 | } 39 | 40 | template 41 | bool TypeInfoPrepareVar(ITypeInfo *info, UINT n, T process) { 42 | VARDESC *desc; 43 | if (info->GetVarDesc(n, &desc) != S_OK) return false; 44 | process(info, nullptr, desc); 45 | info->ReleaseVarDesc(desc); 46 | return true; 47 | } 48 | 49 | template 50 | void TypeInfoPrepare(ITypeInfo *info, int mode, T process) { 51 | UINT cFuncs = 0, cVars = 0; 52 | TYPEATTR *pattr = NULL; 53 | if (info->GetTypeAttr(&pattr) == S_OK) { 54 | cFuncs = pattr->cFuncs; 55 | cVars = pattr->cVars; 56 | info->ReleaseTypeAttr(pattr); 57 | } 58 | if ((mode & 1) != 0) { 59 | for (UINT n = 0; n < cFuncs; n++) { 60 | TypeInfoPrepareFunc(info, n, process); 61 | } 62 | } 63 | if ((mode & 2) != 0) { 64 | for (UINT n = 0; n < cVars; n++) { 65 | TypeInfoPrepareVar(info, n, process); 66 | } 67 | } 68 | } 69 | 70 | template 71 | bool TypeLibEnumerate(ITypeLib *typelib, int mode, T process) { 72 | UINT i, cnt = typelib ? typelib->GetTypeInfoCount() : 0; 73 | for (i = 0; i < cnt; i++) { 74 | CComPtr info; 75 | if (typelib->GetTypeInfo(i, &info) != S_OK) continue; 76 | TypeInfoPrepare(info, mode, process); 77 | } 78 | return cnt > 0; 79 | } 80 | 81 | class DispInfo { 82 | public: 83 | std::weak_ptr parent; 84 | CComPtr ptr; 85 | std::wstring name; 86 | int options; 87 | bool bManaged; 88 | 89 | struct type_t { 90 | DISPID dispid; 91 | int kind; 92 | int argcnt_get; 93 | inline type_t(DISPID dispid_, int kind_) : dispid(dispid_), kind(kind_), argcnt_get(0) {} 94 | inline bool is_property() const { return ((kind & INVOKE_FUNC) == 0); } 95 | inline bool is_property_simple() const { return (((kind & (INVOKE_PROPERTYGET | INVOKE_FUNC))) == INVOKE_PROPERTYGET) && (argcnt_get == 0); } 96 | inline bool is_function_simple() const { return (((kind & (INVOKE_PROPERTYGET | INVOKE_FUNC))) == INVOKE_FUNC) && (argcnt_get == 0); } 97 | inline bool is_property_advanced() const { return kind == (INVOKE_PROPERTYGET | INVOKE_PROPERTYPUT) && (argcnt_get == 1); } 98 | }; 99 | typedef std::shared_ptr type_ptr; 100 | typedef std::map types_by_dispid_t; 101 | types_by_dispid_t types_by_dispid; 102 | 103 | inline DispInfo(IDispatch *disp, const std::wstring &nm, int opt, std::shared_ptr *parnt = nullptr) 104 | : ptr(disp), options(opt), name(nm), bManaged(false) 105 | { 106 | if (parnt) parent = *parnt; 107 | if ((options & option_type) != 0) 108 | Prepare(disp); 109 | } 110 | 111 | void Prepare(IDispatch *disp) { 112 | Enumerate(1/*functions only*/, &bManaged, [this](ITypeInfo *info, FUNCDESC *func, VARDESC *var) { 113 | type_ptr &ptr = this->types_by_dispid[func->memid]; 114 | if (!ptr) ptr.reset(new type_t(func->memid, func->invkind)); 115 | else ptr->kind |= func->invkind; 116 | if ((func->invkind & INVOKE_PROPERTYGET) != 0) { 117 | if (func->cParams > ptr->argcnt_get) 118 | ptr->argcnt_get = func->cParams; 119 | } 120 | }); 121 | bool prepared = types_by_dispid.size() > 3; // QueryInterface, AddRef, Release 122 | if (prepared) options |= option_prepared; 123 | } 124 | 125 | inline bool GetTypeInfo(const DISPID dispid, type_ptr &info) { 126 | if ((options & option_prepared) == 0) return false; 127 | types_by_dispid_t::const_iterator it = types_by_dispid.find(dispid); 128 | if (it == types_by_dispid.end()) return false; 129 | info = it->second; 130 | return true; 131 | } 132 | 133 | HRESULT FindProperty(LPOLESTR name, DISPID *dispid) { 134 | return DispFind(ptr, name, dispid); 135 | } 136 | 137 | HRESULT GetProperty(DISPID dispid, LONG argcnt, VARIANT *args, VARIANT *value, EXCEPINFO *except = 0) { 138 | HRESULT hrcode = DispInvoke(ptr, dispid, argcnt, args, value, DISPATCH_PROPERTYGET, except); 139 | return hrcode; 140 | } 141 | 142 | HRESULT GetProperty(DISPID dispid, LONG index, VARIANT *value, EXCEPINFO *except = 0) { 143 | CComVariant arg(index); 144 | LONG argcnt = (index >= 0) ? 1 : 0; 145 | return DispInvoke(ptr, dispid, argcnt, &arg, value, DISPATCH_PROPERTYGET, except); 146 | } 147 | 148 | HRESULT SetProperty(DISPID dispid, LONG argcnt, VARIANT *args, VARIANT *value, EXCEPINFO *except = 0) { 149 | HRESULT hrcode = DispInvoke(ptr, dispid, argcnt, args, value, DISPATCH_PROPERTYPUT, except); 150 | if FAILED(hrcode) value->vt = VT_EMPTY; 151 | return hrcode; 152 | } 153 | 154 | HRESULT ExecuteMethod(DISPID dispid, LONG argcnt, VARIANT *args, VARIANT *value, EXCEPINFO *except = 0) { 155 | HRESULT hrcode = DispInvoke(ptr, dispid, argcnt, args, value, DISPATCH_METHOD, except); 156 | return hrcode; 157 | } 158 | 159 | template 160 | bool Enumerate(int mode, bool* checkManaged, T process = nullptr) { 161 | UINT i, cnt; 162 | CComPtr prevtypelib; 163 | if (!ptr || FAILED(ptr->GetTypeInfoCount(&cnt))) cnt = 0; 164 | else for (i = 0; i < cnt; i++) { 165 | CComPtr info; 166 | if (ptr->GetTypeInfo(i, 0, &info) != S_OK) continue; 167 | 168 | // Query type library 169 | UINT typeindex; 170 | CComPtr typelib; 171 | if (info->GetContainingTypeLib(&typelib, &typeindex) == S_OK) { 172 | 173 | // Check if typelib is managed 174 | if (checkManaged != nullptr && !*checkManaged) { 175 | CComPtr typeLib2; 176 | if (SUCCEEDED(typelib->QueryInterface(IID_ITypeLib2, (void**)&typeLib2))) { 177 | 178 | // {90883F05-3D28-11D2-8F17-00A0C9A6186D} 179 | static const GUID GUID_ExportedFromComPlus = { 0x90883F05, 0x3D28, 0x11D2, { 0x8F, 0x17, 0x00, 0xA0, 0xC9, 0xA6, 0x18, 0x6D } }; 180 | 181 | CComVariant cv; 182 | if (SUCCEEDED(typeLib2->GetCustData(GUID_ExportedFromComPlus, &cv))) { 183 | *checkManaged = true; 184 | } 185 | } 186 | } 187 | 188 | // Enumerate all types in library types 189 | // May be very slow! need a special method 190 | // if (typelib != prevtypelib) { 191 | // TypeLibEnumerate(typelib, mode, process); 192 | // prevtypelib.Attach(typelib.Detach()); 193 | // } 194 | 195 | CComPtr tinfo; 196 | if (typelib->GetTypeInfo(typeindex, &tinfo) == S_OK) { 197 | TypeInfoPrepare(tinfo, mode, process); 198 | } 199 | } 200 | 201 | // Process types 202 | else { 203 | TypeInfoPrepare(info, mode, process); 204 | } 205 | } 206 | return cnt > 0; 207 | } 208 | }; 209 | 210 | typedef std::shared_ptr DispInfoPtr; 211 | 212 | class DispObject: public NodeObject 213 | { 214 | public: 215 | DispObject(const DispInfoPtr &ptr, const std::wstring &name, DISPID id = DISPID_UNKNOWN, LONG indx = -1, int opt = 0); 216 | ~DispObject(); 217 | 218 | static Persistent inst_template; 219 | static Persistent clazz_template; 220 | static NodeMethods clazz_methods; 221 | 222 | static void NodeInit(const Local &target, Isolate* isolate, Local &ctx); 223 | static bool HasInstance(Isolate *isolate, const Local &obj) { 224 | Local clazz = clazz_template.Get(isolate); 225 | return !clazz.IsEmpty() && clazz->HasInstance(obj); 226 | } 227 | static DispObject *GetPtr(Isolate *isolate, const Local &obj) { 228 | Local clazz = clazz_template.Get(isolate); 229 | if (clazz.IsEmpty() || !clazz->HasInstance(obj)) return nullptr; 230 | return Unwrap(obj); 231 | } 232 | static IDispatch *GetDispPtr(Isolate *isolate, const Local &obj) { 233 | DispObject *self = GetPtr(isolate, obj); 234 | return (self && self->disp) ? self->disp->ptr : nullptr; 235 | } 236 | static bool GetValueOf(Isolate *isolate, const Local &obj, VARIANT &value) { 237 | DispObject *self = GetPtr(isolate, obj); 238 | return self && SUCCEEDED(self->valueOf(isolate, value, false)); 239 | } 240 | static Local NodeCreate(Isolate *isolate, IDispatch *disp, const std::wstring &name, int opt) { 241 | Local parent; 242 | DispInfoPtr ptr(new DispInfo(disp, name, opt)); 243 | return DispObject::NodeCreate(isolate, parent, ptr, name); 244 | } 245 | 246 | private: 247 | static Local NodeCreate(Isolate *isolate, const Local &parent, const DispInfoPtr &ptr, const std::wstring &name, DISPID id = DISPID_UNKNOWN, LONG indx = -1, int opt = 0); 248 | 249 | static void NodeCreate(const FunctionCallbackInfo &args); 250 | static void NodeValueOf(const FunctionCallbackInfo &args); 251 | static void NodeToString(const FunctionCallbackInfo &args); 252 | static void NodeRelease(const FunctionCallbackInfo &args); 253 | static void NodeCast(const FunctionCallbackInfo &args); 254 | static void NodeGet(Local name, const PropertyCallbackInfoGetter &args); 255 | static void NodeSet(Local name, Local value, const PropertyCallbackInfoSetter &args); 256 | static void NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter&args); 257 | static void NodeSetByIndex(uint32_t index, Local value, const PropertyCallbackInfoSetter &args); 258 | static void NodeCall(const FunctionCallbackInfo &args); 259 | 260 | #ifdef NODE_INTERCEPTED 261 | static inline Intercepted InterceptedNodeGet(Local name, const PropertyCallbackInfoGetter& args) { 262 | NodeGet(name, args); 263 | return Intercepted::kYes; 264 | } 265 | static inline Intercepted InterceptedNodeSet(Local name, Local value, const PropertyCallbackInfoSetter& args) { 266 | NodeSet(name, value, args); 267 | return Intercepted::kYes; 268 | } 269 | static inline Intercepted InterceptedNodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter& args) { 270 | NodeGetByIndex(index, args); 271 | return Intercepted::kYes; 272 | } 273 | static inline Intercepted InterceptedNodeSetByIndex(uint32_t index, Local value, const PropertyCallbackInfoSetter& args) { 274 | NodeSetByIndex(index, value, args); 275 | return Intercepted::kYes; 276 | } 277 | #endif 278 | 279 | static void NodeConnectionPoints(const FunctionCallbackInfo &args); 280 | static void PeakAndDispatchMessages(const FunctionCallbackInfo &args); 281 | 282 | protected: 283 | bool release(); 284 | bool get(LPOLESTR tag, LONG index, const PropertyCallbackInfoGetter &args); 285 | bool set(LPOLESTR tag, LONG index, const Local &value, const PropertyCallbackInfoSetter &args); 286 | void call(Isolate *isolate, const FunctionCallbackInfo &args); 287 | 288 | HRESULT valueOf(Isolate *isolate, VARIANT &value, bool simple); 289 | HRESULT valueOf(Isolate *isolate, const Local &self, Local &value); 290 | void toString(const FunctionCallbackInfo &args); 291 | Local getIdentity(Isolate *isolate); 292 | 293 | private: 294 | int options; 295 | inline bool is_null() { return !disp; } 296 | inline bool is_prepared() { return (options & option_prepared) != 0; } 297 | inline bool is_object() { return dispid == DISPID_VALUE /*&& index < 0*/; } 298 | inline bool is_owned() { return (options & option_owned) != 0; } 299 | 300 | Persistent items, methods, vars; 301 | void initTypeInfo(Isolate *isolate); 302 | 303 | DispInfoPtr disp; 304 | std::wstring name; 305 | DISPID dispid; 306 | LONG index; 307 | 308 | HRESULT prepare(); 309 | }; 310 | 311 | class VariantObject : public NodeObject 312 | { 313 | public: 314 | VariantObject() {}; 315 | VariantObject(const VARIANT &v) : value(v) {}; 316 | VariantObject(const FunctionCallbackInfo &args); 317 | 318 | static Persistent inst_template; 319 | static Persistent clazz_template; 320 | static NodeMethods clazz_methods; 321 | 322 | static void NodeInit(const Local &target, Isolate* isolate, Local &ctx); 323 | static bool HasInstance(Isolate *isolate, const Local &obj) { 324 | Local clazz = clazz_template.Get(isolate); 325 | return !clazz.IsEmpty() && clazz->HasInstance(obj); 326 | } 327 | static VariantObject *GetInstanceOf(Isolate *isolate, const Local &obj) { 328 | Local clazz = clazz_template.Get(isolate); 329 | if (clazz.IsEmpty() || !clazz->HasInstance(obj)) return nullptr; 330 | return Unwrap(obj); 331 | } 332 | static bool GetValueOf(Isolate *isolate, const Local &obj, VARIANT &value) { 333 | Local clazz = clazz_template.Get(isolate); 334 | if (clazz.IsEmpty() || !clazz->HasInstance(obj)) return false; 335 | VariantObject *self = Unwrap(obj); 336 | return self && SUCCEEDED(self->value.CopyTo(&value)); 337 | } 338 | 339 | static Local NodeCreateInstance(const FunctionCallbackInfo &args); 340 | static void NodeCreate(const FunctionCallbackInfo &args); 341 | static Local NodeCreate(Isolate* isolate, const VARIANT& var); 342 | 343 | static void NodeClear(const FunctionCallbackInfo &args); 344 | static void NodeAssign(const FunctionCallbackInfo &args); 345 | static void NodeCast(const FunctionCallbackInfo &args); 346 | static void NodeValueOf(const FunctionCallbackInfo &args); 347 | static void NodeToString(const FunctionCallbackInfo &args); 348 | static void NodeGet(Local name, const PropertyCallbackInfoGetter &args); 349 | static void NodeSet(Local name, Local value, const PropertyCallbackInfoSetter&args); 350 | static void NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter&args); 351 | static void NodeSetByIndex(uint32_t index, Local value, const PropertyCallbackInfoSetter&args); 352 | 353 | #ifdef NODE_INTERCEPTED 354 | static inline Intercepted InterceptedNodeGet(Local name, const PropertyCallbackInfoGetter& args) { 355 | NodeGet(name, args); 356 | return Intercepted::kYes; 357 | } 358 | static inline Intercepted InterceptedNodeSet(Local name, Local value, const PropertyCallbackInfoSetter& args) { 359 | NodeSet(name, value, args); 360 | return Intercepted::kYes; 361 | } 362 | static inline Intercepted InterceptedNodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter& args) { 363 | NodeGetByIndex(index, args); 364 | return Intercepted::kYes; 365 | } 366 | static inline Intercepted InterceptedNodeSetByIndex(uint32_t index, Local value, const PropertyCallbackInfoSetter& args) { 367 | NodeSetByIndex(index, value, args); 368 | return Intercepted::kYes; 369 | } 370 | #endif 371 | 372 | private: 373 | CComVariant value, pvalue; 374 | bool assign(Isolate *isolate, Local &val, Local &type); 375 | }; 376 | 377 | class ConnectionPointObject : public NodeObject 378 | { 379 | public: 380 | ConnectionPointObject(IConnectionPoint *p, IDispatch* d); 381 | ConnectionPointObject(const FunctionCallbackInfo &args) {} 382 | static Persistent inst_template; 383 | static Persistent clazz_template; 384 | static Local NodeCreateInstance(Isolate *isolate, IConnectionPoint *p, IDispatch* d); 385 | static void NodeInit(const Local &target, Isolate* isolate, Local &ctx); 386 | static void NodeCreate(const FunctionCallbackInfo &args); 387 | static void NodeAdvise(const FunctionCallbackInfo &args); 388 | static void NodeUnadvise(const FunctionCallbackInfo &args); 389 | static void NodeConnectionPointMethods(const FunctionCallbackInfo &args); 390 | 391 | private: 392 | bool InitIndex(); 393 | 394 | CComPtr ptr; 395 | CComPtr disp; 396 | DispObjectImpl::index_t index; 397 | 398 | std::unordered_set cookies; 399 | 400 | }; 401 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------------------------- 2 | // Project: node-activex 3 | // Author: Yuri Dursin 4 | // Description: Defines the entry point for the NodeJS addon 5 | //------------------------------------------------------------------------------------------------------- 6 | 7 | #include "stdafx.h" 8 | #include "disp.h" 9 | 10 | //---------------------------------------------------------------------------------- 11 | 12 | // Initialize this addon 13 | NODE_MODULE_INIT(/*exports, module, context*/) { 14 | Isolate* isolate = context->GetIsolate(); 15 | 16 | DispObject::NodeInit(exports, isolate, context); 17 | VariantObject::NodeInit(exports, isolate, context); 18 | ConnectionPointObject::NodeInit(exports, isolate, context); 19 | 20 | // Sleep is essential to have proper WScript emulation 21 | exports->Set(context, 22 | String::NewFromUtf8(isolate, "winaxsleep", NewStringType::kNormal) 23 | .ToLocalChecked(), 24 | FunctionTemplate::New(isolate, WinaxSleep) 25 | ->GetFunction(context).ToLocalChecked()).FromJust(); 26 | 27 | /* Example implementation of a context-aware addon: 28 | // Create a new instance of `AddonData` for this instance of the addon and 29 | // tie its life cycle to that of the Node.js environment. 30 | AddonData* data = new AddonData(isolate); 31 | 32 | // Wrap the data in a `v8::External` so we can pass it to the method we 33 | // expose. 34 | Local external = External::New(isolate, data); 35 | 36 | // Expose the method `Method` to JavaScript, and make sure it receives the 37 | // per-addon-instance data we created above by passing `external` as the 38 | // third parameter to the `FunctionTemplate` constructor. 39 | exports->Set(context, 40 | String::NewFromUtf8(isolate, "method", NewStringType::kNormal) 41 | .ToLocalChecked(), 42 | FunctionTemplate::New(isolate, Method, external) 43 | ->GetFunction(context).ToLocalChecked()).FromJust(); 44 | */ 45 | } 46 | 47 | //---------------------------------------------------------------------------------- 48 | 49 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved) { 50 | 51 | switch (ulReason) { 52 | case DLL_PROCESS_ATTACH: 53 | CoInitialize(0); 54 | break; 55 | case DLL_PROCESS_DETACH: 56 | CoUninitialize(); 57 | break; 58 | case DLL_THREAD_ATTACH: 59 | break; 60 | case DLL_THREAD_DETACH: 61 | break; 62 | } 63 | return TRUE; 64 | } 65 | 66 | //---------------------------------------------------------------------------------- 67 | -------------------------------------------------------------------------------- /src/stdafx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // To remove conflicts with recent v8 code std::numeric_limits::max() 4 | #ifndef NOMINMAX 5 | #define NOMINMAX 6 | #endif 7 | 8 | #include 9 | 10 | // Windows Header Files: 11 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 12 | #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit 13 | #include 14 | 15 | // ATL headers 16 | //#define USE_ATL 17 | #ifdef USE_ATL 18 | #include 19 | #include 20 | #else 21 | #include 22 | #include 23 | #endif 24 | 25 | // STD headers 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | // Node JS headers 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | using namespace v8; 42 | using namespace node; 43 | 44 | // Application defines 45 | //#define TEST_ADVISE -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------------------------- 2 | // Project: NodeActiveX 3 | // Author: Yuri Dursin 4 | // Description: Common utilities implementations 5 | //------------------------------------------------------------------------------------------------------- 6 | 7 | #include "stdafx.h" 8 | #include "disp.h" 9 | #include 10 | #include // for AccessibleObjectFromWindow 11 | #pragma comment(lib, "OleAcc.lib") // for AccessibleObjectFromWindow 12 | 13 | const GUID CLSID_DispObjectImpl = { 0x9dce8520, 0x2efe, 0x48c0,{ 0xa0, 0xdc, 0x95, 0x1b, 0x29, 0x18, 0x72, 0xc0 } }; 14 | 15 | const IID IID_IReflect = { 0xAFBF15E5, 0xC37C, 0x11D2,{ 0xB8, 0x8E, 0x00, 0xA0, 0xC9, 0xB4, 0x71, 0xB8} }; 16 | 17 | //------------------------------------------------------------------------------------------------------- 18 | 19 | #define ERROR_MESSAGE_WIDE_MAXSIZE 1024 20 | #define ERROR_MESSAGE_UTF8_MAXSIZE 2048 21 | 22 | void GetScodeString(HRESULT hr, wchar_t* buf, int bufSize) 23 | { 24 | struct HRESULT_ENTRY 25 | { 26 | HRESULT hr; 27 | LPCWSTR lpszName; 28 | }; 29 | #define MAKE_HRESULT_ENTRY(hr) { hr, L#hr } 30 | static const HRESULT_ENTRY hrNameTable[] = 31 | { 32 | MAKE_HRESULT_ENTRY(S_OK), 33 | MAKE_HRESULT_ENTRY(S_FALSE), 34 | 35 | MAKE_HRESULT_ENTRY(CACHE_S_FORMATETC_NOTSUPPORTED), 36 | MAKE_HRESULT_ENTRY(CACHE_S_SAMECACHE), 37 | MAKE_HRESULT_ENTRY(CACHE_S_SOMECACHES_NOTUPDATED), 38 | MAKE_HRESULT_ENTRY(CONVERT10_S_NO_PRESENTATION), 39 | MAKE_HRESULT_ENTRY(DATA_S_SAMEFORMATETC), 40 | MAKE_HRESULT_ENTRY(DRAGDROP_S_CANCEL), 41 | MAKE_HRESULT_ENTRY(DRAGDROP_S_DROP), 42 | MAKE_HRESULT_ENTRY(DRAGDROP_S_USEDEFAULTCURSORS), 43 | MAKE_HRESULT_ENTRY(INPLACE_S_TRUNCATED), 44 | MAKE_HRESULT_ENTRY(MK_S_HIM), 45 | MAKE_HRESULT_ENTRY(MK_S_ME), 46 | MAKE_HRESULT_ENTRY(MK_S_MONIKERALREADYREGISTERED), 47 | MAKE_HRESULT_ENTRY(MK_S_REDUCED_TO_SELF), 48 | MAKE_HRESULT_ENTRY(MK_S_US), 49 | MAKE_HRESULT_ENTRY(OLE_S_MAC_CLIPFORMAT), 50 | MAKE_HRESULT_ENTRY(OLE_S_STATIC), 51 | MAKE_HRESULT_ENTRY(OLE_S_USEREG), 52 | MAKE_HRESULT_ENTRY(OLEOBJ_S_CANNOT_DOVERB_NOW), 53 | MAKE_HRESULT_ENTRY(OLEOBJ_S_INVALIDHWND), 54 | MAKE_HRESULT_ENTRY(OLEOBJ_S_INVALIDVERB), 55 | MAKE_HRESULT_ENTRY(OLEOBJ_S_LAST), 56 | MAKE_HRESULT_ENTRY(STG_S_CONVERTED), 57 | MAKE_HRESULT_ENTRY(VIEW_S_ALREADY_FROZEN), 58 | 59 | MAKE_HRESULT_ENTRY(E_UNEXPECTED), 60 | MAKE_HRESULT_ENTRY(E_NOTIMPL), 61 | MAKE_HRESULT_ENTRY(E_OUTOFMEMORY), 62 | MAKE_HRESULT_ENTRY(E_INVALIDARG), 63 | MAKE_HRESULT_ENTRY(E_NOINTERFACE), 64 | MAKE_HRESULT_ENTRY(E_POINTER), 65 | MAKE_HRESULT_ENTRY(E_HANDLE), 66 | MAKE_HRESULT_ENTRY(E_ABORT), 67 | MAKE_HRESULT_ENTRY(E_FAIL), 68 | MAKE_HRESULT_ENTRY(E_ACCESSDENIED), 69 | 70 | MAKE_HRESULT_ENTRY(CACHE_E_NOCACHE_UPDATED), 71 | MAKE_HRESULT_ENTRY(CLASS_E_CLASSNOTAVAILABLE), 72 | MAKE_HRESULT_ENTRY(CLASS_E_NOAGGREGATION), 73 | MAKE_HRESULT_ENTRY(CLIPBRD_E_BAD_DATA), 74 | MAKE_HRESULT_ENTRY(CLIPBRD_E_CANT_CLOSE), 75 | MAKE_HRESULT_ENTRY(CLIPBRD_E_CANT_EMPTY), 76 | MAKE_HRESULT_ENTRY(CLIPBRD_E_CANT_OPEN), 77 | MAKE_HRESULT_ENTRY(CLIPBRD_E_CANT_SET), 78 | MAKE_HRESULT_ENTRY(CO_E_ALREADYINITIALIZED), 79 | MAKE_HRESULT_ENTRY(CO_E_APPDIDNTREG), 80 | MAKE_HRESULT_ENTRY(CO_E_APPNOTFOUND), 81 | MAKE_HRESULT_ENTRY(CO_E_APPSINGLEUSE), 82 | MAKE_HRESULT_ENTRY(CO_E_BAD_PATH), 83 | MAKE_HRESULT_ENTRY(CO_E_CANTDETERMINECLASS), 84 | MAKE_HRESULT_ENTRY(CO_E_CLASS_CREATE_FAILED), 85 | MAKE_HRESULT_ENTRY(CO_E_CLASSSTRING), 86 | MAKE_HRESULT_ENTRY(CO_E_DLLNOTFOUND), 87 | MAKE_HRESULT_ENTRY(CO_E_ERRORINAPP), 88 | MAKE_HRESULT_ENTRY(CO_E_ERRORINDLL), 89 | MAKE_HRESULT_ENTRY(CO_E_IIDSTRING), 90 | MAKE_HRESULT_ENTRY(CO_E_NOTINITIALIZED), 91 | MAKE_HRESULT_ENTRY(CO_E_OBJISREG), 92 | MAKE_HRESULT_ENTRY(CO_E_OBJNOTCONNECTED), 93 | MAKE_HRESULT_ENTRY(CO_E_OBJNOTREG), 94 | MAKE_HRESULT_ENTRY(CO_E_OBJSRV_RPC_FAILURE), 95 | MAKE_HRESULT_ENTRY(CO_E_SCM_ERROR), 96 | MAKE_HRESULT_ENTRY(CO_E_SCM_RPC_FAILURE), 97 | MAKE_HRESULT_ENTRY(CO_E_SERVER_EXEC_FAILURE), 98 | MAKE_HRESULT_ENTRY(CO_E_SERVER_STOPPING), 99 | MAKE_HRESULT_ENTRY(CO_E_WRONGOSFORAPP), 100 | MAKE_HRESULT_ENTRY(CONVERT10_E_OLESTREAM_BITMAP_TO_DIB), 101 | MAKE_HRESULT_ENTRY(CONVERT10_E_OLESTREAM_FMT), 102 | MAKE_HRESULT_ENTRY(CONVERT10_E_OLESTREAM_GET), 103 | MAKE_HRESULT_ENTRY(CONVERT10_E_OLESTREAM_PUT), 104 | MAKE_HRESULT_ENTRY(CONVERT10_E_STG_DIB_TO_BITMAP), 105 | MAKE_HRESULT_ENTRY(CONVERT10_E_STG_FMT), 106 | MAKE_HRESULT_ENTRY(CONVERT10_E_STG_NO_STD_STREAM), 107 | MAKE_HRESULT_ENTRY(DISP_E_ARRAYISLOCKED), 108 | MAKE_HRESULT_ENTRY(DISP_E_BADCALLEE), 109 | MAKE_HRESULT_ENTRY(DISP_E_BADINDEX), 110 | MAKE_HRESULT_ENTRY(DISP_E_BADPARAMCOUNT), 111 | MAKE_HRESULT_ENTRY(DISP_E_BADVARTYPE), 112 | MAKE_HRESULT_ENTRY(DISP_E_EXCEPTION), 113 | MAKE_HRESULT_ENTRY(DISP_E_MEMBERNOTFOUND), 114 | MAKE_HRESULT_ENTRY(DISP_E_NONAMEDARGS), 115 | MAKE_HRESULT_ENTRY(DISP_E_NOTACOLLECTION), 116 | MAKE_HRESULT_ENTRY(DISP_E_OVERFLOW), 117 | MAKE_HRESULT_ENTRY(DISP_E_PARAMNOTFOUND), 118 | MAKE_HRESULT_ENTRY(DISP_E_PARAMNOTOPTIONAL), 119 | MAKE_HRESULT_ENTRY(DISP_E_TYPEMISMATCH), 120 | MAKE_HRESULT_ENTRY(DISP_E_UNKNOWNINTERFACE), 121 | MAKE_HRESULT_ENTRY(DISP_E_UNKNOWNLCID), 122 | MAKE_HRESULT_ENTRY(DISP_E_UNKNOWNNAME), 123 | MAKE_HRESULT_ENTRY(DRAGDROP_E_ALREADYREGISTERED), 124 | MAKE_HRESULT_ENTRY(DRAGDROP_E_INVALIDHWND), 125 | MAKE_HRESULT_ENTRY(DRAGDROP_E_NOTREGISTERED), 126 | MAKE_HRESULT_ENTRY(DV_E_CLIPFORMAT), 127 | MAKE_HRESULT_ENTRY(DV_E_DVASPECT), 128 | MAKE_HRESULT_ENTRY(DV_E_DVTARGETDEVICE), 129 | MAKE_HRESULT_ENTRY(DV_E_DVTARGETDEVICE_SIZE), 130 | MAKE_HRESULT_ENTRY(DV_E_FORMATETC), 131 | MAKE_HRESULT_ENTRY(DV_E_LINDEX), 132 | MAKE_HRESULT_ENTRY(DV_E_NOIVIEWOBJECT), 133 | MAKE_HRESULT_ENTRY(DV_E_STATDATA), 134 | MAKE_HRESULT_ENTRY(DV_E_STGMEDIUM), 135 | MAKE_HRESULT_ENTRY(DV_E_TYMED), 136 | MAKE_HRESULT_ENTRY(INPLACE_E_NOTOOLSPACE), 137 | MAKE_HRESULT_ENTRY(INPLACE_E_NOTUNDOABLE), 138 | MAKE_HRESULT_ENTRY(MEM_E_INVALID_LINK), 139 | MAKE_HRESULT_ENTRY(MEM_E_INVALID_ROOT), 140 | MAKE_HRESULT_ENTRY(MEM_E_INVALID_SIZE), 141 | MAKE_HRESULT_ENTRY(MK_E_CANTOPENFILE), 142 | MAKE_HRESULT_ENTRY(MK_E_CONNECTMANUALLY), 143 | MAKE_HRESULT_ENTRY(MK_E_ENUMERATION_FAILED), 144 | MAKE_HRESULT_ENTRY(MK_E_EXCEEDEDDEADLINE), 145 | MAKE_HRESULT_ENTRY(MK_E_INTERMEDIATEINTERFACENOTSUPPORTED), 146 | MAKE_HRESULT_ENTRY(MK_E_INVALIDEXTENSION), 147 | MAKE_HRESULT_ENTRY(MK_E_MUSTBOTHERUSER), 148 | MAKE_HRESULT_ENTRY(MK_E_NEEDGENERIC), 149 | MAKE_HRESULT_ENTRY(MK_E_NO_NORMALIZED), 150 | MAKE_HRESULT_ENTRY(MK_E_NOINVERSE), 151 | MAKE_HRESULT_ENTRY(MK_E_NOOBJECT), 152 | MAKE_HRESULT_ENTRY(MK_E_NOPREFIX), 153 | MAKE_HRESULT_ENTRY(MK_E_NOSTORAGE), 154 | MAKE_HRESULT_ENTRY(MK_E_NOTBINDABLE), 155 | MAKE_HRESULT_ENTRY(MK_E_NOTBOUND), 156 | MAKE_HRESULT_ENTRY(MK_E_SYNTAX), 157 | MAKE_HRESULT_ENTRY(MK_E_UNAVAILABLE), 158 | MAKE_HRESULT_ENTRY(OLE_E_ADVF), 159 | MAKE_HRESULT_ENTRY(OLE_E_ADVISENOTSUPPORTED), 160 | MAKE_HRESULT_ENTRY(OLE_E_BLANK), 161 | MAKE_HRESULT_ENTRY(OLE_E_CANT_BINDTOSOURCE), 162 | MAKE_HRESULT_ENTRY(OLE_E_CANT_GETMONIKER), 163 | MAKE_HRESULT_ENTRY(OLE_E_CANTCONVERT), 164 | MAKE_HRESULT_ENTRY(OLE_E_CLASSDIFF), 165 | MAKE_HRESULT_ENTRY(OLE_E_ENUM_NOMORE), 166 | MAKE_HRESULT_ENTRY(OLE_E_INVALIDHWND), 167 | MAKE_HRESULT_ENTRY(OLE_E_INVALIDRECT), 168 | MAKE_HRESULT_ENTRY(OLE_E_NOCACHE), 169 | MAKE_HRESULT_ENTRY(OLE_E_NOCONNECTION), 170 | MAKE_HRESULT_ENTRY(OLE_E_NOSTORAGE), 171 | MAKE_HRESULT_ENTRY(OLE_E_NOT_INPLACEACTIVE), 172 | MAKE_HRESULT_ENTRY(OLE_E_NOTRUNNING), 173 | MAKE_HRESULT_ENTRY(OLE_E_OLEVERB), 174 | MAKE_HRESULT_ENTRY(OLE_E_PROMPTSAVECANCELLED), 175 | MAKE_HRESULT_ENTRY(OLE_E_STATIC), 176 | MAKE_HRESULT_ENTRY(OLE_E_WRONGCOMPOBJ), 177 | MAKE_HRESULT_ENTRY(OLEOBJ_E_INVALIDVERB), 178 | MAKE_HRESULT_ENTRY(OLEOBJ_E_NOVERBS), 179 | MAKE_HRESULT_ENTRY(REGDB_E_CLASSNOTREG), 180 | MAKE_HRESULT_ENTRY(REGDB_E_IIDNOTREG), 181 | MAKE_HRESULT_ENTRY(REGDB_E_INVALIDVALUE), 182 | MAKE_HRESULT_ENTRY(REGDB_E_KEYMISSING), 183 | MAKE_HRESULT_ENTRY(REGDB_E_READREGDB), 184 | MAKE_HRESULT_ENTRY(REGDB_E_WRITEREGDB), 185 | MAKE_HRESULT_ENTRY(RPC_E_ATTEMPTED_MULTITHREAD), 186 | MAKE_HRESULT_ENTRY(RPC_E_CALL_CANCELED), 187 | MAKE_HRESULT_ENTRY(RPC_E_CALL_REJECTED), 188 | MAKE_HRESULT_ENTRY(RPC_E_CANTCALLOUT_AGAIN), 189 | MAKE_HRESULT_ENTRY(RPC_E_CANTCALLOUT_INASYNCCALL), 190 | MAKE_HRESULT_ENTRY(RPC_E_CANTCALLOUT_INEXTERNALCALL), 191 | MAKE_HRESULT_ENTRY(RPC_E_CANTCALLOUT_ININPUTSYNCCALL), 192 | MAKE_HRESULT_ENTRY(RPC_E_CANTPOST_INSENDCALL), 193 | MAKE_HRESULT_ENTRY(RPC_E_CANTTRANSMIT_CALL), 194 | MAKE_HRESULT_ENTRY(RPC_E_CHANGED_MODE), 195 | MAKE_HRESULT_ENTRY(RPC_E_CLIENT_CANTMARSHAL_DATA), 196 | MAKE_HRESULT_ENTRY(RPC_E_CLIENT_CANTUNMARSHAL_DATA), 197 | MAKE_HRESULT_ENTRY(RPC_E_CLIENT_DIED), 198 | MAKE_HRESULT_ENTRY(RPC_E_CONNECTION_TERMINATED), 199 | MAKE_HRESULT_ENTRY(RPC_E_DISCONNECTED), 200 | MAKE_HRESULT_ENTRY(RPC_E_FAULT), 201 | MAKE_HRESULT_ENTRY(RPC_E_INVALID_CALLDATA), 202 | MAKE_HRESULT_ENTRY(RPC_E_INVALID_DATA), 203 | MAKE_HRESULT_ENTRY(RPC_E_INVALID_DATAPACKET), 204 | MAKE_HRESULT_ENTRY(RPC_E_INVALID_PARAMETER), 205 | MAKE_HRESULT_ENTRY(RPC_E_INVALIDMETHOD), 206 | MAKE_HRESULT_ENTRY(RPC_E_NOT_REGISTERED), 207 | MAKE_HRESULT_ENTRY(RPC_E_OUT_OF_RESOURCES), 208 | MAKE_HRESULT_ENTRY(RPC_E_RETRY), 209 | MAKE_HRESULT_ENTRY(RPC_E_SERVER_CANTMARSHAL_DATA), 210 | MAKE_HRESULT_ENTRY(RPC_E_SERVER_CANTUNMARSHAL_DATA), 211 | MAKE_HRESULT_ENTRY(RPC_E_SERVER_DIED), 212 | MAKE_HRESULT_ENTRY(RPC_E_SERVER_DIED_DNE), 213 | MAKE_HRESULT_ENTRY(RPC_E_SERVERCALL_REJECTED), 214 | MAKE_HRESULT_ENTRY(RPC_E_SERVERCALL_RETRYLATER), 215 | MAKE_HRESULT_ENTRY(RPC_E_SERVERFAULT), 216 | MAKE_HRESULT_ENTRY(RPC_E_SYS_CALL_FAILED), 217 | MAKE_HRESULT_ENTRY(RPC_E_THREAD_NOT_INIT), 218 | MAKE_HRESULT_ENTRY(RPC_E_UNEXPECTED), 219 | MAKE_HRESULT_ENTRY(RPC_E_WRONG_THREAD), 220 | MAKE_HRESULT_ENTRY(STG_E_ABNORMALAPIEXIT), 221 | MAKE_HRESULT_ENTRY(STG_E_ACCESSDENIED), 222 | MAKE_HRESULT_ENTRY(STG_E_CANTSAVE), 223 | MAKE_HRESULT_ENTRY(STG_E_DISKISWRITEPROTECTED), 224 | MAKE_HRESULT_ENTRY(STG_E_EXTANTMARSHALLINGS), 225 | MAKE_HRESULT_ENTRY(STG_E_FILEALREADYEXISTS), 226 | MAKE_HRESULT_ENTRY(STG_E_FILENOTFOUND), 227 | MAKE_HRESULT_ENTRY(STG_E_INSUFFICIENTMEMORY), 228 | MAKE_HRESULT_ENTRY(STG_E_INUSE), 229 | MAKE_HRESULT_ENTRY(STG_E_INVALIDFLAG), 230 | MAKE_HRESULT_ENTRY(STG_E_INVALIDFUNCTION), 231 | MAKE_HRESULT_ENTRY(STG_E_INVALIDHANDLE), 232 | MAKE_HRESULT_ENTRY(STG_E_INVALIDHEADER), 233 | MAKE_HRESULT_ENTRY(STG_E_INVALIDNAME), 234 | MAKE_HRESULT_ENTRY(STG_E_INVALIDPARAMETER), 235 | MAKE_HRESULT_ENTRY(STG_E_INVALIDPOINTER), 236 | MAKE_HRESULT_ENTRY(STG_E_LOCKVIOLATION), 237 | MAKE_HRESULT_ENTRY(STG_E_MEDIUMFULL), 238 | MAKE_HRESULT_ENTRY(STG_E_NOMOREFILES), 239 | MAKE_HRESULT_ENTRY(STG_E_NOTCURRENT), 240 | MAKE_HRESULT_ENTRY(STG_E_NOTFILEBASEDSTORAGE), 241 | MAKE_HRESULT_ENTRY(STG_E_OLDDLL), 242 | MAKE_HRESULT_ENTRY(STG_E_OLDFORMAT), 243 | MAKE_HRESULT_ENTRY(STG_E_PATHNOTFOUND), 244 | MAKE_HRESULT_ENTRY(STG_E_READFAULT), 245 | MAKE_HRESULT_ENTRY(STG_E_REVERTED), 246 | MAKE_HRESULT_ENTRY(STG_E_SEEKERROR), 247 | MAKE_HRESULT_ENTRY(STG_E_SHAREREQUIRED), 248 | MAKE_HRESULT_ENTRY(STG_E_SHAREVIOLATION), 249 | MAKE_HRESULT_ENTRY(STG_E_TOOMANYOPENFILES), 250 | MAKE_HRESULT_ENTRY(STG_E_UNIMPLEMENTEDFUNCTION), 251 | MAKE_HRESULT_ENTRY(STG_E_UNKNOWN), 252 | MAKE_HRESULT_ENTRY(STG_E_WRITEFAULT), 253 | MAKE_HRESULT_ENTRY(TYPE_E_AMBIGUOUSNAME), 254 | MAKE_HRESULT_ENTRY(TYPE_E_BADMODULEKIND), 255 | MAKE_HRESULT_ENTRY(TYPE_E_BUFFERTOOSMALL), 256 | MAKE_HRESULT_ENTRY(TYPE_E_CANTCREATETMPFILE), 257 | MAKE_HRESULT_ENTRY(TYPE_E_CANTLOADLIBRARY), 258 | MAKE_HRESULT_ENTRY(TYPE_E_CIRCULARTYPE), 259 | MAKE_HRESULT_ENTRY(TYPE_E_DLLFUNCTIONNOTFOUND), 260 | MAKE_HRESULT_ENTRY(TYPE_E_DUPLICATEID), 261 | MAKE_HRESULT_ENTRY(TYPE_E_ELEMENTNOTFOUND), 262 | MAKE_HRESULT_ENTRY(TYPE_E_INCONSISTENTPROPFUNCS), 263 | MAKE_HRESULT_ENTRY(TYPE_E_INVALIDSTATE), 264 | MAKE_HRESULT_ENTRY(TYPE_E_INVDATAREAD), 265 | MAKE_HRESULT_ENTRY(TYPE_E_IOERROR), 266 | MAKE_HRESULT_ENTRY(TYPE_E_LIBNOTREGISTERED), 267 | MAKE_HRESULT_ENTRY(TYPE_E_NAMECONFLICT), 268 | MAKE_HRESULT_ENTRY(TYPE_E_OUTOFBOUNDS), 269 | MAKE_HRESULT_ENTRY(TYPE_E_QUALIFIEDNAMEDISALLOWED), 270 | MAKE_HRESULT_ENTRY(TYPE_E_REGISTRYACCESS), 271 | MAKE_HRESULT_ENTRY(TYPE_E_SIZETOOBIG), 272 | MAKE_HRESULT_ENTRY(TYPE_E_TYPEMISMATCH), 273 | MAKE_HRESULT_ENTRY(TYPE_E_UNDEFINEDTYPE), 274 | MAKE_HRESULT_ENTRY(TYPE_E_UNKNOWNLCID), 275 | MAKE_HRESULT_ENTRY(TYPE_E_UNSUPFORMAT), 276 | MAKE_HRESULT_ENTRY(TYPE_E_WRONGTYPEKIND), 277 | MAKE_HRESULT_ENTRY(VIEW_E_DRAW), 278 | 279 | MAKE_HRESULT_ENTRY(CONNECT_E_NOCONNECTION), 280 | MAKE_HRESULT_ENTRY(CONNECT_E_ADVISELIMIT), 281 | MAKE_HRESULT_ENTRY(CONNECT_E_CANNOTCONNECT), 282 | MAKE_HRESULT_ENTRY(CONNECT_E_OVERRIDDEN), 283 | 284 | MAKE_HRESULT_ENTRY(CLASS_E_NOTLICENSED), 285 | MAKE_HRESULT_ENTRY(CLASS_E_NOAGGREGATION), 286 | MAKE_HRESULT_ENTRY(CLASS_E_CLASSNOTAVAILABLE), 287 | 288 | MAKE_HRESULT_ENTRY(CTL_E_ILLEGALFUNCTIONCALL), 289 | MAKE_HRESULT_ENTRY(CTL_E_OVERFLOW), 290 | MAKE_HRESULT_ENTRY(CTL_E_OUTOFMEMORY), 291 | MAKE_HRESULT_ENTRY(CTL_E_DIVISIONBYZERO), 292 | MAKE_HRESULT_ENTRY(CTL_E_OUTOFSTRINGSPACE), 293 | MAKE_HRESULT_ENTRY(CTL_E_OUTOFSTACKSPACE), 294 | MAKE_HRESULT_ENTRY(CTL_E_BADFILENAMEORNUMBER), 295 | MAKE_HRESULT_ENTRY(CTL_E_FILENOTFOUND), 296 | MAKE_HRESULT_ENTRY(CTL_E_BADFILEMODE), 297 | MAKE_HRESULT_ENTRY(CTL_E_FILEALREADYOPEN), 298 | MAKE_HRESULT_ENTRY(CTL_E_DEVICEIOERROR), 299 | MAKE_HRESULT_ENTRY(CTL_E_FILEALREADYEXISTS), 300 | MAKE_HRESULT_ENTRY(CTL_E_BADRECORDLENGTH), 301 | MAKE_HRESULT_ENTRY(CTL_E_DISKFULL), 302 | MAKE_HRESULT_ENTRY(CTL_E_BADRECORDNUMBER), 303 | MAKE_HRESULT_ENTRY(CTL_E_BADFILENAME), 304 | MAKE_HRESULT_ENTRY(CTL_E_TOOMANYFILES), 305 | MAKE_HRESULT_ENTRY(CTL_E_DEVICEUNAVAILABLE), 306 | MAKE_HRESULT_ENTRY(CTL_E_PERMISSIONDENIED), 307 | MAKE_HRESULT_ENTRY(CTL_E_DISKNOTREADY), 308 | MAKE_HRESULT_ENTRY(CTL_E_PATHFILEACCESSERROR), 309 | MAKE_HRESULT_ENTRY(CTL_E_PATHNOTFOUND), 310 | MAKE_HRESULT_ENTRY(CTL_E_INVALIDPATTERNSTRING), 311 | MAKE_HRESULT_ENTRY(CTL_E_INVALIDUSEOFNULL), 312 | MAKE_HRESULT_ENTRY(CTL_E_INVALIDFILEFORMAT), 313 | MAKE_HRESULT_ENTRY(CTL_E_INVALIDPROPERTYVALUE), 314 | MAKE_HRESULT_ENTRY(CTL_E_INVALIDPROPERTYARRAYINDEX), 315 | MAKE_HRESULT_ENTRY(CTL_E_SETNOTSUPPORTEDATRUNTIME), 316 | MAKE_HRESULT_ENTRY(CTL_E_SETNOTSUPPORTED), 317 | MAKE_HRESULT_ENTRY(CTL_E_NEEDPROPERTYARRAYINDEX), 318 | MAKE_HRESULT_ENTRY(CTL_E_SETNOTPERMITTED), 319 | MAKE_HRESULT_ENTRY(CTL_E_GETNOTSUPPORTEDATRUNTIME), 320 | MAKE_HRESULT_ENTRY(CTL_E_GETNOTSUPPORTED), 321 | MAKE_HRESULT_ENTRY(CTL_E_PROPERTYNOTFOUND), 322 | MAKE_HRESULT_ENTRY(CTL_E_INVALIDCLIPBOARDFORMAT), 323 | MAKE_HRESULT_ENTRY(CTL_E_INVALIDPICTURE), 324 | MAKE_HRESULT_ENTRY(CTL_E_PRINTERERROR), 325 | MAKE_HRESULT_ENTRY(CTL_E_CANTSAVEFILETOTEMP), 326 | MAKE_HRESULT_ENTRY(CTL_E_SEARCHTEXTNOTFOUND), 327 | MAKE_HRESULT_ENTRY(CTL_E_REPLACEMENTSTOOLONG), 328 | }; 329 | 330 | // look for scode in the table 331 | for (int i = 0; i < sizeof(hrNameTable) / sizeof(hrNameTable[0]); i++) 332 | { 333 | if (hr == hrNameTable[i].hr) { 334 | swprintf_s(buf, (size_t)(bufSize - 1), hrNameTable[i].lpszName); 335 | return; 336 | } 337 | } 338 | // not found - make one up 339 | swprintf_s(buf, (size_t)(bufSize - 1), L"OLE error 0x%08x", hr); 340 | } 341 | 342 | 343 | uint16_t *GetWin32ErrorMessage(uint16_t *buf, size_t buflen, Isolate *isolate, HRESULT hrcode, LPCOLESTR msg, LPCOLESTR msg2, LPCOLESTR desc) { 344 | uint16_t *bufptr = buf; 345 | size_t len; 346 | if (msg) { 347 | len = wcslen(msg); 348 | if (len >= buflen) len = buflen - 1; 349 | if (len > 0) memcpy(bufptr, msg, len * sizeof(uint16_t)); 350 | buflen -= len; 351 | bufptr += len; 352 | if (buflen > 2) { 353 | bufptr[0] = ':'; 354 | bufptr[1] = ' '; 355 | buflen -= 2; 356 | bufptr += 2; 357 | } 358 | } 359 | if (msg2) { 360 | len = wcslen(msg2); 361 | if (len >= buflen) len = buflen - 1; 362 | if (len > 0) memcpy(bufptr, msg2, len * sizeof(uint16_t)); 363 | buflen -= len; 364 | bufptr += len; 365 | if (buflen > 1) { 366 | bufptr[0] = ' '; 367 | buflen -= 1; 368 | bufptr += 1; 369 | } 370 | } 371 | if (buflen > 1) { 372 | len = desc ? wcslen(desc) : 0; 373 | if (len > 0) { 374 | if (len >= buflen) len = buflen - 1; 375 | memcpy(bufptr, desc, len * sizeof(OLECHAR)); 376 | } 377 | else { 378 | len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, hrcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPOLESTR)bufptr, (DWORD)buflen - 1, 0); 379 | if (len == 0) len = swprintf_s((LPOLESTR)bufptr, buflen - 1, L"Error 0x%08X", hrcode); 380 | } 381 | buflen -= len; 382 | bufptr += len; 383 | } 384 | if (buflen > 0) bufptr[0] = 0; 385 | return buf; 386 | } 387 | 388 | char *GetWin32ErrorMessage(char *buf, size_t buflen, Isolate *isolate, HRESULT hrcode, LPCOLESTR msg, LPCOLESTR msg2, LPCOLESTR desc) { 389 | uint16_t buf_wide[ERROR_MESSAGE_WIDE_MAXSIZE]; 390 | GetWin32ErrorMessage(buf_wide, ERROR_MESSAGE_WIDE_MAXSIZE, isolate, hrcode, msg, msg2, desc); 391 | int rcode = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)buf_wide, -1, buf, buflen, NULL, NULL); 392 | if (rcode < 0) rcode = 0; 393 | buf[rcode] = 0; 394 | return buf; 395 | } 396 | 397 | Local GetWin32ErrorMessage(Isolate *isolate, HRESULT hrcode, LPCOLESTR msg, LPCOLESTR msg2, LPCOLESTR desc) { 398 | uint16_t buf_wide[ERROR_MESSAGE_WIDE_MAXSIZE]; 399 | return v8str(isolate, GetWin32ErrorMessage(buf_wide, ERROR_MESSAGE_WIDE_MAXSIZE, isolate, hrcode, msg, msg2, desc)); 400 | } 401 | 402 | //------------------------------------------------------------------------------------------------------- 403 | 404 | Local Variant2Array(Isolate *isolate, const VARIANT &v) { 405 | if ((v.vt & VT_ARRAY) == 0) return Null(isolate); 406 | SAFEARRAY *varr = (v.vt & VT_BYREF) != 0 ? *v.pparray : v.parray; 407 | if (!varr || varr->cDims > 2 || varr->cDims == 0) return Null(isolate); 408 | else if ( varr->cDims == 2 ) return Variant2Array2( isolate, v ); 409 | Local ctx = isolate->GetCurrentContext(); 410 | VARTYPE vt = v.vt & VT_TYPEMASK; 411 | LONG cnt = (LONG)varr->rgsabound[0].cElements; 412 | Local arr = Array::New(isolate, cnt); 413 | for (LONG i = varr->rgsabound[0].lLbound; i < varr->rgsabound[0].lLbound + cnt; i++) { 414 | CComVariant vi; 415 | if SUCCEEDED(SafeArrayGetElement(varr, &i, (vt == VT_VARIANT) ? (void*)&vi : (void*)&vi.byref)) { 416 | if (vt != VT_VARIANT) vi.vt = vt; 417 | uint32_t jsi = i - varr->rgsabound[ 0 ].lLbound; 418 | arr->Set(ctx, jsi, Variant2Value(isolate, vi, true)); 419 | } 420 | } 421 | return arr; 422 | } 423 | 424 | static Local Variant2Array2(Isolate *isolate, const VARIANT &v) { 425 | if ((v.vt & VT_ARRAY) == 0) return Null(isolate); 426 | SAFEARRAY *varr = (v.vt & VT_BYREF) != 0 ? *v.pparray : v.parray; 427 | if (!varr || varr->cDims != 2) return Null(isolate); 428 | Local ctx = isolate->GetCurrentContext(); 429 | VARTYPE vt = v.vt & VT_TYPEMASK; 430 | LONG cnt1 = (LONG)varr->rgsabound[0].cElements; 431 | LONG cnt2 = (LONG)varr->rgsabound[1].cElements; 432 | Local arr1 = Array::New(isolate, cnt2); 433 | LONG rgIndices[ 2 ]; 434 | for (LONG i2 = varr->rgsabound[1].lLbound; i2 < varr->rgsabound[1].lLbound + cnt2; i2++) { 435 | rgIndices[ 0 ] = i2; 436 | Local arr2 = Array::New(isolate, cnt1); 437 | for (LONG i1 = varr->rgsabound[0].lLbound; i1 < varr->rgsabound[0].lLbound + cnt1; i1++) { 438 | CComVariant vi; 439 | rgIndices[ 1 ] = i1; 440 | if SUCCEEDED(SafeArrayGetElement(varr, &rgIndices[0], (vt == VT_VARIANT) ? (void*)&vi : (void*)&vi.byref)) { 441 | if (vt != VT_VARIANT) vi.vt = vt; 442 | uint32_t jsi = (uint32_t)i1 - varr->rgsabound[ 0 ].lLbound; 443 | arr2->Set(ctx, jsi, Variant2Value(isolate, vi, true)); 444 | } 445 | } 446 | uint32_t jsi = i2 - varr->rgsabound[ 1 ].lLbound; 447 | arr1->Set(ctx, jsi, arr2); 448 | } 449 | return arr1; 450 | } 451 | 452 | Local Variant2Value(Isolate *isolate, const VARIANT &v, bool allow_disp) { 453 | if ((v.vt & VT_ARRAY) != 0) return Variant2Array(isolate, v); 454 | VARTYPE vt = (v.vt & VT_TYPEMASK); 455 | bool by_ref = (v.vt & VT_BYREF) != 0; 456 | switch (vt) { 457 | case VT_NULL: 458 | return Null(isolate); 459 | case VT_I1: 460 | return Int32::New(isolate, (int32_t)(by_ref ? *v.pcVal : v.cVal)); 461 | case VT_I2: 462 | return Int32::New(isolate, (int32_t)(by_ref ? *v.piVal : v.iVal)); 463 | case VT_I4: 464 | return Int32::New(isolate, (int32_t)(by_ref ? *v.plVal : v.lVal)); 465 | case VT_INT: 466 | return Int32::New(isolate, (int32_t)(by_ref ? *v.pintVal : v.intVal)); 467 | case VT_UI1: 468 | return Int32::New(isolate, (uint32_t)(by_ref ? *v.pbVal : v.bVal)); 469 | case VT_UI2: 470 | return Int32::New(isolate, (uint32_t)(by_ref ? *v.puiVal : v.uiVal)); 471 | case VT_UI4: 472 | return Int32::New(isolate, (uint32_t)(by_ref ? *v.pulVal : v.ulVal)); 473 | case VT_UINT: 474 | return Int32::New(isolate, (uint32_t)(by_ref ? *v.puintVal : v.uintVal)); 475 | case VT_I8: 476 | return Number::New(isolate, (double)(by_ref ? *v.pllVal : v.llVal)); 477 | case VT_UI8: 478 | return Number::New(isolate, (double)(by_ref ? *v.pullVal : v.ullVal)); 479 | case VT_CY: 480 | return Number::New(isolate, (double)(by_ref ? v.pcyVal : &v.cyVal)->int64 / 10000.); 481 | case VT_R4: 482 | return Number::New(isolate, by_ref ? *v.pfltVal : v.fltVal); 483 | case VT_R8: 484 | return Number::New(isolate, by_ref ? *v.pdblVal : v.dblVal); 485 | case VT_DATE: { 486 | Local ret; 487 | if (!Date::New(isolate->GetCurrentContext(), FromOleDate(by_ref ? *v.pdate : v.date)).ToLocal(&ret)) 488 | ret = Undefined(isolate); 489 | return ret; 490 | } 491 | case VT_DECIMAL: { 492 | DOUBLE dblval; 493 | if FAILED(VarR8FromDec(by_ref ? v.pdecVal : &v.decVal, &dblval)) return Undefined(isolate); 494 | return Number::New(isolate, dblval); 495 | } 496 | case VT_BOOL: 497 | return Boolean::New(isolate, (by_ref ? *v.pboolVal : v.boolVal) != VARIANT_FALSE); 498 | case VT_DISPATCH: { 499 | IDispatch *disp = (by_ref ? *v.ppdispVal : v.pdispVal); 500 | if (!disp) return Null(isolate); 501 | if (allow_disp) { 502 | DispObjectImpl *impl; 503 | if (disp->QueryInterface(CLSID_DispObjectImpl, (void**)&impl) == S_OK) { 504 | return impl->obj.Get(isolate); 505 | } 506 | return DispObject::NodeCreate(isolate, disp, L"Dispatch", option_auto); 507 | } 508 | return v8str(isolate, "[Dispatch]"); 509 | } 510 | case VT_UNKNOWN: { 511 | if (allow_disp) 512 | { 513 | CComPtr disp; 514 | if (UnknownDispGet(by_ref ? *v.ppunkVal : v.punkVal, &disp)) { 515 | return DispObject::NodeCreate(isolate, disp, L"Unknown", option_auto); 516 | } 517 | } 518 | // Check for NULL value 519 | if ((by_ref && *v.ppunkVal) || v.punkVal) 520 | { 521 | if (allow_disp) { 522 | return VariantObject::NodeCreate(isolate, v); 523 | } 524 | else { 525 | return v8str(isolate, "[Unknown]"); 526 | } 527 | } 528 | else if (!v.punkVal) { 529 | return Null(isolate); 530 | } 531 | } 532 | case VT_BSTR: { 533 | BSTR bstr = by_ref ? (v.pbstrVal ? *v.pbstrVal : nullptr) : v.bstrVal; 534 | //if (!bstr) return String::Empty(isolate); 535 | // Sometimes we need to distinguish between NULL and empty string 536 | if (!bstr) return Null(isolate); 537 | return v8str(isolate, bstr); 538 | } 539 | case VT_VARIANT: 540 | if (v.pvarVal) return Variant2Value(isolate, *v.pvarVal, allow_disp); 541 | } 542 | return Undefined(isolate); 543 | } 544 | 545 | Local Variant2String(Isolate *isolate, const VARIANT &v) { 546 | char buf[256] = {}; 547 | VARTYPE vt = (v.vt & VT_TYPEMASK); 548 | bool by_ref = (v.vt & VT_BYREF) != 0; 549 | switch (vt) { 550 | case VT_EMPTY: 551 | strcpy(buf, "EMPTY"); 552 | break; 553 | case VT_NULL: 554 | strcpy(buf, "NULL"); 555 | break; 556 | case VT_I1: 557 | sprintf_s(buf, "%i", (int)(by_ref ? *v.pcVal : v.cVal)); 558 | break; 559 | case VT_I2: 560 | sprintf_s(buf, "%i", (int)(by_ref ? *v.piVal : v.iVal)); 561 | break; 562 | case VT_I4: 563 | sprintf_s(buf, "%i", (int)(by_ref ? *v.plVal : v.lVal)); 564 | break; 565 | case VT_INT: 566 | sprintf_s(buf, "%i", (int)(by_ref ? *v.pintVal : v.intVal)); 567 | break; 568 | case VT_UI1: 569 | sprintf_s(buf, "%u", (unsigned int)(by_ref ? *v.pbVal : v.bVal)); 570 | break; 571 | case VT_UI2: 572 | sprintf_s(buf, "%u", (unsigned int)(by_ref ? *v.puiVal : v.uiVal)); 573 | break; 574 | case VT_UI4: 575 | sprintf_s(buf, "%u", (unsigned int)(by_ref ? *v.pulVal : v.ulVal)); 576 | break; 577 | case VT_UINT: 578 | sprintf_s(buf, "%u", (unsigned int)(by_ref ? *v.puintVal : v.uintVal)); 579 | break; 580 | case VT_CY: 581 | case VT_I8: 582 | sprintf_s(buf, "%lld", (by_ref ? *v.pllVal : v.llVal)); 583 | break; 584 | case VT_UI8: 585 | sprintf_s(buf, "%llu", (by_ref ? *v.pullVal : v.ullVal)); 586 | break; 587 | case VT_R4: 588 | sprintf_s(buf, "%f", (double)(by_ref ? *v.pfltVal : v.fltVal)); 589 | break; 590 | case VT_R8: 591 | sprintf_s(buf, "%f", (double)(by_ref ? *v.pdblVal : v.dblVal)); 592 | break; 593 | case VT_DATE: { 594 | Local ret; 595 | if (!Date::New(isolate->GetCurrentContext(), FromOleDate(by_ref ? *v.pdate : v.date)).ToLocal(&ret)) 596 | ret = Undefined(isolate); 597 | return ret; 598 | } 599 | case VT_DECIMAL: { 600 | DOUBLE dblval; 601 | if FAILED(VarR8FromDec(by_ref ? v.pdecVal : &v.decVal, &dblval)) return Undefined(isolate); 602 | sprintf_s(buf, "%f", (double)dblval); 603 | break; 604 | } 605 | case VT_BOOL: 606 | strcpy(buf, ((by_ref ? *v.pboolVal : v.boolVal) == VARIANT_FALSE) ? "false" : "true"); 607 | case VT_DISPATCH: 608 | strcpy(buf, "[Dispatch]"); 609 | break; 610 | case VT_UNKNOWN: 611 | strcpy(buf, "[Unknown]"); 612 | break; 613 | case VT_VARIANT: 614 | if (v.pvarVal) return Variant2String(isolate, *v.pvarVal); 615 | break; 616 | default: 617 | CComVariant tmp; 618 | if (SUCCEEDED(VariantChangeType(&tmp, &v, 0, VT_BSTR)) && tmp.vt == VT_BSTR && v.bstrVal != nullptr) { 619 | return v8str(isolate, v.bstrVal); 620 | } 621 | } 622 | return v8str(isolate, buf); 623 | } 624 | 625 | void Value2Variant(Isolate *isolate, Local &val, VARIANT &var, VARTYPE vt) { 626 | Local ctx = isolate->GetCurrentContext(); 627 | if (val.IsEmpty() || val->IsUndefined()) { 628 | var.vt = VT_EMPTY; 629 | } 630 | else if (val->IsNull()) { 631 | var.vt = VT_NULL; 632 | } 633 | else if (val->IsInt32()) { 634 | //var.lVal = val->Int32Value(); 635 | var.lVal = val->Int32Value(ctx).FromMaybe(0); 636 | var.vt = VT_I4; 637 | } 638 | else if (val->IsUint32()) { 639 | //var.ulVal = val->Uint32Value(); 640 | var.ulVal = val->Uint32Value(ctx).FromMaybe(0); 641 | var.vt = (var.ulVal <= 0x7FFFFFFF) ? VT_I4 : VT_UI4; 642 | } 643 | else if (val->IsNumber()) { 644 | //var.dblVal = val->NumberValue(); 645 | var.dblVal = val->NumberValue(ctx).FromMaybe(0); 646 | var.vt = VT_R8; 647 | } 648 | else if (val->IsDate()) { 649 | //var.date = ToOleDate(val->NumberValue()); 650 | var.date = ToOleDate(val->NumberValue(ctx).FromMaybe(0)); 651 | var.vt = VT_DATE; 652 | } 653 | else if (val->IsBoolean()) { 654 | var.boolVal = NODE_BOOL(isolate, val) ? VARIANT_TRUE : VARIANT_FALSE; 655 | var.vt = VT_BOOL; 656 | } 657 | else if (val->IsArray() && (vt != VT_NULL)) { 658 | Local arr = v8::Local::Cast(val); 659 | uint32_t len = arr->Length(); 660 | if (vt == VT_EMPTY) vt = VT_VARIANT; 661 | var.vt = VT_ARRAY | vt; 662 | // if array of arrays, create a 2 dim array, choose the 2nd bound 663 | uint32_t second_len = 0; 664 | if (len) { 665 | Local first_value; 666 | if (arr->Get(ctx, 0).ToLocal(&first_value)) { 667 | if (first_value->IsArray()) second_len = v8::Local::Cast(first_value)->Length(); 668 | } 669 | } 670 | if ( second_len == 0 ) { 671 | var.parray = SafeArrayCreateVector(vt, 0, len); 672 | for (uint32_t i = 0; i < len; i++) { 673 | CComVariant v; 674 | Local val; 675 | if (!arr->Get(ctx, i).ToLocal(&val)) val = Undefined(isolate); 676 | Value2Variant(isolate, val, v, vt); 677 | void *pv; 678 | if (vt == VT_VARIANT) pv = (void*)&v; 679 | else if (vt == VT_DISPATCH || vt == VT_UNKNOWN || vt == VT_BSTR) pv = v.byref; 680 | else pv = (void*)&v.byref; 681 | SafeArrayPutElement(var.parray, (LONG*)&i, pv); 682 | } 683 | } 684 | else { 685 | SAFEARRAYBOUND rgsabounds[ 2 ]; 686 | rgsabounds[ 0 ].lLbound = rgsabounds[ 1 ].lLbound = 0; 687 | rgsabounds[ 0 ].cElements = len; 688 | rgsabounds[ 1 ].cElements = second_len; 689 | var.parray = SafeArrayCreate(vt, 2, rgsabounds); 690 | LONG rgIndices[ 2 ]; 691 | for (uint32_t i = 0; i < len; i++) { 692 | rgIndices[ 0 ] = i; 693 | Local maybearray; 694 | Local arr2; 695 | bool bGotArray = false; 696 | if (arr->Get( ctx, i ).ToLocal( &maybearray ) && maybearray->IsArray()) { 697 | bGotArray = true; 698 | arr2 = v8::Local::Cast(maybearray); 699 | } 700 | for (uint32_t j = 0; j < second_len; j++) { 701 | rgIndices[ 1 ] = j; 702 | Local val; 703 | if ( bGotArray ) { 704 | if (!arr2->Get(ctx, j).ToLocal(&val)) val = Undefined(isolate); 705 | } 706 | else { 707 | // if no arrays for a "row", the value is put only in first "col" 708 | if ( j == 0 ) val = maybearray; 709 | else val = Undefined(isolate); 710 | } 711 | CComVariant v; 712 | Value2Variant(isolate, val, v, vt); 713 | void *pv; 714 | if (vt == VT_VARIANT) pv = (void*)&v; 715 | else if (vt == VT_DISPATCH || vt == VT_UNKNOWN || vt == VT_BSTR) pv = v.byref; 716 | else pv = (void*)&v.byref; 717 | SafeArrayPutElement(var.parray, rgIndices, pv); 718 | } 719 | } 720 | } 721 | vt = VT_EMPTY; 722 | } 723 | else if (val->IsObject()) { 724 | auto obj = Local::Cast(val); 725 | if (!DispObject::GetValueOf(isolate, obj, var) && !VariantObject::GetValueOf(isolate, obj, var)) { 726 | var.vt = VT_DISPATCH; 727 | var.pdispVal = new DispObjectImpl(obj); 728 | var.pdispVal->AddRef(); 729 | } 730 | } 731 | else { 732 | String::Value str(isolate, val); 733 | var.vt = VT_BSTR; 734 | // For some apps there is still a difference between "" and NULL string, so we should support it here gracefully 735 | if (*str) 736 | { 737 | var.bstrVal = SysAllocString((LPOLESTR)*str); 738 | } 739 | else { 740 | var.bstrVal = 0; 741 | } 742 | //var.bstrVal = (str.length() > 0) ? SysAllocString((LPOLESTR)*str) : 0; 743 | } 744 | if (vt != VT_EMPTY && vt != VT_NULL && vt != VT_VARIANT) { 745 | if FAILED(VariantChangeType(&var, &var, 0, vt)) 746 | VariantClear(&var); 747 | } 748 | } 749 | 750 | void Value2SafeArray(Isolate *isolate, Local &val, VARIANT &var, VARTYPE vt) { 751 | Local ctx = isolate->GetCurrentContext(); 752 | if (val.IsEmpty() || val->IsUndefined()) { 753 | var.vt = VT_EMPTY; 754 | } 755 | else if (val->IsNull()) { 756 | var.vt = VT_NULL; 757 | } 758 | else if (val->IsObject()) { 759 | // Test conversion dispatch pointer to Uint8Array 760 | if (vt == VT_UI1) { 761 | auto obj = Local::Cast(val); 762 | auto ptr = DispObject::GetDispPtr(isolate, obj); 763 | size_t len = sizeof(UINT_PTR); 764 | var.vt = VT_ARRAY | vt; 765 | var.parray = SafeArrayCreateVector(vt, 0, len); 766 | if (var.parray != nullptr) memcpy(var.parray->pvData, &ptr, len); 767 | } 768 | } 769 | else { 770 | var.vt = VT_EMPTY; 771 | } 772 | } 773 | 774 | bool Value2Unknown(Isolate *isolate, Local &val, IUnknown **unk) { 775 | if (val.IsEmpty() || !val->IsObject()) return false; 776 | auto obj = Local::Cast(val); 777 | CComVariant var; 778 | if (!DispObject::GetValueOf(isolate, obj, var) && !VariantObject::GetValueOf(isolate, obj, var)) return false; 779 | return VariantUnkGet(&var, unk); 780 | } 781 | 782 | bool UnknownDispGet(IUnknown *unk, IDispatch **disp) { 783 | if (!unk) return false; 784 | if SUCCEEDED(unk->QueryInterface(__uuidof(IDispatch), (void**)disp)) { 785 | return true; 786 | } 787 | CComPtr enum_ptr; 788 | if SUCCEEDED(unk->QueryInterface(__uuidof(IEnumVARIANT), (void**)&enum_ptr)) { 789 | *disp = new DispEnumImpl(enum_ptr); 790 | (*disp)->AddRef(); 791 | return true; 792 | } 793 | return false; 794 | } 795 | 796 | bool VariantUnkGet(VARIANT *v, IUnknown **punk) { 797 | IUnknown *unk = NULL; 798 | if ((v->vt & VT_TYPEMASK) == VT_DISPATCH) { 799 | unk = ((v->vt & VT_BYREF) != 0) ? *v->ppdispVal : v->pdispVal; 800 | } 801 | else if ((v->vt & VT_TYPEMASK) == VT_UNKNOWN) { 802 | unk = ((v->vt & VT_BYREF) != 0) ? *v->ppunkVal : v->punkVal; 803 | } 804 | if (!unk) return false; 805 | unk->AddRef(); 806 | *punk = unk; 807 | return true; 808 | } 809 | 810 | bool VariantDispGet(VARIANT *v, IDispatch **pdisp) { 811 | /* 812 | if ((v->vt & VT_ARRAY) != 0) { 813 | *disp = new DispArrayImpl(*v); 814 | (*disp)->AddRef(); 815 | return true; 816 | } 817 | */ 818 | if ((v->vt & VT_TYPEMASK) == VT_DISPATCH) { 819 | IDispatch *disp = ((v->vt & VT_BYREF) != 0) ? *v->ppdispVal : v->pdispVal; 820 | if (!disp) return false; 821 | disp->AddRef(); 822 | *pdisp = disp; 823 | return true; 824 | } 825 | if ((v->vt & VT_TYPEMASK) == VT_UNKNOWN) { 826 | return UnknownDispGet(((v->vt & VT_BYREF) != 0) ? *v->ppunkVal : v->punkVal, pdisp); 827 | } 828 | return false; 829 | } 830 | 831 | //------------------------------------------------------------------------------------------------------- 832 | // DispArrayImpl implemetation 833 | 834 | HRESULT STDMETHODCALLTYPE DispArrayImpl::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { 835 | if (cNames != 1 || !rgszNames[0]) return DISP_E_UNKNOWNNAME; 836 | LPOLESTR name = rgszNames[0]; 837 | if (wcscmp(name, L"length") == 0) *rgDispId = 1; 838 | else return DISP_E_UNKNOWNNAME; 839 | return S_OK; 840 | } 841 | 842 | HRESULT STDMETHODCALLTYPE DispArrayImpl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { 843 | HRESULT hrcode = S_OK; 844 | UINT argcnt = pDispParams->cArgs; 845 | VARIANT *args = pDispParams->rgvarg; 846 | 847 | if ((var.vt & VT_ARRAY) == 0) return E_NOTIMPL; 848 | SAFEARRAY *arr = ((var.vt & VT_BYREF) != 0) ? *var.pparray : var.parray; 849 | 850 | switch (dispIdMember) { 851 | case 1: { 852 | if (pVarResult) { 853 | pVarResult->vt = VT_INT; 854 | pVarResult->intVal = (INT)(arr ? arr->rgsabound[0].cElements : 0); 855 | } 856 | return hrcode; } 857 | } 858 | return E_NOTIMPL; 859 | } 860 | 861 | //------------------------------------------------------------------------------------------------------- 862 | // DispEnumImpl implemetation 863 | 864 | HRESULT STDMETHODCALLTYPE DispEnumImpl::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { 865 | if (cNames != 1 || !rgszNames[0]) return DISP_E_UNKNOWNNAME; 866 | LPOLESTR name = rgszNames[0]; 867 | if (wcscmp(name, L"Next") == 0) *rgDispId = 1; 868 | else if (wcscmp(name, L"Skip") == 0) *rgDispId = 2; 869 | else if (wcscmp(name, L"Reset") == 0) *rgDispId = 3; 870 | else if (wcscmp(name, L"Clone") == 0) *rgDispId = 4; 871 | else return DISP_E_UNKNOWNNAME; 872 | return S_OK; 873 | } 874 | 875 | HRESULT STDMETHODCALLTYPE DispEnumImpl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { 876 | HRESULT hrcode = S_OK; 877 | UINT argcnt = pDispParams->cArgs; 878 | VARIANT *args = pDispParams->rgvarg; 879 | switch (dispIdMember) { 880 | case 1: { 881 | CComVariant arr; 882 | ULONG fetched, celt = (argcnt > 0) ? Variant2Int(args[argcnt - 1], (ULONG)1) : 1; 883 | if (!pVarResult || celt == 0) hrcode = E_INVALIDARG; 884 | if SUCCEEDED(hrcode) hrcode = arr.ArrayCreate(VT_VARIANT, celt); 885 | if SUCCEEDED(hrcode) hrcode = ptr->Next(celt, arr.ArrayGet(0), &fetched); 886 | if SUCCEEDED(hrcode) { 887 | if (fetched == 0) pVarResult->vt = VT_EMPTY; 888 | else if (fetched == 1) { 889 | VARIANT *v = arr.ArrayGet(0); 890 | *pVarResult = *v; 891 | v->vt = VT_EMPTY; 892 | } 893 | else { 894 | if (fetched < celt) hrcode = arr.ArrayResize(fetched); 895 | if SUCCEEDED(hrcode) arr.Detach(pVarResult); 896 | } 897 | } 898 | return hrcode; 899 | } 900 | case 2: { 901 | if (pVarResult) pVarResult->vt = VT_EMPTY; 902 | ULONG celt = (argcnt > 0) ? Variant2Int(args[argcnt - 1], (ULONG)1) : 1; 903 | return ptr->Skip(celt); 904 | } 905 | case 3: { 906 | if (pVarResult) pVarResult->vt = VT_EMPTY; 907 | return ptr->Reset(); 908 | } 909 | case 4: { 910 | std::unique_ptr disp; 911 | hrcode = pVarResult ? ptr->Clone(&disp->ptr) : E_INVALIDARG; 912 | if SUCCEEDED(hrcode) { 913 | disp->AddRef(); 914 | pVarResult->vt = VT_DISPATCH; 915 | pVarResult->pdispVal = disp.release(); 916 | } 917 | return hrcode; } 918 | } 919 | return E_NOTIMPL; 920 | } 921 | 922 | //------------------------------------------------------------------------------------------------------- 923 | // DispObjectImpl implemetation 924 | 925 | HRESULT STDMETHODCALLTYPE DispObjectImpl::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) { 926 | if (cNames != 1 || !rgszNames[0]) return DISP_E_UNKNOWNNAME; 927 | std::wstring name(rgszNames[0]); 928 | name_ptr &ptr = names[name]; 929 | if (!ptr) { 930 | ptr.reset(new name_t(dispid_next++, name)); 931 | index.insert(index_t::value_type(ptr->dispid, ptr)); 932 | } 933 | *rgDispId = ptr->dispid; 934 | return S_OK; 935 | } 936 | 937 | HRESULT STDMETHODCALLTYPE DispObjectImpl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) { 938 | Isolate *isolate = Isolate::GetCurrent(); 939 | Local ctx = isolate->GetCurrentContext(); 940 | Local self = obj.Get(isolate); 941 | Local name, val, ret; 942 | 943 | // Prepare name by member id 944 | if (!index.empty()) { 945 | index_t::const_iterator p = index.find(dispIdMember); 946 | if (p == index.end()) 947 | { 948 | // DispID may be 0 for regular member or for DISPID_VALUE. 949 | // Since regular method not found assume DISPID_VALUE if it is 0. 950 | if(dispIdMember != DISPID_VALUE) return DISP_E_MEMBERNOTFOUND; 951 | } else { 952 | name_t& info = *p->second; 953 | name = v8str(isolate, info.name.c_str()); 954 | } 955 | } 956 | // Set property value 957 | if ((wFlags & DISPATCH_PROPERTYPUT) != 0) { 958 | UINT argcnt = pDispParams->cArgs; 959 | VARIANT *key = (argcnt > 1) ? &pDispParams->rgvarg[--argcnt] : nullptr; 960 | if (argcnt > 0) val = Variant2Value(isolate, pDispParams->rgvarg[--argcnt], true); 961 | else val = Undefined(isolate); 962 | bool rcode; 963 | 964 | // Set simple object property value 965 | if (!key) { 966 | if (name.IsEmpty()) return DISP_E_MEMBERNOTFOUND; 967 | rcode = self->Set(ctx, name, val).FromMaybe(false); 968 | } 969 | 970 | // Set object/array item value 971 | else { 972 | Local target; 973 | if (name.IsEmpty()) target = self; 974 | else { 975 | Local obj; 976 | if (self->Get(ctx, name).ToLocal(&obj) && !obj.IsEmpty()) target = Local::Cast(obj); 977 | if (target.IsEmpty()) return DISP_E_BADCALLEE; 978 | } 979 | 980 | LONG index = Variant2Int(*key, -1); 981 | if (index >= 0) rcode = target->Set(ctx, (uint32_t)index, val).FromMaybe(false); 982 | else rcode = target->Set(ctx, Variant2Value(isolate, *key, false), val).FromMaybe(false); 983 | } 984 | 985 | // Store result 986 | if (pVarResult) { 987 | pVarResult->vt = VT_BOOL; 988 | pVarResult->boolVal = rcode ? VARIANT_TRUE : VARIANT_FALSE; 989 | } 990 | return S_OK; 991 | } 992 | 993 | // Prepare property item 994 | if (name.IsEmpty()) val = self; 995 | else self->Get(ctx, name).ToLocal(&val); 996 | 997 | // Call property as method 998 | if ((wFlags & DISPATCH_METHOD) != 0) { 999 | wFlags = 0; 1000 | NodeArguments args(isolate, pDispParams, true, reverse_arguments); 1001 | int argcnt = (int)args.items.size(); 1002 | Local *argptr = (argcnt > 0) ? &args.items[0] : nullptr; 1003 | if (val->IsFunction()) { 1004 | Local func = Local::Cast(val); 1005 | if (func.IsEmpty()) return DISP_E_BADCALLEE; 1006 | func->Call(isolate->GetCurrentContext(), self, argcnt, argptr).ToLocal(&ret); 1007 | } 1008 | else if (val->IsObject()) { 1009 | wFlags = DISPATCH_PROPERTYGET; 1010 | //Local target = val->ToObject(); 1011 | //target->CallAsFunction(isolate->GetCurrentContext(), target, args.items.size(), &args.items[0]).ToLocal(&ret); 1012 | } 1013 | else { 1014 | ret = val; 1015 | } 1016 | } 1017 | 1018 | // Get property value 1019 | if ((wFlags & DISPATCH_PROPERTYGET) != 0) { 1020 | if (pDispParams->cArgs == 1) { 1021 | Local target; 1022 | if (!val.IsEmpty()) target = Local::Cast(val); 1023 | if (target.IsEmpty()) return DISP_E_BADCALLEE; 1024 | VARIANT &key = pDispParams->rgvarg[0]; 1025 | LONG index = Variant2Int(key, -1); 1026 | if (index >= 0) target->Get(ctx, (uint32_t)index).ToLocal(&ret); 1027 | else target->Get(ctx, Variant2Value(isolate, key, false)).ToLocal(&ret); 1028 | } 1029 | else { 1030 | ret = val; 1031 | } 1032 | } 1033 | 1034 | // Store result 1035 | if (pVarResult) { 1036 | Value2Variant(isolate, ret, *pVarResult, VT_NULL); 1037 | } 1038 | return S_OK; 1039 | } 1040 | 1041 | /* 1042 | * Microsoft OLE Date type: 1043 | * https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/82ab7w69(v=vs.90) 1044 | */ 1045 | 1046 | double FromOleDate(double oleDate) { 1047 | double posixDate = oleDate - 25569; // days from 1899 dec 30 1048 | posixDate *= 24 * 60 * 60 * 1000; // days to milliseconds 1049 | return posixDate; 1050 | } 1051 | 1052 | double ToOleDate(double posixDate) { 1053 | double oleDate = posixDate / (24 * 60 * 60 * 1000); // milliseconds to days 1054 | oleDate += 25569; // days from 1899 dec 30 1055 | return oleDate; 1056 | } 1057 | 1058 | //------------------------------------------------------------------------------------------------------- 1059 | 1060 | bool NodeMethods::get(Isolate* isolate, const std::wstring &name, Local* value) { 1061 | auto ptr = items.find(name); 1062 | if (ptr == items.end()) return false; 1063 | Local ft = ptr->second->Get(isolate); 1064 | return ft->GetFunction(isolate->GetCurrentContext()).ToLocal(value); 1065 | } 1066 | 1067 | void NodeMethods::add(Isolate* isolate, Local& clazz, const char* name, FunctionCallback callback) { 1068 | // see NODE_SET_PROTOTYPE_METHOD 1069 | HandleScope handle_scope(isolate); 1070 | Local s = Signature::New(isolate, clazz); 1071 | Local t = FunctionTemplate::New(isolate, callback, Local(), s); 1072 | Local fn_name = String::NewFromUtf8(isolate, name, NewStringType::kInternalized).ToLocalChecked(); 1073 | t->SetClassName(fn_name); 1074 | 1075 | clazz->PrototypeTemplate()->Set(fn_name, t); 1076 | 1077 | String::Value vname(isolate, fn_name); 1078 | item_type item(new Persistent(isolate, t)); 1079 | items.emplace(std::wstring((const wchar_t *)*vname), item); 1080 | } 1081 | 1082 | //------------------------------------------------------------------------------------------------------- 1083 | 1084 | /* Message loop. Just like with WScript it is executed while the script is waiting. 1085 | So if you have something showing, for example, a popup message, then you will only 1086 | see it when you do WScript.Sleep. 1087 | */ 1088 | 1089 | void DoEvents() 1090 | { 1091 | MSG msg; 1092 | BOOL result; 1093 | 1094 | if (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) 1095 | { 1096 | result = ::GetMessage(&msg, NULL, 0, 0); 1097 | if (result == 0) // WM_QUIT 1098 | { 1099 | ::PostQuitMessage(msg.wParam); 1100 | } 1101 | else if (result == -1) 1102 | { 1103 | // Handle errors/exit application, etc. 1104 | } 1105 | else 1106 | { 1107 | ::TranslateMessage(&msg); 1108 | ::DispatchMessage(&msg); 1109 | } 1110 | } 1111 | } 1112 | 1113 | /* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both 1114 | * windows and linux. */ 1115 | 1116 | long long GetTimeMs64() 1117 | { 1118 | /* Windows */ 1119 | FILETIME ft; 1120 | LARGE_INTEGER li; 1121 | 1122 | /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it 1123 | * to a LARGE_INTEGER structure. */ 1124 | GetSystemTimeAsFileTime(&ft); 1125 | li.LowPart = ft.dwLowDateTime; 1126 | li.HighPart = ft.dwHighDateTime; 1127 | 1128 | long long ret = li.QuadPart; 1129 | ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */ 1130 | ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */ 1131 | 1132 | return ret; 1133 | 1134 | } 1135 | 1136 | // Sleep is essential to have proper WScript emulation 1137 | void WinaxSleep(const FunctionCallbackInfo& args) { 1138 | Isolate* isolate = args.GetIsolate(); 1139 | Local ctx = isolate->GetCurrentContext(); 1140 | 1141 | if (args.Length() == 0 && !args[0]->IsUint32()) { 1142 | isolate->ThrowException(InvalidArgumentsError(isolate)); 1143 | return; 1144 | } 1145 | uint32_t ms = (args[0]->Uint32Value(ctx)).FromMaybe(0); 1146 | long long start = GetTimeMs64(); 1147 | do 1148 | { 1149 | DoEvents(); 1150 | Sleep(1); 1151 | } while (GetTimeMs64() - start < ms); 1152 | args.GetReturnValue().SetUndefined(); 1153 | } 1154 | 1155 | // Get a COM pointer from a window found by it's text 1156 | HRESULT GetAccessibleObject(const wchar_t* pszWindowText, CComPtr& spIUnknown) { 1157 | struct ew { 1158 | static BOOL CALLBACK ecp(HWND hWnd, LPARAM lParam) { 1159 | wchar_t szWindowText[128]; 1160 | if (GetWindowTextW(hWnd, szWindowText, _countof(szWindowText))) { 1161 | ewp* pparams = reinterpret_cast(lParam); 1162 | if (!wcscmp(szWindowText, pparams->pszWindowText)) { 1163 | pparams->hWnd = hWnd; 1164 | return FALSE; 1165 | } 1166 | } 1167 | return TRUE; 1168 | } 1169 | struct ewp { 1170 | const wchar_t* pszWindowText; 1171 | HWND hWnd; 1172 | }; 1173 | }; 1174 | ew::ewp params{ pszWindowText, nullptr }; 1175 | EnumChildWindows(GetDesktopWindow(), ew::ecp, reinterpret_cast(¶ms)); 1176 | if (params.hWnd == nullptr) return _HRESULT_TYPEDEF_(0x80070057L); // ERROR_INVALID_PARAMETER 1177 | return AccessibleObjectFromWindow(params.hWnd, OBJID_NATIVEOM, IID_IUnknown, 1178 | reinterpret_cast(&spIUnknown)); 1179 | } -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------------------------------- 2 | // Project: NodeActiveX 3 | // Author: Yuri Dursin 4 | // Description: Common utilities for translation COM - NodeJS 5 | //------------------------------------------------------------------------------------------------------- 6 | 7 | #pragma once 8 | 9 | //------------------------------------------------------------------------------------------------------- 10 | 11 | #ifdef _DEBUG 12 | #define NODE_DEBUG 13 | #endif 14 | 15 | #if (V8_MAJOR_VERSION > 7) || (V8_MAJOR_VERSION == 7 && V8_MINOR_VERSION >= 1) 16 | #define NODE_BOOL_ISOLATE 17 | #endif 18 | 19 | #ifdef NODE_BOOL_ISOLATE 20 | #define NODE_BOOL(isolate, v) v->BooleanValue(isolate) 21 | #else 22 | #define NODE_BOOL(isolate, v) v->BooleanValue(isolate->GetCurrentContext()).FromMaybe(false) 23 | #endif 24 | 25 | #if (V8_MAJOR_VERSION >= 12) 26 | #define NODE_INTERCEPTED 27 | typedef PropertyCallbackInfo PropertyCallbackInfoGetter; 28 | typedef PropertyCallbackInfo PropertyCallbackInfoSetter; 29 | #else 30 | typedef PropertyCallbackInfo PropertyCallbackInfoGetter; 31 | typedef PropertyCallbackInfo PropertyCallbackInfoSetter; 32 | #endif 33 | 34 | #ifdef NODE_DEBUG 35 | #define NODE_DEBUG_PREFIX "### " 36 | #define NODE_DEBUG_MSG(msg) { printf(NODE_DEBUG_PREFIX"%s", msg); std::cout << std::endl; } 37 | #define NODE_DEBUG_FMT(msg, arg) { std::cout << NODE_DEBUG_PREFIX; printf(msg, arg); std::cout << std::endl; } 38 | #define NODE_DEBUG_FMT2(msg, arg, arg2) { std::cout << NODE_DEBUG_PREFIX; printf(msg, arg, arg2); std::cout << std::endl; } 39 | #else 40 | #define NODE_DEBUG_MSG(msg) 41 | #define NODE_DEBUG_FMT(msg, arg) 42 | #define NODE_DEBUG_FMT2(msg, arg, arg2) 43 | #endif 44 | 45 | inline Local v8str(Isolate *isolate, const char *text) { 46 | Local str; 47 | if (!text || !String::NewFromUtf8(isolate, text, NewStringType::kNormal).ToLocal(&str)) { 48 | str = String::Empty(isolate); 49 | } 50 | return str; 51 | } 52 | 53 | inline Local v8str(Isolate *isolate, const uint16_t *text) { 54 | Local str; 55 | if (!text || !String::NewFromTwoByte(isolate, (const uint16_t*)text, NewStringType::kNormal).ToLocal(&str)) { 56 | str = String::Empty(isolate); 57 | } 58 | return str; 59 | } 60 | 61 | inline Local v8str(Isolate *isolate, const wchar_t *text) { 62 | return v8str(isolate, (const uint16_t *)text); 63 | } 64 | 65 | //------------------------------------------------------------------------------------------------------- 66 | #ifndef USE_ATL 67 | 68 | class CComVariant : public VARIANT { 69 | public: 70 | inline CComVariant() { 71 | memset((VARIANT*)this, 0, sizeof(VARIANT)); 72 | } 73 | inline CComVariant(const CComVariant &src) { 74 | memset((VARIANT*)this, 0, sizeof(VARIANT)); 75 | VariantCopyInd(this, &src); 76 | } 77 | inline CComVariant(const VARIANT &src) { 78 | memset((VARIANT*)this, 0, sizeof(VARIANT)); 79 | VariantCopyInd(this, &src); 80 | } 81 | inline CComVariant(LONG v) { 82 | memset((VARIANT*)this, 0, sizeof(VARIANT)); 83 | vt = VT_I4; 84 | lVal = v; 85 | } 86 | inline CComVariant(LPOLESTR v) { 87 | memset((VARIANT*)this, 0, sizeof(VARIANT)); 88 | vt = VT_BSTR; 89 | bstrVal = SysAllocString(v); 90 | } 91 | inline ~CComVariant() { 92 | Clear(); 93 | } 94 | inline void Clear() { 95 | if (vt != VT_EMPTY) 96 | VariantClear(this); 97 | } 98 | inline void Detach(VARIANT *dst) { 99 | *dst = *this; 100 | vt = VT_EMPTY; 101 | } 102 | inline HRESULT CopyTo(VARIANT *dst) { 103 | return VariantCopy(dst, this); 104 | } 105 | 106 | inline HRESULT ChangeType(VARTYPE vtNew, const VARIANT* pSrc = NULL) { 107 | return VariantChangeType(this, pSrc ? pSrc : this, 0, vtNew); 108 | } 109 | 110 | inline ULONG ArrayLength() { 111 | if ((vt & VT_ARRAY) == 0) return 0; 112 | SAFEARRAY *varr = (vt & VT_BYREF) != 0 ? *pparray : parray; 113 | return varr ? varr->rgsabound[0].cElements : 0; 114 | } 115 | 116 | inline HRESULT ArrayGet(LONG index, CComVariant &var) { 117 | if ((vt & VT_ARRAY) == 0) return E_NOTIMPL; 118 | SAFEARRAY *varr = (vt & VT_BYREF) != 0 ? *pparray : parray; 119 | if (!varr) return E_FAIL; 120 | index += varr->rgsabound[0].lLbound; 121 | VARTYPE vart = vt & VT_TYPEMASK; 122 | HRESULT hr = SafeArrayGetElement(varr, &index, (vart == VT_VARIANT) ? (void*)&var : (void*)&var.byref); 123 | if (SUCCEEDED(hr) && vart != VT_VARIANT) var.vt = vart; 124 | return hr; 125 | } 126 | template 127 | inline T* ArrayGet(ULONG index = 0) { 128 | return ((T*)parray->pvData) + index; 129 | } 130 | inline HRESULT ArrayCreate(VARTYPE avt, ULONG cnt) { 131 | Clear(); 132 | parray = SafeArrayCreateVector(avt, 0, cnt); 133 | if (!parray) return E_UNEXPECTED; 134 | vt = VT_ARRAY | avt; 135 | return S_OK; 136 | } 137 | inline HRESULT ArrayResize(ULONG cnt) { 138 | SAFEARRAYBOUND bnds = { cnt, 0 }; 139 | return SafeArrayRedim(parray, &bnds); 140 | } 141 | }; 142 | 143 | class CComBSTR { 144 | public: 145 | BSTR p; 146 | inline CComBSTR() : p(0) {} 147 | inline CComBSTR(const CComBSTR &src) : p(0) {} 148 | inline ~CComBSTR() { Free(); } 149 | inline void Attach(BSTR _p) { Free(); p = _p; } 150 | inline BSTR Detach() { BSTR pp = p; p = 0; return pp; } 151 | inline void Free() { if (p) { SysFreeString(p); p = 0; } } 152 | 153 | inline operator BSTR () const { return p; } 154 | inline BSTR* operator&() { return &p; } 155 | inline bool operator!() const { return (p == 0); } 156 | inline bool operator!=(BSTR _p) const { return !operator==(_p); } 157 | inline bool operator==(BSTR _p) const { return p == _p; } 158 | inline BSTR operator = (BSTR _p) { 159 | if (p != _p) Attach(_p ? SysAllocString(_p) : 0); 160 | return p; 161 | } 162 | }; 163 | 164 | class CComException: public EXCEPINFO { 165 | public: 166 | inline CComException() { 167 | memset((EXCEPINFO*)this, 0, sizeof(EXCEPINFO)); 168 | } 169 | inline ~CComException() { 170 | Clear(true); 171 | } 172 | inline void Clear(bool internal = false) { 173 | if (bstrSource) SysFreeString(bstrSource); 174 | if (bstrDescription) SysFreeString(bstrDescription); 175 | if (bstrHelpFile) SysFreeString(bstrHelpFile); 176 | if (!internal) memset((EXCEPINFO*)this, 0, sizeof(EXCEPINFO)); 177 | } 178 | }; 179 | 180 | template 181 | class CComPtr { 182 | public: 183 | T *p; 184 | inline CComPtr() : p(0) {} 185 | inline CComPtr(T *_p) : p(0) { Attach(_p); } 186 | inline CComPtr(const CComPtr &ptr) : p(0) { if (ptr.p) Attach(ptr.p); } 187 | inline ~CComPtr() { Release(); } 188 | 189 | inline void Attach(T *_p) { Release(); p = _p; if (p) p->AddRef(); } 190 | inline T *Detach() { T *pp = p; p = 0; return pp; } 191 | inline void Release() { if (p) { p->Release(); p = 0; } } 192 | 193 | inline operator T*() const { return p; } 194 | inline T* operator->() const { return p; } 195 | inline T& operator*() const { return *p; } 196 | inline T** operator&() { return &p; } 197 | inline bool operator!() const { return (p == 0); } 198 | inline bool operator!=(T* _p) const { return !operator==(_p); } 199 | inline bool operator==(T* _p) const { return p == _p; } 200 | inline T* operator = (T* _p) { 201 | if (p != _p) Attach(_p); 202 | return p; 203 | } 204 | 205 | inline HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter = NULL, DWORD dwClsContext = CLSCTX_ALL) { 206 | Release(); 207 | return ::CoCreateInstance(rclsid, pUnkOuter, dwClsContext, __uuidof(T), (void**)&p); 208 | } 209 | 210 | inline HRESULT CoCreateInstance(LPCOLESTR szProgID, LPUNKNOWN pUnkOuter = NULL, DWORD dwClsContext = CLSCTX_ALL) { 211 | Release(); 212 | CLSID clsid; 213 | HRESULT hr = CLSIDFromProgID(szProgID, &clsid); 214 | if FAILED(hr) return hr; 215 | return ::CoCreateInstance(clsid, pUnkOuter, dwClsContext, __uuidof(T), (void**)&p); 216 | } 217 | }; 218 | 219 | #endif 220 | //------------------------------------------------------------------------------------------------------- 221 | 222 | Local GetWin32ErrorMessage(Isolate *isolate, HRESULT hrcode, LPCOLESTR msg, LPCOLESTR msg2 = 0, LPCOLESTR desc = 0); 223 | 224 | inline Local Win32Error(Isolate *isolate, HRESULT hrcode, LPCOLESTR id = 0, LPCOLESTR msg = 0) { 225 | auto err = Exception::Error(GetWin32ErrorMessage(isolate, hrcode, id, msg)); 226 | auto obj = Local::Cast(err); 227 | obj->Set(isolate->GetCurrentContext(), v8str(isolate, "errno"), Integer::New(isolate, hrcode)); 228 | return err; 229 | } 230 | 231 | void GetScodeString(HRESULT hr, wchar_t* buf, int bufSize); 232 | 233 | inline Local DispError(Isolate *isolate, HRESULT hrcode, LPCOLESTR id = 0, LPCOLESTR msg = 0, EXCEPINFO *except = 0) { 234 | Local ctx = isolate->GetCurrentContext(); 235 | CComBSTR desc; 236 | CComPtr errinfo; 237 | 238 | std::wstring emsg; 239 | emsg.reserve(1024); 240 | if (except && except->scode) { 241 | wchar_t wc[1024]; 242 | GetScodeString(except->scode, wc, sizeof(wc) / sizeof(wc[0])); 243 | emsg += msg; 244 | emsg += L": "; 245 | emsg += wc; 246 | } 247 | else { 248 | emsg = std::wstring(msg); 249 | } 250 | 251 | HRESULT hr = GetErrorInfo(0, &errinfo); 252 | if (hr == S_OK) errinfo->GetDescription(&desc); 253 | auto err = Exception::Error(GetWin32ErrorMessage(isolate, hrcode, id, emsg.c_str(), desc)); 254 | auto obj = Local::Cast(err); 255 | obj->Set(ctx, v8str(isolate, "errno"), Integer::New(isolate, hrcode)); 256 | if (except) { 257 | if (except->scode != 0) obj->Set(ctx, v8str(isolate, "code"), Integer::New(isolate, except->scode)); 258 | else if (except->wCode != 0) obj->Set(ctx, v8str(isolate, "code"), Integer::New(isolate, except->wCode)); 259 | if (except->bstrSource != 0) obj->Set(ctx, v8str(isolate, "source"), v8str(isolate, except->bstrSource)); 260 | if (except->bstrDescription != 0) { 261 | obj->Set(ctx, v8str(isolate, "message"), v8str(isolate, except->bstrDescription)); 262 | obj->Set(ctx, v8str(isolate, "description"), v8str(isolate, except->bstrDescription)); 263 | } 264 | } 265 | return err; 266 | } 267 | 268 | inline Local DispErrorNull(Isolate *isolate) { 269 | return Exception::TypeError(v8str(isolate, "DispNull")); 270 | } 271 | 272 | inline Local DispErrorInvalid(Isolate *isolate) { 273 | return Exception::TypeError(v8str(isolate, "DispInvalid")); 274 | } 275 | 276 | inline Local TypeError(Isolate *isolate, const char *msg) { 277 | return Exception::TypeError(v8str(isolate, msg)); 278 | } 279 | 280 | inline Local InvalidArgumentsError(Isolate *isolate) { 281 | return Exception::TypeError(v8str(isolate, "Invalid arguments")); 282 | } 283 | 284 | inline Local Error(Isolate *isolate, const char *msg) { 285 | return Exception::Error(v8str(isolate, msg)); 286 | } 287 | 288 | //------------------------------------------------------------------------------------------------------- 289 | 290 | class NodeObject : public ObjectWrap 291 | { 292 | public: 293 | template 294 | static inline T *Unwrap(Local handle) { 295 | if (handle.IsEmpty() || handle->InternalFieldCount() == 0) { 296 | return NULL; 297 | } 298 | void *ptr = handle->GetAlignedPointerFromInternalField(0); 299 | NodeObject *obj = static_cast(ptr); 300 | return static_cast(obj); 301 | } 302 | }; 303 | 304 | //------------------------------------------------------------------------------------------------------- 305 | 306 | inline HRESULT DispFind(IDispatch *disp, LPOLESTR name, DISPID *dispid) { 307 | LPOLESTR names[] = { name }; 308 | return disp->GetIDsOfNames(GUID_NULL, names, 1, 0, dispid); 309 | } 310 | 311 | inline HRESULT DispInvoke(IDispatch *disp, DISPID dispid, UINT argcnt = 0, VARIANT *args = 0, VARIANT *ret = 0, WORD flags = DISPATCH_METHOD, EXCEPINFO *except = 0) { 312 | DISPPARAMS params = { args, 0, argcnt, 0 }; 313 | DISPID dispidNamed = DISPID_PROPERTYPUT; 314 | if (flags == DISPATCH_PROPERTYPUT) { // It`s a magic 315 | params.cNamedArgs = 1; 316 | params.rgdispidNamedArgs = &dispidNamed; 317 | if (params.rgvarg && params.rgvarg->vt == VT_DISPATCH) flags = DISPATCH_PROPERTYPUTREF; 318 | } 319 | return disp->Invoke(dispid, IID_NULL, 0, flags, ¶ms, ret, except, 0); 320 | } 321 | 322 | inline HRESULT DispInvoke(IDispatch *disp, LPOLESTR name, UINT argcnt = 0, VARIANT *args = 0, VARIANT *ret = 0, WORD flags = DISPATCH_METHOD, DISPID *dispid = 0, EXCEPINFO *except = 0) { 323 | LPOLESTR names[] = { name }; 324 | DISPID dispids[] = { 0 }; 325 | HRESULT hrcode = disp->GetIDsOfNames(GUID_NULL, names, 1, 0, dispids); 326 | if SUCCEEDED(hrcode) hrcode = DispInvoke(disp, dispids[0], argcnt, args, ret, flags, except); 327 | if (dispid) *dispid = dispids[0]; 328 | return hrcode; 329 | } 330 | 331 | //------------------------------------------------------------------------------------------------------- 332 | 333 | template 334 | inline INTTYPE Variant2Int(const VARIANT &v, const INTTYPE def) { 335 | VARTYPE vt = (v.vt & VT_TYPEMASK); 336 | bool by_ref = (v.vt & VT_BYREF) != 0; 337 | switch (vt) { 338 | case VT_EMPTY: 339 | case VT_NULL: 340 | return def; 341 | case VT_I1: 342 | case VT_I2: 343 | case VT_I4: 344 | case VT_INT: 345 | return (INTTYPE)(by_ref ? *v.plVal : v.lVal); 346 | case VT_UI1: 347 | case VT_UI2: 348 | case VT_UI4: 349 | case VT_UINT: 350 | return (INTTYPE)(by_ref ? *v.pulVal : v.ulVal); 351 | case VT_CY: 352 | return (INTTYPE)((by_ref ? v.pcyVal : &v.cyVal)->int64 / 10000); 353 | case VT_R4: 354 | return (INTTYPE)(by_ref ? *v.pfltVal : v.fltVal); 355 | case VT_R8: 356 | return (INTTYPE)(by_ref ? *v.pdblVal : v.dblVal); 357 | case VT_DATE: 358 | return (INTTYPE)(by_ref ? *v.pdate : v.date); 359 | case VT_DECIMAL: { 360 | LONG64 int64val; 361 | return SUCCEEDED(VarI8FromDec(by_ref ? v.pdecVal : &v.decVal, &int64val)) ? (INTTYPE)int64val : def; 362 | } 363 | case VT_BOOL: 364 | return (v.boolVal == VARIANT_TRUE) ? 1 : 0; 365 | case VT_VARIANT: 366 | if (v.pvarVal) return Variant2Int(*v.pvarVal, def); 367 | } 368 | VARIANT dst; 369 | return SUCCEEDED(VariantChangeType(&dst, &v, 0, VT_INT)) ? (INTTYPE)dst.intVal : def; 370 | } 371 | 372 | Local Variant2Array(Isolate *isolate, const VARIANT &v); 373 | Local Variant2Array2(Isolate *isolate, const VARIANT &v); 374 | Local Variant2Value(Isolate *isolate, const VARIANT &v, bool allow_disp = false); 375 | Local Variant2String(Isolate *isolate, const VARIANT &v); 376 | void Value2Variant(Isolate *isolate, Local &val, VARIANT &var, VARTYPE vt = VT_EMPTY); 377 | void Value2SafeArray(Isolate *isolate, Local &val, VARIANT &var, VARTYPE vt); 378 | bool Value2Unknown(Isolate *isolate, Local &val, IUnknown **unk); 379 | bool VariantUnkGet(VARIANT *v, IUnknown **unk); 380 | bool VariantDispGet(VARIANT *v, IDispatch **disp); 381 | bool UnknownDispGet(IUnknown *unk, IDispatch **disp); 382 | 383 | //------------------------------------------------------------------------------------------------------- 384 | 385 | inline bool v8val2bool(Isolate *isolate, const Local &v, bool def) { 386 | Local ctx = isolate->GetCurrentContext(); 387 | if (v.IsEmpty()) return def; 388 | if (v->IsBoolean()) return NODE_BOOL(isolate, v); 389 | if (v->IsInt32()) return v->Int32Value(ctx).FromMaybe(def ? 1 : 0) != 0; 390 | if (v->IsUint32()) return v->Uint32Value(ctx).FromMaybe(def ? 1 : 0) != 0; 391 | return def; 392 | } 393 | 394 | //------------------------------------------------------------------------------------------------------- 395 | 396 | class VarArguments { 397 | public: 398 | std::vector items; 399 | VarArguments() {} 400 | VarArguments(Isolate *isolate, Local value) { 401 | items.resize(1); 402 | Value2Variant(isolate, value, items[0]); 403 | } 404 | VarArguments(Isolate *isolate, const FunctionCallbackInfo &args) { 405 | int argcnt = args.Length(); 406 | items.resize(argcnt); 407 | for (int i = 0; i < argcnt; i++) { 408 | auto arg = args[argcnt - i - 1]; 409 | Value2Variant(isolate, arg, items[i]); 410 | } 411 | } 412 | inline bool IsDefault() { 413 | if (items.size() != 1) return false; 414 | auto &arg = items[0]; 415 | if (arg.vt != VT_BSTR || arg.bstrVal == nullptr) return false; 416 | return wcscmp(arg.bstrVal, L"default") == 0; 417 | } 418 | }; 419 | 420 | class NodeArguments { 421 | public: 422 | std::vector> items; 423 | NodeArguments(Isolate *isolate, DISPPARAMS *pDispParams, bool allow_disp, bool reverse_arguments = true) { 424 | UINT argcnt = pDispParams->cArgs; 425 | items.resize(argcnt); 426 | for (UINT i = 0; i < argcnt; i++) { 427 | items[i] = Variant2Value(isolate, pDispParams->rgvarg[reverse_arguments ? argcnt - i - 1 : i], allow_disp); 428 | } 429 | } 430 | }; 431 | 432 | //------------------------------------------------------------------------------------------------------- 433 | 434 | template 435 | class UnknownImpl : public IBASE { 436 | public: 437 | inline UnknownImpl() : refcnt(0) {} 438 | virtual ~UnknownImpl() {} 439 | 440 | // IUnknown interface 441 | virtual HRESULT __stdcall QueryInterface(REFIID qiid, void **ppvObject) { 442 | if ((qiid == IID_IUnknown) || (qiid == __uuidof(IBASE))) { 443 | *ppvObject = this; 444 | AddRef(); 445 | return S_OK; 446 | } 447 | return E_NOINTERFACE; 448 | } 449 | 450 | virtual ULONG __stdcall AddRef() { 451 | return InterlockedIncrement(&refcnt); 452 | } 453 | 454 | virtual ULONG __stdcall Release() { 455 | if (InterlockedDecrement(&refcnt) != 0) return refcnt; 456 | delete this; 457 | return 0; 458 | } 459 | 460 | protected: 461 | LONG refcnt; 462 | 463 | }; 464 | 465 | class DispArrayImpl : public UnknownImpl { 466 | public: 467 | CComVariant var; 468 | DispArrayImpl(const VARIANT &v): var(v) {} 469 | 470 | // IDispatch interface 471 | virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) { *pctinfo = 0; return S_OK; } 472 | virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; } 473 | virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId); 474 | virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr); 475 | }; 476 | 477 | class DispEnumImpl : public UnknownImpl { 478 | public: 479 | CComPtr ptr; 480 | DispEnumImpl() {} 481 | DispEnumImpl(IEnumVARIANT *p) : ptr(p) {} 482 | 483 | // IDispatch interface 484 | virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) { *pctinfo = 0; return S_OK; } 485 | virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; } 486 | virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId); 487 | virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr); 488 | }; 489 | 490 | 491 | // {9DCE8520-2EFE-48C0-A0DC-951B291872C0} 492 | extern const GUID CLSID_DispObjectImpl; 493 | 494 | // {AFBF15E5-C37C-11D2-B88E-00A0C9B471B8} 495 | extern const IID IID_IReflect; 496 | 497 | class DispObjectImpl : public UnknownImpl { 498 | public: 499 | Persistent obj; 500 | 501 | struct name_t { 502 | DISPID dispid; 503 | std::wstring name; 504 | inline name_t(DISPID id, const std::wstring &nm): dispid(id), name(nm) {} 505 | }; 506 | typedef std::shared_ptr name_ptr; 507 | typedef std::map names_t; 508 | typedef std::map index_t; 509 | DISPID dispid_next; 510 | names_t names; 511 | index_t index; 512 | bool reverse_arguments; 513 | IID connIfIID; 514 | 515 | inline DispObjectImpl(const Local &_obj, bool revargs = true, IID connIfIID=IID_NULL) : obj(Isolate::GetCurrent(), _obj), dispid_next(1), reverse_arguments(revargs), connIfIID(connIfIID){} 516 | virtual ~DispObjectImpl() { obj.Reset(); } 517 | 518 | // IUnknown interface 519 | virtual HRESULT __stdcall QueryInterface(REFIID qiid, void **ppvObject) { 520 | if( qiid==this->connIfIID) { return UnknownImpl::QueryInterface(IID_IDispatch, ppvObject); } 521 | //if (qiid == this->connIfIID) { *ppvObject = this; return S_OK; } 522 | if (qiid == CLSID_DispObjectImpl) { *ppvObject = this; return S_OK; } 523 | HRESULT res = UnknownImpl::QueryInterface(qiid, ppvObject); 524 | NODE_DEBUG_MSG("RES") 525 | return res; 526 | } 527 | 528 | // IDispatch interface 529 | virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) { *pctinfo = 0; return S_OK; } 530 | virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; } 531 | virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId); 532 | virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr); 533 | }; 534 | 535 | double FromOleDate(double); 536 | double ToOleDate(double); 537 | 538 | //------------------------------------------------------------------------------------------------------- 539 | 540 | class NodeMethods : public std::map > { 541 | public: 542 | typedef std::shared_ptr> item_type; 543 | typedef std::map map_type; 544 | map_type items; 545 | 546 | bool get(Isolate* isolate, const std::wstring& name, Local* value); 547 | void add(Isolate* isolate, Local& clazz, const char* name, FunctionCallback callback); 548 | }; 549 | 550 | //------------------------------------------------------------------------------------------------------- 551 | // Sleep is essential to have proper WScript emulation 552 | 553 | void WinaxSleep(const FunctionCallbackInfo& args); 554 | 555 | //------------------------------------------------------------------------------------------------------- 556 | HRESULT GetAccessibleObject(const wchar_t* pszWindowText, CComPtr& spIUnknown); 557 | -------------------------------------------------------------------------------- /test/ado.js: -------------------------------------------------------------------------------- 1 | var winax = require('../activex'); 2 | 3 | var path = require('path'); 4 | const assert = require('assert'); 5 | 6 | var data_path = path.join(__dirname, '../data/'); 7 | var filename = "persons.dbf"; 8 | var provider = "Microsoft.ACE.OLEDB.12.0"; 9 | var constr = "Provider=" + provider + ";Data Source=" + data_path + ";Extended Properties=\"DBASE IV;\""; 10 | var fso, con, rs, fields, reccnt; 11 | 12 | describe("Scripting.FileSystemObject", function() { 13 | 14 | it("create", function() { 15 | fso = new ActiveXObject("Scripting.FileSystemObject"); 16 | }); 17 | 18 | it("create data folder if not exists", function() { 19 | if (fso) { 20 | if (!fso.FolderExists(data_path)) 21 | fso.CreateFolder(data_path); 22 | } 23 | }); 24 | 25 | it("delete DBF file if exists", function() { 26 | if (fso) { 27 | if (fso.FileExists(data_path + filename)) 28 | fso.DeleteFile(data_path + filename); 29 | } 30 | }); 31 | 32 | }); 33 | 34 | describe("ADODB.Connection", function() { 35 | 36 | it("create and open", function() { 37 | this.timeout(5000); 38 | con = new ActiveXObject("ADODB.Connection"); 39 | con.Open(constr, "", ""); 40 | this.test.title += ': ver=' + con.Version; 41 | }); 42 | 43 | it("create and fill table", function() { 44 | if (con) { 45 | con.Execute("create Table " + filename + " (Name char(50), City char(50), Phone char(20), Zip decimal(5))"); 46 | con.Execute("insert into " + filename + " values('John', 'London','123-45-67','14589')"); 47 | con.Execute("insert into " + filename + " values('Andrew', 'Paris','333-44-55','38215')"); 48 | con.Execute("insert into " + filename + " values('Romeo', 'Rom','222-33-44','54323')"); 49 | reccnt = 3; 50 | } 51 | }); 52 | 53 | it("select records from table", function() { 54 | if (con) { 55 | var rs = con.Execute("Select * from " + filename); 56 | var fields = rs.Fields; 57 | } 58 | }); 59 | 60 | it("loop by records", function() { 61 | if (rs && fields) { 62 | var cnt = 0; 63 | rs.MoveFirst(); 64 | while (!rs.EOF) { 65 | cnt++; 66 | var name = fields("Name").Value; 67 | var town = fields["City"].value; 68 | var phone = fields[2].value; 69 | var zip = fields[3].value; 70 | rs.MoveNext(); 71 | } 72 | assert.equal(cnt, reccnt); 73 | } 74 | }); 75 | 76 | }); 77 | 78 | describe("Release objects", function() { 79 | 80 | it("try call", function() { 81 | if (con) try { this.test.title += ': SUCCESS (' + con.Version + ')'; } 82 | catch (e) { this.test.title += ': FAILED (' + e.message + ')'; } 83 | }); 84 | 85 | it("release", function() { 86 | this.test.title += ': ' + winax.release(fso, con, rs, fields); 87 | }); 88 | 89 | it("double release", function() { 90 | this.test.title += ': ' + winax.release(fso, con, rs, fields); 91 | }); 92 | 93 | if (typeof global.gc === 'function') { 94 | global.gc(); 95 | const mem_usage = process.memoryUsage().heapUsed / 1024; 96 | it("check memory", function() { 97 | global.gc(); 98 | const mem = process.memoryUsage().heapUsed / 1024; 99 | if (mem > mem_usage) throw new Error(`used memory increased from ${mem_usage.toFixed(2)}Kb to ${mem.toFixed(2)}Kb`); 100 | }); 101 | } 102 | }); -------------------------------------------------------------------------------- /test/excel.js: -------------------------------------------------------------------------------- 1 | require('../activex'); 2 | 3 | var path = require('path'); 4 | const assert = require('assert'); 5 | 6 | var template_filename = path.join(__dirname, '../data/test.xltm'); 7 | var excel, wbk; 8 | 9 | var test_value = 'value'; 10 | var test_value2 = 'value2'; 11 | var test_value3 = 'value3'; 12 | var test_func_arg = 10; 13 | var com_obj, js_obj = { 14 | text: test_value, 15 | obj: { params: test_value }, 16 | arr: [test_value, test_value, test_value], 17 | func: function(v) { return v * 2; }, 18 | func2: function(obj) { return obj.text; } 19 | }; 20 | 21 | describe("COM from JS object", function() { 22 | 23 | it("create", function() { 24 | com_obj = new ActiveXObject(js_obj); 25 | }); 26 | 27 | it("read simple property", function() { 28 | if (com_obj) assert.equal(com_obj.text, js_obj.text); 29 | }); 30 | 31 | it("read object property", function() { 32 | if (com_obj) assert.equal(com_obj.obj.params, js_obj.obj.params); 33 | }); 34 | 35 | it("read array property", function() { 36 | if (com_obj) { 37 | assert.equal(com_obj.arr.length, js_obj.arr.length); 38 | assert.equal(com_obj.arr[0], js_obj.arr[0]); 39 | } 40 | }); 41 | 42 | it("change simple property", function() { 43 | if (com_obj) { 44 | com_obj.text = test_value2; 45 | assert.equal(com_obj.text, test_value2); 46 | assert.equal(js_obj.text, test_value2); 47 | } 48 | }); 49 | 50 | it("change object property", function() { 51 | if (com_obj) { 52 | com_obj.obj.params = test_value2; 53 | assert.equal(com_obj.obj.params, test_value2); 54 | assert.equal(js_obj.obj.params, test_value2); 55 | } 56 | }); 57 | 58 | it("change array property", function() { 59 | if (com_obj) { 60 | com_obj.arr[0] = test_value2; 61 | assert.equal(com_obj.arr[0], test_value2); 62 | assert.equal(js_obj.arr[0], test_value2); 63 | } 64 | }); 65 | 66 | it("call method", function() { 67 | if (com_obj) assert.equal(com_obj.func(test_func_arg), js_obj.func(test_func_arg)); 68 | }); 69 | 70 | it("call method with object argument", function() { 71 | if (com_obj) assert.equal(com_obj.func2(com_obj), js_obj.text); 72 | }); 73 | 74 | it("COM error message", function() { 75 | var fso = new ActiveXObject("Scripting.FileSystemObject"); 76 | try { 77 | fso.DeleteFile("c:\\noexist.txt"); 78 | } catch(e) { 79 | // assert.equal(e.message, "DispInvoke: DeleteFile: CTL_E_FILENOTFOUND Exception occurred.\r\n"); 80 | assert(e.message.startsWith("DispInvoke: DeleteFile: CTL_E_FILENOTFOUND")); 81 | } 82 | }); 83 | }); 84 | 85 | describe("Excel with JS object", function() { 86 | 87 | it("create", function() { 88 | this.timeout(10000); 89 | excel = new ActiveXObject("Excel.Application", { activate: true }); 90 | }); 91 | 92 | it("create workbook from test template", function() { 93 | this.timeout(10000); 94 | if (excel) wbk = excel.Workbooks.Add(template_filename); 95 | }); 96 | 97 | it("worksheet access", function() { 98 | var wsh1 = wbk.Worksheets.Item(1); 99 | var wsh2 = wbk.Worksheets.Item[1]; 100 | assert.equal(wsh1.Name, 'Sheet1'); 101 | assert.equal(wsh2.Name, 'Sheet1'); 102 | }); 103 | 104 | it("cell access", function() { 105 | var wsh = wbk.Worksheets.Item(1); 106 | wsh.Cells(1, 1).Value = 'test'; 107 | var val = wsh.Cells(1, 1).Value; 108 | assert.equal(val, 'test'); 109 | }); 110 | 111 | it("invoke test simple property", function() { 112 | if (wbk && com_obj) assert.equal(test_value3, wbk.Test(com_obj, 'text', 0, test_value3)); 113 | }); 114 | 115 | it("invoke test object property", function() { 116 | if (wbk && com_obj) assert.equal(test_value3, wbk.Test(com_obj, 'obj', 0, test_value3)); 117 | }); 118 | 119 | it("invoke test array property", function() { 120 | if (wbk && com_obj) assert.equal(test_value3, wbk.Test(com_obj, 'arr', 0, test_value3)); 121 | }); 122 | 123 | it("invoke test method", function() { 124 | if (wbk && com_obj) assert.equal(js_obj.func(test_func_arg, 1), wbk.Test(com_obj, 'func', 0, test_func_arg)); 125 | }); 126 | 127 | it("range read, write with two dimension arrays", function() { 128 | if (wbk) { 129 | var wsh = wbk.Worksheets.Item(1); 130 | wsh.Range("A1:B3").Value = [["A1", "B1"], ["A2", "B2"], ["A3", "B3"]]; 131 | const data = wsh.Range("A1:B3").Value.valueOf(); 132 | assert(data instanceof Array); 133 | assert.equal(data.length, 3); 134 | assert(data[0] instanceof Array); 135 | assert.equal(data[0].length, 2); 136 | assert.equal(data[0][0], "A1"); 137 | assert.equal(data[2][1], "B3"); 138 | } 139 | }); 140 | 141 | it("quit", function() { 142 | if (wbk) wbk.Close(false); 143 | if (excel) excel.Quit(); 144 | }); 145 | 146 | if (typeof global.gc === 'function') { 147 | global.gc(); 148 | const mem_usage = process.memoryUsage().heapUsed / 1024; 149 | it("check memory", function() { 150 | global.gc(); 151 | const mem = process.memoryUsage().heapUsed / 1024; 152 | if (mem > mem_usage) throw new Error(`used memory increased from ${mem_usage.toFixed(2)}Kb to ${mem.toFixed(2)}Kb`); 153 | }); 154 | } 155 | }); -------------------------------------------------------------------------------- /test/variant.js: -------------------------------------------------------------------------------- 1 | const winax = require('../activex'); 2 | 3 | const path = require('path'); 4 | const assert = require('assert'); 5 | const x64 = process.arch.indexOf('64') >= 0; 6 | 7 | const js_arr = ['1', 2, 3]; 8 | 9 | describe("Variants", function() { 10 | 11 | it("Short Array", function() { 12 | const arr = new winax.Variant(js_arr, 'short'); 13 | assert.equal(arr.length, js_arr.length); 14 | assert.strictEqual(arr[0], 1); 15 | }); 16 | 17 | it("String Array", function() { 18 | const arr = new winax.Variant(js_arr, 'string'); 19 | assert.equal(arr.length, js_arr.length); 20 | assert.strictEqual(arr[1], '2'); 21 | }); 22 | 23 | it("Variant Array", function() { 24 | const arr = new winax.Variant(js_arr, 'variant'); 25 | assert.equal(arr.length, js_arr.length); 26 | assert.strictEqual(arr[0], js_arr[0]); 27 | assert.strictEqual(arr[1], js_arr[1]); 28 | }); 29 | 30 | it("References", function() { 31 | const v = new winax.Variant(); 32 | const ref = new winax.Variant(v, 'byref'); 33 | 34 | v.assign(1, 'string'); 35 | assert.strictEqual(v.valueOf(), '1'); 36 | assert.strictEqual(ref.valueOf(), '1'); 37 | 38 | v.cast('int'); 39 | assert.strictEqual(v.valueOf(), 1); 40 | assert.strictEqual(ref.valueOf(), 1); 41 | 42 | v.clear(); 43 | assert.strictEqual(v.valueOf(), undefined); 44 | assert.strictEqual(ref.valueOf(), undefined); 45 | }); 46 | 47 | it("Dispatch pointer as Uint8Array", function() { 48 | 49 | // create simple dispatch object 50 | const obj = new winax.Object({ 51 | test: function(v) { return v * 2; } 52 | }); 53 | 54 | // convert dispatch pointer to bytes array 55 | const varr = new winax.Variant(obj, 'uint8[]'); 56 | 57 | // convert to javascript Uint8Array and check length 58 | const arr = new Uint8Array(varr.valueOf()); 59 | assert.strictEqual(arr.length, x64 ? 8 : 4); 60 | 61 | // create dispatch object from array pointer and test 62 | const obj2 = new winax.Object(arr); 63 | assert.strictEqual(obj2.test(2), 4); 64 | }); 65 | 66 | if (typeof global.gc === 'function') { 67 | global.gc(); 68 | const mem_usage = process.memoryUsage().heapUsed / 1024; 69 | it("Check memory", function() { 70 | global.gc(); 71 | const mem = process.memoryUsage().heapUsed / 1024; 72 | if (mem > mem_usage) throw new Error(`used memory increased from ${mem_usage.toFixed(2)}Kb to ${mem.toFixed(2)}Kb`); 73 | }); 74 | } 75 | }); 76 | -------------------------------------------------------------------------------- /test/wscript.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | function r(name) { 4 | const {execSync} = require('child_process') 5 | execSync(__dirname+'/wscript/nodewscript_test_cli.cmd '+__dirname+'/wscript/'+name+'.js', {timeout: 10000}) 6 | } 7 | 8 | describe("Execute WScript Tests", function() { 9 | 10 | it('WScript Arguments', function (done) { 11 | r('arguments'); 12 | done() 13 | }) 14 | 15 | it('WScript Enumerator', function (done) { 16 | r('enumerator'); 17 | done() 18 | }) 19 | 20 | 21 | it('WScript Mixed', function (done) { 22 | r('mixed'); 23 | done() 24 | }) 25 | 26 | it('WScript WMI', function (done) { 27 | r('wmi'); 28 | done() 29 | }) 30 | 31 | 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /test/wscript/README.md: -------------------------------------------------------------------------------- 1 | # WScript JScript samples 2 | 3 | Here we have a number of .js files taken from available tutorials and samples just as is. Our goal is to demonstrante that `nodewscript` may be used to launch them without any modification. 4 | 5 | `wmi.js` https://www.activexperts.com/admin/scripts/wmi/jscript/0383/ 6 | 7 | `enumerator.js` https://gist.github.com/mlhaufe/1569247 8 | 9 | -------------------------------------------------------------------------------- /test/wscript/arguments.js: -------------------------------------------------------------------------------- 1 | WScript.Echo("Same count: " + (WScript.Arguments.length==WScript.Arguments.Count()) ); 2 | 3 | objArgs = WScript.Arguments 4 | WScript.Echo(WScript.Arguments.Count()); 5 | for (i=0; i 2 | var listFileTypes = (function(){ 3 | var fso = new ActiveXObject("Scripting.FileSystemObject"); 4 | var shell = new ActiveXObject("WScript.Shell"); 5 | 6 | function isKnown(file){ 7 | var fName = file.Name; 8 | //the rules of capitalization are strange in windows... 9 | //there are rare cases where this can fail 10 | //for example: .HKEY_CLASSES_ROOT\.HeartsSave-ms 11 | var ext = fName.slice(fName.lastIndexOf(".")).toLowerCase(); 12 | 13 | try{ 14 | shell.RegRead("HKCR\\"+ext+"\\"); 15 | return "Yes" 16 | } catch(e){ 17 | return "No" 18 | } 19 | } 20 | 21 | return function(folder){ 22 | var files = new Enumerator(fso.GetFolder(folder).Files); 23 | for(;!files.atEnd();files.moveNext()){ 24 | var file = files.item(); 25 | WScript.Echo(file.Name + "\t" + file.Type + "\t" + isKnown(file)); 26 | } 27 | } 28 | })() 29 | 30 | listFileTypes("./") -------------------------------------------------------------------------------- /test/wscript/mixed.js: -------------------------------------------------------------------------------- 1 | // This test should have both node and WScript mixed 2 | 3 | // First, we get count of files using FSO 4 | 5 | var fso = new ActiveXObject("Scripting.FileSystemObject"); 6 | 7 | var filesCount = fso.GetFolder(".").Files.Count; 8 | 9 | WScript.Echo("FSO Path: "+fso.GetFolder(".").Path+" Files count: "+filesCount); 10 | 11 | // This if makes sure you still may run this file using cscript 12 | if(WScript.Version=='NODE.WIN32') 13 | { 14 | // And then use node fs 15 | console.log("path.resolve(.): "+require('path').resolve('.')); 16 | 17 | var fs = require('fs'); 18 | var files = fs.readdirSync("."); 19 | 20 | var nodeFilesCount = 0; 21 | for(var f in files) 22 | { 23 | if( !fs.lstatSync(files[f]).isDirectory() ) 24 | { 25 | nodeFilesCount++; 26 | } 27 | } 28 | 29 | if(nodeFilesCount!=filesCount) 30 | { 31 | console.log("FSO files: ", filesCount, "fs files: ", nodeFilesCount); 32 | WScript.Quit(-5); 33 | } else { 34 | console.log("Same number of files: "+nodeFilesCount); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/wscript/nodewscript_test_cli.cmd: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | SETLOCAL 3 | CALL :find_dp0 4 | 5 | IF EXIST "%dp0%\node.exe" ( 6 | SET "_prog=%dp0%\node.exe" 7 | ) ELSE ( 8 | SET "_prog=node" 9 | SET PATHEXT=%PATHEXT:;.JS;=;% 10 | ) 11 | 12 | "%_prog%" "%dp0%\..\..\NodeWScript.js" %* 13 | ENDLOCAL 14 | EXIT /b %errorlevel% 15 | :find_dp0 16 | SET dp0=%~dp0 17 | EXIT /b 18 | -------------------------------------------------------------------------------- /test/wscript/wmi.js: -------------------------------------------------------------------------------- 1 | var wbemFlagReturnImmediately = 0x10; 2 | var wbemFlagForwardOnly = 0x20; 3 | 4 | var arrComputers = new Array("."); 5 | for (i = 0; i < arrComputers.length; i++) { 6 | WScript.Echo(); 7 | WScript.Echo("=========================================="); 8 | WScript.Echo("Computer: " + arrComputers[i]); 9 | WScript.Echo("=========================================="); 10 | 11 | var objWMIService = GetObject("winmgmts:\\\\" + arrComputers[i] + "\\root\\CIMV2"); 12 | var colItems = objWMIService.ExecQuery("SELECT * FROM Win32_ComputerSystem", "WQL", 13 | wbemFlagReturnImmediately | wbemFlagForwardOnly); 14 | 15 | var enumItems = new Enumerator(colItems); 16 | for (; !enumItems.atEnd(); enumItems.moveNext()) { 17 | var objItem = enumItems.item(); 18 | 19 | WScript.Echo("AdminPasswordStatus: " + objItem.AdminPasswordStatus); 20 | WScript.Echo("AutomaticResetBootOption: " + objItem.AutomaticResetBootOption); 21 | WScript.Echo("AutomaticResetCapability: " + objItem.AutomaticResetCapability); 22 | WScript.Echo("BootOptionOnLimit: " + objItem.BootOptionOnLimit); 23 | WScript.Echo("BootOptionOnWatchDog: " + objItem.BootOptionOnWatchDog); 24 | WScript.Echo("BootROMSupported: " + objItem.BootROMSupported); 25 | WScript.Echo("BootupState: " + objItem.BootupState); 26 | WScript.Echo("Caption: " + objItem.Caption); 27 | WScript.Echo("ChassisBootupState: " + objItem.ChassisBootupState); 28 | WScript.Echo("CreationClassName: " + objItem.CreationClassName); 29 | WScript.Echo("CurrentTimeZone: " + objItem.CurrentTimeZone); 30 | WScript.Echo("DaylightInEffect: " + objItem.DaylightInEffect); 31 | WScript.Echo("Description: " + objItem.Description); 32 | WScript.Echo("DNSHostName: " + objItem.DNSHostName); 33 | WScript.Echo("Domain: " + objItem.Domain); 34 | WScript.Echo("DomainRole: " + objItem.DomainRole); 35 | WScript.Echo("EnableDaylightSavingsTime: " + objItem.EnableDaylightSavingsTime); 36 | WScript.Echo("FrontPanelResetStatus: " + objItem.FrontPanelResetStatus); 37 | WScript.Echo("InfraredSupported: " + objItem.InfraredSupported); 38 | try { WScript.Echo("InitialLoadInfo: " + (objItem.InitialLoadInfo.toArray()).join(",")); } 39 | catch(e) { WScript.Echo("InitialLoadInfo: null"); } 40 | WScript.Echo("InstallDate: " + WMIDateStringToDate(""+objItem.InstallDate)); 41 | WScript.Echo("KeyboardPasswordStatus: " + objItem.KeyboardPasswordStatus); 42 | WScript.Echo("LastLoadInfo: " + objItem.LastLoadInfo); 43 | WScript.Echo("Manufacturer: " + objItem.Manufacturer); 44 | WScript.Echo("Model: " + objItem.Model); 45 | WScript.Echo("Name: " + objItem.Name); 46 | WScript.Echo("NameFormat: " + objItem.NameFormat); 47 | WScript.Echo("NetworkServerModeEnabled: " + objItem.NetworkServerModeEnabled); 48 | WScript.Echo("NumberOfProcessors: " + objItem.NumberOfProcessors); 49 | try { WScript.Echo("OEMLogoBitmap: " + (objItem.OEMLogoBitmap.toArray()).join(",")); } 50 | catch(e) { WScript.Echo("OEMLogoBitmap: null"); } 51 | try { WScript.Echo("OEMStringArray: " + (objItem.OEMStringArray.toArray()).join(",")); } 52 | catch(e) { WScript.Echo("OEMStringArray: null"); } 53 | WScript.Echo("PartOfDomain: " + objItem.PartOfDomain); 54 | WScript.Echo("PauseAfterReset: " + objItem.PauseAfterReset); 55 | try { WScript.Echo("PowerManagementCapabilities: " + (objItem.PowerManagementCapabilities.toArray()).join(",")); } 56 | catch(e) { WScript.Echo("PowerManagementCapabilities: null"); } 57 | WScript.Echo("PowerManagementSupported: " + objItem.PowerManagementSupported); 58 | WScript.Echo("PowerOnPasswordStatus: " + objItem.PowerOnPasswordStatus); 59 | WScript.Echo("PowerState: " + objItem.PowerState); 60 | WScript.Echo("PowerSupplyState: " + objItem.PowerSupplyState); 61 | WScript.Echo("PrimaryOwnerContact: " + objItem.PrimaryOwnerContact); 62 | WScript.Echo("PrimaryOwnerName: " + objItem.PrimaryOwnerName); 63 | WScript.Echo("ResetCapability: " + objItem.ResetCapability); 64 | WScript.Echo("ResetCount: " + objItem.ResetCount); 65 | WScript.Echo("ResetLimit: " + objItem.ResetLimit); 66 | try { WScript.Echo("Roles: " + (objItem.Roles.toArray()).join(",")); } 67 | catch(e) { WScript.Echo("Roles: null"); } 68 | WScript.Echo("Status: " + objItem.Status); 69 | try { WScript.Echo("SupportContactDescription: " + (objItem.SupportContactDescription.toArray()).join(",")); } 70 | catch(e) { WScript.Echo("SupportContactDescription: null"); } 71 | WScript.Echo("SystemStartupDelay: " + objItem.SystemStartupDelay); 72 | try { WScript.Echo("SystemStartupOptions: " + (objItem.SystemStartupOptions.toArray()).join(",")); } 73 | catch(e) { WScript.Echo("SystemStartupOptions: null"); } 74 | WScript.Echo("SystemStartupSetting: " + objItem.SystemStartupSetting); 75 | WScript.Echo("SystemType: " + objItem.SystemType); 76 | WScript.Echo("ThermalState: " + objItem.ThermalState); 77 | WScript.Echo("TotalPhysicalMemory: " + objItem.TotalPhysicalMemory); 78 | WScript.Echo("UserName: " + objItem.UserName); 79 | WScript.Echo("WakeUpType: " + objItem.WakeUpType); 80 | WScript.Echo("Workgroup: " + objItem.Workgroup); 81 | } 82 | } 83 | 84 | function WMIDateStringToDate(dtmDate) 85 | { 86 | if (dtmDate == null || dtmDate=="null") 87 | { 88 | return "null date"; 89 | } 90 | var strDateTime; 91 | if (dtmDate.substr(4, 1) == 0) 92 | { 93 | strDateTime = dtmDate.substr(5, 1) + "/"; 94 | } 95 | else 96 | { 97 | strDateTime = dtmDate.substr(4, 2) + "/"; 98 | } 99 | if (dtmDate.substr(6, 1) == 0) 100 | { 101 | strDateTime = strDateTime + dtmDate.substr(7, 1) + "/"; 102 | } 103 | else 104 | { 105 | strDateTime = strDateTime + dtmDate.substr(6, 2) + "/"; 106 | } 107 | strDateTime = strDateTime + dtmDate.substr(0, 4) + " " + 108 | dtmDate.substr(8, 2) + ":" + 109 | dtmDate.substr(10, 2) + ":" + 110 | dtmDate.substr(12, 2); 111 | return(strDateTime); 112 | } --------------------------------------------------------------------------------