Full Code of durs/node-activex for AI

master 8cdae1053db0 cached
32 files
186.8 KB
50.5k tokens
103 symbols
1 requests
Download .txt
Repository: durs/node-activex
Branch: master
Commit: 8cdae1053db0
Files: 32
Total size: 186.8 KB

Directory structure:
gitextract_4__tj8qf/

├── .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.json
├── src/
│   ├── disp.cpp
│   ├── disp.h
│   ├── main.cpp
│   ├── stdafx.h
│   ├── utils.cpp
│   └── utils.h
└── test/
    ├── ado.js
    ├── excel.js
    ├── variant.js
    ├── wscript/
    │   ├── README.md
    │   ├── arguments.js
    │   ├── enumerator.js
    │   ├── mixed.js
    │   ├── nodewscript_test_cli.cmd
    │   └── wmi.js
    └── wscript.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
# https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-nodejs

name: ci
on: [push, pull_request]
jobs:
  ci:
    strategy:
      fail-fast: false
      matrix:
        node-version: ['18.x', '20.x', '22.x', '24.x', '25.x']
        os: [windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm run build --if-present
      # https://community.chocolatey.org/packages?q=Excel
      - name: "Installing Office 365 takes about 5 minutes..."
        run: choco install office365proplus
      - run: |
          npm install -g mocha
          # mocha --expose-gc test  # Three tests will fail!
      - run: npm test


================================================
FILE: .gitignore
================================================
.vscode
.idea
node_modules
build
npm-debug.log
test.js
/data/PERSONS.DBF
/debug*

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Yuri Dursin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: NodeWScript.js
================================================
#!/usr/bin/env node 
var g_winax = require('./activex');
var path = require('path');

if(process.argv.length<3)
{
    console.error("Missing argument. Usage:\nnodewscript SomeJS.js <other arguments>")
}

// For the case we want to use 'require' or 'module' - we need to make it available through global.
global.require = require;
global.exports = exports;
global.module = module;

global.SeSIncludeFile = function (includePath, isLocal, ctx) {

    var vm = require("vm");

    var fs = require("fs");
    var path = require("path");

    var absolutePath = path.resolve(includePath);
    var data = fs.readFileSync(absolutePath);
    var script = new vm.Script(data, { filename: absolutePath, displayErrors: true });
    if (isLocal && ctx) {
        if (!ctx.__runctx__) ctx.__runctx__ = vm.createContext(ctx);
        script.runInContext(ctx.__runctx__);
    } else {
        script.runInThisContext();
    }

    return '';
};

global.GetObject = function GetObject(strMoniker) {
    return new g_winax.Object(strMoniker, {getobject:true});
}

var WinAXActiveXObject = ActiveXObject;

function ActiveXObjectCreate(progId, pfx)
{
    var res = new WinAXActiveXObject(progId);
    if(progId=="SeSHelper")
    {
        // SeSHelper implements Include that we want to override
        res = new Proxy({helper:res}, {
            get(target, propKey, receiver) {
                return function (...args) {
                    var helper = target.helper;
                    var ovr =
                    {
                        IncludeLocal(/**Object*/fName, ctx) /**Object*/
                        {
                            return this.Include(fName, true, ctx);
                        },
                
                        Include(/**Object*/fName, local, ctx) /**Object*/
                        {
                            var includePath = helper.ResolvePath(fName);
                            if(!includePath)
                            {
                                var err = "Error trying to include file. File not found:"+fName;
                                return "console.error("+JSON.stringify(err)+")";
                            }
                        
                            if(!local) 
                            {
                                helper.SetAlreadyIncluded(includePath);
                            }
                    
                            return global.SeSIncludeFile(includePath, local, ctx);
                    
                        },
                
                        IncludeOnce(/**Object*/fName) /**Object*/
                        {
                            if(!helper.IsAlreadyIncluded(fName))
                            {
                                return this.Include(fName);
                            }
                            return "";
                        }
                    }

                    if(propKey in ovr)
                    {
                        return ovr[propKey](...args);
                    } else {
                        let result = target.helper[propKey](...args);
                        return result;    
                    }

                };
            }
          });
    }

    if(res&&pfx)
    {
        WScript.ConnectObject(res, pfx);
    }
    
    return res;
}

global.ActiveXObject=new Proxy(ActiveXObject, {
    construct(target,args)
    {
        //console.log("Creating ActiveXObject: "+args);
        return ActiveXObjectCreate(...args);
    }
});

global.Enumerator = function (arr) {
    this.arr = arr;
    this.enum = arr._NewEnum;
    this.nextItem = null;
    this.atEnd = function () { return this.nextItem===undefined; }
    this.moveNext = function () {  this.nextItem = this.enum.Next(1); return this.atEnd(); }
    this.moveFirst = function () { this.enum.Reset(); this.nextItem=null; return this.moveNext(); }
    this.item = function () { return this.nextItem; }
    
    this.moveNext();
}

// See interface here
// https://www.devguru.com/content/technologies/wsh/objects-wscript.html

var WScript = {
    // Properties
    Application : null,
    BuildVersion : "6.0",
    FullName : null,
    Interactive : true,
    Name : "Windows / Node Script Host",
    Path : __filename,
    ScriptFullName : null,
    ScriptName : null,
    StdErr: {
        Write(txt) { console.log(txt); }
    },
    StdIn: {
        ReadLine() { 
            var readline = require('readline');
            var gotLine = false;
            var line = undefined;

            var rl = readline.createInterface({
              input: process.stdin,
              output: process.stdout,
              terminal: false
            });
            
            rl.on('line', function (cmd) {
                line = cmd;
                rl.close();
            });
            while(!gotLine)
            {
                WScript.Sleep(10);
            }
            return line
        }
    },
    StdOut: {
        Write(txt) { console.err(txt); }
    },
    Timeout : -1,
    Version : 'NODE.WIN32',

    // Methods
    
    Echo : console.log,

    __Connected : [],

    ConnectObject(obj, pfx)
    {
        var connectionPoints = g_winax.getConnectionPoints(obj);
        var connectionPoint = connectionPoints[0];
        var allMethods = connectionPoint.getMethods();

        var advobj={};
        var found = false;
        for(var i=0;i<allMethods.length;i++)
        {
            var a = allMethods[i];
            
            var cpFunc = null;
            var fName = pfx + a;
            
            eval('if(typeof '+fName+'!="undefined") cpFunc = '+fName+';')

            if(cpFunc)
            {
                found = true;
                advobj[a]=cpFunc;
            }
        }

        if(found)
        {
            var dwCookie = connectionPoint.advise(advobj);
            this.__Connected.push({obj,pfx,connectionPoint,dwCookie});
        }

    },

    DisconnectObject(obj)
    {
        for(var i=0;i<this.__Connected.length;i++)
        {
            var o = this.__Connected[i];
            if(o.obj==obj)
            {
                o.connectionPoint.unadvise(o.dwCookie);
                // Remove connection from list
                this.__Connected.splice(i,1);
                break;
            }
        }
    },
    
    CreateObject : ActiveXObjectCreate,

    GetObject : ActiveXObjectCreate,

    Quit (exitCode) { 
        // Gracefully disconnect and release all ActiveX objects, if needed
        while(this.__Connected.length>0)
        {
            WScript.DisconnectObject(this.__Connected[0].obj);
        }
        WScript.Sleep(60);
        process.exit(exitCode); 
    },

    Sleep (ms) { 
        var begin = (new Date()).valueOf();
        MessageLoop();
        var dt = (new Date()).valueOf()-begin;

        while(dt<ms)
        {
            MessageLoop();
            g_winax.winaxsleep(1);
            dt = (new Date()).valueOf()-begin;
        }
    },

    __Args : process.argv,
    __ShowLogo : true,
    __Debug : false,

    // Collections
    Arguments : null,

    __InitArgs(){
        this.Application = this;
        this.__Args=[];
        for(let i=0;i<process.argv.length;i++)
        {
            if(i==0)
            {
                this.FullName = path.resolve(process.argv[0])
            } else if(i==1) {
                this.Name = process.argv[1];
            } else if(i==2) {
                this.ScriptFullName = path.resolve(process.argv[2]);
                this.ScriptName = path.basename(this.ScriptFullName);
            } else {
                // All other args
                var arg = process.argv[i];
                if(arg.startsWith("//"))
                {
/* 
Parse all arguments and process those related to core WScript:
 //B         Batch mode: Suppresses script errors and prompts from displaying
 //D         Enable Active Debugging
 //E:engine  Use engine for executing script
 //H:CScript Changes the default script host to CScript.exe
 //H:WScript Changes the default script host to WScript.exe (default)
 //I         Interactive mode (default, opposite of //B)
 //Job:xxxx  Execute a WSF job
 //Logo      Display logo (default)
 //Nologo    Prevent logo display: No banner will be shown at execution time
 //S         Save current command line options for this user
 //T:nn      Time out in seconds:  Maximum time a script is permitted to run
 //X         Execute script in debugger
 //U         Use Unicode for redirected I/O from the console
*/
                    if(arg.toLowerCase()=="//nologo")
                    {
                        this.__ShowLogo = false;
                    } else if(arg.toLowerCase().indexOf("//t:")==0) {
                        var timeOut = parseInt(arg.substr("//t:".length));
                        this.Timeout = timeOut;
                        setTimeout(()=>{WScript.Quit(0);},timeOut*1000)
                    } else if(arg.toLowerCase()=="//x"||arg.toLowerCase()=="//d") {
                        this.__Debug = true;
                    }
                } else {
                    this.__Args.push(arg);
                }
            }
        }

        // The trick starts here
        // We want WScript.Arguments to be accessible in all of the following ways:
        // WScript.Arguments(i)
        // WScript.Arguments.item(i)
        // WScript.Arguments.length
        // Since it is not directly possible, we use a number of tricks.
        // Function.length is a number of expected arguments
        // And we make this 'fake' number
        var argArr=[];
        for(var ai=0;ai<WScript.__Args.length;ai++)argArr.push("arg"+ai);
        var ArgF=null;
        eval("ArgF = function ("+argArr.join(",")+") { return WScript.__Args[arguments[0]];  }");
        ArgF.item = function(i) { return WScript.__Args[i] };
        ArgF.toString = function() { return WScript.__Args.join(' '); }
        ArgF.Count = function() { return WScript.__Args.length; };

        this.Arguments = ArgF;
    }
}

WScript.__InitArgs();

global.WScript = WScript;

if(WScript.__ShowLogo)
{
    WScript.Echo(WScript.Name+" "+WScript.BuildVersion);
}

function MessageLoop()
{
    g_winax.peekAndDispatchMessages(); // allows ActiveX event to be dispatched
}

const __rapise_global_context__ = require("vm").createContext(this);
global.__rapise_global_context__ = __rapise_global_context__;

if(WScript.ScriptFullName)
{
    SeSIncludeFile(WScript.ScriptFullName);
} else {
    WScript.StdErr.Write("File.js missing.\nUsage wscriptnode <File.js>")
}


================================================
FILE: README.md
================================================
# Name

	Windows C++ Node.JS addon, that implements COM IDispatch object wrapper, analog ActiveXObject on cscript.exe

# Features

 * Using *ITypeInfo* for conflict resolution between property and method 
 (for example !rs.EOF not working without type information, becose need define EOF as an object) 

 * Using optional parameters on constructor call    
``` js 
var con = new ActiveXObject("Object.Name", {
	activate: false, // Allow activate existance object instance (through CoGetObject), false by default
	getobject: false, // Allow using name of the file in the ROT (through GetAccessibleObject), false by default
	type: true	// Allow using type information, true by default
});
```

 * Create COM object from JS object and may be send as argument (for example send to Excel procedure)
``` js 
var com_obj = new ActiveXObject({
	text: test_value,
	obj: { params: test_value },
	arr: [ test_value, test_value, test_value ],
	func: function(v) { return v*2; }
});
```

 * Additional COM variant types support:
	- **int** - VT_INT
	- **uint** - VT_UINT
	- **int8**, **char** - VT_I1
	- **uint8**, **uchar**, **byte** - VT_UI1
	- **int16**, **short** - VT_I2
	- **uint16**, **ushort** - VT_UI2
	- **int32** - VT_I4
	- **uint32** - VT_UI4
	- **int64**, **long** - VT_I8
	- **uint64**, **ulong** - VT_UI8
	- **currency** - VT_CY
	- **float** - VT_R4
	- **double** - VT_R8
	- **string** - VT_BSTR
	- **date** - VT_DATE
	- **decimal** - VT_DECIMAL
	- **variant** - VT_VARIANT
	- **null** - VT_NULL
	- **empty** - VT_EMPTY
	- **byref** - VT_BYREF or use prefix **'p'** to indicate reference to the current type

``` js 
var winax = require('winax');
var Variant = winax.Variant;

// create variant instance 
var v_short = new Variant(17, 'short');
var v_short_byref = new Variant(17, 'pshort');
var v_int_byref = new Variant(17, 'byref');
var v_byref = new Variant(v_short, 'byref');

// create variant arrays
var v_array_of_variant = new Variant([1,'2',3]);
var v_array_of_short = new Variant([1,'2',3], 'short');
var v_array_of_string = new Variant([1,'2',3], 'string');	

// change variant content
var v_test = new Variant();
v_test.assign(17);
v_test.cast('string');
v_test.clear();

// also may be used cast function
var v_short_from_cast = winax.cast(17, 'short');
```

 * Additional dignostic propeties:
	- **__id** - dispatch identity, for exmplae: ADODB.Connection.@Execute.Fields
	- **__value** - value of dispatch object, equiles valueOf()
	- **__type** - array all member items with their properties
	- **__methods** - list member mathods by names (ITypeInfo::GetFuncDesc)
	- **__vars** - list member variables by names (ITypeInfo::GetVarDesc) 

 * Full WScript Emulation Support through nodewscript
	- **ActiveXObject** type
	- **WScript.CreateObject**
	- **WScript.ConnectObject**
	- **WScript.DisconnectObject**
	- **WScript.Sleep**
	- **WScript.Arguments**
	- **WScript.Version**
	- **GetObject**
	- **Enumerator**

# Usage example

Install package throw NPM (see below **Building** for details)
```
npm install winax
npm install winax --msvs_version=2015
npm install winax --msvs_version=2017
```

Create ADO Connection throw global function
``` js
require('winax');
var con = new ActiveXObject('ADODB.Connection');
```
Or using Object prototype
``` js
var winax = require('winax');
var con = new winax.Object('ADODB.Connection');
```
Open connection and create simple table
``` js
con.Open('Provider=Microsoft.ACE.OLEDB.12.0;Data Source=c:\tmp;Extended Properties="DBASE IV;"', '', '');
con.Execute("Create Table persons.dbf (Name char(50), City char(50), Phone char(20), Zip decimal(5))");
con.Execute("Insert into persons.dbf Values('John', 'London','123-45-67','14589')");
con.Execute("Insert into persons.dbf Values('Andrew', 'Paris','333-44-55','38215')");
con.Execute("Insert into persons.dbf Values('Romeo', 'Rom','222-33-44','54323')");
```
Select query and return RecordSet object
``` js
var rs = con.Execute("Select * from persons.dbf"); 
var reccnt = rs.RecordCount;
```
Inspect RecordSet fields
``` js
var fields = rs.Fields;
var fldcnt = fields.Count;
```
Process records
``` js
rs.MoveFirst();
while (!rs.EOF) {
	var name = fields("Name").value;
	var town = fields("City").value;
	var phone = fields("Phone").value;
	var zip = fields("Zip").value;   
	console.log("> Person: " + name + " from " + town + " phone: " + phone + " zip: " + zip);    
	rs.MoveNext();
}
```
Or using fields by index
``` js
rs.MoveFirst();
while (rs.EOF != false) {
	var name = fields[0].value;
	var town = fields[1].value;
	var phone = fields[2].value;
	var zip = fields[3].value;   
	console.log("> Person: " + name + " from " + town + " phone: " + phone + " zip: " + zip);    
	rs.MoveNext();
}
```
Release COM objects (but other temporary objects may be keep references too)
``` js
winax.release(con, rs, fields)
```
Working with Excel ranges using two dimension arrays (from 1.18.0 version)
* The second dimension is only deduced from the first array.
* If data is missing at the time of SafeArrayPutElement, VT_EMPTY is used.
* Best way to explicitly pass VT_EMPTY is to use null
* If the SAFEARRAY dims are smaller than those of the range, the missing cells are emptied.
* Exception to 4! Excel may process the SAFEARRAY as it does when processing a copy/paste operation
``` js
var excel = new winax.Object("Excel.Application", { activate: true });
var wbk = excel.Workbooks.Add(template_filename);
var wsh = wbk.Worksheets.Item(1);
wsh.Range("C3:E4").Value = [ ["C3", "D3", "E3" ], ["C4", "D4", "E4" ] ];
wsh.Range("C3:E4").Value = [ ["C3", "D3", "E3" ], "C4" ]; // will let D4 and E4 empty
wsh.Range("C3:E4").Value = [ [null, "D3", "E3" ], "C4" ]; // will let C3, D4 and E4 empty
wsh.Range("C3:F4").Value = [ [100], [200] ]; // will duplicate the two rows in colums C, D, E, and F
wsh.Range("C3:F4").Value = [ [100, 200, 300, 400] ]; // will duplicate the for cols in rows 3 and 4
wsh.Range("C3:F4").Value = [ [100, 200] ]; // Will correctly duplicate the first two cols, but col E and F with contains "#N/A"
const data = wsh.Range("C3:E4").Value.valueOf();
console.log("Cell E4 value is", data[1][2]);
```

## TypeScript
Your `tsconfig.json` needs at least the following configuration:

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:
```json
{
	"compilerOptions": {
		"target": "esnext",
		"module": "nodenext"
	}
}
```

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:
```json
{
	"compilerOptions": {
		"lib": [ "ESNext" ]
	}
}
```

# Tutorial and Examples

- [examples/ado.js](https://github.com/durs/node-activex/blob/master/examples/ado.js)

# Usage as a library

The repo allows to re-use some of its code as a library for your own native node addon.

To include it, install it as a normal node-dependency and then add it
to the `"dependencies"` section of your `binding.gyp` file like this:
```
"dependencies": [
  "<!(node -p \"require.resolve('winax/lib_binding.gyp')\"):lib_node_activex"
]
```

In your code, you can then include it using:
```cpp
#include <node_activex.h>
```

This makes functions like `Variant2Value` or `Value2Variant` that translate between COM VARIANT and node types
available in your code.
Note that providing this library functionality is not the core target of this repo however,
so importing it eg. currently declares
all methods in the global namespace and opens the namespaces `v8` and `node`.

Check the source code [`src/disp.h`](src/disp.h) and [`src/utils.h`](src/utils.h) for details.

# Building

This project uses Visual C++ 2013 (or later versions then support C++11 standard).
Bulding also requires node-gyp and python 2.6 (or later) to be installed. 
Supported NodeJS Versions (x86 or x64): 10, 12, 16, 18, 20, 22, 23 
You can do this with npm:
```
npm install --global --production windows-build-tools
```
To obtain and build use console commands:
```
git clone git://github.com/durs/node-axtivex.git
cd node-activex
npm install
```
or debug version
```
npm install --debug
```
or using node-gyp directly
```
npm install -g node_gyp
node-gyp configure
node-gyp build
node-gyp rebuild
```

For Electron users, need rebuild with a different V8 version:
```
npm rebuild winax --runtime=electron --target=41.1.0 --arch=x64 --dist-url=https://electronjs.org/headers
```
or using electron rebuild module
```
npm install --save-dev @electron/rebuild
.\node_modules\.bin\electron-rebuild.cmd 

```
Change --target value to your electron version.
See also Electron Documentation: [Using Native Node Modules](https://electron.atom.io/docs/tutorial/using-native-node-modules/).

# WScript

[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.

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.

So NodeJS is more convenient for writing things like async web services. Also with NodeJS you get access to invaluable collection of **npm** modules.

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. 

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.

If you install this package with -g key, you will have `nodewscript` command. 

Usage:
```
nodewscript [options] <Filename.js>
```

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).

Where `Filename.js` is a file designed for windows scripting host.

## WScript Limitations

### Implicit Properties

This is a key drawback of v8 engine compared to MS. Consider an example:

```javascript
    var a = WScript.CreateObject(...)
    if( a.Prop )
    {
        // If 'Prop' is a dynamic property (i.e. it is not defined in TypeInfo and
        // not marked explicitly as a property, then execution never gets here. Even if a.Prop is null or false.
    }
```

### Function Setters

```javascript
var WshShell = new ActiveXObject("WScript.Shell");
var processEnv = WshShell.Environment("PROCESS");
processEnv("ENV_VAR") = "CUSTOM_VALUE";  // This syntax is valid for JScript, but will throw syntax error on v8
```
### Events & Sleep

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. 

### 32 vs 64 Bit

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**.


# Tests

[mocha](https://github.com/visionmedia/mocha) is required to run unit tests.
```
npm install -g mocha
mocha --expose-gc test
```

# Contributors

* [durs](https://github.com/durs)
* [somanuell](https://github.com/somanuell)
* [Daniel-Userlane](https://github.com/Daniel-Userlane)
* [alexeygrinevich](https://github.com/alexeygrinevich)


================================================
FILE: activex.js
================================================
var ActiveX = module.exports = require('./build/Release/node_activex.node');

global.ActiveXObject = function(id, opt) {
    return new ActiveX.Object(id, opt);
};


================================================
FILE: binding.gyp
================================================
{
  'targets': [
    {
      'target_name': 'node_activex',
      'conditions': [
        ['OS=="win"', {
          'sources': [
            'src/main.cpp',
            'src/utils.cpp',
            'src/disp.cpp'
          ],
          'libraries': [],
          'dependencies': []
        }]
      ]
    }
  ]
}

================================================
FILE: examples/ado.js
================================================
//-------------------------------------------------------------------------------------------------------
// Project: node-activex
// Author: Yuri Dursin
// Description: Example of using ActiveX addon with ADO
//-------------------------------------------------------------------------------------------------------

//require('winax');
require('../activex');

var path = require('path'); 
var data_path = path.join(__dirname, '../data/');
var filename = "persons.dbf";
var constr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + data_path + ";Extended Properties=\"DBASE IV;\"";

console.log("==> Preapre directory and delete DBF file on exists");
var fso = new ActiveXObject("Scripting.FileSystemObject");
if (!fso.FolderExists(data_path)) fso.CreateFolder(data_path);
if (fso.FileExists(data_path + filename)) fso.DeleteFile(data_path + filename);

console.log("==> Open connection");
var con = new ActiveXObject("ADODB.Connection");
console.log("ADO version: " + con.Version);
con.Open(constr, "", "");

console.log("==> Create new DBF file")
con.Execute("create Table " + filename + " (Name char(50), City char(50), Phone char(20), Zip decimal(5))");

console.log("==> Insert records to DBF")
con.Execute("insert into " + filename + " values('John', 'London','123-45-67','14589')");
con.Execute("insert into " + filename + " values('Andrew', 'Paris','333-44-55','38215')");
con.Execute("insert into " + filename + " values('Romeo', 'Rom','222-33-44','54323')");

console.log("==> Select records from DBF")
var rs = con.Execute("Select * from " + filename); 
var fields = rs.Fields;
console.log("Result field count: " + fields.Count);
console.log("Result record count: " + rs.RecordCount);

rs.MoveFirst();
while (!rs.EOF) {
    // Access as property by string key
    var name = fields["Name"].Value;

    // Access as method with string argument
    var town = fields("City").value;

    // Access as indexed array
    var phone = fields[2].value;
    var zip = fields[3].value;    

    console.log("> Person: "+name+" from " + town + " phone: " + phone + " zip: " + zip);
    rs.MoveNext();
}

con.Close();


================================================
FILE: examples/event.js
================================================
var winax = require('..');
var interval = setInterval(function () {
	winax.peekAndDispatchMessages(); // allows ActiveX event to be dispatched
}, 50);

var application = new ActiveXObject('Excel.Application');

var connectionPoints = winax.getConnectionPoints(application);
var connectionPoint = connectionPoints[0];

// Excel Application events: https://docs.microsoft.com/en-us/office/vba/api/excel.application(object)#events
connectionPoint.advise({
  WindowActivate: function (workbook, win) {
    console.log('WindowActivate event triggerred');
  },
  WindowDeactivate: function (workbook, win) {
    console.log('WindowDeactivate event triggerred');
  },
  WindowResize: function (workbook, win) {
    console.log('WindowResize event triggerred');
  },
  NewWorkbook: function (workbook) {
    console.log('NewWorkbook event triggerred');
    var worksheet = workbook.Worksheets(1);
    if (worksheet) {
      var cell = worksheet.Cells(1, 1);
      if (cell) {
        cell.value = "Hello world";
      }
    }
  },
  SheetActivate: function (worksheet) {
    console.log('SheetActivate event triggerred');
  },
  SheetDeactivate: function (worksheet) {
    console.log('SheetDeactivate event triggerred');
  },
  SheetSelectionChange: function (worksheet, range) {
    //var worksheet1 = range; // it's a bug now: args in reversal order
    //var range = worksheet;
    console.log('SheetSelectionChange event triggerred', range.Address);
  },
  SheetChange: function (worksheet, range) {
    //var worksheet1 = range; // it's a bug now: args in reversal order
    //var range = worksheet;
    console.log('SheetChange event triggerred', range.Address, range.Value);
  },
});

application.Visible = true;
console.log('Excel application has been open.');
console.log('--> Please create a new workbook to trigger event "NewWorkbook"');

setTimeout(function () {
  clearInterval(interval);
	application.Quit();
	winax.release(application);
}, 1000 * 60);



================================================
FILE: examples/wbem.js
================================================
var ActiveX = require('../activex');
var conn = new ActiveX.Object('WbemScripting.SWbemLocator');
var svr = conn.ConnectServer('.', '\\root\\cimv2');
const resp = svr.ExecQuery('SELECT ProcessorId FROM Win32_Processor');
for (let i = 0; i < resp.Count; i += 1) {
	const properties = resp.ItemIndex(i).Properties_;
	let count = properties.Count;
	const propEnum = properties._NewEnum;
	while (count--) {
		const prop = propEnum.Next();
		if (prop) console.log(prop.Name + '=' + prop.Value);
	}
}

================================================
FILE: include/node_activex.h
================================================
#pragma once

// Include all the relevant definitions for use of the library code in a dependency.
// Note that this does use the namespaces v8 and node in the current configuration

#include "../src/stdafx.h"
#include "../src/disp.h"
#include "../src/utils.h"


================================================
FILE: index.d.ts
================================================
export class Object {
  constructor(id: string, options?: ActiveXObjectOptions);
  
  // Define properties typically found on COM objects
  __id?: string;
  __value?: any;
  __type?: any[];
  __methods?: string[];
  __vars?: string[];

  // Define general method signatures if any known
  [key: string]: any;
}

/** @deprecated Use `ActiveXObjectOptions` instead. */
export interface ActiveXOptions extends ActiveXObjectOptions {}

export class Variant {
  constructor(value?: any, type?: VariantType);
  assign(value: any): void;
  cast(type: VariantType): void;
  clear(): void;
  valueOf(): any;
}

export type VariantType =
  | 'int' | 'uint' | 'int8' | 'char' | 'uint8' | 'uchar' | 'byte'
  | 'int16' | 'short' | 'uint16' | 'ushort'
  | 'int32' | 'uint32'
  | 'int64' | 'long' | 'uint64' | 'ulong'
  | 'currency' | 'float' | 'double' | 'string'
  | 'date' | 'decimal' | 'variant' | 'null' | 'empty'
  | 'byref' | 'pbyref';

export function cast(value: any, type: VariantType): any;

// Utility function to release COM objects
export function release(...objects: any[]): void;

declare global {
  function ActiveXObject(id: string, options?: ActiveXObjectOptions): any;
  function ActiveXObject(obj: Record<string, any>): any;

  interface ActiveXObjectOptions {
    /** Allow activating existing object instance. */
    activate?: boolean;
    
    /** Allow using the name of the file in the ROT. **/
    getobject?: boolean;

    /** Allow using type information. */
    type?: boolean;
  }
}


================================================
FILE: index.js
================================================
module.exports = require('./activex');

================================================
FILE: lib_binding.gyp
================================================
{
  'targets': [
    {
      'target_name': 'lib_node_activex',
      'type': 'static_library',
      'sources': [
        'src/utils.cpp',
        'src/disp.cpp'
      ],
      'defines': [
        'BUILDING_NODE_EXTENSION',
      ],
      'direct_dependent_settings': {
        'include_dirs': ['include']
      },
      'dependencies': [
      ]
    }
  ]
}


================================================
FILE: package.json
================================================
{
  "name": "winax",
  "version": "3.6.9",
  "description": "Windows COM bindings",
  "homepage": "https://github.com/durs/node-activex",
  "keywords": [
    "OLE",
    "COM",
    "ActiveX",
    "ActiveXObject",
    "CreateObject",
    "Variant",
    "Dispatch",
    "WSH",
    "WMI",
    "Excel",
    "Word",
    "WScript",
    "ConnectObject",
    "DisconnectObject"
  ],
  "author": {
    "name": "Yuri Dursin",
    "url": "https://github.com/durs",
    "email": "yuri.dursin@gmail.com"
  },
  "bin": {
    "nodewscript": "NodeWScript.js"
  },
  "contributors": [
    "Yuri Dursin <yuri.dursin@gmail.com>",
    "Somanuell",
    "Daniel-Userlane",
    "Alexey Grinevich <alexey.grinevich@inflectra.com>"
  ],
  "repository": {
    "type": "git",
    "url": "git://github.com/durs/node-activex.git"
  },
  "dependencies": {},
  "devDependencies": {},
  "engines": {
    "node": ">= 10.0.0"
  },
  "scripts": {
    "test": "mocha test"
  },
  "license": "MIT",
  "main": "index.js"
}


================================================
FILE: src/disp.cpp
================================================
//-------------------------------------------------------------------------------------------------------
// Project: NodeActiveX
// Author: Yuri Dursin
// Description: DispObject class implementations
//-------------------------------------------------------------------------------------------------------

#include "stdafx.h"
#include "disp.h"

Persistent<ObjectTemplate> DispObject::inst_template;
Persistent<FunctionTemplate> DispObject::clazz_template;
NodeMethods DispObject::clazz_methods;

Persistent<ObjectTemplate> VariantObject::inst_template;
Persistent<FunctionTemplate> VariantObject::clazz_template;
NodeMethods VariantObject::clazz_methods;

Persistent<ObjectTemplate> ConnectionPointObject::inst_template;
Persistent<FunctionTemplate> ConnectionPointObject::clazz_template;

//-------------------------------------------------------------------------------------------------------
// DispObject implemetation

DispObject::DispObject(const DispInfoPtr &ptr, const std::wstring &nm, DISPID id, LONG indx, int opt)
    : disp(ptr), options((ptr->options & option_mask) | opt), name(nm), dispid(id), index(indx)
{	
    if (dispid == DISPID_UNKNOWN) {
        dispid = DISPID_VALUE;
        options |= option_prepared;
    }
    else options |= option_owned;
    NODE_DEBUG_FMT("DispObject '%S' constructor", name.c_str());
}

DispObject::~DispObject() {
    items.Reset();
    methods.Reset();
    vars.Reset();
    NODE_DEBUG_FMT("DispObject '%S' destructor", name.c_str());
}

HRESULT DispObject::prepare() {
    CComVariant value;
    HRESULT hrcode = disp ? disp->GetProperty(dispid, index, &value) : E_UNEXPECTED;

    // Init dispatch interface
    options |= option_prepared;
    CComPtr<IDispatch> ptr;
    if (VariantDispGet(&value, &ptr)) {
        disp.reset(new DispInfo(ptr, name, options, &disp));
        dispid = DISPID_VALUE;
    }
    else if ((value.vt & VT_ARRAY) != 0) {
        
    }
    return hrcode;
}

bool DispObject::release() {
    if (!disp) return false;
    NODE_DEBUG_FMT("DispObject '%S' release", name.c_str());
    disp.reset();            
    items.Reset();
    methods.Reset();
    vars.Reset();
    return true;
}

bool DispObject::get(LPOLESTR tag, LONG index, const PropertyCallbackInfoGetter &args) {
    Isolate *isolate = args.GetIsolate();
    if (!is_prepared()) prepare();
    if (!disp) {
        isolate->ThrowException(DispErrorNull(isolate));
        return false;
    }

    // Search dispid
    HRESULT hrcode;
    DISPID propid;
    bool prop_by_key = false;
    bool this_prop = false;
    if (!tag || !*tag) {
        tag = (LPOLESTR)name.c_str();
        propid = dispid;
        this_prop = true;
    }
    else {
        hrcode = disp->FindProperty(tag, &propid);
        if (SUCCEEDED(hrcode) && propid == DISPID_UNKNOWN) hrcode = E_INVALIDARG;
        if FAILED(hrcode) {
            prop_by_key = (options & option_property) != 0;
            if (!prop_by_key) {
                //isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyFind", tag));
                args.GetReturnValue().SetUndefined();
                return false;
            }
            propid = dispid;
        }
    }

    // Check type info
    int opt = 0;
    bool is_property_simple = false;
    if (prop_by_key) {
        is_property_simple = true;
        opt |= option_property;
    }
    else {
        DispInfo::type_ptr disp_info;
        if (disp->GetTypeInfo(propid, disp_info)) {
            if (disp_info->is_function_simple()) opt |= option_function_simple;
            else {
                if (disp_info->is_property()) opt |= option_property;
                is_property_simple = disp_info->is_property_simple();
                
                if (disp->bManaged && tag && *tag && wcscmp(tag, L"ToString")==0)
                {
                    // .NET ToString is reported as a property while we normally use it via .ToString() - i.e. as a method. 
                    is_property_simple = false;
                }

            }
        }
        else if ( disp->bManaged && tag && *tag && wcscmp(tag, L"length") == 0) {
            DISPID lenprop;
            if SUCCEEDED(disp->FindProperty((LPOLESTR)L"length", &lenprop)) {

                // If we have 'IReflect' and '.length' - assume it is .NET JS Array or JS Object
                is_property_simple = true;
            }
        }
        else if (disp->bManaged && tag && *tag && index>=0 ) {
            // jsarray[x]
            is_property_simple = true;
        }
    }

    // Return as property value
    if (is_property_simple) {
        CComException except;
        CComVariant value;
        VarArguments vargs;
        if (prop_by_key) vargs.items.push_back(CComVariant(tag));
        if (index >= 0) vargs.items.push_back(CComVariant(index));
        LONG argcnt = (LONG)vargs.items.size();
        VARIANT *pargs = (argcnt > 0) ? &vargs.items.front() : 0;
        hrcode = disp->GetProperty(propid, argcnt, pargs, &value, &except);
        if (FAILED(hrcode) && dispid != DISPID_VALUE){
            isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyGet", tag, &except));
            return false;
        }
        CComPtr<IDispatch> ptr;
        if (VariantDispGet(&value, &ptr)) {
            DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp));
            Local<Object> result = DispObject::NodeCreate(isolate, v8this(args), disp_result, tag, DISPID_UNKNOWN, -1, opt);
            args.GetReturnValue().Set(result);
        }
        else {
            args.GetReturnValue().Set(Variant2Value(isolate, value));
        }
    }

    // Return as dispatch object 
    else {
        Local<Object> result = DispObject::NodeCreate(isolate, v8this(args), disp, tag, propid, index, opt);
        args.GetReturnValue().Set(result);
    }
    return true;
}

bool DispObject::set(LPOLESTR tag, LONG index, const Local<Value> &value, const PropertyCallbackInfoSetter &args) {
    Isolate *isolate = args.GetIsolate();
    if (!is_prepared()) prepare();
    if (!disp) {
        isolate->ThrowException(DispErrorNull(isolate));
        return false;
    }
    
    // Search dispid
    HRESULT hrcode;
    DISPID propid;
    if (!tag || !*tag) {
        tag = (LPOLESTR)name.c_str();
        propid = dispid;
    }
    else {
        hrcode = disp->FindProperty(tag, &propid);
        if (SUCCEEDED(hrcode) && propid == DISPID_UNKNOWN) hrcode = E_INVALIDARG;
        if FAILED(hrcode) {
            isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyFind", tag));
            return false;
        }
    }

    // Set value using dispatch
    CComException except;
    CComVariant ret;
    VarArguments vargs(isolate, value);
    if (index >= 0) vargs.items.push_back(CComVariant(index));
    LONG argcnt = (LONG)vargs.items.size();
    VARIANT *pargs = (argcnt > 0) ? &vargs.items.front() : 0;
    hrcode = disp->SetProperty(propid, argcnt, pargs, &ret, &except);
    if FAILED(hrcode) {
        isolate->ThrowException(DispError(isolate, hrcode, L"DispPropertyPut", tag, &except));
        return false;
    }

    // Send result
    // V8 deprecation: ReturnValue<void> setter is deprecated
    /*
    CComPtr<IDispatch> ptr;
    if (VariantDispGet(&ret, &ptr)) {
        std::wstring rtag;
        rtag.reserve(32);
        rtag += L"@";
        rtag += tag;
        DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp));
        Local<Object> result = DispObject::NodeCreate(isolate, args.This(), disp_result, rtag);
        args.GetReturnValue().Set(result);
    }
    else {
        args.GetReturnValue().Set(Variant2Value(isolate, ret));
    }
    */
    return true;
}

void DispObject::call(Isolate *isolate, const FunctionCallbackInfo<Value> &args) {
    if (!disp) {
        isolate->ThrowException(DispErrorNull(isolate));
        return;
    }

    CComException except;
    CComVariant ret;
    VarArguments vargs(isolate, args);
    LONG argcnt = (LONG)vargs.items.size();
    VARIANT* pargs = (argcnt > 0) ? &vargs.items.front() : 0;
    HRESULT hrcode;

    if (vargs.IsDefault()) {
        hrcode = valueOf(isolate, ret, true);
    }
    else if ((options & option_property) == 0) {
        hrcode = disp->ExecuteMethod(dispid, argcnt, pargs, &ret, &except);
    }
    else {
        DispInfo::type_ptr disp_info;
        disp->GetTypeInfo(dispid, disp_info);

        if(disp_info->is_property_advanced() && argcnt > 1) {
            hrcode = disp->SetProperty(dispid, argcnt, pargs, &ret, &except);
        }
        else {
            hrcode = disp->GetProperty(dispid, argcnt, pargs, &ret, &except);
        }
    }
    if FAILED(hrcode) {
        isolate->ThrowException(DispError(isolate, hrcode, L"DispInvoke", name.c_str(), &except));
        return;
    }

    // Prepare result
    Local<Value> result;
    CComPtr<IDispatch> ptr;
    if (VariantDispGet(&ret, &ptr)) {
        std::wstring tag;
        tag.reserve(32);
        tag += L"@";
        tag += name;
        DispInfoPtr disp_result(new DispInfo(ptr, tag, options, &disp));
        result = DispObject::NodeCreate(isolate, args.This(), disp_result, tag);
    }
    else {
        result = Variant2Value(isolate, ret, true);
    }
    args.GetReturnValue().Set(result);
}

HRESULT DispObject::valueOf(Isolate *isolate, VARIANT &value, bool simple) {
    if (!is_prepared()) prepare();
    HRESULT hrcode;
    if (!disp) hrcode = E_UNEXPECTED;

    // simple function without arguments
    else if ((options & option_function_simple) != 0) {
        hrcode = disp->ExecuteMethod(dispid, 0, 0, &value);
    }

    // property or array element
    else if (dispid != DISPID_VALUE || index >= 0 || simple) {
        hrcode = disp->GetProperty(dispid, index, &value);
    }

    // self dispatch object
    else /*if (is_object())*/ {
        value.vt = VT_DISPATCH;
        value.pdispVal = (IDispatch*)disp->ptr;
        if (value.pdispVal) value.pdispVal->AddRef();
        hrcode = S_OK;
    }
    return hrcode;
}

HRESULT DispObject::valueOf(Isolate *isolate, const Local<Object> &self, Local<Value> &value) {
    if (!is_prepared()) prepare();
    HRESULT hrcode;
    if (!disp) hrcode = E_UNEXPECTED;
    else {
        CComVariant val;

        // simple function without arguments

        if ((options & option_function_simple) != 0) {
            hrcode = disp->ExecuteMethod(dispid, 0, 0, &val);
        }

        // self value, property or array element
        else {
            hrcode = disp->GetProperty(dispid, index, &val);
            // Try to get some primitive value
            if FAILED(hrcode) {
                hrcode = disp->ExecuteMethod(dispid, 0, 0, &val);
            }
        }

        // convert result to v8 value
        if SUCCEEDED(hrcode) {
            value = Variant2Value(isolate, val);
        }

        // or return self as object
        else  {
            value = self;
            hrcode = S_OK;
        }
    }
    return hrcode;
}

void DispObject::toString(const FunctionCallbackInfo<Value> &args) {
    Isolate *isolate = args.GetIsolate();
    CComVariant val;
    HRESULT hrcode = valueOf(isolate, val, true);
    if FAILED(hrcode) {
        isolate->ThrowException(Win32Error(isolate, hrcode, L"DispToString"));
        return;
    }
    args.GetReturnValue().Set(Variant2String(isolate, val));
}

Local<Value> DispObject::getIdentity(Isolate *isolate) {
    //wchar_t buf[64];
    std::wstring id;
    id.reserve(128);
    id += name;
    DispInfoPtr ptr = disp;
    if (ptr && ptr->name == id)
        ptr = ptr->parent.lock();
    while (ptr) {
        id.insert(0, L".");
        id.insert(0, ptr->name);
        /*
        if (ptr->index >= 0) {
            swprintf_s(buf, L"[%ld]", index);
            id += buf;
        }
        */
        ptr = ptr->parent.lock();
    }
    return v8str(isolate, id.c_str());
}

void DispObject::initTypeInfo(Isolate *isolate) {
    if ((options & option_type) == 0 || !disp) {
        return;
    }
    uint32_t index = 0;
    Local<v8::Object> _items = v8::Array::New(isolate);
    Local<v8::Object> _methods = v8::Object::New(isolate);
    Local<v8::Object> _vars = v8::Object::New(isolate);
    disp->Enumerate(1+2/*functions and variables*/, nullptr, [isolate, &_items, &_vars, &_methods, &index](ITypeInfo *info, FUNCDESC *func, VARDESC *var) {
        Local<Context> ctx = isolate->GetCurrentContext();
        CComBSTR name;
        MEMBERID memid = func != nullptr ? func->memid : var->memid;
        TypeInfoGetName(info, memid, &name);
        Local<Object> item(Object::New(isolate));
        Local<String> vname;
        if (name) {
            vname = v8str(isolate, (BSTR)name);
            item->Set(ctx, v8str(isolate, "name"), vname);
        }
        item->Set(ctx, v8str(isolate, "dispid"), Int32::New(isolate, memid));
        if (func != nullptr) {
            item->Set(ctx, v8str(isolate, "invkind"), Int32::New(isolate, (int32_t)func->invkind));
            item->Set(ctx, v8str(isolate, "flags"), Int32::New(isolate, (int32_t)func->wFuncFlags));
            item->Set(ctx, v8str(isolate, "argcnt"), Int32::New(isolate, (int32_t)func->cParams));
            _methods->Set(ctx, vname, item);
        } else {
            item->Set(ctx, v8str(isolate, "varkind"), Int32::New(isolate, (int32_t)var->varkind));
            item->Set(ctx, v8str(isolate, "flags"), Int32::New(isolate, (int32_t)var->wVarFlags));
            if (var->varkind == VAR_CONST && var->lpvarValue != nullptr) {
                v8::Local<v8::Value> value = Variant2Value(isolate, *var->lpvarValue, false);
                item->Set(ctx, v8str(isolate, "value"), value);
            }
            _vars->Set(ctx, vname, item);
        }
        _items->Set(ctx, index++, item);
    });
    items.Reset(isolate, _items);
    methods.Reset(isolate, _methods);
    vars.Reset(isolate, _vars);
}

//-----------------------------------------------------------------------------------
// Static Node JS callbacks

void DispObject::NodeInit(const Local<Object> &target, Isolate* isolate, Local<Context> &ctx) {

    // Prepare constructor template
    Local<FunctionTemplate> clazz = FunctionTemplate::New(isolate, NodeCreate);
    clazz->SetClassName(v8str(isolate, "Dispatch"));

    clazz_methods.add(isolate, clazz, "toString", NodeToString);
    clazz_methods.add(isolate, clazz, "valueOf", NodeValueOf);

    Local<ObjectTemplate> inst = clazz->InstanceTemplate();
    inst->SetInternalFieldCount(1);

#ifdef NODE_INTERCEPTED
    inst->SetHandler(NamedPropertyHandlerConfiguration(InterceptedNodeGet, InterceptedNodeSet));
    inst->SetHandler(IndexedPropertyHandlerConfiguration(InterceptedNodeGetByIndex, InterceptedNodeSetByIndex));
#else
    inst->SetHandler(NamedPropertyHandlerConfiguration(NodeGet, NodeSet));
    inst->SetHandler(IndexedPropertyHandlerConfiguration(NodeGetByIndex, NodeSetByIndex));
#endif

    inst->SetCallAsFunctionHandler(NodeCall);
    inst->SetNativeDataProperty(v8str(isolate, "__id"), NodeGet);
    inst->SetNativeDataProperty(v8str(isolate, "__value"), NodeGet);
    //inst->SetLazyDataProperty(v8str(isolate, "__type"), NodeGet, Local<Value>(), ReadOnly);
    //inst->SetLazyDataProperty(v8str(isolate, "__methods"), NodeGet, Local<Value>(), ReadOnly);
    //inst->SetLazyDataProperty(v8str(isolate, "__vars"), NodeGet, Local<Value>(), ReadOnly);
    inst->SetNativeDataProperty(v8str(isolate, "__type"), NodeGet);
    inst->SetNativeDataProperty(v8str(isolate, "__methods"), NodeGet);
    inst->SetNativeDataProperty(v8str(isolate, "__vars"), NodeGet);

    inst_template.Reset(isolate, inst);
    clazz_template.Reset(isolate, clazz);
    target->Set(ctx, v8str(isolate, "Object"), clazz->GetFunction(ctx).ToLocalChecked());
    target->Set(ctx, v8str(isolate, "cast"), FunctionTemplate::New(isolate, NodeCast)->GetFunction(ctx).ToLocalChecked());
    target->Set(ctx, v8str(isolate, "release"), FunctionTemplate::New(isolate, NodeRelease)->GetFunction(ctx).ToLocalChecked());

    target->Set(ctx, v8str(isolate, "getConnectionPoints"), FunctionTemplate::New(isolate, NodeConnectionPoints)->GetFunction(ctx).ToLocalChecked());
    target->Set(ctx, v8str(isolate, "peekAndDispatchMessages"), FunctionTemplate::New(isolate, PeakAndDispatchMessages)->GetFunction(ctx).ToLocalChecked());

    //Context::GetCurrent()->Global()->Set(v8str(isolate, "ActiveXObject"), t->GetFunction());
    NODE_DEBUG_MSG("DispObject initialized");
}

Local<Object> DispObject::NodeCreate(Isolate *isolate, const Local<Object> &parent, const DispInfoPtr &ptr, const std::wstring &name, DISPID id, LONG index, int opt) {
    Local<Object> self;
    if (!inst_template.IsEmpty()) {
        if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) {
            (new DispObject(ptr, name, id, index, opt))->Wrap(self);
            //Local<String> prop_id(v8str(isolate, "_identity"));
            //self->Set(prop_id, v8str(isolate, name.c_str()));
        }
    }
    return self;
}

void DispObject::NodeCreate(const FunctionCallbackInfo<Value> &args) {
    Isolate *isolate = args.GetIsolate();
    Local<Context> context = isolate->GetCurrentContext();

    // Invoked as plain function
    if (!args.IsConstructCall()) {
        Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
        if (clazz.IsEmpty()) {
            isolate->ThrowException(TypeError(isolate, "FunctionTemplateIsEmpty"));
            return;
        }

        int argc = args.Length();
        std::vector<Local<Value>> argv(argc);
        for (int i = 0; i < argc; ++i) {
            argv[i] = args[i];
        }

        Local<Function> cons;
        if (clazz->GetFunction(context).ToLocal(&cons)) {
            Local<Object> self;
            if (cons->NewInstance(context, argc, argv.data()).ToLocal(&self)) {
                args.GetReturnValue().Set(self);
            }
        }
        return;
    }

    // Check argument count
    int argcnt = args.Length();
    if (argcnt < 1) {
        isolate->ThrowException(InvalidArgumentsError(isolate));
        return;
    }

    // Process optional arguments
    bool isGetObject = false;
    bool isGetAccessibleObject = false;
    int options = (option_async | option_type);
    if (argcnt > 1) {
        Local<Value> val, argopt = args[1];
        bool isEmpty = argopt.IsEmpty();
        bool isObject = argopt->IsObject();
        if (!isEmpty && isObject) {
            auto opt = Local<Object>::Cast(argopt);
            if (opt->Get(context, v8str(isolate, "async")).ToLocal(&val)) {
                if (!v8val2bool(isolate, val, true)) options &= ~option_async;
            }
            if (opt->Get(context, v8str(isolate, "type")).ToLocal(&val)) {
                if (!v8val2bool(isolate, val, true)) options &= ~option_type;
            }
            if (opt->Get(context, v8str(isolate, "activate")).ToLocal(&val)) {
                if (v8val2bool(isolate, val, false)) options |= option_activate;
            }
            if (opt->Get(context, v8str(isolate, "getobject")).ToLocal(&val)) {
                if (v8val2bool(isolate, val, false)) isGetObject = true;
            }
            if (opt->Get(context, v8str(isolate, "getaccessibleobject")).ToLocal(&val)) {
                if (v8val2bool(isolate, val, false)) isGetAccessibleObject = true;
            }
        }
    }

    // Create dispatch object from ProgId
    HRESULT hrcode;
    std::wstring name;
    CComPtr<IDispatch> disp;
    if (args[0]->IsString()) {

        // Prepare arguments
        String::Value vname(isolate, args[0]);
        if (vname.length() <= 0) hrcode = E_INVALIDARG;
        else {
            name.assign((LPOLESTR)*vname, vname.length());

            CComPtr<IUnknown> unk;
            if (isGetObject)
            {
                hrcode = CoGetObject(name.c_str(), NULL, IID_IUnknown, (void**)&unk);
                if SUCCEEDED(hrcode) hrcode = unk->QueryInterface(&disp);
            } else {
                if (isGetAccessibleObject)
                {
                    hrcode = GetAccessibleObject(name.c_str(), unk);
                    if SUCCEEDED(hrcode) hrcode = unk->QueryInterface(&disp);
                } else {
                    CLSID clsid;
                    hrcode = CLSIDFromProgID(name.c_str(), &clsid);
                    if SUCCEEDED(hrcode) {
                        if ((options & option_activate) == 0) hrcode = E_FAIL;
                        else {
                            hrcode = GetActiveObject(clsid, NULL, &unk);
                            if SUCCEEDED(hrcode) hrcode = unk->QueryInterface(&disp);
                        }
                        if FAILED(hrcode) {
                            hrcode = disp.CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER);
                        }
                    }
                }
            }
        }
    }

    // Use supplied dispatch pointer
    else if (args[0]->IsUint8Array()) {
        size_t len = node::Buffer::Length(args[0]);
        void *data = node::Buffer::Data(args[0]);
        IDispatch *p = (len == sizeof(INT_PTR)) ? (IDispatch *) *(static_cast<INT_PTR*>(data)) : nullptr;
        if (!p) {
            isolate->ThrowException(InvalidArgumentsError(isolate));
            return;
        }
        disp.Attach(p);
        hrcode = S_OK;
    }

    // Create dispatch object from javascript object
    else if (args[0]->IsObject()) {
        name = L"#";
        disp = new DispObjectImpl(Local<Object>::Cast(args[0]));
        hrcode = S_OK;
    }

    // Other
    else {
        hrcode = E_INVALIDARG;
    }

    // Prepare result
    if FAILED(hrcode) {
        isolate->ThrowException(DispError(isolate, hrcode, L"CreateInstance", name.c_str()));
    }
    else {
        Local<Object> self = args.This();
        DispInfoPtr ptr(new DispInfo(disp, name, options));
        (new DispObject(ptr, name))->Wrap(self);
        args.GetReturnValue().Set(self);
    }
}

void DispObject::NodeGet(Local<Name> name, const PropertyCallbackInfoGetter& args) {
    Isolate *isolate = args.GetIsolate();
    DispObject *self = DispObject::Unwrap<DispObject>(v8this(args));
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    String::Value vname(isolate, name);
    LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L"valueOf";
    NODE_DEBUG_FMT2("DispObject '%S.%S' get", self->name.c_str(), id);
    if (_wcsicmp(id, L"__value") == 0) {
        Local<Value> result;
        HRESULT hrcode = self->valueOf(isolate, v8this(args), result);
        if FAILED(hrcode) isolate->ThrowException(Win32Error(isolate, hrcode, L"DispValueOf"));
        else args.GetReturnValue().Set(result);
    }
    else if (_wcsicmp(id, L"__id") == 0) {
        args.GetReturnValue().Set(self->getIdentity(isolate));
    }
    else if (_wcsicmp(id, L"__type") == 0) {
        if (self->items.IsEmpty()) {
            self->initTypeInfo(isolate);
        }
        Local<Value> result = self->items.Get(isolate);
        args.GetReturnValue().Set(result);
    }
    else if (_wcsicmp(id, L"__methods") == 0) {
        if (self->methods.IsEmpty()) {
            self->initTypeInfo(isolate);
        }
        Local<Value> result = self->methods.Get(isolate);
        args.GetReturnValue().Set(result);
    }
    else if (_wcsicmp(id, L"__vars") == 0) {
        if (self->vars.IsEmpty()) {
            self->initTypeInfo(isolate);
        }
        Local<Value> result = self->vars.Get(isolate);
        args.GetReturnValue().Set(result);
    }
    else if (_wcsicmp(id, L"__proto__") == 0) {
        Local<Function> func;
        Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
        Local<Context> ctx = isolate->GetCurrentContext();
        if (!clazz.IsEmpty() && clazz->GetFunction(ctx).ToLocal(&func)) {
            args.GetReturnValue().Set(func);
        }
        else {
            args.GetReturnValue().SetNull();
        }
    }
    else {
        Local<Function> func;
        if (clazz_methods.get(isolate, id, &func)) {
            args.GetReturnValue().Set(func);
        }

        else if (!self->get(id, -1, args)) {
            Local<Value> result;
            HRESULT hrcode = self->valueOf(isolate, v8this(args), result);
            if FAILED(hrcode) isolate->ThrowException(Win32Error(isolate, hrcode, L"Unable to Get Value"));
            
            Local<Context> ctx = isolate->GetCurrentContext();
            MaybeLocal<Object> localObj = result->ToObject(ctx);
            if (localObj.IsEmpty()) {
                args.GetReturnValue().SetUndefined();
                return;
            }

            Local<Object> obj = localObj.ToLocalChecked();
            MaybeLocal<Value> realProp = obj->GetRealNamedPropertyInPrototypeChain(ctx, v8str(isolate, id));
            if (realProp.IsEmpty()) {
                // We may call non-existing property for an object to check its existence
                // So we should return undefined in this case
                args.GetReturnValue().SetUndefined();
            }
            else {
                Local<Value> ownProp = realProp.ToLocalChecked();
                if (ownProp->IsFunction()) {
                    Local<Function> func = Local<Function>::Cast(ownProp);
                    if (func.IsEmpty()) return;
                    args.GetReturnValue().Set(func);
                    return;
                }
            }
        }
    }
}

void DispObject::NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter& args) {
    Isolate *isolate = args.GetIsolate();
    DispObject *self = DispObject::Unwrap<DispObject>(v8this(args));
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    NODE_DEBUG_FMT2("DispObject '%S[%u]' get", self->name.c_str(), index);
    self->get(0, index, args);
}

void DispObject::NodeSet(Local<Name> name, Local<Value> value, const PropertyCallbackInfoSetter& args) {
    Isolate *isolate = args.GetIsolate();
    DispObject *self = DispObject::Unwrap<DispObject>(v8this(args));
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    String::Value vname(isolate, name);
    LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L"";
    NODE_DEBUG_FMT2("DispObject '%S.%S' set", self->name.c_str(), id);
    self->set(id, -1, value, args);
}

void DispObject::NodeSetByIndex(uint32_t index, Local<Value> value, const PropertyCallbackInfoSetter& args) {
    Isolate *isolate = args.GetIsolate();
    DispObject *self = DispObject::Unwrap<DispObject>(v8this(args));
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    NODE_DEBUG_FMT2("DispObject '%S[%u]' set", self->name.c_str(), index);
    self->set(0, index, value, args);
}

void DispObject::NodeCall(const FunctionCallbackInfo<Value> &args) {
    Isolate *isolate = args.GetIsolate();
    DispObject *self = DispObject::Unwrap<DispObject>(args.This());
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    NODE_DEBUG_FMT("DispObject '%S' call", self->name.c_str());
    self->call(isolate, args);
}

void DispObject::NodeValueOf(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    DispObject *self = DispObject::Unwrap<DispObject>(args.This());
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    Local<Value> result;
    HRESULT hrcode = self->valueOf(isolate, args.This(), result);
    if FAILED(hrcode) {
        isolate->ThrowException(Win32Error(isolate, hrcode, L"DispValueOf"));
        return;
    }
    args.GetReturnValue().Set(result);
}

void DispObject::NodeToString(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    DispObject *self = DispObject::Unwrap<DispObject>(args.This());
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    self->toString(args);
}

void DispObject::NodeRelease(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    int rcnt = 0, argcnt = args.Length();
    for (int argi = 0; argi < argcnt; argi++) {
        auto obj = args[argi];
        if (obj->IsObject()) {
            auto disp_obj = Local<Object>::Cast(obj);
            DispObject *disp = DispObject::Unwrap<DispObject>(disp_obj);
            if (disp && disp->release())
                rcnt ++;
        }
    }
    args.GetReturnValue().Set(rcnt);
}

void DispObject::NodeCast(const FunctionCallbackInfo<Value>& args) {
    Local<Object> inst = VariantObject::NodeCreateInstance(args);
    args.GetReturnValue().Set(inst);
}

void DispObject::NodeConnectionPoints(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    Local<Context> ctx = isolate->GetCurrentContext();
    Local<Array> items = Array::New(isolate);
    CComPtr<IDispatch> ptr;
    CComPtr<IConnectionPointContainer> cp_cont;
    CComPtr<IEnumConnectionPoints> cp_enum;
    
    // prepare connecton points from arguments
    int argcnt = args.Length();
    if (argcnt >= 1) {
        auto arg = args[0];
        if (Value2Unknown(isolate, arg, (IUnknown**)&ptr)) {
            if SUCCEEDED(ptr->QueryInterface(&cp_cont)) {
                cp_cont->EnumConnectionPoints(&cp_enum);
            }
        }
    }

    // enumerate connection points
    if (cp_enum) {
        ULONG cnt_fetched;
        CComPtr<IConnectionPoint> cp_ptr;
        uint32_t cnt = 0;
        while (SUCCEEDED(cp_enum->Next(1, &cp_ptr, &cnt_fetched)) && cnt_fetched == 1) {
            items->Set(ctx, cnt++, ConnectionPointObject::NodeCreateInstance(isolate, cp_ptr, ptr));
            cp_ptr.Release();
        }
    }

    // return array of connection points
    args.GetReturnValue().Set(items);
}
void DispObject::PeakAndDispatchMessages(const FunctionCallbackInfo<Value>& args) {
    MSG msg;
    while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
        DispatchMessage(&msg);
    }
}

//-------------------------------------------------------------------------------------------------------

class vtypes_t {
public:
    inline vtypes_t(std::initializer_list<std::pair<std::wstring, VARTYPE>> recs) {
        for (auto &rec : recs) {
            str2vt.emplace(rec.first, rec.second);
            vt2str.emplace(rec.second, rec.first);
        }
    }
    inline bool find(VARTYPE vt, std::wstring &name) {
        auto it = vt2str.find(vt);
        if (it == vt2str.end()) return false;
        name = it->second;
        return true;
    }
    inline VARTYPE find(const std::wstring &name) {
        auto it = str2vt.find(name);
        if (it == str2vt.end()) return VT_EMPTY;
        return it->second;
    }
private:
    std::map<std::wstring, VARTYPE> str2vt;
    std::map<VARTYPE, std::wstring> vt2str;
};

static vtypes_t vtypes({
    { L"char", VT_I1 },
    { L"uchar", VT_UI1 },
    { L"byte", VT_UI1 },
    { L"short", VT_I2 },
    { L"ushort", VT_UI2 },
    { L"int", VT_INT },
    { L"uint", VT_UINT },
    { L"long", VT_I8 },
    { L"ulong", VT_UI8 },

    { L"int8", VT_I1 },
    { L"uint8", VT_UI1 },
    { L"int16", VT_I2 },
    { L"uint16", VT_UI2 },
    { L"int32", VT_I4 },
    { L"uint32", VT_UI4 },
    { L"int64", VT_I8 },
    { L"uint64", VT_UI8 },
    { L"currency", VT_CY },

    { L"float", VT_R4 },
    { L"double", VT_R8 },
    { L"date", VT_DATE },
    { L"decimal", VT_DECIMAL },

    { L"string", VT_BSTR },
    { L"empty", VT_EMPTY },
    { L"variant", VT_VARIANT },
    { L"null", VT_NULL },
    { L"byref", VT_BYREF }
});

bool VariantObject::assign(Isolate *isolate, Local<Value> &val, Local<Value> &type) {
    VARTYPE vt = VT_EMPTY;
    if (!type.IsEmpty()) {
        if (type->IsString()) {
            String::Value vtstr(isolate, type);
            const wchar_t *pvtstr = (const wchar_t *)*vtstr;
            int vtstr_len = vtstr.length();
            if (vtstr_len > 1) {
                if (pvtstr[0] == 'p') {
                    vt |= VT_BYREF;
                    vtstr_len--;
                    pvtstr++;
                }
                else if (pvtstr[vtstr_len - 1] == '*') {
                    vt |= VT_BYREF;
                    vtstr_len--;
                }
                else if (pvtstr[vtstr_len - 2] == '[' || pvtstr[vtstr_len - 1] == ']') {
                    vt |= VT_ARRAY;
                    vtstr_len -= 2;
                }
            }
            if (vtstr_len > 0) {
                std::wstring type(pvtstr, vtstr_len);
                vt |= vtypes.find(type);
            }
        }
        else if (type->IsInt32()) {
            vt |= type->Int32Value(isolate->GetCurrentContext()).FromMaybe(0);
        }
    }

    if (val.IsEmpty()) {
        if FAILED(value.ChangeType(vt)) return false;
        if ((value.vt & VT_BYREF) == 0) pvalue.Clear();
        return true;
    }

    value.Clear();
    pvalue.Clear();
    if ((vt & VT_ARRAY) != 0) {
        Value2SafeArray(isolate, val, value, vt & ~VT_ARRAY);
    }
    else if ((vt & VT_BYREF) == 0) {
        Value2Variant(isolate, val, value, vt);
    }
    else {
        VARIANT *refvalue = nullptr;
        VARTYPE vt_noref = vt & ~VT_BYREF;
        VariantObject *ref = (!val.IsEmpty() && val->IsObject()) ? GetInstanceOf(isolate, Local<Object>::Cast(val)) : nullptr;
        if (ref) {
            if ((ref->value.vt & VT_BYREF) != 0) value = ref->value;
            else refvalue = &ref->value;
        }
        else {
            Value2Variant(isolate, val, pvalue, vt_noref);
            refvalue = &pvalue;
        }
        if (refvalue) {
            if (vt_noref == 0 || vt_noref == VT_VARIANT || refvalue->vt == VT_EMPTY) {
                value.vt = VT_VARIANT | VT_BYREF;
                value.pvarVal = refvalue;
            }
            else {
                value.vt = refvalue->vt | VT_BYREF;
                value.byref = &refvalue->intVal;
            }
        }
    }
    return true;
}

VariantObject::VariantObject(const FunctionCallbackInfo<Value> &args) {
    Local<Value> val, type;
    int argcnt = args.Length();
    if (argcnt > 0) val = args[0];
    if (argcnt > 1) type = args[1];
    assign(args.GetIsolate(), val, type);
}

void VariantObject::NodeInit(const Local<Object> &target, Isolate* isolate, Local<Context> &ctx) {

    // Prepare constructor template
    Local<FunctionTemplate> clazz = FunctionTemplate::New(isolate, NodeCreate);
    clazz->SetClassName(v8str(isolate, "Variant"));
    clazz_methods.add(isolate, clazz, "clear", NodeClear);
    clazz_methods.add(isolate, clazz, "assign", NodeAssign);
    clazz_methods.add(isolate, clazz, "cast", NodeCast);
    clazz_methods.add(isolate, clazz, "toString", NodeToString);
    clazz_methods.add(isolate, clazz, "valueOf", NodeValueOf);

    Local<ObjectTemplate> inst = clazz->InstanceTemplate();
    inst->SetInternalFieldCount(1);

#ifdef NODE_INTERCEPTED
    inst->SetHandler(NamedPropertyHandlerConfiguration(InterceptedNodeGet, InterceptedNodeSet));
    inst->SetHandler(IndexedPropertyHandlerConfiguration(InterceptedNodeGetByIndex, InterceptedNodeSetByIndex));
#else
    inst->SetHandler(NamedPropertyHandlerConfiguration(NodeGet, NodeSet));
    inst->SetHandler(IndexedPropertyHandlerConfiguration(NodeGetByIndex, NodeSetByIndex));
#endif
    //inst->SetCallAsFunctionHandler(NodeCall);
    //inst->SetNativeDataProperty(v8str(isolate, "__id"), NodeGet);

    inst->SetNativeDataProperty(v8str(isolate, "__value"), NodeGet);
    //inst->SetLazyDataProperty(v8str(isolate, "__type"), NodeGet, Local<Value>(), ReadOnly);
    inst->SetNativeDataProperty(v8str(isolate, "__type"), NodeGet);

    inst_template.Reset(isolate, inst);
    clazz_template.Reset(isolate, clazz);
    Local<Function> func;
    if (clazz->GetFunction(ctx).ToLocal(&func)) {
        target->Set(ctx, v8str(isolate, "Variant"), func);
    }
    NODE_DEBUG_MSG("VariantObject initialized");
}

Local<Object> VariantObject::NodeCreateInstance(const FunctionCallbackInfo<Value> &args) {
    Local<Object> self;
    Isolate *isolate = args.GetIsolate();
    if (!inst_template.IsEmpty()) {
        if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) {
            (new VariantObject(args))->Wrap(self);
        }
    }
    return self;
}

void VariantObject::NodeCreate(const FunctionCallbackInfo<Value> &args) {
    Isolate *isolate = args.GetIsolate();
    Local<Object> self = args.This();
    (new VariantObject(args))->Wrap(self);
    args.GetReturnValue().Set(self);
}

void VariantObject::NodeClear(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    self->value.Clear();
    self->pvalue.Clear();
}

void VariantObject::NodeAssign(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    Local<Value> val, type;
    int argcnt = args.Length();
    if (argcnt > 0) val = args[0];
    if (argcnt > 1) type = args[1];
    self->assign(isolate, val, type);
}

void VariantObject::NodeCast(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    Local<Value> val, type;
    int argcnt = args.Length();
    if (argcnt > 0) type = args[0];
    self->assign(isolate, val, type);
}

void VariantObject::NodeValueOf(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    // Last parameter false because valueOf should return primitive value
    Local<Value> result = Variant2Value(isolate, self->value, false);
    args.GetReturnValue().Set(result);
}

void VariantObject::NodeToString(const FunctionCallbackInfo<Value>& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(args.This());
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    Local<Value> result = Variant2String(isolate, self->value);
    args.GetReturnValue().Set(result);
}

void VariantObject::NodeGet(Local<Name> name, const PropertyCallbackInfoGetter& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(v8this(args));
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }

    String::Value vname(isolate, name);
    LPOLESTR id = (vname.length() > 0) ? (LPOLESTR)*vname : (LPOLESTR)L"valueOf";
    if (_wcsicmp(id, L"__value") == 0) {
        Local<Value> result = Variant2Value(isolate, self->value);
        args.GetReturnValue().Set(result);
    }
    else if (_wcsicmp(id, L"__type") == 0) {
        std::wstring type, name;
        if (self->value.vt & VT_BYREF) type += L"byref:";
        if (self->value.vt & VT_ARRAY) type = L"array:";
        if (vtypes.find(self->value.vt & VT_TYPEMASK, name)) {
            type += name;
        }
        else if (self->value.vt & VT_UNKNOWN) {
            type += L"IUnknown";
        }
        else {
            type += std::to_wstring(self->value.vt & VT_TYPEMASK);
        }
        Local<String> text = v8str(isolate, type.c_str());
        // Debugger was crashing on IUnknown due to missing return value here
        args.GetReturnValue().Set(text);
    }
    else if (_wcsicmp(id, L"__proto__") == 0) {
        Local<Function> func;
        Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
        Local<Context> ctx = isolate->GetCurrentContext();
        if (!clazz.IsEmpty() && clazz_template.Get(isolate)->GetFunction(ctx).ToLocal(&func)) {
            args.GetReturnValue().Set(func);
        }
        else {
            args.GetReturnValue().SetNull();
        }
    }
    else if (_wcsicmp(id, L"length") == 0) {
        if ((self->value.vt & VT_ARRAY) != 0) {
            args.GetReturnValue().Set((uint32_t)self->value.ArrayLength());
        }
        else {
            args.GetReturnValue().SetUndefined();
        }
    }
    else {
        Local<Function> func;
        if (clazz_methods.get(isolate, id, &func)) {
            args.GetReturnValue().Set(func);
        }
        else {
            args.GetReturnValue().SetUndefined();
        }
    }
}

void VariantObject::NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(v8this(args));
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    Local<Value> result;
    if ((self->value.vt & VT_ARRAY) == 0) {
         result = Variant2Value(isolate, self->value);
    }
    else {
        CComVariant value;
        if SUCCEEDED(self->value.ArrayGet((LONG)index, value)) {
            result = Variant2Value(isolate, value);
        }
    }
    args.GetReturnValue().Set(result);
}

void VariantObject::NodeSet(Local<Name> name, Local<Value> val, const PropertyCallbackInfoSetter& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(v8this(args));
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    isolate->ThrowException(DispError(isolate, E_NOTIMPL));
}

void VariantObject::NodeSetByIndex(uint32_t index, Local<Value> value, const PropertyCallbackInfoSetter& args) {
    Isolate *isolate = args.GetIsolate();
    VariantObject *self = VariantObject::Unwrap<VariantObject>(v8this(args));
    if (!self) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    isolate->ThrowException(DispError(isolate, E_NOTIMPL));
}

Local<Object> VariantObject::NodeCreate(Isolate* isolate, const VARIANT& var) {
    Local<Object> self;
    if (!inst_template.IsEmpty()) {
        if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) {
            (new VariantObject(var))->Wrap(self);
        }
    }
    return self;
}

//-------------------------------------------------------------------------------------------------------

ConnectionPointObject::ConnectionPointObject(IConnectionPoint *p, IDispatch *d)
  : ptr(p), disp(d) {
    InitIndex();
}

bool ConnectionPointObject::InitIndex() {
  if (!ptr || !disp) {
    return false;
  }
  UINT typeindex = 0;
  CComPtr<ITypeInfo> typeinfo;
  if FAILED(disp->GetTypeInfo(typeindex, LOCALE_USER_DEFAULT, &typeinfo)) {
    return false;
  }

  CComPtr<ITypeLib> typelib;
  if FAILED(typeinfo->GetContainingTypeLib(&typelib, &typeindex)) {
    return false;
  }

  IID conniid;
  if FAILED(ptr->GetConnectionInterface(&conniid)) {
    return false;
  }

  CComPtr<ITypeInfo> conninfo;
  if FAILED(typelib->GetTypeInfoOfGuid(conniid, &conninfo)) {
    return false;
  }

  TYPEATTR *typeattr = nullptr;
  if FAILED(conninfo->GetTypeAttr(&typeattr)) {
    return false;
  }

  if (typeattr->typekind != TKIND_DISPATCH) {
    conninfo->ReleaseTypeAttr(typeattr);
    return false;
  }

  for (UINT fd = 0; fd < typeattr->cFuncs; ++fd) {
    FUNCDESC *funcdesc;
    if FAILED(conninfo->GetFuncDesc(fd, &funcdesc)) {
      continue;
    }
    if (!funcdesc) {
      break;
    }

    if (funcdesc->invkind != INVOKE_FUNC || funcdesc->funckind != FUNC_DISPATCH) {
      conninfo->ReleaseFuncDesc(funcdesc);
      continue;
    }

    const size_t nameSize = 2;
    BSTR bstrNames[nameSize];
    UINT maxNames = 1; // only event function name required
    UINT maxNamesOut = 0;
    if SUCCEEDED(conninfo->GetNames(funcdesc->memid, reinterpret_cast<BSTR *>(&bstrNames), maxNames, &maxNamesOut)) {
      DISPID id = funcdesc->memid;
      std::wstring funcname(bstrNames[0]);
      index.insert(std::pair<DISPID, DispObjectImpl::name_ptr>(id, new DispObjectImpl::name_t(id, funcname)));

      for (size_t i = 0; i < maxNamesOut; i++) {
        SysFreeString(bstrNames[i]);
      }
    }

    conninfo->ReleaseFuncDesc(funcdesc);
  }

  conninfo->ReleaseTypeAttr(typeattr);

  return true;
}

Local<Object> ConnectionPointObject::NodeCreateInstance(Isolate *isolate, IConnectionPoint *p, IDispatch* d) {
    Local<Object> self;
    if (!inst_template.IsEmpty()) {
        if (inst_template.Get(isolate)->NewInstance(isolate->GetCurrentContext()).ToLocal(&self)) {
            (new ConnectionPointObject(p, d))->Wrap(self);
        }
    }
    return self;
}

void ConnectionPointObject::NodeInit(const Local<Object> &target, Isolate* isolate, Local<Context> &ctx) {

    // Prepare constructor template
    Local<FunctionTemplate> clazz = FunctionTemplate::New(isolate, NodeCreate);
    clazz->SetClassName(v8str(isolate, "ConnectionPoint"));

    NODE_SET_PROTOTYPE_METHOD(clazz, "advise", NodeAdvise);
    NODE_SET_PROTOTYPE_METHOD(clazz, "unadvise", NodeUnadvise);
    NODE_SET_PROTOTYPE_METHOD(clazz, "getMethods", NodeConnectionPointMethods);

    Local<ObjectTemplate> inst = clazz->InstanceTemplate();
    inst->SetInternalFieldCount(1);

    inst_template.Reset(isolate, inst);
    clazz_template.Reset(isolate, clazz);
    //target->Set(v8str(isolate, "ConnectionPoint"), clazz->GetFunction());
    NODE_DEBUG_MSG("ConnectionPointObject initialized");
}

void ConnectionPointObject::NodeCreate(const FunctionCallbackInfo<Value> &args) {
    Isolate *isolate = args.GetIsolate();
    Local<Object> self = args.This();
    (new ConnectionPointObject(args))->Wrap(self);
    args.GetReturnValue().Set(self);
}

void ConnectionPointObject::NodeAdvise(const FunctionCallbackInfo<Value> &args) {
    Isolate *isolate = args.GetIsolate();
    ConnectionPointObject *self = ConnectionPointObject::Unwrap<ConnectionPointObject>(args.This());
    if (!self || !self->ptr) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }
    CComPtr<IUnknown> unk;
    int argcnt = args.Length();
    if (argcnt > 0) {
        Local<Value> val = args[0];
        if (!Value2Unknown(isolate, val, &unk)) {
            Local<Object> obj;
            if (!val.IsEmpty() && val->IsObject() && val->ToObject(isolate->GetCurrentContext()).ToLocal(&obj)) {

                // .NET Connection Points require to implement specific interface
                // So we need to remember its IID for the case when Container does QueryInterface for it
                IID connif;
                self->ptr->GetConnectionInterface(&connif);
                DispObjectImpl *impl = new DispObjectImpl(obj, false, connif);
                // It requires reversed arguments
                impl->reverse_arguments = true;
                impl->index = self->index;
                if (self->index.size()) {
                    impl->dispid_next = self->index.rbegin()->first + 1;
                }
                unk.Attach(impl);
            }
        }
    }
    if (!unk) {
        isolate->ThrowException(InvalidArgumentsError(isolate));
        return;
    }
    DWORD dwCookie;
    HRESULT hrcode = self->ptr->Advise(unk, &dwCookie);
    if FAILED(hrcode) {
        isolate->ThrowException(DispError(isolate, hrcode));
        return;
    }
    self->cookies.insert(dwCookie);
    args.GetReturnValue().Set(v8::Integer::New(isolate, (uint32_t)dwCookie));
}

void ConnectionPointObject::NodeUnadvise(const FunctionCallbackInfo<Value> &args) {
    Isolate *isolate = args.GetIsolate();
    Local<Context> ctx = isolate->GetCurrentContext();
    ConnectionPointObject *self = ConnectionPointObject::Unwrap<ConnectionPointObject>(args.This());
    if (!self || !self->ptr) {
        isolate->ThrowException(DispErrorInvalid(isolate));
        return;
    }

    if (args.Length()==0 || !args[0]->IsUint32()) {
        isolate->ThrowException(InvalidArgumentsError(isolate));
        return;
    }
    DWORD dwCookie = (args[0]->Uint32Value(ctx)).FromMaybe(0);
    if (dwCookie == 0 || self->cookies.find(dwCookie) == self->cookies.end()) {
        isolate->ThrowException(InvalidArgumentsError(isolate));
    return;
    }
    self->cookies.erase(dwCookie);
    HRESULT hrcode = self->ptr->Unadvise(dwCookie);
    if FAILED(hrcode) {
        isolate->ThrowException(DispError(isolate, hrcode));
        return;
    }
}

void ConnectionPointObject::NodeConnectionPointMethods(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    Local<Context> ctx = isolate->GetCurrentContext();
    Local<Array> items = Array::New(isolate);

    ConnectionPointObject* self = ConnectionPointObject::Unwrap<ConnectionPointObject>(args.This());

    DispObjectImpl::index_t::iterator it;
    uint32_t cnt = 0;

    for (it = self->index.begin(); it != self->index.end(); it++)
    {
        items->Set(ctx, cnt++, v8str(isolate, it->second->name.c_str()));
    }

    args.GetReturnValue().Set(items);
}

//-------------------------------------------------------------------------------------------------------


================================================
FILE: src/disp.h
================================================
//-------------------------------------------------------------------------------------------------------
// Project: NodeActiveX
// Author: Yuri Dursin
// Description: DispObject class declarations. This class incapsulates COM IDispatch interface to Node JS Object
//-------------------------------------------------------------------------------------------------------

#pragma once

#include "utils.h"

enum options_t { 
    option_none = 0, 
    option_async = 0x0001, 
    option_type = 0x0002,
    option_activate = 0x0004,
    option_prepared = 0x0100,
    option_owned = 0x0200,
    option_property = 0x0400,
    option_function_simple = 0x0800,
    option_mask = 0x00FF,
    option_auto = (option_async | option_type)
};

inline bool TypeInfoGetName(ITypeInfo *info, DISPID dispid, BSTR *name) {
    HRESULT hrcode = info->GetDocumentation(dispid, name, NULL, NULL, NULL);
    if SUCCEEDED(hrcode) return true;
    UINT cnt_ret;
    return info->GetNames(dispid, name, 1, &cnt_ret) == S_OK && cnt_ret > 0;
}

template<typename T>
bool TypeInfoPrepareFunc(ITypeInfo *info, UINT n, T process) {
    FUNCDESC *desc;
    if (info->GetFuncDesc(n, &desc) != S_OK) return false;
    process(info, desc, nullptr);
    info->ReleaseFuncDesc(desc);
    return true;
}

template<typename T>
bool TypeInfoPrepareVar(ITypeInfo *info, UINT n, T process) {
    VARDESC *desc;
    if (info->GetVarDesc(n, &desc) != S_OK) return false;
    process(info, nullptr, desc);
    info->ReleaseVarDesc(desc);
    return true;
}

template<typename T>
void TypeInfoPrepare(ITypeInfo *info, int mode, T process) {
    UINT cFuncs = 0, cVars = 0;
    TYPEATTR *pattr = NULL;
    if (info->GetTypeAttr(&pattr) == S_OK) {
        cFuncs = pattr->cFuncs;
        cVars = pattr->cVars;
        info->ReleaseTypeAttr(pattr);
    }
    if ((mode & 1) != 0) {
        for (UINT n = 0; n < cFuncs; n++) {
            TypeInfoPrepareFunc<T>(info, n, process);
        }
    }
    if ((mode & 2) != 0) {
        for (UINT n = 0; n < cVars; n++) {
            TypeInfoPrepareVar<T>(info, n, process);
        }
    }
}

template<typename T>
bool TypeLibEnumerate(ITypeLib *typelib, int mode, T process) {
    UINT i, cnt = typelib ? typelib->GetTypeInfoCount() : 0;
    for (i = 0; i < cnt; i++) {
        CComPtr<ITypeInfo> info;
        if (typelib->GetTypeInfo(i, &info) != S_OK) continue;
        TypeInfoPrepare<T>(info, mode, process);
    }
    return cnt > 0;
}

class DispInfo {
public:
    std::weak_ptr<DispInfo> parent;
    CComPtr<IDispatch> ptr;
    std::wstring name;
    int options;
    bool bManaged;

    struct type_t { 
        DISPID dispid; 
        int kind; 
        int argcnt_get; 
        inline type_t(DISPID dispid_, int kind_) : dispid(dispid_), kind(kind_), argcnt_get(0) {}
        inline bool is_property() const { return ((kind & INVOKE_FUNC) == 0); }
        inline bool is_property_simple() const { return (((kind & (INVOKE_PROPERTYGET | INVOKE_FUNC))) == INVOKE_PROPERTYGET) && (argcnt_get == 0); }
        inline bool is_function_simple() const { return (((kind & (INVOKE_PROPERTYGET | INVOKE_FUNC))) == INVOKE_FUNC) && (argcnt_get == 0); }
        inline bool is_property_advanced() const { return kind == (INVOKE_PROPERTYGET | INVOKE_PROPERTYPUT) && (argcnt_get == 1); }
    };
    typedef std::shared_ptr<type_t> type_ptr;
    typedef std::map<DISPID, type_ptr> types_by_dispid_t;
    types_by_dispid_t types_by_dispid;

    inline DispInfo(IDispatch *disp, const std::wstring &nm, int opt, std::shared_ptr<DispInfo> *parnt = nullptr)
        : ptr(disp), options(opt), name(nm), bManaged(false)
    { 
        if (parnt) parent = *parnt;
        if ((options & option_type) != 0)
            Prepare(disp);
    }

    void Prepare(IDispatch *disp) {
        Enumerate(1/*functions only*/, &bManaged, [this](ITypeInfo *info, FUNCDESC *func, VARDESC *var) {
            type_ptr &ptr = this->types_by_dispid[func->memid];
            if (!ptr) ptr.reset(new type_t(func->memid, func->invkind));
            else ptr->kind |= func->invkind;
            if ((func->invkind & INVOKE_PROPERTYGET) != 0) {
                if (func->cParams > ptr->argcnt_get)
                    ptr->argcnt_get = func->cParams;
            }
        });
        bool prepared = types_by_dispid.size() > 3; // QueryInterface, AddRef, Release
        if (prepared) options |= option_prepared;
    }

    inline bool GetTypeInfo(const DISPID dispid, type_ptr &info) {
        if ((options & option_prepared) == 0) return false;
        types_by_dispid_t::const_iterator it = types_by_dispid.find(dispid);
        if (it == types_by_dispid.end()) return false;
        info = it->second;
        return true;
    }

    HRESULT FindProperty(LPOLESTR name, DISPID *dispid) {
        return DispFind(ptr, name, dispid);
    }

    HRESULT GetProperty(DISPID dispid, LONG argcnt, VARIANT *args, VARIANT *value, EXCEPINFO *except = 0) {
        HRESULT hrcode = DispInvoke(ptr, dispid, argcnt, args, value, DISPATCH_PROPERTYGET, except);
        return hrcode;
    }

    HRESULT GetProperty(DISPID dispid, LONG index, VARIANT *value, EXCEPINFO *except = 0) {
        CComVariant arg(index);
        LONG argcnt = (index >= 0) ? 1 : 0;
        return DispInvoke(ptr, dispid, argcnt, &arg, value, DISPATCH_PROPERTYGET, except);
    }

    HRESULT SetProperty(DISPID dispid, LONG argcnt, VARIANT *args, VARIANT *value, EXCEPINFO *except = 0) {
        HRESULT hrcode = DispInvoke(ptr, dispid, argcnt, args, value, DISPATCH_PROPERTYPUT, except);
        if FAILED(hrcode) value->vt = VT_EMPTY;
        return hrcode;
    }

    HRESULT ExecuteMethod(DISPID dispid, LONG argcnt, VARIANT *args, VARIANT *value, EXCEPINFO *except = 0) {
        HRESULT hrcode = DispInvoke(ptr, dispid, argcnt, args, value, DISPATCH_METHOD, except);
        return hrcode;
    }

    template<typename T>
    bool Enumerate(int mode, bool* checkManaged, T process = nullptr) {
        UINT i, cnt;
        CComPtr<ITypeLib> prevtypelib;
        if (!ptr || FAILED(ptr->GetTypeInfoCount(&cnt))) cnt = 0;
        else for (i = 0; i < cnt; i++) {
            CComPtr<ITypeInfo> info;
            if (ptr->GetTypeInfo(i, 0, &info) != S_OK) continue;

            // Query type library
            UINT typeindex;
            CComPtr<ITypeLib> typelib;
            if (info->GetContainingTypeLib(&typelib, &typeindex) == S_OK) {

                // Check if typelib is managed
                if (checkManaged != nullptr && !*checkManaged) {
                    CComPtr<ITypeLib2> typeLib2;
                    if (SUCCEEDED(typelib->QueryInterface(IID_ITypeLib2, (void**)&typeLib2))) {

                        // {90883F05-3D28-11D2-8F17-00A0C9A6186D}
                        static const GUID GUID_ExportedFromComPlus = { 0x90883F05, 0x3D28, 0x11D2, { 0x8F, 0x17, 0x00, 0xA0, 0xC9, 0xA6, 0x18, 0x6D } };

                        CComVariant cv;
                        if (SUCCEEDED(typeLib2->GetCustData(GUID_ExportedFromComPlus, &cv))) {
                            *checkManaged = true;
                        }
                    }
                }

                // Enumerate all types in library types
                // May be very slow! need a special method
                // if (typelib != prevtypelib) {
                //     TypeLibEnumerate<T>(typelib, mode, process);
                //	   prevtypelib.Attach(typelib.Detach());
                // }

                CComPtr<ITypeInfo> tinfo;
                if (typelib->GetTypeInfo(typeindex, &tinfo) == S_OK) {
                    TypeInfoPrepare<T>(tinfo, mode, process);
                }
            }

            // Process types
            else {
                TypeInfoPrepare<T>(info, mode, process);
            }
        }
        return cnt > 0;
    }
};

typedef std::shared_ptr<DispInfo> DispInfoPtr;

class DispObject: public NodeObject
{
public:
    DispObject(const DispInfoPtr &ptr, const std::wstring &name, DISPID id = DISPID_UNKNOWN, LONG indx = -1, int opt = 0);
    ~DispObject();

    static Persistent<ObjectTemplate> inst_template;
    static Persistent<FunctionTemplate> clazz_template;
    static NodeMethods clazz_methods;

    static void NodeInit(const Local<Object> &target, Isolate* isolate, Local<Context> &ctx);
    static bool HasInstance(Isolate *isolate, const Local<Value> &obj) {
        Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
        return !clazz.IsEmpty() && clazz->HasInstance(obj);
    }
    static DispObject *GetPtr(Isolate *isolate, const Local<Object> &obj) {
        Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
        if (clazz.IsEmpty() || !clazz->HasInstance(obj)) return nullptr;
        return Unwrap<DispObject>(obj);
    }
    static IDispatch *GetDispPtr(Isolate *isolate, const Local<Object> &obj) {
        DispObject *self = GetPtr(isolate, obj);
        return (self && self->disp) ? self->disp->ptr : nullptr;
    }
    static bool GetValueOf(Isolate *isolate, const Local<Object> &obj, VARIANT &value) {
        DispObject *self = GetPtr(isolate, obj);
        return self && SUCCEEDED(self->valueOf(isolate, value, false));
    }
    static Local<Object> NodeCreate(Isolate *isolate, IDispatch *disp, const std::wstring &name, int opt) {
        Local<Object> parent;
        DispInfoPtr ptr(new DispInfo(disp, name, opt));
        return DispObject::NodeCreate(isolate, parent, ptr, name);
    }

private:
    static Local<Object> NodeCreate(Isolate *isolate, const Local<Object> &parent, const DispInfoPtr &ptr, const std::wstring &name, DISPID id = DISPID_UNKNOWN, LONG indx = -1, int opt = 0);

    static void NodeCreate(const FunctionCallbackInfo<Value> &args);
    static void NodeValueOf(const FunctionCallbackInfo<Value> &args);
    static void NodeToString(const FunctionCallbackInfo<Value> &args);
    static void NodeRelease(const FunctionCallbackInfo<Value> &args);
    static void NodeCast(const FunctionCallbackInfo<Value> &args);
    static void NodeGet(Local<Name> name, const PropertyCallbackInfoGetter &args);
    static void NodeSet(Local<Name> name, Local<Value> value, const PropertyCallbackInfoSetter &args);
    static void NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter&args);
    static void NodeSetByIndex(uint32_t index, Local<Value> value, const PropertyCallbackInfoSetter &args);
    static void NodeCall(const FunctionCallbackInfo<Value> &args);

#ifdef NODE_INTERCEPTED
    static inline Intercepted InterceptedNodeGet(Local<Name> name, const PropertyCallbackInfoGetter& args) {
        NodeGet(name, args);
        return Intercepted::kYes;
    }
    static inline Intercepted InterceptedNodeSet(Local<Name> name, Local<Value> value, const PropertyCallbackInfoSetter& args) {
        NodeSet(name, value, args);
        return Intercepted::kYes;
    }
    static inline Intercepted InterceptedNodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter& args) {
        NodeGetByIndex(index, args);
        return Intercepted::kYes;
    }
    static inline Intercepted InterceptedNodeSetByIndex(uint32_t index, Local<Value> value, const PropertyCallbackInfoSetter& args) {
        NodeSetByIndex(index, value, args);
        return Intercepted::kYes;
    }
#endif

    static void NodeConnectionPoints(const FunctionCallbackInfo<Value> &args);
    static void PeakAndDispatchMessages(const FunctionCallbackInfo<Value> &args);

protected:
    bool release();
    bool get(LPOLESTR tag, LONG index, const PropertyCallbackInfoGetter &args);
    bool set(LPOLESTR tag, LONG index, const Local<Value> &value, const PropertyCallbackInfoSetter &args);
    void call(Isolate *isolate, const FunctionCallbackInfo<Value> &args);

    HRESULT valueOf(Isolate *isolate, VARIANT &value, bool simple);
    HRESULT valueOf(Isolate *isolate, const Local<Object> &self, Local<Value> &value);
    void toString(const FunctionCallbackInfo<Value> &args);
    Local<Value> getIdentity(Isolate *isolate);

private:
    int options;
    inline bool is_null() { return !disp; }
    inline bool is_prepared() { return (options & option_prepared) != 0; }
    inline bool is_object() { return dispid == DISPID_VALUE /*&& index < 0*/; }
    inline bool is_owned() { return (options & option_owned) != 0; }

    Persistent<Value> items, methods, vars;
    void initTypeInfo(Isolate *isolate);

    DispInfoPtr disp;
    std::wstring name;
    DISPID dispid;
    LONG index;

    HRESULT prepare();
};

class VariantObject : public NodeObject
{
public:
    VariantObject() {};
    VariantObject(const VARIANT &v) : value(v) {};
    VariantObject(const FunctionCallbackInfo<Value> &args);

    static Persistent<ObjectTemplate> inst_template;
    static Persistent<FunctionTemplate> clazz_template;
    static NodeMethods clazz_methods;

    static void NodeInit(const Local<Object> &target, Isolate* isolate, Local<Context> &ctx);
    static bool HasInstance(Isolate *isolate, const Local<Value> &obj) {
        Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
        return !clazz.IsEmpty() && clazz->HasInstance(obj);
    }
    static VariantObject *GetInstanceOf(Isolate *isolate, const Local<Object> &obj) {
        Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
        if (clazz.IsEmpty() || !clazz->HasInstance(obj)) return nullptr;
        return Unwrap<VariantObject>(obj);
    }
    static bool GetValueOf(Isolate *isolate, const Local<Object> &obj, VARIANT &value) {
        Local<FunctionTemplate> clazz = clazz_template.Get(isolate);
        if (clazz.IsEmpty() || !clazz->HasInstance(obj)) return false;
        VariantObject *self = Unwrap<VariantObject>(obj);
        return self && SUCCEEDED(self->value.CopyTo(&value));
    }

    static Local<Object> NodeCreateInstance(const FunctionCallbackInfo<Value> &args);
    static void NodeCreate(const FunctionCallbackInfo<Value> &args);
    static Local<Object> NodeCreate(Isolate* isolate, const VARIANT& var);

    static void NodeClear(const FunctionCallbackInfo<Value> &args);
    static void NodeAssign(const FunctionCallbackInfo<Value> &args);
    static void NodeCast(const FunctionCallbackInfo<Value> &args);
    static void NodeValueOf(const FunctionCallbackInfo<Value> &args);
    static void NodeToString(const FunctionCallbackInfo<Value> &args);
    static void NodeGet(Local<Name> name, const PropertyCallbackInfoGetter &args);
    static void NodeSet(Local<Name> name, Local<Value> value, const PropertyCallbackInfoSetter&args);
    static void NodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter&args);
    static void NodeSetByIndex(uint32_t index, Local<Value> value, const PropertyCallbackInfoSetter&args);

#ifdef NODE_INTERCEPTED
    static inline Intercepted InterceptedNodeGet(Local<Name> name, const PropertyCallbackInfoGetter& args) {
        NodeGet(name, args);
        return Intercepted::kYes;
    }
    static inline Intercepted InterceptedNodeSet(Local<Name> name, Local<Value> value, const PropertyCallbackInfoSetter& args) {
        NodeSet(name, value, args);
        return Intercepted::kYes;
    }
    static inline Intercepted InterceptedNodeGetByIndex(uint32_t index, const PropertyCallbackInfoGetter& args) {
        NodeGetByIndex(index, args);
        return Intercepted::kYes;
    }
    static inline Intercepted InterceptedNodeSetByIndex(uint32_t index, Local<Value> value, const PropertyCallbackInfoSetter& args) {
        NodeSetByIndex(index, value, args);
        return Intercepted::kYes;
    }
#endif

private:
    CComVariant value, pvalue;
    bool assign(Isolate *isolate, Local<Value> &val, Local<Value> &type);
};

class ConnectionPointObject : public NodeObject
{
public:
    ConnectionPointObject(IConnectionPoint *p, IDispatch* d);
    ConnectionPointObject(const FunctionCallbackInfo<Value> &args) {}
    static Persistent<ObjectTemplate> inst_template;
    static Persistent<FunctionTemplate> clazz_template;
    static Local<Object> NodeCreateInstance(Isolate *isolate, IConnectionPoint *p, IDispatch* d);
    static void NodeInit(const Local<Object> &target, Isolate* isolate, Local<Context> &ctx);
    static void NodeCreate(const FunctionCallbackInfo<Value> &args);
    static void NodeAdvise(const FunctionCallbackInfo<Value> &args);
    static void NodeUnadvise(const FunctionCallbackInfo<Value> &args);
    static void NodeConnectionPointMethods(const FunctionCallbackInfo<Value> &args);

private:
    bool InitIndex();

    CComPtr<IConnectionPoint> ptr;
    CComPtr<IDispatch> disp;
    DispObjectImpl::index_t index;

    std::unordered_set<DWORD> cookies;

};


================================================
FILE: src/main.cpp
================================================
//-------------------------------------------------------------------------------------------------------
// Project: node-activex
// Author: Yuri Dursin
// Description: Defines the entry point for the NodeJS addon
//-------------------------------------------------------------------------------------------------------

#include "stdafx.h"
#include "disp.h"

//----------------------------------------------------------------------------------

// Initialize this addon
NODE_MODULE_INIT(/*exports, module, context*/) {
  Isolate* isolate = Isolate::GetCurrent();

  DispObject::NodeInit(exports, isolate, context);
  VariantObject::NodeInit(exports, isolate, context);
  ConnectionPointObject::NodeInit(exports, isolate, context);

  // Sleep is essential to have proper WScript emulation
  exports->Set(context,
      String::NewFromUtf8(isolate, "winaxsleep", NewStringType::kNormal)
      .ToLocalChecked(),
      FunctionTemplate::New(isolate, WinaxSleep)
      ->GetFunction(context).ToLocalChecked()).FromJust();

  /* Example implementation of a context-aware addon:
  // Create a new instance of `AddonData` for this instance of the addon and
  // tie its life cycle to that of the Node.js environment.
  AddonData* data = new AddonData(isolate);

  // Wrap the data in a `v8::External` so we can pass it to the method we
  // expose.
  Local<External> external = External::New(isolate, data);

  // Expose the method `Method` to JavaScript, and make sure it receives the
  // per-addon-instance data we created above by passing `external` as the
  // third parameter to the `FunctionTemplate` constructor.
  exports->Set(context,
               String::NewFromUtf8(isolate, "method", NewStringType::kNormal)
                  .ToLocalChecked(),
               FunctionTemplate::New(isolate, Method, external)
                  ->GetFunction(context).ToLocalChecked()).FromJust();
  */
}

//----------------------------------------------------------------------------------

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ulReason, LPVOID lpReserved) {
    
    switch (ulReason) {
    case DLL_PROCESS_ATTACH:
        CoInitialize(0);
        break;
    case DLL_PROCESS_DETACH:
        CoUninitialize();
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    }
    return TRUE;
}

//----------------------------------------------------------------------------------


================================================
FILE: src/stdafx.h
================================================
#pragma once

// To remove conflicts with recent v8 code std::numeric_limits<int>::max()
#ifndef NOMINMAX
#define NOMINMAX 
#endif

#include <SDKDDKVer.h>

// Windows Header Files:
#define WIN32_LEAN_AND_MEAN                     // Exclude rarely-used stuff from Windows headers
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS      // some CString constructors will be explicit
#include <windows.h>

// ATL headers
//#define USE_ATL
#ifdef USE_ATL
#include <atlbase.h>
#include <atlstr.h>
#else
#include <ole2.h>
#include <ocidl.h>
#endif

// STD headers
#include <iostream>
#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include <unordered_set>
#include <memory>
#include <initializer_list>

// Node JS headers
#include <v8.h>
#include <node.h>
#include <node_version.h>
#include <node_object_wrap.h>
#include <node_buffer.h>
using namespace v8;
using namespace node;

// Application defines
//#define TEST_ADVISE

================================================
FILE: src/utils.cpp
================================================
//-------------------------------------------------------------------------------------------------------
// Project: NodeActiveX
// Author: Yuri Dursin
// Description:  Common utilities implementations
//-------------------------------------------------------------------------------------------------------

#include "stdafx.h"
#include "disp.h"
#include <OleCtl.h>
#include <oleacc.h>                  // for AccessibleObjectFromWindow
#pragma comment(lib, "OleAcc.lib")   // for AccessibleObjectFromWindow

const GUID CLSID_DispObjectImpl = { 0x9dce8520, 0x2efe, 0x48c0,{ 0xa0, 0xdc, 0x95, 0x1b, 0x29, 0x18, 0x72, 0xc0 } };

const IID IID_IReflect = { 0xAFBF15E5, 0xC37C, 0x11D2,{ 0xB8, 0x8E, 0x00, 0xA0, 0xC9, 0xB4, 0x71, 0xB8} };

//-------------------------------------------------------------------------------------------------------

#define ERROR_MESSAGE_WIDE_MAXSIZE 1024
#define ERROR_MESSAGE_UTF8_MAXSIZE 2048

void GetScodeString(HRESULT hr, wchar_t* buf, int bufSize)
{
    struct HRESULT_ENTRY
    {
        HRESULT hr;
        LPCWSTR lpszName;
    };
#define MAKE_HRESULT_ENTRY(hr)    { hr, L#hr }
    static const HRESULT_ENTRY hrNameTable[] =
    {
        MAKE_HRESULT_ENTRY(S_OK),
        MAKE_HRESULT_ENTRY(S_FALSE),

        MAKE_HRESULT_ENTRY(CACHE_S_FORMATETC_NOTSUPPORTED),
        MAKE_HRESULT_ENTRY(CACHE_S_SAMECACHE),
        MAKE_HRESULT_ENTRY(CACHE_S_SOMECACHES_NOTUPDATED),
        MAKE_HRESULT_ENTRY(CONVERT10_S_NO_PRESENTATION),
        MAKE_HRESULT_ENTRY(DATA_S_SAMEFORMATETC),
        MAKE_HRESULT_ENTRY(DRAGDROP_S_CANCEL),
        MAKE_HRESULT_ENTRY(DRAGDROP_S_DROP),
        MAKE_HRESULT_ENTRY(DRAGDROP_S_USEDEFAULTCURSORS),
        MAKE_HRESULT_ENTRY(INPLACE_S_TRUNCATED),
        MAKE_HRESULT_ENTRY(MK_S_HIM),
        MAKE_HRESULT_ENTRY(MK_S_ME),
        MAKE_HRESULT_ENTRY(MK_S_MONIKERALREADYREGISTERED),
        MAKE_HRESULT_ENTRY(MK_S_REDUCED_TO_SELF),
        MAKE_HRESULT_ENTRY(MK_S_US),
        MAKE_HRESULT_ENTRY(OLE_S_MAC_CLIPFORMAT),
        MAKE_HRESULT_ENTRY(OLE_S_STATIC),
        MAKE_HRESULT_ENTRY(OLE_S_USEREG),
        MAKE_HRESULT_ENTRY(OLEOBJ_S_CANNOT_DOVERB_NOW),
        MAKE_HRESULT_ENTRY(OLEOBJ_S_INVALIDHWND),
        MAKE_HRESULT_ENTRY(OLEOBJ_S_INVALIDVERB),
        MAKE_HRESULT_ENTRY(OLEOBJ_S_LAST),
        MAKE_HRESULT_ENTRY(STG_S_CONVERTED),
        MAKE_HRESULT_ENTRY(VIEW_S_ALREADY_FROZEN),

        MAKE_HRESULT_ENTRY(E_UNEXPECTED),
        MAKE_HRESULT_ENTRY(E_NOTIMPL),
        MAKE_HRESULT_ENTRY(E_OUTOFMEMORY),
        MAKE_HRESULT_ENTRY(E_INVALIDARG),
        MAKE_HRESULT_ENTRY(E_NOINTERFACE),
        MAKE_HRESULT_ENTRY(E_POINTER),
        MAKE_HRESULT_ENTRY(E_HANDLE),
        MAKE_HRESULT_ENTRY(E_ABORT),
        MAKE_HRESULT_ENTRY(E_FAIL),
        MAKE_HRESULT_ENTRY(E_ACCESSDENIED),

        MAKE_HRESULT_ENTRY(CACHE_E_NOCACHE_UPDATED),
        MAKE_HRESULT_ENTRY(CLASS_E_CLASSNOTAVAILABLE),
        MAKE_HRESULT_ENTRY(CLASS_E_NOAGGREGATION),
        MAKE_HRESULT_ENTRY(CLIPBRD_E_BAD_DATA),
        MAKE_HRESULT_ENTRY(CLIPBRD_E_CANT_CLOSE),
        MAKE_HRESULT_ENTRY(CLIPBRD_E_CANT_EMPTY),
        MAKE_HRESULT_ENTRY(CLIPBRD_E_CANT_OPEN),
        MAKE_HRESULT_ENTRY(CLIPBRD_E_CANT_SET),
        MAKE_HRESULT_ENTRY(CO_E_ALREADYINITIALIZED),
        MAKE_HRESULT_ENTRY(CO_E_APPDIDNTREG),
        MAKE_HRESULT_ENTRY(CO_E_APPNOTFOUND),
        MAKE_HRESULT_ENTRY(CO_E_APPSINGLEUSE),
        MAKE_HRESULT_ENTRY(CO_E_BAD_PATH),
        MAKE_HRESULT_ENTRY(CO_E_CANTDETERMINECLASS),
        MAKE_HRESULT_ENTRY(CO_E_CLASS_CREATE_FAILED),
        MAKE_HRESULT_ENTRY(CO_E_CLASSSTRING),
        MAKE_HRESULT_ENTRY(CO_E_DLLNOTFOUND),
        MAKE_HRESULT_ENTRY(CO_E_ERRORINAPP),
        MAKE_HRESULT_ENTRY(CO_E_ERRORINDLL),
        MAKE_HRESULT_ENTRY(CO_E_IIDSTRING),
        MAKE_HRESULT_ENTRY(CO_E_NOTINITIALIZED),
        MAKE_HRESULT_ENTRY(CO_E_OBJISREG),
        MAKE_HRESULT_ENTRY(CO_E_OBJNOTCONNECTED),
        MAKE_HRESULT_ENTRY(CO_E_OBJNOTREG),
        MAKE_HRESULT_ENTRY(CO_E_OBJSRV_RPC_FAILURE),
        MAKE_HRESULT_ENTRY(CO_E_SCM_ERROR),
        MAKE_HRESULT_ENTRY(CO_E_SCM_RPC_FAILURE),
        MAKE_HRESULT_ENTRY(CO_E_SERVER_EXEC_FAILURE),
        MAKE_HRESULT_ENTRY(CO_E_SERVER_STOPPING),
        MAKE_HRESULT_ENTRY(CO_E_WRONGOSFORAPP),
        MAKE_HRESULT_ENTRY(CONVERT10_E_OLESTREAM_BITMAP_TO_DIB),
        MAKE_HRESULT_ENTRY(CONVERT10_E_OLESTREAM_FMT),
        MAKE_HRESULT_ENTRY(CONVERT10_E_OLESTREAM_GET),
        MAKE_HRESULT_ENTRY(CONVERT10_E_OLESTREAM_PUT),
        MAKE_HRESULT_ENTRY(CONVERT10_E_STG_DIB_TO_BITMAP),
        MAKE_HRESULT_ENTRY(CONVERT10_E_STG_FMT),
        MAKE_HRESULT_ENTRY(CONVERT10_E_STG_NO_STD_STREAM),
        MAKE_HRESULT_ENTRY(DISP_E_ARRAYISLOCKED),
        MAKE_HRESULT_ENTRY(DISP_E_BADCALLEE),
        MAKE_HRESULT_ENTRY(DISP_E_BADINDEX),
        MAKE_HRESULT_ENTRY(DISP_E_BADPARAMCOUNT),
        MAKE_HRESULT_ENTRY(DISP_E_BADVARTYPE),
        MAKE_HRESULT_ENTRY(DISP_E_EXCEPTION),
        MAKE_HRESULT_ENTRY(DISP_E_MEMBERNOTFOUND),
        MAKE_HRESULT_ENTRY(DISP_E_NONAMEDARGS),
        MAKE_HRESULT_ENTRY(DISP_E_NOTACOLLECTION),
        MAKE_HRESULT_ENTRY(DISP_E_OVERFLOW),
        MAKE_HRESULT_ENTRY(DISP_E_PARAMNOTFOUND),
        MAKE_HRESULT_ENTRY(DISP_E_PARAMNOTOPTIONAL),
        MAKE_HRESULT_ENTRY(DISP_E_TYPEMISMATCH),
        MAKE_HRESULT_ENTRY(DISP_E_UNKNOWNINTERFACE),
        MAKE_HRESULT_ENTRY(DISP_E_UNKNOWNLCID),
        MAKE_HRESULT_ENTRY(DISP_E_UNKNOWNNAME),
        MAKE_HRESULT_ENTRY(DRAGDROP_E_ALREADYREGISTERED),
        MAKE_HRESULT_ENTRY(DRAGDROP_E_INVALIDHWND),
        MAKE_HRESULT_ENTRY(DRAGDROP_E_NOTREGISTERED),
        MAKE_HRESULT_ENTRY(DV_E_CLIPFORMAT),
        MAKE_HRESULT_ENTRY(DV_E_DVASPECT),
        MAKE_HRESULT_ENTRY(DV_E_DVTARGETDEVICE),
        MAKE_HRESULT_ENTRY(DV_E_DVTARGETDEVICE_SIZE),
        MAKE_HRESULT_ENTRY(DV_E_FORMATETC),
        MAKE_HRESULT_ENTRY(DV_E_LINDEX),
        MAKE_HRESULT_ENTRY(DV_E_NOIVIEWOBJECT),
        MAKE_HRESULT_ENTRY(DV_E_STATDATA),
        MAKE_HRESULT_ENTRY(DV_E_STGMEDIUM),
        MAKE_HRESULT_ENTRY(DV_E_TYMED),
        MAKE_HRESULT_ENTRY(INPLACE_E_NOTOOLSPACE),
        MAKE_HRESULT_ENTRY(INPLACE_E_NOTUNDOABLE),
        MAKE_HRESULT_ENTRY(MEM_E_INVALID_LINK),
        MAKE_HRESULT_ENTRY(MEM_E_INVALID_ROOT),
        MAKE_HRESULT_ENTRY(MEM_E_INVALID_SIZE),
        MAKE_HRESULT_ENTRY(MK_E_CANTOPENFILE),
        MAKE_HRESULT_ENTRY(MK_E_CONNECTMANUALLY),
        MAKE_HRESULT_ENTRY(MK_E_ENUMERATION_FAILED),
        MAKE_HRESULT_ENTRY(MK_E_EXCEEDEDDEADLINE),
        MAKE_HRESULT_ENTRY(MK_E_INTERMEDIATEINTERFACENOTSUPPORTED),
        MAKE_HRESULT_ENTRY(MK_E_INVALIDEXTENSION),
        MAKE_HRESULT_ENTRY(MK_E_MUSTBOTHERUSER),
        MAKE_HRESULT_ENTRY(MK_E_NEEDGENERIC),
        MAKE_HRESULT_ENTRY(MK_E_NO_NORMALIZED),
        MAKE_HRESULT_ENTRY(MK_E_NOINVERSE),
        MAKE_HRESULT_ENTRY(MK_E_NOOBJECT),
        MAKE_HRESULT_ENTRY(MK_E_NOPREFIX),
        MAKE_HRESULT_ENTRY(MK_E_NOSTORAGE),
        MAKE_HRESULT_ENTRY(MK_E_NOTBINDABLE),
        MAKE_HRESULT_ENTRY(MK_E_NOTBOUND),
        MAKE_HRESULT_ENTRY(MK_E_SYNTAX),
        MAKE_HRESULT_ENTRY(MK_E_UNAVAILABLE),
        MAKE_HRESULT_ENTRY(OLE_E_ADVF),
        MAKE_HRESULT_ENTRY(OLE_E_ADVISENOTSUPPORTED),
        MAKE_HRESULT_ENTRY(OLE_E_BLANK),
        MAKE_HRESULT_ENTRY(OLE_E_CANT_BINDTOSOURCE),
        MAKE_HRESULT_ENTRY(OLE_E_CANT_GETMONIKER),
        MAKE_HRESULT_ENTRY(OLE_E_CANTCONVERT),
        MAKE_HRESULT_ENTRY(OLE_E_CLASSDIFF),
        MAKE_HRESULT_ENTRY(OLE_E_ENUM_NOMORE),
        MAKE_HRESULT_ENTRY(OLE_E_INVALIDHWND),
        MAKE_HRESULT_ENTRY(OLE_E_INVALIDRECT),
        MAKE_HRESULT_ENTRY(OLE_E_NOCACHE),
        MAKE_HRESULT_ENTRY(OLE_E_NOCONNECTION),
        MAKE_HRESULT_ENTRY(OLE_E_NOSTORAGE),
        MAKE_HRESULT_ENTRY(OLE_E_NOT_INPLACEACTIVE),
        MAKE_HRESULT_ENTRY(OLE_E_NOTRUNNING),
        MAKE_HRESULT_ENTRY(OLE_E_OLEVERB),
        MAKE_HRESULT_ENTRY(OLE_E_PROMPTSAVECANCELLED),
        MAKE_HRESULT_ENTRY(OLE_E_STATIC),
        MAKE_HRESULT_ENTRY(OLE_E_WRONGCOMPOBJ),
        MAKE_HRESULT_ENTRY(OLEOBJ_E_INVALIDVERB),
        MAKE_HRESULT_ENTRY(OLEOBJ_E_NOVERBS),
        MAKE_HRESULT_ENTRY(REGDB_E_CLASSNOTREG),
        MAKE_HRESULT_ENTRY(REGDB_E_IIDNOTREG),
        MAKE_HRESULT_ENTRY(REGDB_E_INVALIDVALUE),
        MAKE_HRESULT_ENTRY(REGDB_E_KEYMISSING),
        MAKE_HRESULT_ENTRY(REGDB_E_READREGDB),
        MAKE_HRESULT_ENTRY(REGDB_E_WRITEREGDB),
        MAKE_HRESULT_ENTRY(RPC_E_ATTEMPTED_MULTITHREAD),
        MAKE_HRESULT_ENTRY(RPC_E_CALL_CANCELED),
        MAKE_HRESULT_ENTRY(RPC_E_CALL_REJECTED),
        MAKE_HRESULT_ENTRY(RPC_E_CANTCALLOUT_AGAIN),
        MAKE_HRESULT_ENTRY(RPC_E_CANTCALLOUT_INASYNCCALL),
        MAKE_HRESULT_ENTRY(RPC_E_CANTCALLOUT_INEXTERNALCALL),
        MAKE_HRESULT_ENTRY(RPC_E_CANTCALLOUT_ININPUTSYNCCALL),
        MAKE_HRESULT_ENTRY(RPC_E_CANTPOST_INSENDCALL),
        MAKE_HRESULT_ENTRY(RPC_E_CANTTRANSMIT_CALL),
        MAKE_HRESULT_ENTRY(RPC_E_CHANGED_MODE),
        MAKE_HRESULT_ENTRY(RPC_E_CLIENT_CANTMARSHAL_DATA),
        MAKE_HRESULT_ENTRY(RPC_E_CLIENT_CANTUNMARSHAL_DATA),
        MAKE_HRESULT_ENTRY(RPC_E_CLIENT_DIED),
        MAKE_HRESULT_ENTRY(RPC_E_CONNECTION_TERMINATED),
        MAKE_HRESULT_ENTRY(RPC_E_DISCONNECTED),
        MAKE_HRESULT_ENTRY(RPC_E_FAULT),
        MAKE_HRESULT_ENTRY(RPC_E_INVALID_CALLDATA),
        MAKE_HRESULT_ENTRY(RPC_E_INVALID_DATA),
        MAKE_HRESULT_ENTRY(RPC_E_INVALID_DATAPACKET),
        MAKE_HRESULT_ENTRY(RPC_E_INVALID_PARAMETER),
        MAKE_HRESULT_ENTRY(RPC_E_INVALIDMETHOD),
        MAKE_HRESULT_ENTRY(RPC_E_NOT_REGISTERED),
        MAKE_HRESULT_ENTRY(RPC_E_OUT_OF_RESOURCES),
        MAKE_HRESULT_ENTRY(RPC_E_RETRY),
        MAKE_HRESULT_ENTRY(RPC_E_SERVER_CANTMARSHAL_DATA),
        MAKE_HRESULT_ENTRY(RPC_E_SERVER_CANTUNMARSHAL_DATA),
        MAKE_HRESULT_ENTRY(RPC_E_SERVER_DIED),
        MAKE_HRESULT_ENTRY(RPC_E_SERVER_DIED_DNE),
        MAKE_HRESULT_ENTRY(RPC_E_SERVERCALL_REJECTED),
        MAKE_HRESULT_ENTRY(RPC_E_SERVERCALL_RETRYLATER),
        MAKE_HRESULT_ENTRY(RPC_E_SERVERFAULT),
        MAKE_HRESULT_ENTRY(RPC_E_SYS_CALL_FAILED),
        MAKE_HRESULT_ENTRY(RPC_E_THREAD_NOT_INIT),
        MAKE_HRESULT_ENTRY(RPC_E_UNEXPECTED),
        MAKE_HRESULT_ENTRY(RPC_E_WRONG_THREAD),
        MAKE_HRESULT_ENTRY(STG_E_ABNORMALAPIEXIT),
        MAKE_HRESULT_ENTRY(STG_E_ACCESSDENIED),
        MAKE_HRESULT_ENTRY(STG_E_CANTSAVE),
        MAKE_HRESULT_ENTRY(STG_E_DISKISWRITEPROTECTED),
        MAKE_HRESULT_ENTRY(STG_E_EXTANTMARSHALLINGS),
        MAKE_HRESULT_ENTRY(STG_E_FILEALREADYEXISTS),
        MAKE_HRESULT_ENTRY(STG_E_FILENOTFOUND),
        MAKE_HRESULT_ENTRY(STG_E_INSUFFICIENTMEMORY),
        MAKE_HRESULT_ENTRY(STG_E_INUSE),
        MAKE_HRESULT_ENTRY(STG_E_INVALIDFLAG),
        MAKE_HRESULT_ENTRY(STG_E_INVALIDFUNCTION),
        MAKE_HRESULT_ENTRY(STG_E_INVALIDHANDLE),
        MAKE_HRESULT_ENTRY(STG_E_INVALIDHEADER),
        MAKE_HRESULT_ENTRY(STG_E_INVALIDNAME),
        MAKE_HRESULT_ENTRY(STG_E_INVALIDPARAMETER),
        MAKE_HRESULT_ENTRY(STG_E_INVALIDPOINTER),
        MAKE_HRESULT_ENTRY(STG_E_LOCKVIOLATION),
        MAKE_HRESULT_ENTRY(STG_E_MEDIUMFULL),
        MAKE_HRESULT_ENTRY(STG_E_NOMOREFILES),
        MAKE_HRESULT_ENTRY(STG_E_NOTCURRENT),
        MAKE_HRESULT_ENTRY(STG_E_NOTFILEBASEDSTORAGE),
        MAKE_HRESULT_ENTRY(STG_E_OLDDLL),
        MAKE_HRESULT_ENTRY(STG_E_OLDFORMAT),
        MAKE_HRESULT_ENTRY(STG_E_PATHNOTFOUND),
        MAKE_HRESULT_ENTRY(STG_E_READFAULT),
        MAKE_HRESULT_ENTRY(STG_E_REVERTED),
        MAKE_HRESULT_ENTRY(STG_E_SEEKERROR),
        MAKE_HRESULT_ENTRY(STG_E_SHAREREQUIRED),
        MAKE_HRESULT_ENTRY(STG_E_SHAREVIOLATION),
        MAKE_HRESULT_ENTRY(STG_E_TOOMANYOPENFILES),
        MAKE_HRESULT_ENTRY(STG_E_UNIMPLEMENTEDFUNCTION),
        MAKE_HRESULT_ENTRY(STG_E_UNKNOWN),
        MAKE_HRESULT_ENTRY(STG_E_WRITEFAULT),
        MAKE_HRESULT_ENTRY(TYPE_E_AMBIGUOUSNAME),
        MAKE_HRESULT_ENTRY(TYPE_E_BADMODULEKIND),
        MAKE_HRESULT_ENTRY(TYPE_E_BUFFERTOOSMALL),
        MAKE_HRESULT_ENTRY(TYPE_E_CANTCREATETMPFILE),
        MAKE_HRESULT_ENTRY(TYPE_E_CANTLOADLIBRARY),
        MAKE_HRESULT_ENTRY(TYPE_E_CIRCULARTYPE),
        MAKE_HRESULT_ENTRY(TYPE_E_DLLFUNCTIONNOTFOUND),
        MAKE_HRESULT_ENTRY(TYPE_E_DUPLICATEID),
        MAKE_HRESULT_ENTRY(TYPE_E_ELEMENTNOTFOUND),
        MAKE_HRESULT_ENTRY(TYPE_E_INCONSISTENTPROPFUNCS),
        MAKE_HRESULT_ENTRY(TYPE_E_INVALIDSTATE),
        MAKE_HRESULT_ENTRY(TYPE_E_INVDATAREAD),
        MAKE_HRESULT_ENTRY(TYPE_E_IOERROR),
        MAKE_HRESULT_ENTRY(TYPE_E_LIBNOTREGISTERED),
        MAKE_HRESULT_ENTRY(TYPE_E_NAMECONFLICT),
        MAKE_HRESULT_ENTRY(TYPE_E_OUTOFBOUNDS),
        MAKE_HRESULT_ENTRY(TYPE_E_QUALIFIEDNAMEDISALLOWED),
        MAKE_HRESULT_ENTRY(TYPE_E_REGISTRYACCESS),
        MAKE_HRESULT_ENTRY(TYPE_E_SIZETOOBIG),
        MAKE_HRESULT_ENTRY(TYPE_E_TYPEMISMATCH),
        MAKE_HRESULT_ENTRY(TYPE_E_UNDEFINEDTYPE),
        MAKE_HRESULT_ENTRY(TYPE_E_UNKNOWNLCID),
        MAKE_HRESULT_ENTRY(TYPE_E_UNSUPFORMAT),
        MAKE_HRESULT_ENTRY(TYPE_E_WRONGTYPEKIND),
        MAKE_HRESULT_ENTRY(VIEW_E_DRAW),

        MAKE_HRESULT_ENTRY(CONNECT_E_NOCONNECTION),
        MAKE_HRESULT_ENTRY(CONNECT_E_ADVISELIMIT),
        MAKE_HRESULT_ENTRY(CONNECT_E_CANNOTCONNECT),
        MAKE_HRESULT_ENTRY(CONNECT_E_OVERRIDDEN),

        MAKE_HRESULT_ENTRY(CLASS_E_NOTLICENSED),
        MAKE_HRESULT_ENTRY(CLASS_E_NOAGGREGATION),
        MAKE_HRESULT_ENTRY(CLASS_E_CLASSNOTAVAILABLE),

        MAKE_HRESULT_ENTRY(CTL_E_ILLEGALFUNCTIONCALL),
        MAKE_HRESULT_ENTRY(CTL_E_OVERFLOW),
        MAKE_HRESULT_ENTRY(CTL_E_OUTOFMEMORY),
        MAKE_HRESULT_ENTRY(CTL_E_DIVISIONBYZERO),
        MAKE_HRESULT_ENTRY(CTL_E_OUTOFSTRINGSPACE),
        MAKE_HRESULT_ENTRY(CTL_E_OUTOFSTACKSPACE),
        MAKE_HRESULT_ENTRY(CTL_E_BADFILENAMEORNUMBER),
        MAKE_HRESULT_ENTRY(CTL_E_FILENOTFOUND),
        MAKE_HRESULT_ENTRY(CTL_E_BADFILEMODE),
        MAKE_HRESULT_ENTRY(CTL_E_FILEALREADYOPEN),
        MAKE_HRESULT_ENTRY(CTL_E_DEVICEIOERROR),
        MAKE_HRESULT_ENTRY(CTL_E_FILEALREADYEXISTS),
        MAKE_HRESULT_ENTRY(CTL_E_BADRECORDLENGTH),
        MAKE_HRESULT_ENTRY(CTL_E_DISKFULL),
        MAKE_HRESULT_ENTRY(CTL_E_BADRECORDNUMBER),
        MAKE_HRESULT_ENTRY(CTL_E_BADFILENAME),
        MAKE_HRESULT_ENTRY(CTL_E_TOOMANYFILES),
        MAKE_HRESULT_ENTRY(CTL_E_DEVICEUNAVAILABLE),
        MAKE_HRESULT_ENTRY(CTL_E_PERMISSIONDENIED),
        MAKE_HRESULT_ENTRY(CTL_E_DISKNOTREADY),
        MAKE_HRESULT_ENTRY(CTL_E_PATHFILEACCESSERROR),
        MAKE_HRESULT_ENTRY(CTL_E_PATHNOTFOUND),
        MAKE_HRESULT_ENTRY(CTL_E_INVALIDPATTERNSTRING),
        MAKE_HRESULT_ENTRY(CTL_E_INVALIDUSEOFNULL),
        MAKE_HRESULT_ENTRY(CTL_E_INVALIDFILEFORMAT),
        MAKE_HRESULT_ENTRY(CTL_E_INVALIDPROPERTYVALUE),
        MAKE_HRESULT_ENTRY(CTL_E_INVALIDPROPERTYARRAYINDEX),
        MAKE_HRESULT_ENTRY(CTL_E_SETNOTSUPPORTEDATRUNTIME),
        MAKE_HRESULT_ENTRY(CTL_E_SETNOTSUPPORTED),
        MAKE_HRESULT_ENTRY(CTL_E_NEEDPROPERTYARRAYINDEX),
        MAKE_HRESULT_ENTRY(CTL_E_SETNOTPERMITTED),
        MAKE_HRESULT_ENTRY(CTL_E_GETNOTSUPPORTEDATRUNTIME),
        MAKE_HRESULT_ENTRY(CTL_E_GETNOTSUPPORTED),
        MAKE_HRESULT_ENTRY(CTL_E_PROPERTYNOTFOUND),
        MAKE_HRESULT_ENTRY(CTL_E_INVALIDCLIPBOARDFORMAT),
        MAKE_HRESULT_ENTRY(CTL_E_INVALIDPICTURE),
        MAKE_HRESULT_ENTRY(CTL_E_PRINTERERROR),
        MAKE_HRESULT_ENTRY(CTL_E_CANTSAVEFILETOTEMP),
        MAKE_HRESULT_ENTRY(CTL_E_SEARCHTEXTNOTFOUND),
        MAKE_HRESULT_ENTRY(CTL_E_REPLACEMENTSTOOLONG),
    };

    // look for scode in the table
    for (int i = 0; i < sizeof(hrNameTable) / sizeof(hrNameTable[0]); i++)
    {
        if (hr == hrNameTable[i].hr) {
            swprintf_s(buf, (size_t)(bufSize - 1), hrNameTable[i].lpszName);
            return;
        }
    }
    // not found - make one up
    swprintf_s(buf, (size_t)(bufSize - 1), L"OLE error 0x%08x", hr);
}


uint16_t *GetWin32ErrorMessage(uint16_t *buf, size_t buflen, Isolate *isolate, HRESULT hrcode, LPCOLESTR msg, LPCOLESTR msg2, LPCOLESTR desc) {
    uint16_t *bufptr = buf;
    size_t len;
    if (msg) {
        len = wcslen(msg);
        if (len >= buflen) len = buflen - 1;
        if (len > 0) memcpy(bufptr, msg, len * sizeof(uint16_t));
        buflen -= len;
        bufptr += len;
        if (buflen > 2) {
            bufptr[0] = ':';
            bufptr[1] = ' ';
            buflen -= 2;
            bufptr += 2;
        }
    }
    if (msg2) {
        len = wcslen(msg2);
        if (len >= buflen) len = buflen - 1;
        if (len > 0) memcpy(bufptr, msg2, len * sizeof(uint16_t));
        buflen -= len;
        bufptr += len;
        if (buflen > 1) {
            bufptr[0] = ' ';
            buflen -= 1;
            bufptr += 1;
        }
    }
    if (buflen > 1) {
        len = desc ? wcslen(desc) : 0;
        if (len > 0) {
            if (len >= buflen) len = buflen - 1;
            memcpy(bufptr, desc, len * sizeof(OLECHAR));
        }
        else {
            len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, 0, hrcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPOLESTR)bufptr, (DWORD)buflen - 1, 0);
            if (len == 0) len = swprintf_s((LPOLESTR)bufptr, buflen - 1, L"Error 0x%08X", hrcode);
        }
        buflen -= len;
        bufptr += len;
    }
    if (buflen > 0) bufptr[0] = 0;
    return buf;
}

char *GetWin32ErrorMessage(char *buf, size_t buflen, Isolate *isolate, HRESULT hrcode, LPCOLESTR msg, LPCOLESTR msg2, LPCOLESTR desc) {
    uint16_t buf_wide[ERROR_MESSAGE_WIDE_MAXSIZE];
    GetWin32ErrorMessage(buf_wide, ERROR_MESSAGE_WIDE_MAXSIZE, isolate, hrcode, msg, msg2, desc);
    int rcode = WideCharToMultiByte(CP_UTF8, 0, (WCHAR*)buf_wide, -1, buf, buflen, NULL, NULL);
    if (rcode < 0) rcode = 0;
    buf[rcode] = 0;
    return buf;
}

Local<String> GetWin32ErrorMessage(Isolate *isolate, HRESULT hrcode, LPCOLESTR msg, LPCOLESTR msg2, LPCOLESTR desc) {
    uint16_t buf_wide[ERROR_MESSAGE_WIDE_MAXSIZE];
    return v8str(isolate, GetWin32ErrorMessage(buf_wide, ERROR_MESSAGE_WIDE_MAXSIZE, isolate, hrcode, msg, msg2, desc));
}

//-------------------------------------------------------------------------------------------------------

Local<Value> Variant2Array(Isolate *isolate, const VARIANT &v) {
    if ((v.vt & VT_ARRAY) == 0) return Null(isolate);
    SAFEARRAY *varr = (v.vt & VT_BYREF) != 0 ? *v.pparray : v.parray;
    if (!varr || varr->cDims > 2 || varr->cDims == 0) return Null(isolate);
    else if ( varr->cDims == 2 ) return Variant2Array2( isolate, v );
    Local<Context> ctx = isolate->GetCurrentContext();
    VARTYPE vt = v.vt & VT_TYPEMASK;
    LONG cnt = (LONG)varr->rgsabound[0].cElements;
    Local<Array> arr = Array::New(isolate, cnt);
    for (LONG i = varr->rgsabound[0].lLbound; i < varr->rgsabound[0].lLbound + cnt; i++) {
        CComVariant vi;
        if SUCCEEDED(SafeArrayGetElement(varr, &i, (vt == VT_VARIANT) ? (void*)&vi : (void*)&vi.byref)) {
            if (vt != VT_VARIANT) vi.vt = vt;
            uint32_t jsi = i - varr->rgsabound[ 0 ].lLbound;
            arr->Set(ctx, jsi, Variant2Value(isolate, vi, true));
        }
    }
    return arr;
}

static Local<Value> Variant2Array2(Isolate *isolate, const VARIANT &v) {
    if ((v.vt & VT_ARRAY) == 0) return Null(isolate);
    SAFEARRAY *varr = (v.vt & VT_BYREF) != 0 ? *v.pparray : v.parray;
    if (!varr || varr->cDims != 2) return Null(isolate);
    Local<Context> ctx = isolate->GetCurrentContext();
    VARTYPE vt = v.vt & VT_TYPEMASK;
    LONG cnt1 = (LONG)varr->rgsabound[0].cElements;
    LONG cnt2 = (LONG)varr->rgsabound[1].cElements;
    Local<Array> arr1 = Array::New(isolate, cnt2);
    LONG rgIndices[ 2 ];
    for (LONG i2 = varr->rgsabound[1].lLbound; i2 < varr->rgsabound[1].lLbound + cnt2; i2++) {
        rgIndices[ 0 ] = i2;
        Local<Array> arr2 = Array::New(isolate, cnt1);
        for (LONG i1 = varr->rgsabound[0].lLbound; i1 < varr->rgsabound[0].lLbound + cnt1; i1++) {
            CComVariant vi;
            rgIndices[ 1 ] = i1;
            if SUCCEEDED(SafeArrayGetElement(varr, &rgIndices[0], (vt == VT_VARIANT) ? (void*)&vi : (void*)&vi.byref)) {
                if (vt != VT_VARIANT) vi.vt = vt;
                uint32_t jsi = (uint32_t)i1 - varr->rgsabound[ 0 ].lLbound;
                arr2->Set(ctx, jsi, Variant2Value(isolate, vi, true));
            }
        }
        uint32_t jsi = i2 - varr->rgsabound[ 1 ].lLbound;
        arr1->Set(ctx, jsi, arr2);
    }
    return arr1;
}

Local<Value> Variant2Value(Isolate *isolate, const VARIANT &v, bool allow_disp) {
    if ((v.vt & VT_ARRAY) != 0) return Variant2Array(isolate, v);
    VARTYPE vt = (v.vt & VT_TYPEMASK);
    bool by_ref = (v.vt & VT_BYREF) != 0;
    switch (vt) {
    case VT_NULL:
        return Null(isolate);
    case VT_I1:
        return Int32::New(isolate, (int32_t)(by_ref ? *v.pcVal : v.cVal));
    case VT_I2:
        return Int32::New(isolate, (int32_t)(by_ref ? *v.piVal : v.iVal));
    case VT_I4:
        return Int32::New(isolate, (int32_t)(by_ref ? *v.plVal : v.lVal));
    case VT_INT:
        return Int32::New(isolate, (int32_t)(by_ref ? *v.pintVal : v.intVal));
    case VT_UI1:
        return Int32::New(isolate, (uint32_t)(by_ref ? *v.pbVal : v.bVal));
    case VT_UI2:
        return Int32::New(isolate, (uint32_t)(by_ref ? *v.puiVal : v.uiVal));
    case VT_UI4:
        return Int32::New(isolate, (uint32_t)(by_ref ? *v.pulVal : v.ulVal));
    case VT_UINT:
        return Int32::New(isolate, (uint32_t)(by_ref ? *v.puintVal : v.uintVal));
    case VT_I8:
        return Number::New(isolate, (double)(by_ref ? *v.pllVal : v.llVal));
    case VT_UI8:
        return Number::New(isolate, (double)(by_ref ? *v.pullVal : v.ullVal));
    case VT_CY:
        return Number::New(isolate, (double)(by_ref ? v.pcyVal : &v.cyVal)->int64 / 10000.);
    case VT_R4:
        return Number::New(isolate, by_ref ? *v.pfltVal : v.fltVal);
    case VT_R8:
        return Number::New(isolate, by_ref ? *v.pdblVal : v.dblVal);
    case VT_DATE: {
        Local<Value> ret;
        if (!Date::New(isolate->GetCurrentContext(), FromOleDate(by_ref ? *v.pdate : v.date)).ToLocal(&ret))
            ret = Undefined(isolate);
        return ret;
    }
    case VT_DECIMAL: {
        DOUBLE dblval;
        if FAILED(VarR8FromDec(by_ref ? v.pdecVal : &v.decVal, &dblval)) return Undefined(isolate);
        return Number::New(isolate, dblval);		
    }
    case VT_BOOL:
        return Boolean::New(isolate, (by_ref ? *v.pboolVal : v.boolVal) != VARIANT_FALSE);
    case VT_DISPATCH: {
        IDispatch *disp = (by_ref ? *v.ppdispVal : v.pdispVal);
        if (!disp) return Null(isolate);
        if (allow_disp) {
            DispObjectImpl *impl;
            if (disp->QueryInterface(CLSID_DispObjectImpl, (void**)&impl) == S_OK) {
                return impl->obj.Get(isolate);
            }
            return DispObject::NodeCreate(isolate, disp, L"Dispatch", option_auto);
        }
        return v8str(isolate, "[Dispatch]");
    }
    case VT_UNKNOWN: {
        if (allow_disp)
        {
            CComPtr<IDispatch> disp;
            if (UnknownDispGet(by_ref ? *v.ppunkVal : v.punkVal, &disp)) {
                return DispObject::NodeCreate(isolate, disp, L"Unknown", option_auto);
            }
        }
        // Check for NULL value
        if ((by_ref && *v.ppunkVal) || v.punkVal)
        {
            if (allow_disp) {
                return VariantObject::NodeCreate(isolate, v);
            }
            return v8str(isolate, "[Unknown]");
        }
        return Null(isolate);
    }
    case VT_BSTR: {
        BSTR bstr = by_ref ? (v.pbstrVal ? *v.pbstrVal : nullptr) : v.bstrVal;
        //if (!bstr) return String::Empty(isolate);
        // Sometimes we need to distinguish between NULL and empty string
        if (!bstr) return Null(isolate);
        return v8str(isolate, bstr);
    }
    case VT_VARIANT: 
        if (v.pvarVal) return Variant2Value(isolate, *v.pvarVal, allow_disp);
    }
    return Undefined(isolate);
}

Local<Value> Variant2String(Isolate *isolate, const VARIANT &v) {
    char buf[256] = {};
    VARTYPE vt = (v.vt & VT_TYPEMASK);
    bool by_ref = (v.vt & VT_BYREF) != 0;
    switch (vt) {
    case VT_EMPTY:
        strcpy(buf, "EMPTY");
        break;
    case VT_NULL:
        strcpy(buf, "NULL");
        break;
    case VT_I1:
        sprintf_s(buf, "%i", (int)(by_ref ? *v.pcVal : v.cVal));
        break;
    case VT_I2:
        sprintf_s(buf, "%i", (int)(by_ref ? *v.piVal : v.iVal));
        break;
    case VT_I4:
        sprintf_s(buf, "%i", (int)(by_ref ? *v.plVal : v.lVal));
        break;
    case VT_INT:
        sprintf_s(buf, "%i", (int)(by_ref ? *v.pintVal : v.intVal));
        break;
    case VT_UI1:
        sprintf_s(buf, "%u", (unsigned int)(by_ref ? *v.pbVal : v.bVal));
        break;
    case VT_UI2:
        sprintf_s(buf, "%u", (unsigned int)(by_ref ? *v.puiVal : v.uiVal));
        break;
    case VT_UI4:
        sprintf_s(buf, "%u", (unsigned int)(by_ref ? *v.pulVal : v.ulVal));
        break;
    case VT_UINT:
        sprintf_s(buf, "%u", (unsigned int)(by_ref ? *v.puintVal : v.uintVal));
        break;
    case VT_CY:
    case VT_I8:
        sprintf_s(buf, "%lld", (by_ref ? *v.pllVal : v.llVal));
        break;
    case VT_UI8:
        sprintf_s(buf, "%llu", (by_ref ? *v.pullVal : v.ullVal));
        break;
    case VT_R4:
        sprintf_s(buf, "%f", (double)(by_ref ? *v.pfltVal : v.fltVal));
        break;
    case VT_R8:
        sprintf_s(buf, "%f", (double)(by_ref ? *v.pdblVal : v.dblVal));
        break;
    case VT_DATE: {
        Local<Value> ret;
        if (!Date::New(isolate->GetCurrentContext(), FromOleDate(by_ref ? *v.pdate : v.date)).ToLocal(&ret))
            ret = Undefined(isolate);
        return ret;
    }
    case VT_DECIMAL: {
        DOUBLE dblval;
        if FAILED(VarR8FromDec(by_ref ? v.pdecVal : &v.decVal, &dblval)) return Undefined(isolate); 
        sprintf_s(buf, "%f", (double)dblval);
        break;		
    }
    case VT_BOOL:
        strcpy(buf, ((by_ref ? *v.pboolVal : v.boolVal) == VARIANT_FALSE) ? "false" : "true");
        break;
    case VT_DISPATCH:
        strcpy(buf, "[Dispatch]");
        break;
    case VT_UNKNOWN: 
        strcpy(buf, "[Unknown]");
        break;
    case VT_VARIANT:
        if (v.pvarVal) return Variant2String(isolate, *v.pvarVal);
        break;
    default:
        CComVariant tmp;
        if (SUCCEEDED(VariantChangeType(&tmp, &v, 0, VT_BSTR)) && tmp.vt == VT_BSTR && v.bstrVal != nullptr) {
            return v8str(isolate, v.bstrVal);
        }
    }
    return v8str(isolate, buf);
}

void Value2Variant(Isolate *isolate, Local<Value> &val, VARIANT &var, VARTYPE vt) {
    Local<Context> ctx = isolate->GetCurrentContext();
    if (val.IsEmpty() || val->IsUndefined()) {
        var.vt = VT_EMPTY;
    }
    else if (val->IsNull()) {
        var.vt = VT_NULL;
    }
    else if (val->IsInt32()) {
        //var.lVal = val->Int32Value();
        var.lVal = val->Int32Value(ctx).FromMaybe(0);
        var.vt = VT_I4;
    }
    else if (val->IsUint32()) {
        //var.ulVal = val->Uint32Value();
        var.ulVal = val->Uint32Value(ctx).FromMaybe(0);
        var.vt = (var.ulVal <= 0x7FFFFFFF) ? VT_I4 : VT_UI4;
    }
    else if (val->IsNumber()) {
        //var.dblVal = val->NumberValue();
        var.dblVal = val->NumberValue(ctx).FromMaybe(0);
        var.vt = VT_R8;
    }
    else if (val->IsDate()) {
        //var.date = ToOleDate(val->NumberValue());
        var.date = ToOleDate(val->NumberValue(ctx).FromMaybe(0));
        var.vt = VT_DATE;
    }
    else if (val->IsBoolean()) {
        var.boolVal = NODE_BOOL(isolate, val) ? VARIANT_TRUE : VARIANT_FALSE;
        var.vt = VT_BOOL;
    }
    else if (val->IsArray() && (vt != VT_NULL)) {
        Local<Array> arr = v8::Local<Array>::Cast(val);
        uint32_t len = arr->Length();
        if (vt == VT_EMPTY) vt = VT_VARIANT;
        var.vt = VT_ARRAY | vt;
        // if array of arrays, create a 2 dim array, choose the 2nd bound
        uint32_t second_len = 0;
        if (len) {
            Local<Value> first_value;
            if (arr->Get(ctx, 0).ToLocal(&first_value)) {
                if (first_value->IsArray()) second_len = v8::Local<Array>::Cast(first_value)->Length();
            }
        }
        if ( second_len == 0 ) {
            var.parray = SafeArrayCreateVector(vt, 0, len);
            for (uint32_t i = 0; i < len; i++) {
                CComVariant v;
                Local<Value> val;
                if (!arr->Get(ctx, i).ToLocal(&val)) val = Undefined(isolate);
                Value2Variant(isolate, val, v, vt);
                void *pv;
                if (vt == VT_VARIANT) pv = (void*)&v;
                else if (vt == VT_DISPATCH || vt == VT_UNKNOWN || vt == VT_BSTR) pv = v.byref;
                else pv = (void*)&v.byref;
                SafeArrayPutElement(var.parray, (LONG*)&i, pv);
            }
        }
        else {
            SAFEARRAYBOUND rgsabounds[ 2 ];
            rgsabounds[ 0 ].lLbound = rgsabounds[ 1 ].lLbound = 0;
            rgsabounds[ 0 ].cElements = len;
            rgsabounds[ 1 ].cElements = second_len;
            var.parray = SafeArrayCreate(vt, 2, rgsabounds);
            LONG rgIndices[ 2 ];
            for (uint32_t i = 0; i < len; i++) {
                rgIndices[ 0 ] = i;
                Local<Value> maybearray;
                Local<Array> arr2;
                bool bGotArray = false;
                if (arr->Get( ctx, i ).ToLocal( &maybearray ) && maybearray->IsArray()) {
                    bGotArray = true;
                    arr2 = v8::Local<Array>::Cast(maybearray);
                }
                for (uint32_t j = 0; j < second_len; j++) {
                    rgIndices[ 1 ] = j;
                    Local<Value> val;
                    if ( bGotArray ) {
                        if (!arr2->Get(ctx, j).ToLocal(&val)) val = Undefined(isolate);
                    }
                    else {
                        // if no arrays for a "row", the value is put only in first "col"
                        if ( j == 0 ) val = maybearray;
                        else val = Undefined(isolate);
                    }
                    CComVariant v;
                    Value2Variant(isolate, val, v, vt);
                    void *pv;
                    if (vt == VT_VARIANT) pv = (void*)&v;
                    else if (vt == VT_DISPATCH || vt == VT_UNKNOWN || vt == VT_BSTR) pv = v.byref;
                    else pv = (void*)&v.byref;
                    SafeArrayPutElement(var.parray, rgIndices, pv);
                }
            }
        }
        vt = VT_EMPTY;
    }
    else if (val->IsObject()) {
        auto obj = Local<Object>::Cast(val);
        if (!DispObject::GetValueOf(isolate, obj, var) && !VariantObject::GetValueOf(isolate, obj, var)) {
            var.vt = VT_DISPATCH;
            var.pdispVal = new DispObjectImpl(obj);
            var.pdispVal->AddRef();
        }
    }
    else {
        String::Value str(isolate, val);
        var.vt = VT_BSTR;
        // For some apps there is still a difference between "" and NULL string, so we should support it here gracefully
        if (*str)
        {
            var.bstrVal = SysAllocString((LPOLESTR)*str);
        }
        else {
            var.bstrVal = 0;
        }
        //var.bstrVal = (str.length() > 0) ? SysAllocString((LPOLESTR)*str) : 0;
    }
    if (vt != VT_EMPTY && vt != VT_NULL && vt != VT_VARIANT) {
        if FAILED(VariantChangeType(&var, &var, 0, vt))
            VariantClear(&var);
    }
}

void Value2SafeArray(Isolate *isolate, Local<Value> &val, VARIANT &var, VARTYPE vt) {
    Local<Context> ctx = isolate->GetCurrentContext();
    if (val.IsEmpty() || val->IsUndefined()) {
        var.vt = VT_EMPTY;
    }
    else if (val->IsNull()) {
        var.vt = VT_NULL;
    }
    else if (val->IsObject()) {
        // Test conversion dispatch pointer to Uint8Array
        if (vt == VT_UI1) {
            auto obj = Local<Object>::Cast(val);
            auto ptr = DispObject::GetDispPtr(isolate, obj);
            size_t len = sizeof(UINT_PTR);
            var.vt = VT_ARRAY | vt;
            var.parray = SafeArrayCreateVector(vt, 0, len);
            if (var.parray != nullptr) memcpy(var.parray->pvData, &ptr, len);
        }
    }
    else {
        var.vt = VT_EMPTY;
    }
}

bool Value2Unknown(Isolate *isolate, Local<Value> &val, IUnknown **unk) {
    if (val.IsEmpty() || !val->IsObject()) return false;
    auto obj = Local<Object>::Cast(val);
    CComVariant var;
    if (!DispObject::GetValueOf(isolate, obj, var) && !VariantObject::GetValueOf(isolate, obj, var)) return false;
    return VariantUnkGet(&var, unk);
}

bool UnknownDispGet(IUnknown *unk, IDispatch **disp) {
    if (!unk) return false;
    if SUCCEEDED(unk->QueryInterface(__uuidof(IDispatch), (void**)disp)) {
        return true;
    }
    CComPtr<IEnumVARIANT> enum_ptr;
    if SUCCEEDED(unk->QueryInterface(__uuidof(IEnumVARIANT), (void**)&enum_ptr)) {
        *disp = new DispEnumImpl(enum_ptr);
        (*disp)->AddRef();
        return true;
    }
    return false;
}

bool VariantUnkGet(VARIANT *v, IUnknown **punk) {
    IUnknown *unk = NULL;
    if ((v->vt & VT_TYPEMASK) == VT_DISPATCH) {
        unk = ((v->vt & VT_BYREF) != 0) ? *v->ppdispVal : v->pdispVal;
    }
    else if ((v->vt & VT_TYPEMASK) == VT_UNKNOWN) {
        unk = ((v->vt & VT_BYREF) != 0) ? *v->ppunkVal : v->punkVal;
    }
    if (!unk) return false;
    unk->AddRef();
    *punk = unk;
    return true;
}

bool VariantDispGet(VARIANT *v, IDispatch **pdisp) {
    /*
    if ((v->vt & VT_ARRAY) != 0) {
        *disp = new DispArrayImpl(*v);
        (*disp)->AddRef();
        return true;
    }
    */
    if ((v->vt & VT_TYPEMASK) == VT_DISPATCH) {
        IDispatch *disp = ((v->vt & VT_BYREF) != 0) ? *v->ppdispVal : v->pdispVal;
        if (!disp) return false;
        disp->AddRef();
        *pdisp = disp;
        return true;
    }
    if ((v->vt & VT_TYPEMASK) == VT_UNKNOWN) {
        return UnknownDispGet(((v->vt & VT_BYREF) != 0) ? *v->ppunkVal : v->punkVal, pdisp);
    }
    return false;
}

//-------------------------------------------------------------------------------------------------------
// DispArrayImpl implemetation

HRESULT STDMETHODCALLTYPE DispArrayImpl::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) {
    if (cNames != 1 || !rgszNames[0]) return DISP_E_UNKNOWNNAME;
    LPOLESTR name = rgszNames[0];
    if (wcscmp(name, L"length") == 0) *rgDispId = 1;
    else return DISP_E_UNKNOWNNAME;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE DispArrayImpl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) {
    HRESULT hrcode = S_OK;
    UINT argcnt = pDispParams->cArgs;
    VARIANT *args = pDispParams->rgvarg;

    if ((var.vt & VT_ARRAY) == 0) return E_NOTIMPL;
    SAFEARRAY *arr = ((var.vt & VT_BYREF) != 0) ? *var.pparray : var.parray;

    switch (dispIdMember) {
    case 1: {
        if (pVarResult) {
            pVarResult->vt = VT_INT;
            pVarResult->intVal = (INT)(arr ? arr->rgsabound[0].cElements : 0);
        }
        return hrcode; }
    }
    return E_NOTIMPL;
}

//-------------------------------------------------------------------------------------------------------
// DispEnumImpl implemetation

HRESULT STDMETHODCALLTYPE DispEnumImpl::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) {
    if (cNames != 1 || !rgszNames[0]) return DISP_E_UNKNOWNNAME;
    LPOLESTR name = rgszNames[0];
    if (wcscmp(name, L"Next") == 0) *rgDispId = 1;
    else if (wcscmp(name, L"Skip") == 0) *rgDispId = 2;
    else if (wcscmp(name, L"Reset") == 0) *rgDispId = 3;
    else if (wcscmp(name, L"Clone") == 0) *rgDispId = 4;
    else return DISP_E_UNKNOWNNAME;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE DispEnumImpl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) {
    HRESULT hrcode = S_OK;
    UINT argcnt = pDispParams->cArgs;
    VARIANT *args = pDispParams->rgvarg;
    switch (dispIdMember) {
    case 1: {
        CComVariant arr;
        ULONG fetched, celt = (argcnt > 0) ? Variant2Int(args[argcnt - 1], (ULONG)1) : 1;
        if (!pVarResult || celt == 0) hrcode = E_INVALIDARG;
        if SUCCEEDED(hrcode) hrcode = arr.ArrayCreate(VT_VARIANT, celt);
        if SUCCEEDED(hrcode) hrcode = ptr->Next(celt, arr.ArrayGet<VARIANT>(0), &fetched);
        if SUCCEEDED(hrcode) {
            if (fetched == 0) pVarResult->vt = VT_EMPTY;
            else if (fetched == 1) {
                VARIANT *v = arr.ArrayGet<VARIANT>(0);
                *pVarResult = *v;
                v->vt = VT_EMPTY;
            }
            else {
                if (fetched < celt) hrcode = arr.ArrayResize(fetched);
                if SUCCEEDED(hrcode) arr.Detach(pVarResult);
            }
        }
        return hrcode; 
        }
    case 2: {
        if (pVarResult) pVarResult->vt = VT_EMPTY;
        ULONG celt = (argcnt > 0) ? Variant2Int(args[argcnt - 1], (ULONG)1) : 1;
        return ptr->Skip(celt); 
        }
    case 3: {
        if (pVarResult) pVarResult->vt = VT_EMPTY;
        return ptr->Reset(); 
        }
    case 4: {
        std::unique_ptr<DispEnumImpl> disp;
        hrcode = pVarResult ? ptr->Clone(&disp->ptr) : E_INVALIDARG;
        if SUCCEEDED(hrcode) {
            disp->AddRef();
            pVarResult->vt = VT_DISPATCH;
            pVarResult->pdispVal = disp.release();
        }
        return hrcode; }
    }
    return E_NOTIMPL;
}

//-------------------------------------------------------------------------------------------------------
// DispObjectImpl implemetation

HRESULT STDMETHODCALLTYPE DispObjectImpl::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) {
    if (cNames != 1 || !rgszNames[0]) return DISP_E_UNKNOWNNAME;
    std::wstring name(rgszNames[0]);
    name_ptr &ptr = names[name];
    if (!ptr) {
        ptr.reset(new name_t(dispid_next++, name));
        index.insert(index_t::value_type(ptr->dispid, ptr));
    }
    *rgDispId = ptr->dispid;
    return S_OK;
}

HRESULT STDMETHODCALLTYPE DispObjectImpl::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) {
    Isolate *isolate = Isolate::GetCurrent();
    Local<Context> ctx = isolate->GetCurrentContext();
    Local<Object> self = obj.Get(isolate);
    Local<Value> name, val, ret;

    // Prepare name by member id
    if (!index.empty()) {
        index_t::const_iterator p = index.find(dispIdMember);
        if (p == index.end())
        {
            // DispID may be 0 for  regular member or for DISPID_VALUE.
            // Since regular method not found assume DISPID_VALUE if it is 0.
            if(dispIdMember != DISPID_VALUE) return DISP_E_MEMBERNOTFOUND;
        } else {
            name_t& info = *p->second;
            name = v8str(isolate, info.name.c_str());
        }
    }
    // Set property value
    if ((wFlags & DISPATCH_PROPERTYPUT) != 0) {
        UINT argcnt = pDispParams->cArgs;
        VARIANT *key = (argcnt > 1) ? &pDispParams->rgvarg[--argcnt] : nullptr;
        if (argcnt > 0) val = Variant2Value(isolate, pDispParams->rgvarg[--argcnt], true);
        else val = Undefined(isolate);
        bool rcode;

        // Set simple object property value
        if (!key) {
            if (name.IsEmpty()) return DISP_E_MEMBERNOTFOUND;
            rcode = self->Set(ctx, name, val).FromMaybe(false);
        }

        // Set object/array item value
        else {
            Local<Object> target;
            if (name.IsEmpty()) target = self;
            else {
                Local<Value> obj;
                if (self->Get(ctx, name).ToLocal(&obj) && !obj.IsEmpty()) target = Local<Object>::Cast(obj);
                if (target.IsEmpty()) return DISP_E_BADCALLEE;
            }

            LONG index = Variant2Int<LONG>(*key, -1);
            if (index >= 0) rcode = target->Set(ctx, (uint32_t)index, val).FromMaybe(false);
            else rcode = target->Set(ctx, Variant2Value(isolate, *key, false), val).FromMaybe(false);
        }

        // Store result
        if (pVarResult) {
            pVarResult->vt = VT_BOOL;
            pVarResult->boolVal = rcode ? VARIANT_TRUE : VARIANT_FALSE;
        }
        return S_OK;
    }

    // Prepare property item
    if (name.IsEmpty()) val = self;
    else self->Get(ctx, name).ToLocal(&val);

    // Call property as method
    if ((wFlags & DISPATCH_METHOD) != 0) {
        wFlags = 0;
        NodeArguments args(isolate, pDispParams, true, reverse_arguments);
        int argcnt = (int)args.items.size();
        Local<Value> *argptr = (argcnt > 0) ? &args.items[0] : nullptr;
        if (val->IsFunction()) {
            Local<Function> func = Local<Function>::Cast(val);
            if (func.IsEmpty()) return DISP_E_BADCALLEE;
            func->Call(isolate->GetCurrentContext(), self, argcnt, argptr).ToLocal(&ret);
        }
        else if (val->IsObject()) {
            wFlags = DISPATCH_PROPERTYGET;
            //Local<Object> target = val->ToObject();
            //target->CallAsFunction(isolate->GetCurrentContext(), target, args.items.size(), &args.items[0]).ToLocal(&ret);
        }
        else {
            ret = val;
        }
    }

    // Get property value
    if ((wFlags & DISPATCH_PROPERTYGET) != 0) {
        if (pDispParams->cArgs == 1) {
            Local<Object> target;
            if (!val.IsEmpty()) target = Local<Object>::Cast(val);
            if (target.IsEmpty()) return DISP_E_BADCALLEE;
            VARIANT &key = pDispParams->rgvarg[0];
            LONG index = Variant2Int<LONG>(key, -1);
            if (index >= 0) target->Get(ctx, (uint32_t)index).ToLocal(&ret);
            else target->Get(ctx, Variant2Value(isolate, key, false)).ToLocal(&ret);
        }
        else {
            ret = val;
        }
    }

    // Store result
    if (pVarResult) {
        Value2Variant(isolate, ret, *pVarResult, VT_NULL);
    }
    return S_OK;
}

/*
* Microsoft OLE Date type:
* https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/82ab7w69(v=vs.90)
*/

double FromOleDate(double oleDate) {
    double posixDate = oleDate - 25569; // days from 1899 dec 30
    posixDate *= 24 * 60 * 60 * 1000;   // days to milliseconds
    return posixDate;
}

double ToOleDate(double posixDate) {
    double oleDate = posixDate / (24 * 60 * 60 * 1000); // milliseconds to days
    oleDate += 25569;                                   // days from 1899 dec 30
    return oleDate;
}

//-------------------------------------------------------------------------------------------------------

bool NodeMethods::get(Isolate* isolate, const std::wstring &name, Local<Function>* value) {
    auto ptr = items.find(name);
    if (ptr == items.end()) return false;
    Local<FunctionTemplate> ft = ptr->second->Get(isolate);
    return ft->GetFunction(isolate->GetCurrentContext()).ToLocal(value);
}

void NodeMethods::add(Isolate* isolate, Local<FunctionTemplate>& clazz, const char* name, FunctionCallback callback) {
    // see NODE_SET_PROTOTYPE_METHOD
    HandleScope handle_scope(isolate);
    Local<Signature> s = Signature::New(isolate, clazz);
    Local<FunctionTemplate> t = FunctionTemplate::New(isolate, callback, Local<Value>(), s);
    Local<String> fn_name = String::NewFromUtf8(isolate, name, NewStringType::kInternalized).ToLocalChecked();
    t->SetClassName(fn_name);
    
    clazz->PrototypeTemplate()->Set(fn_name, t);

    String::Value vname(isolate, fn_name);
    item_type item(new Persistent<FunctionTemplate>(isolate, t));
    items.emplace(std::wstring((const wchar_t *)*vname), item);
}

//-------------------------------------------------------------------------------------------------------

/* Message loop. Just like with WScript it is executed while the script is waiting. 
   So if you have something showing, for example, a popup message, then you will only
   see it when you do WScript.Sleep.
*/

void DoEvents()
{
    MSG msg;
    BOOL result;

    if (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
    {
        result = ::GetMessage(&msg, NULL, 0, 0);
        if (result == 0) // WM_QUIT
        {
            ::PostQuitMessage(msg.wParam);
        }
        else if (result == -1)
        {
            // Handle errors/exit application, etc.
        }
        else
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
    }
}

/* Returns the amount of milliseconds elapsed since the UNIX epoch. Works on both
 * windows and linux. */

long long GetTimeMs64()
{
    /* Windows */
    FILETIME ft;
    LARGE_INTEGER li;

    /* Get the amount of 100 nano seconds intervals elapsed since January 1, 1601 (UTC) and copy it
     * to a LARGE_INTEGER structure. */
    GetSystemTimeAsFileTime(&ft);
    li.LowPart = ft.dwLowDateTime;
    li.HighPart = ft.dwHighDateTime;

    long long ret = li.QuadPart;
    ret -= 116444736000000000LL; /* Convert from file time to UNIX epoch time. */
    ret /= 10000; /* From 100 nano seconds (10^-7) to 1 millisecond (10^-3) intervals */

    return ret;

}

// Sleep is essential to have proper WScript emulation
void WinaxSleep(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    Local<Context> ctx = isolate->GetCurrentContext();

    if (args.Length() == 0 && !args[0]->IsUint32()) {
        isolate->ThrowException(InvalidArgumentsError(isolate));
        return;
    }
    uint32_t ms = (args[0]->Uint32Value(ctx)).FromMaybe(0);
    long long start = GetTimeMs64();
    do
    {
        DoEvents();
        Sleep(1);
    } while (GetTimeMs64() - start < ms);
    args.GetReturnValue().SetUndefined();
}

// Get a COM pointer from a window found by it's text
HRESULT GetAccessibleObject(const wchar_t* pszWindowText, CComPtr<IUnknown>& spIUnknown) {
    struct ew {
        static BOOL CALLBACK ecp(HWND hWnd, LPARAM lParam) {
            wchar_t szWindowText[128];
            if (GetWindowTextW(hWnd, szWindowText, _countof(szWindowText))) {
                ewp* pparams = reinterpret_cast<ewp*>(lParam);
                if (!wcscmp(szWindowText, pparams->pszWindowText)) {
                    pparams->hWnd = hWnd;
                    return FALSE;
                }
            }
            return TRUE;
        }
        struct ewp {
            const wchar_t* pszWindowText;
            HWND hWnd;
        };
    };
    ew::ewp params{ pszWindowText, nullptr };
    EnumChildWindows(GetDesktopWindow(), ew::ecp, reinterpret_cast<LPARAM>(&params));
    if (params.hWnd == nullptr) return _HRESULT_TYPEDEF_(0x80070057L); // ERROR_INVALID_PARAMETER
    return AccessibleObjectFromWindow(params.hWnd, OBJID_NATIVEOM, IID_IUnknown,
                                      reinterpret_cast<void**>(&spIUnknown));
}

================================================
FILE: src/utils.h
================================================
//-------------------------------------------------------------------------------------------------------
// Project: NodeActiveX
// Author: Yuri Dursin
// Description: Common utilities for translation COM - NodeJS
//-------------------------------------------------------------------------------------------------------

#pragma once

//-------------------------------------------------------------------------------------------------------

#ifdef _DEBUG
#define NODE_DEBUG
#endif

#if (V8_MAJOR_VERSION > 7) || (V8_MAJOR_VERSION == 7 && V8_MINOR_VERSION >= 1)
    #define NODE_BOOL_ISOLATE
#endif

#ifdef NODE_BOOL_ISOLATE
    #define NODE_BOOL(isolate, v) v->BooleanValue(isolate)
#else
    #define NODE_BOOL(isolate, v) v->BooleanValue(isolate->GetCurrentContext()).FromMaybe(false)
#endif

#if (V8_MAJOR_VERSION >= 12)
#define NODE_INTERCEPTED
typedef PropertyCallbackInfo<Value> PropertyCallbackInfoGetter;
typedef PropertyCallbackInfo<void> PropertyCallbackInfoSetter;
#else
typedef PropertyCallbackInfo<Value> PropertyCallbackInfoGetter;
typedef PropertyCallbackInfo<Value> PropertyCallbackInfoSetter;
#endif

#ifdef NODE_DEBUG
    #define NODE_DEBUG_PREFIX "### "
    #define NODE_DEBUG_MSG(msg) { printf(NODE_DEBUG_PREFIX"%s", msg); std::cout << std::endl; }
    #define NODE_DEBUG_FMT(msg, arg) { std::cout << NODE_DEBUG_PREFIX; printf(msg, arg); std::cout << std::endl; }
    #define NODE_DEBUG_FMT2(msg, arg, arg2) { std::cout << NODE_DEBUG_PREFIX; printf(msg, arg, arg2); std::cout << std::endl; }
#else
    #define NODE_DEBUG_MSG(msg)
    #define NODE_DEBUG_FMT(msg, arg)
    #define NODE_DEBUG_FMT2(msg, arg, arg2)
#endif

inline Local<String> v8str(Isolate *isolate, const char *text) {
    Local<String> str;
    if (!text || !String::NewFromUtf8(isolate, text, NewStringType::kNormal).ToLocal(&str)) {
        str = String::Empty(isolate);
    }
    return str;
}

inline Local<String> v8str(Isolate *isolate, const uint16_t *text) {
    Local<String> str;
    if (!text || !String::NewFromTwoByte(isolate, (const uint16_t*)text, NewStringType::kNormal).ToLocal(&str)) {
        str = String::Empty(isolate);
    }
    return str;
}

inline Local<String> v8str(Isolate *isolate, const wchar_t *text) {
    return v8str(isolate, (const uint16_t *)text);
}

template <class T>
using is_holder_v2 = std::is_member_function_pointer<decltype(&T::HolderV2)>;
 
template<class T>
inline std::enable_if<!is_holder_v2<T>::value, Local<Object>>::type
v8this(const T& args) { return args.This(); }

template<class T>
inline std::enable_if<is_holder_v2<T>::value, Local<Object>>::type
v8this(const T& args) { return args.HolderV2();  }

//-------------------------------------------------------------------------------------------------------
#ifndef USE_ATL

class CComVariant : public VARIANT {
public:
    inline CComVariant() { 
        memset((VARIANT*)this, 0, sizeof(VARIANT));
    }
    inline CComVariant(const CComVariant &src) { 
        memset((VARIANT*)this, 0, sizeof(VARIANT));
        VariantCopyInd(this, &src);
    }
    inline CComVariant(const VARIANT &src) { 
        memset((VARIANT*)this, 0, sizeof(VARIANT));
        VariantCopyInd(this, &src);
    }
    inline CComVariant(LONG v) { 
        memset((VARIANT*)this, 0, sizeof(VARIANT));
        vt = VT_I4;
        lVal = v; 
    }
    inline CComVariant(LPOLESTR v) {
        memset((VARIANT*)this, 0, sizeof(VARIANT));
        vt = VT_BSTR;
        bstrVal = SysAllocString(v);
    }
    inline ~CComVariant() {
        Clear(); 
    }
    inline void Clear() {
        if (vt != VT_EMPTY) {
            VariantClear(this);
        }
    }
    inline void Detach(VARIANT *dst) {
        *dst = *this;
        vt = VT_EMPTY;
    }
    inline HRESULT CopyTo(VARIANT *dst) {
        return VariantCopy(dst, this);
    }

    inline HRESULT ChangeType(VARTYPE vtNew, const VARIANT* pSrc = NULL) {
        return VariantChangeType(this, pSrc ? pSrc : this, 0, vtNew);
    }

    inline ULONG ArrayLength() {
        if ((vt & VT_ARRAY) == 0) return 0;
        SAFEARRAY *varr = (vt & VT_BYREF) != 0 ? *pparray : parray;
        return varr ? varr->rgsabound[0].cElements : 0;
    }

    inline HRESULT ArrayGet(LONG index, CComVariant &var) {
        if ((vt & VT_ARRAY) == 0) return E_NOTIMPL;
        SAFEARRAY *varr = (vt & VT_BYREF) != 0 ? *pparray : parray;
        if (!varr) return E_FAIL;
        index += varr->rgsabound[0].lLbound;
        VARTYPE vart = vt & VT_TYPEMASK;
        HRESULT hr = SafeArrayGetElement(varr, &index, (vart == VT_VARIANT) ? (void*)&var : (void*)&var.byref);
        if (SUCCEEDED(hr) && vart != VT_VARIANT) var.vt = vart;
        return hr;
    }
    template<typename T>
    inline T* ArrayGet(ULONG index = 0) {
        return ((T*)parray->pvData) + index;
    }
    inline HRESULT ArrayCreate(VARTYPE avt, ULONG cnt) {
        Clear();
        parray = SafeArrayCreateVector(avt, 0, cnt);
        if (!parray) return E_UNEXPECTED;
        vt = VT_ARRAY | avt;
        return S_OK;
    }
    inline HRESULT ArrayResize(ULONG cnt) {
        SAFEARRAYBOUND bnds = { cnt, 0 };
        return SafeArrayRedim(parray, &bnds);
    }
};

class CComBSTR {
public:
    BSTR p;
    inline CComBSTR() : p(0) {}
    inline CComBSTR(const CComBSTR &src) : p(0) {}
    inline ~CComBSTR() { Free(); }
    inline void Attach(BSTR _p) { Free(); p = _p; }
    inline BSTR Detach() { BSTR pp = p; p = 0; return pp; }
    inline void Free() { if (p) { SysFreeString(p); p = 0; } }

    inline operator BSTR () const { return p; }
    inline BSTR* operator&() { return &p; }
    inline bool operator!() const { return (p == 0); }
    inline bool operator!=(BSTR _p) const { return !operator==(_p); }
    inline bool operator==(BSTR _p) const { return p == _p; }
    inline BSTR operator = (BSTR _p) {
        if (p != _p) Attach(_p ? SysAllocString(_p) : 0);
        return p;
    }
};

class CComException: public EXCEPINFO {
public:
    inline CComException() {
        memset((EXCEPINFO*)this, 0, sizeof(EXCEPINFO));
    }
    inline ~CComException() {
        Clear(true);
    }
    inline void Clear(bool internal = false) {
        if (bstrSource) SysFreeString(bstrSource);
        if (bstrDescription) SysFreeString(bstrDescription);
        if (bstrHelpFile) SysFreeString(bstrHelpFile);
        if (!internal) memset((EXCEPINFO*)this, 0, sizeof(EXCEPINFO));
    }
};

template <typename T = IUnknown>
class CComPtr {
public:
    T *p;
    inline CComPtr() : p(0) {}
    inline CComPtr(T *_p) : p(0) { Attach(_p); }
    inline CComPtr(const CComPtr<T> &ptr) : p(0) { if (ptr.p) Attach(ptr.p); }
    inline ~CComPtr() { Release(); }

    inline void Attach(T *_p) { Release(); p = _p; if (p) p->AddRef(); }
    inline T *Detach() { T *pp = p; p = 0; return pp; }
    inline void Release() { if (p) { p->Release(); p = 0; } }

    inline operator T*() const { return p; }
    inline T* operator->() const { return p; }
    inline T& operator*() const { return *p; }
    inline T** operator&() { return &p; }
    inline bool operator!() const { return (p == 0); }
    inline bool operator!=(T* _p) const { return !operator==(_p); }
    inline bool operator==(T* _p) const { return p == _p; }
    inline T* operator = (T* _p) {
        if (p != _p) Attach(_p);
        return p;
    }

    inline HRESULT CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter = NULL, DWORD dwClsContext = CLSCTX_ALL) {
        Release();
        return ::CoCreateInstance(rclsid, pUnkOuter, dwClsContext, __uuidof(T), (void**)&p);
    }

    inline HRESULT CoCreateInstance(LPCOLESTR szProgID, LPUNKNOWN pUnkOuter = NULL, DWORD dwClsContext = CLSCTX_ALL) {
        Release();
        CLSID clsid;
        HRESULT hr = CLSIDFromProgID(szProgID, &clsid);
        if FAILED(hr) return hr;
        return ::CoCreateInstance(clsid, pUnkOuter, dwClsContext, __uuidof(T), (void**)&p);
    }
};

#endif
//-------------------------------------------------------------------------------------------------------

Local<String> GetWin32ErrorMessage(Isolate *isolate, HRESULT hrcode, LPCOLESTR msg, LPCOLESTR msg2 = 0, LPCOLESTR desc = 0);

inline Local<Value> Win32Error(Isolate *isolate, HRESULT hrcode, LPCOLESTR id = 0, LPCOLESTR msg = 0) {
    auto err = Exception::Error(GetWin32ErrorMessage(isolate, hrcode, id, msg));
    auto obj = Local<Object>::Cast(err);
    obj->Set(isolate->GetCurrentContext(), v8str(isolate, "errno"), Integer::New(isolate, hrcode));
    return err;
}

void GetScodeString(HRESULT hr, wchar_t* buf, int bufSize);

inline Local<Value> DispError(Isolate *isolate, HRESULT hrcode, LPCOLESTR id = 0, LPCOLESTR msg = 0, EXCEPINFO *except = 0) {
    Local<Context> ctx = isolate->GetCurrentContext();
    CComBSTR desc;
    CComPtr<IErrorInfo> errinfo;

    std::wstring emsg;
    emsg.reserve(1024);
    if (except && except->scode != 0) {
        wchar_t wc[1024];
        GetScodeString(except->scode, wc, sizeof(wc) / sizeof(wc[0]));
        emsg += msg;
        emsg += L": ";
        emsg += wc;
    }
    else {
        emsg = std::wstring(msg);
    }

    HRESULT hr = GetErrorInfo(0, &errinfo);
    if (hr == S_OK) errinfo->GetDescription(&desc);
    auto err = Exception::Error(GetWin32ErrorMessage(isolate, hrcode, id, emsg.c_str(), desc));
    auto obj = Local<Object>::Cast(err);
    obj->Set(ctx, v8str(isolate, "errno"), Integer::New(isolate, hrcode));
    if (except) {
        if (except->scode != 0) obj->Set(ctx, v8str(isolate, "code"), Integer::New(isolate, except->scode));
        else if (except->wCode != 0) obj->Set(ctx, v8str(isolate, "code"), Integer::New(isolate, except->wCode));
        if (except->bstrSource != 0) obj->Set(ctx, v8str(isolate, "source"), v8str(isolate, except->bstrSource));
        if (except->bstrDescription != 0) {
            obj->Set(ctx, v8str(isolate, "message"), v8str(isolate, except->bstrDescription));
            obj->Set(ctx, v8str(isolate, "description"), v8str(isolate, except->bstrDescription));
        }
    }
    return err;
}

inline Local<Value> DispErrorNull(Isolate *isolate) {
    return Exception::TypeError(v8str(isolate, "DispNull"));
}

inline Local<Value> DispErrorInvalid(Isolate *isolate) {
    return Exception::TypeError(v8str(isolate, "DispInvalid"));
}

inline Local<Value> TypeError(Isolate *isolate, const char *msg) {
    return Exception::TypeError(v8str(isolate, msg));
}

inline Local<Value> InvalidArgumentsError(Isolate *isolate) {
    return Exception::TypeError(v8str(isolate, "Invalid arguments"));
}

inline Local<Value> Error(Isolate *isolate, const char *msg) {
    return Exception::Error(v8str(isolate, msg));
}

//-------------------------------------------------------------------------------------------------------

class NodeObject : public ObjectWrap
{
public:
    template<typename T>
    static inline T *Unwrap(Local<Object> handle) {
        if (handle.IsEmpty() || handle->InternalFieldCount() == 0) {
            return nullptr;
        }
        void *ptr = nullptr;

#if (V8_MAJOR_VERSION > 14 || (V8_MAJOR_VERSION == 14 && V8_MINOR_VERSION >= 3))
        ptr = handle->GetAlignedPointerFromInternalField(0, v8::kEmbedderDataTypeTagDefault);
#else
        ptr = handle->GetAlignedPointerFromInternalField(0);
#endif
        NodeObject *obj = static_cast<NodeObject*>(ptr);
        return static_cast<T*>(obj);
    }
};

//-------------------------------------------------------------------------------------------------------

inline HRESULT DispFind(IDispatch *disp, LPOLESTR name, DISPID *dispid) {
    LPOLESTR names[] = { name };
    return disp->GetIDsOfNames(GUID_NULL, names, 1, 0, dispid);
}

inline HRESULT DispInvoke(IDispatch *disp, DISPID dispid, UINT argcnt = 0, VARIANT *args = 0, VARIANT *ret = 0, WORD  flags = DISPATCH_METHOD, EXCEPINFO *except = 0) {
    DISPPARAMS params = { args, 0, argcnt, 0 };
    DISPID dispidNamed = DISPID_PROPERTYPUT;
    if (flags == DISPATCH_PROPERTYPUT) { // It`s a magic
        params.cNamedArgs = 1;
        params.rgdispidNamedArgs = &dispidNamed;
        if (params.rgvarg && params.rgvarg->vt == VT_DISPATCH) flags = DISPATCH_PROPERTYPUTREF;
    }
    return disp->Invoke(dispid, IID_NULL, 0, flags, &params, ret, except, 0);
}

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) {
    LPOLESTR names[] = { name };
    DISPID dispids[] = { 0 };
    HRESULT hrcode = disp->GetIDsOfNames(GUID_NULL, names, 1, 0, dispids);
    if SUCCEEDED(hrcode) hrcode = DispInvoke(disp, dispids[0], argcnt, args, ret, flags, except);
    if (dispid) *dispid = dispids[0];
    return hrcode;
}

//-------------------------------------------------------------------------------------------------------

template<typename INTTYPE>
inline INTTYPE Variant2Int(const VARIANT &v, const INTTYPE def) {
    VARTYPE vt = (v.vt & VT_TYPEMASK);
    bool by_ref = (v.vt & VT_BYREF) != 0;
    switch (vt) {
    case VT_EMPTY:
    case VT_NULL:
        return def;
    case VT_I1:
    case VT_I2:
    case VT_I4:
    case VT_INT:
        return (INTTYPE)(by_ref ? *v.plVal : v.lVal);
    case VT_UI1:
    case VT_UI2:
    case VT_UI4:
    case VT_UINT:
        return (INTTYPE)(by_ref ? *v.pulVal : v.ulVal);
    case VT_CY:
        return (INTTYPE)((by_ref ? v.pcyVal : &v.cyVal)->int64 / 10000);
    case VT_R4:
        return (INTTYPE)(by_ref ? *v.pfltVal : v.fltVal);
    case VT_R8:
        return (INTTYPE)(by_ref ? *v.pdblVal : v.dblVal);
    case VT_DATE:
        return (INTTYPE)(by_ref ? *v.pdate : v.date);
    case VT_DECIMAL: {
        LONG64 int64val;
        return SUCCEEDED(VarI8FromDec(by_ref ? v.pdecVal : &v.decVal, &int64val)) ? (INTTYPE)int64val : def; 
    }
    case VT_BOOL:
        return (v.boolVal == VARIANT_TRUE) ? 1 : 0;
    case VT_VARIANT:
        if (v.pvarVal) return Variant2Int<INTTYPE>(*v.pvarVal, def);
    }
    CComVariant dst;
    if SUCCEEDED(VariantChangeType(&dst, &v, 0, VT_INT)) {
        return (INTTYPE)dst.intVal;
    }
    return def;
}

Local<Value> Variant2Array(Isolate *isolate, const VARIANT &v);
Local<Value> Variant2Array2(Isolate *isolate, const VARIANT &v);
Local<Value> Variant2Value(Isolate *isolate, const VARIANT &v, bool allow_disp = false);
Local<Value> Variant2String(Isolate *isolate, const VARIANT &v);
void Value2Variant(Isolate *isolate, Local<Value> &val, VARIANT &var, VARTYPE vt = VT_EMPTY);
void Value2SafeArray(Isolate *isolate, Local<Value> &val, VARIANT &var, VARTYPE vt);
bool Value2Unknown(Isolate *isolate, Local<Value> &val, IUnknown **unk);
bool VariantUnkGet(VARIANT *v, IUnknown **unk);
bool VariantDispGet(VARIANT *v, IDispatch **disp);
bool UnknownDispGet(IUnknown *unk, IDispatch **disp);

//-------------------------------------------------------------------------------------------------------

inline bool v8val2bool(Isolate *isolate, const Local<Value> &v, bool def) {
    Local<Context> ctx = isolate->GetCurrentContext();
    if (v.IsEmpty()) return def;
    if (v->IsBoolean()) return NODE_BOOL(isolate, v);
    if (v->IsInt32()) return v->Int32Value(ctx).FromMaybe(def ? 1 : 0) != 0;
    if (v->IsUint32()) return v->Uint32Value(ctx).FromMaybe(def ? 1 : 0) != 0;
    return def;
}

//-------------------------------------------------------------------------------------------------------

class VarArguments {
public:
    std::vector<CComVariant> items;
    VarArguments() {}
    VarArguments(Isolate *isolate, Local<Value> value) {
        items.resize(1);
        Value2Variant(isolate, value, items[0]);
    }
    VarArguments(Isolate *isolate, const FunctionCallbackInfo<Value> &args) {
        int argcnt = args.Length();
        items.resize(argcnt);
        for (int i = 0; i < argcnt; i++) {
            auto arg = args[argcnt - i - 1];
            Value2Variant(isolate, arg, items[i]);
        }
    }
    inline bool IsDefault() {
        if (items.size() != 1) return false;
        auto &arg = items[0];
        if (arg.vt != VT_BSTR || arg.bstrVal == nullptr) return false;
        return wcscmp(arg.bstrVal, L"default") == 0;
    }
};

class NodeArguments {
public:
    std::vector<Local<Value>> items;
    NodeArguments(Isolate *isolate, DISPPARAMS *pDispParams, bool allow_disp, bool reverse_arguments = true) {
        UINT argcnt = pDispParams->cArgs;
        items.resize(argcnt);
        for (UINT i = 0; i < argcnt; i++) {
            items[i] = Variant2Value(isolate, pDispParams->rgvarg[reverse_arguments ? argcnt - i - 1 : i], allow_disp);
        }
    }
};

//-------------------------------------------------------------------------------------------------------

template<typename IBASE = IUnknown>
class UnknownImpl : public IBASE {
public:
    inline UnknownImpl() : refcnt(0) {}
    virtual ~UnknownImpl() {}

    // IUnknown interface
    virtual HRESULT __stdcall QueryInterface(REFIID qiid, void **ppvObject) {
        if ((qiid == IID_IUnknown) || (qiid == __uuidof(IBASE))) {
            *ppvObject = this;
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    virtual ULONG __stdcall AddRef() {
        return InterlockedIncrement(&refcnt);
    }

    virtual ULONG __stdcall Release() {
        if (InterlockedDecrement(&refcnt) != 0) return refcnt;
        delete this;
        return 0;
    }

protected:
    LONG refcnt;

};

class DispArrayImpl : public UnknownImpl<IDispatch> {
public:
    CComVariant var;
    DispArrayImpl(const VARIANT &v): var(v) {}

    // IDispatch interface
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) { *pctinfo = 0; return S_OK; }
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);
};

class DispEnumImpl : public UnknownImpl<IDispatch> {
public:
    CComPtr<IEnumVARIANT> ptr;
    DispEnumImpl() {}
    DispEnumImpl(IEnumVARIANT *p) : ptr(p) {}

    // IDispatch interface
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) { *pctinfo = 0; return S_OK; }
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);
};


// {9DCE8520-2EFE-48C0-A0DC-951B291872C0}
extern const GUID CLSID_DispObjectImpl;

// {AFBF15E5-C37C-11D2-B88E-00A0C9B471B8}
extern const IID IID_IReflect;

class DispObjectImpl : public UnknownImpl<IDispatch> {
public:
    Persistent<Object> obj;

    struct name_t { 
        DISPID dispid;
        std::wstring name;
        inline name_t(DISPID id, const std::wstring &nm): dispid(id), name(nm) {}
    };
    typedef std::shared_ptr<name_t> name_ptr;
    typedef std::map<std::wstring, name_ptr> names_t;
    typedef std::map<DISPID, name_ptr> index_t;
    DISPID dispid_next;
    names_t names;
    index_t index;
    bool reverse_arguments;
    IID connIfIID;

    inline DispObjectImpl(const Local<Object> &_obj, bool revargs = true, IID connIfIID=IID_NULL) : obj(Isolate::GetCurrent(), _obj), dispid_next(1), reverse_arguments(revargs), connIfIID(connIfIID){}
    virtual ~DispObjectImpl() { obj.Reset(); }

    // IUnknown interface
    virtual HRESULT __stdcall QueryInterface(REFIID qiid, void **ppvObject) {
        if( qiid==this->connIfIID) { return UnknownImpl<IDispatch>::QueryInterface(IID_IDispatch, ppvObject); }
        //if (qiid == this->connIfIID) { *ppvObject = this; return S_OK; }
        if (qiid == CLSID_DispObjectImpl) { *ppvObject = this; return S_OK; }
        HRESULT res = UnknownImpl<IDispatch>::QueryInterface(qiid, ppvObject);
        NODE_DEBUG_MSG("RES")
        return res;
    }

    // IDispatch interface
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) { *pctinfo = 0; return S_OK; }
    virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; }
    virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    virtual HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);
};

double FromOleDate(double);
double ToOleDate(double);

//-------------------------------------------------------------------------------------------------------

class NodeMethods : public std::map<std::wstring, Persistent<FunctionTemplate> > {
public:
    typedef std::shared_ptr<Persistent<FunctionTemplate>> item_type;
    typedef std::map<std::wstring, item_type> map_type;
    map_type items;

    bool get(Isolate* isolate, const std::wstring& name, Local<Function>* value);
    void add(Isolate* isolate, Local<FunctionTemplate>& clazz, const char* name, FunctionCallback callback);
};

//-------------------------------------------------------------------------------------------------------
// Sleep is essential to have proper WScript emulation

void WinaxSleep(const FunctionCallbackInfo<Value>& args);

//-------------------------------------------------------------------------------------------------------
HRESULT GetAccessibleObject(const wchar_t* pszWindowText, CComPtr<IUnknown>& spIUnknown);


================================================
FILE: test/ado.js
================================================
var winax = require('../activex');

var path = require('path');
const assert = require('assert');

var data_path = path.join(__dirname, '../data/');
var filename = "persons.dbf";
var provider = "Microsoft.ACE.OLEDB.12.0";
var constr = "Provider=" + provider + ";Data Source=" + data_path + ";Extended Properties=\"DBASE IV;\"";
var fso, con, rs, fields, reccnt;

describe("Scripting.FileSystemObject", function() {

    it("create", function() {
        fso = new ActiveXObject("Scripting.FileSystemObject");
    });

    it("create data folder if not exists", function() {
        if (fso) {
            if (!fso.FolderExists(data_path))
                fso.CreateFolder(data_path);
        }
    });

    it("delete DBF file if exists", function() {
        if (fso) {
            if (fso.FileExists(data_path + filename))
                fso.DeleteFile(data_path + filename);
        }
    });

});

describe("ADODB.Connection", function() {

    it("create and open", function() {
        this.timeout(5000);
        con = new ActiveXObject("ADODB.Connection");
        con.Open(constr, "", "");
        this.test.title += ': ver=' + con.Version;
    });

    it("create and fill table", function() {
        if (con) {
            con.Execute("create Table " + filename + " (Name char(50), City char(50), Phone char(20), Zip decimal(5))");
            con.Execute("insert into " + filename + " values('John', 'London','123-45-67','14589')");
            con.Execute("insert into " + filename + " values('Andrew', 'Paris','333-44-55','38215')");
            con.Execute("insert into " + filename + " values('Romeo', 'Rom','222-33-44','54323')");
            reccnt = 3;
        }
    });

    it("select records from table", function() {
        if (con) {
            var rs = con.Execute("Select * from " + filename);
            var fields = rs.Fields;
        }
    });

    it("loop by records", function() {
        if (rs && fields) {
            var cnt = 0;
            rs.MoveFirst();
            while (!rs.EOF) {
                cnt++;
                var name = fields("Name").Value;
                var town = fields["City"].value;
                var phone = fields[2].value;
                var zip = fields[3].value;
                rs.MoveNext();
            }
            assert.equal(cnt, reccnt);
        }
    });

});

describe("Release objects", function() {

    it("try call", function() {
        if (con) try { this.test.title += ': SUCCESS (' + con.Version + ')'; }
            catch (e) { this.test.title += ': FAILED (' + e.message + ')'; }
    });

    it("release", function() {
        this.test.title += ': ' + winax.release(fso, con, rs, fields);
    });

    it("double release", function() {
        this.test.title += ': ' + winax.release(fso, con, rs, fields);
    });

    if (typeof global.gc === 'function') {
        global.gc();
        const mem_usage = process.memoryUsage().heapUsed / 1024;
        it("check memory", function() {
            global.gc();
            const mem = process.memoryUsage().heapUsed / 1024;
            if (mem > mem_usage) throw new Error(`used memory increased from ${mem_usage.toFixed(2)}Kb to ${mem.toFixed(2)}Kb`);
        });
    }
});

================================================
FILE: test/excel.js
================================================
require('../activex');

var path = require('path');
const assert = require('assert');

var template_filename = path.join(__dirname, '../data/test.xltm');
var excel, wbk;

var test_value = 'value';
var test_value2 = 'value2';
var test_value3 = 'value3';
var test_func_arg = 10;
var com_obj, js_obj = {
    text: test_value,
    obj: { params: test_value },
    arr: [test_value, test_value, test_value],
    func: function(v) { return v * 2; },
    func2: function(obj) { return obj.text; }
};

describe("COM from JS object", function() {

    it("create", function() {
        com_obj = new ActiveXObject(js_obj);
    });

    it("read simple property", function() {
        if (com_obj) assert.equal(com_obj.text, js_obj.text);
    });

    it("read object property", function() {
        if (com_obj) assert.equal(com_obj.obj.params, js_obj.obj.params);
    });

    it("read array property", function() {
        if (com_obj) {
            assert.equal(com_obj.arr.length, js_obj.arr.length);
            assert.equal(com_obj.arr[0], js_obj.arr[0]);
        }
    });

    it("change simple property", function() {
        if (com_obj) {
            com_obj.text = test_value2;
            assert.equal(com_obj.text, test_value2);
            assert.equal(js_obj.text, test_value2);
        }
    });

    it("change object property", function() {
        if (com_obj) {
            com_obj.obj.params = test_value2;
            assert.equal(com_obj.obj.params, test_value2);
            assert.equal(js_obj.obj.params, test_value2);
        }
    });

    it("change array property", function() {
        if (com_obj) {
            com_obj.arr[0] = test_value2;
            assert.equal(com_obj.arr[0], test_value2);
            assert.equal(js_obj.arr[0], test_value2);
        }
    });

    it("call method", function() {
        if (com_obj) assert.equal(com_obj.func(test_func_arg), js_obj.func(test_func_arg));
    });

    it("call method with object argument", function() {
        if (com_obj) assert.equal(com_obj.func2(com_obj), js_obj.text);
    });
    
    it("COM error message", function() {
        var fso = new ActiveXObject("Scripting.FileSystemObject"); 
        try {
            fso.DeleteFile("c:\\noexist.txt");
        } catch(e) {
            // assert.equal(e.message, "DispInvoke: DeleteFile: CTL_E_FILENOTFOUND Exception occurred.\r\n");
            assert(e.message.startsWith("DispInvoke: DeleteFile: CTL_E_FILENOTFOUND"));
        }
    });
});

describe("Excel with JS object", function() {

    it("create", function() {
        this.timeout(10000);
        excel = new ActiveXObject("Excel.Application", { activate: true });
    });

    it("create workbook from test template", function() {
        this.timeout(10000);
        if (excel) wbk = excel.Workbooks.Add(template_filename);
    });

    it("worksheet access", function() {
        var wsh1 = wbk.Worksheets.Item(1);
        var wsh2 = wbk.Worksheets.Item[1];
        assert.equal(wsh1.Name, 'Sheet1');
        assert.equal(wsh2.Name, 'Sheet1');
    });

    it("cell access", function() {
        var wsh = wbk.Worksheets.Item(1);
        wsh.Cells(1, 1).Value = 'test';
        var val = wsh.Cells(1, 1).Value;
        assert.equal(val, 'test');
    });

    it("invoke test simple property", function() {
        if (wbk && com_obj) assert.equal(test_value3, wbk.Test(com_obj, 'text', 0, test_value3));
    });

    it("invoke test object property", function() {
        if (wbk && com_obj) assert.equal(test_value3, wbk.Test(com_obj, 'obj', 0, test_value3));
    });

    it("invoke test array property", function() {
        if (wbk && com_obj) assert.equal(test_value3, wbk.Test(com_obj, 'arr', 0, test_value3));
    });

    it("invoke test method", function() {
        if (wbk && com_obj) assert.equal(js_obj.func(test_func_arg, 1), wbk.Test(com_obj, 'func', 0, test_func_arg));
    });

    it("range read, write with two dimension arrays", function() {
        if (wbk) {
            var wsh = wbk.Worksheets.Item(1);
            wsh.Range("A1:B3").Value = [["A1", "B1"], ["A2", "B2"], ["A3", "B3"]];
            const data = wsh.Range("A1:B3").Value.valueOf();
            assert(data instanceof Array);
            assert.equal(data.length, 3);
            assert(data[0] instanceof Array);
            assert.equal(data[0].length, 2);
            assert.equal(data[0][0], "A1");
            assert.equal(data[2][1], "B3");
        }
    });

    it("quit", function() {
        if (wbk) wbk.Close(false);
        if (excel) excel.Quit();
    });

    if (typeof global.gc === 'function') {
        global.gc();
        const mem_usage = process.memoryUsage().heapUsed / 1024;
        it("check memory", function() {
            global.gc();
            const mem = process.memoryUsage().heapUsed / 1024;
            if (mem > mem_usage) throw new Error(`used memory increased from ${mem_usage.toFixed(2)}Kb to ${mem.toFixed(2)}Kb`);
        });
    }
});

================================================
FILE: test/variant.js
================================================
const winax = require('../activex');

const path = require('path');
const assert = require('assert');
const x64 = process.arch.indexOf('64') >= 0;

const js_arr = ['1', 2, 3];

describe("Variants", function() {

    it("Short Array", function() {
        const arr = new winax.Variant(js_arr, 'short');
        assert.equal(arr.length, js_arr.length);
        assert.strictEqual(arr[0], 1);
    });

    it("String Array", function() {
        const arr = new winax.Variant(js_arr, 'string');
        assert.equal(arr.length, js_arr.length);
        assert.strictEqual(arr[1], '2');
    });

    it("Variant Array", function() {
        const arr = new winax.Variant(js_arr, 'variant');
        assert.equal(arr.length, js_arr.length);
        assert.strictEqual(arr[0], js_arr[0]);
        assert.strictEqual(arr[1], js_arr[1]);
    });

    it("References", function() {
        const v = new winax.Variant();
        const ref = new winax.Variant(v, 'byref');

        v.assign(1, 'string');
        assert.strictEqual(v.valueOf(), '1');
        assert.strictEqual(ref.valueOf(), '1');

        v.cast('int');
        assert.strictEqual(v.valueOf(), 1);
        assert.strictEqual(ref.valueOf(), 1);

        v.clear();
        assert.strictEqual(v.valueOf(), undefined);
        assert.strictEqual(ref.valueOf(), undefined);
    });

    it("Dispatch pointer as Uint8Array", function() {

        // create simple dispatch object
        const obj = new winax.Object({
            test: function(v) { return v * 2; }
        });

        // convert dispatch pointer to bytes array
        const varr = new winax.Variant(obj, 'uint8[]');

        // convert to javascript Uint8Array and check length
        const arr = new Uint8Array(varr.valueOf());
        assert.strictEqual(arr.length, x64 ? 8 : 4);

        // create dispatch object from array pointer and test
        const obj2 = new winax.Object(arr);
        assert.strictEqual(obj2.test(2), 4);
    });

    if (typeof global.gc === 'function') {
        global.gc();
        const mem_usage = process.memoryUsage().heapUsed / 1024;
        it("Check memory", function() {
            global.gc();
            const mem = process.memoryUsage().heapUsed / 1024;
            if (mem > mem_usage) throw new Error(`used memory increased from ${mem_usage.toFixed(2)}Kb to ${mem.toFixed(2)}Kb`);
        });
    }
});


================================================
FILE: test/wscript/README.md
================================================
# WScript JScript samples

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.

`wmi.js` https://www.activexperts.com/admin/scripts/wmi/jscript/0383/

`enumerator.js` https://gist.github.com/mlhaufe/1569247



================================================
FILE: test/wscript/arguments.js
================================================
WScript.Echo("Same count: " + (WScript.Arguments.length==WScript.Arguments.Count()) );

objArgs = WScript.Arguments
WScript.Echo(WScript.Arguments.Count());
for (i=0; i<objArgs.length; i++)
{
    WScript.Echo(objArgs(i))
}

================================================
FILE: test/wscript/enumerator.js
================================================
//Reference: <https://groups.google.com/group/comp.lang.javascript/browse_thread/thread/684ad16518c837a2/67d00aa5dbe854c2?show_docid=67d00aa5dbe854c2>
var listFileTypes = (function(){
    var fso = new ActiveXObject("Scripting.FileSystemObject");
    var shell = new ActiveXObject("WScript.Shell");

    function isKnown(file){
        var fName = file.Name;
        //the rules of capitalization are strange in windows...
        //there are rare cases where this can fail
        //for example: .HKEY_CLASSES_ROOT\.HeartsSave-ms
        var ext = fName.slice(fName.lastIndexOf(".")).toLowerCase();

        try{
            shell.RegRead("HKCR\\"+ext+"\\");
            return "Yes"
        } catch(e){
            return "No"
        }
    }

    return function(folder){
        var files = new Enumerator(fso.GetFolder(folder).Files);
        for(;!files.atEnd();files.moveNext()){
            var file = files.item();
            WScript.Echo(file.Name + "\t" + file.Type + "\t" + isKnown(file));
        }
    }
})()

listFileTypes("./")

================================================
FILE: test/wscript/mixed.js
================================================
// This test should have both node and WScript mixed

// First, we get count of files using FSO

var fso = new ActiveXObject("Scripting.FileSystemObject");

var filesCount = fso.GetFolder(".").Files.Count;

WScript.Echo("FSO Path: "+fso.GetFolder(".").Path+" Files count: "+filesCount);

// This if makes sure you still may run this file using cscript
if(WScript.Version=='NODE.WIN32')
{
    // And then use node fs
    console.log("path.resolve(.): "+require('path').resolve('.'));

    var fs = require('fs');
    var files = fs.readdirSync(".");

    var nodeFilesCount = 0;
    for(var f in files) 
    {
        if( !fs.lstatSync(files[f]).isDirectory() )
        {
            nodeFilesCount++;
        }
    }

    if(nodeFilesCount!=filesCount)
    {
        console.log("FSO files: ", filesCount, "fs files: ", nodeFilesCount);
        WScript.Quit(-5);
    } else {
        console.log("Same number of files: "+nodeFilesCount);
    }
}


================================================
FILE: test/wscript/nodewscript_test_cli.cmd
================================================
@ECHO off
SETLOCAL
CALL :find_dp0

IF EXIST "%dp0%\node.exe" (
  SET "_prog=%dp0%\node.exe"
) ELSE (
  SET "_prog=node"
  SET PATHEXT=%PATHEXT:;.JS;=;%
)

"%_prog%"  "%dp0%\..\..\NodeWScript.js" %*
ENDLOCAL
EXIT /b %errorlevel%
:find_dp0
SET dp0=%~dp0
EXIT /b


================================================
FILE: test/wscript/wmi.js
================================================
  var wbemFlagReturnImmediately = 0x10;
var wbemFlagForwardOnly = 0x20;

var arrComputers = new Array(".");
for (i = 0; i < arrComputers.length; i++) {
   WScript.Echo();
   WScript.Echo("==========================================");
   WScript.Echo("Computer: " + arrComputers[i]);
   WScript.Echo("==========================================");

   var objWMIService = GetObject("winmgmts:\\\\" + arrComputers[i] + "\\root\\CIMV2");
   var colItems = objWMIService.ExecQuery("SELECT * FROM Win32_ComputerSystem", "WQL",
                                          wbemFlagReturnImmediately | wbemFlagForwardOnly);

   var enumItems = new Enumerator(colItems);
   for (; !enumItems.atEnd(); enumItems.moveNext()) {
      var objItem = enumItems.item();

      WScript.Echo("AdminPasswordStatus: " + objItem.AdminPasswordStatus);
      WScript.Echo("AutomaticResetBootOption: " + objItem.AutomaticResetBootOption);
      WScript.Echo("AutomaticResetCapability: " + objItem.AutomaticResetCapability);
      WScript.Echo("BootOptionOnLimit: " + objItem.BootOptionOnLimit);
      WScript.Echo("BootOptionOnWatchDog: " + objItem.BootOptionOnWatchDog);
      WScript.Echo("BootROMSupported: " + objItem.BootROMSupported);
      WScript.Echo("BootupState: " + objItem.BootupState);
      WScript.Echo("Caption: " + objItem.Caption);
      WScript.Echo("ChassisBootupState: " + objItem.ChassisBootupState);
      WScript.Echo("CreationClassName: " + objItem.CreationClassName);
      WScript.Echo("CurrentTimeZone: " + objItem.CurrentTimeZone);
      WScript.Echo("DaylightInEffect: " + objItem.DaylightInEffect);
      WScript.Echo("Description: " + objItem.Description);
      WScript.Echo("DNSHostName: " + objItem.DNSHostName);
      WScript.Echo("Domain: " + objItem.Domain);
      WScript.Echo("DomainRole: " + objItem.DomainRole);
      WScript.Echo("EnableDaylightSavingsTime: " + objItem.EnableDaylightSavingsTime);
      WScript.Echo("FrontPanelResetStatus: " + objItem.FrontPanelResetStatus);
      WScript.Echo("InfraredSupported: " + objItem.InfraredSupported);
      try { WScript.Echo("InitialLoadInfo: " + (objItem.InitialLoadInfo.toArray()).join(",")); }
         catch(e) { WScript.Echo("InitialLoadInfo: null"); }
      WScript.Echo("InstallDate: " + WMIDateStringToDate(""+objItem.InstallDate));
      WScript.Echo("KeyboardPasswordStatus: " + objItem.KeyboardPasswordStatus);
      WScript.Echo("LastLoadInfo: " + objItem.LastLoadInfo);
      WScript.Echo("Manufacturer: " + objItem.Manufacturer);
      WScript.Echo("Model: " + objItem.Model);
      WScript.Echo("Name: " + objItem.Name);
      WScript.Echo("NameFormat: " + objItem.NameFormat);
      WScript.Echo("NetworkServerModeEnabled: " + objItem.NetworkServerModeEnabled);
      WScript.Echo("NumberOfProcessors: " + objItem.NumberOfProcessors);
      try { WScript.Echo("OEMLogoBitmap: " + (objItem.OEMLogoBitmap.toArray()).join(",")); }
         catch(e) { WScript.Echo("OEMLogoBitmap: null"); }
      try { WScript.Echo("OEMStringArray: " + (objItem.OEMStringArray.toArray()).join(",")); }
         catch(e) { WScript.Echo("OEMStringArray: null"); }
      WScript.Echo("PartOfDomain: " + objItem.PartOfDomain);
      WScript.Echo("PauseAfterReset: " + objItem.PauseAfterReset);
      try { WScript.Echo("PowerManagementCapabilities: " + (objItem.PowerManagementCapabilities.toArray()).join(",")); }
         catch(e) { WScript.Echo("PowerManagementCapabilities: null"); }
      WScript.Echo("PowerManagementSupported: " + objItem.PowerManagementSupported);
      WScript.Echo("PowerOnPasswordStatus: " + objItem.PowerOnPasswordStatus);
      WScript.Echo("PowerState: " + objItem.PowerState);
      WScript.Echo("PowerSupplyState: " + objItem.PowerSupplyState);
      WScript.Echo("PrimaryOwnerContact: " + objItem.PrimaryOwnerContact);
      WScript.Echo("PrimaryOwnerName: " + objItem.PrimaryOwnerName);
      WScript.Echo("ResetCapability: " + objItem.ResetCapability);
      WScript.Echo("ResetCount: " + objItem.ResetCount);
      WScript.Echo("ResetLimit: " + objItem.ResetLimit);
      try { WScript.Echo("Roles: " + (objItem.Roles.toArray()).join(",")); }
         catch(e) { WScript.Echo("Roles: null"); }
      WScript.Echo("Status: " + objItem.Status);
      try { WScript.Echo("SupportContactDescription: " + (objItem.SupportContactDescription.toArray()).join(",")); }
         catch(e) { WScript.Echo("SupportContactDescription: null"); }
      WScript.Echo("SystemStartupDelay: " + objItem.SystemStartupDelay);
      try { WScript.Echo("SystemStartupOptions: " + (objItem.SystemStartupOptions.toArray()).join(",")); }
         catch(e) { WScript.Echo("SystemStartupOptions: null"); }
      WScript.Echo("SystemStartupSetting: " + objItem.SystemStartupSetting);
      WScript.Echo("SystemType: " + objItem.SystemType);
      WScript.Echo("ThermalState: " + objItem.ThermalState);
      WScript.Echo("TotalPhysicalMemory: " + objItem.TotalPhysicalMemory);
      WScript.Echo("UserName: " + objItem.UserName);
      WScript.Echo("WakeUpType: " + objItem.WakeUpType);
      WScript.Echo("Workgroup: " + objItem.Workgroup);
   }
}

function WMIDateStringToDate(dtmDate)
{
   if (dtmDate == null || dtmDate=="null")
   {
      return "null date";
   }
   var strDateTime;
   if (dtmDate.substr(4, 1) == 0)
   {
      strDateTime = dtmDate.substr(5, 1) + "/";
   }
   else
   {
      strDateTime = dtmDate.substr(4, 2) + "/";
   }
   if (dtmDate.substr(6, 1) == 0)
   {
      strDateTime = strDateTime + dtmDate.substr(7, 1) + "/";
   }
   else
   {
      strDateTime = strDateTime + dtmDate.substr(6, 2) + "/";
   }
   strDateTime = strDateTime + dtmDate.substr(0, 4) + " " +
   dtmDate.substr(8, 2) + ":" +
   dtmDate.substr(10, 2) + ":" +
   dtmDate.substr(12, 2);
   return(strDateTime);
}

================================================
FILE: test/wscript.js
================================================
const assert = require('assert');

function r(name) {
    const {execSync} = require('child_process')
    execSync(__dirname+'/wscript/nodewscript_test_cli.cmd '+__dirname+'/wscript/'+name+'.js', {timeout: 10000})
}

describe("Execute WScript Tests", function() {

    it('WScript Arguments', function (done) {
        r('arguments');
        done()
    })

    it('WScript Enumerator', function (done) {
        r('enumerator');
        done()
    })


    it('WScript Mixed', function (done) {
        r('mixed');
        done()
    })

    it('WScript WMI', function (done) {
        r('wmi');
        done()
    })


});

Download .txt
gitextract_4__tj8qf/

├── .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.json
├── src/
│   ├── disp.cpp
│   ├── disp.h
│   ├── main.cpp
│   ├── stdafx.h
│   ├── utils.cpp
│   └── utils.h
└── test/
    ├── ado.js
    ├── excel.js
    ├── variant.js
    ├── wscript/
    │   ├── README.md
    │   ├── arguments.js
    │   ├── enumerator.js
    │   ├── mixed.js
    │   ├── nodewscript_test_cli.cmd
    │   └── wmi.js
    └── wscript.js
Download .txt
SYMBOL INDEX (103 symbols across 10 files)

FILE: NodeWScript.js
  function ActiveXObjectCreate (line 41) | function ActiveXObjectCreate(progId, pfx)
  method construct (line 108) | construct(target,args)
  method Write (line 141) | Write(txt) { console.log(txt); }
  method ReadLine (line 144) | ReadLine() {
  method Write (line 167) | Write(txt) { console.err(txt); }
  method ConnectObject (line 178) | ConnectObject(obj, pfx)
  method DisconnectObject (line 210) | DisconnectObject(obj)
  method Quit (line 229) | Quit (exitCode) {
  method Sleep (line 239) | Sleep (ms) {
  method __InitArgs (line 259) | __InitArgs(){
  function MessageLoop (line 338) | function MessageLoop()

FILE: index.d.ts
  class Object (line 1) | class Object {
  type ActiveXOptions (line 16) | interface ActiveXOptions extends ActiveXObjectOptions {}
  class Variant (line 18) | class Variant {
  type VariantType (line 26) | type VariantType =
  type ActiveXObjectOptions (line 44) | interface ActiveXObjectOptions {

FILE: src/disp.cpp
  function HRESULT (line 42) | HRESULT DispObject::prepare() {
  function FAILED (line 90) | FAILED(hrcode) {
  function FAILED (line 189) | FAILED(hrcode) {
  function FAILED (line 203) | FAILED(hrcode) {
  function FAILED (line 258) | FAILED(hrcode) {
  function HRESULT (line 280) | HRESULT DispObject::valueOf(Isolate *isolate, VARIANT &value, bool simpl...
  function HRESULT (line 305) | HRESULT DispObject::valueOf(Isolate *isolate, const Local<Object> &self,...
  function FAILED (line 345) | FAILED(hrcode) {
  function SUCCEEDED (line 561) | SUCCEEDED(hrcode) {
  function FAILED (line 602) | FAILED(hrcode) {
  function FAILED (line 757) | FAILED(hrcode) {
  class vtypes_t (line 836) | class vtypes_t {
    method vtypes_t (line 838) | inline vtypes_t(std::initializer_list<std::pair<std::wstring, VARTYPE>...
    method find (line 844) | inline bool find(VARTYPE vt, std::wstring &name) {
    method VARTYPE (line 850) | inline VARTYPE find(const std::wstring &name) {
  function FAILED (line 1350) | FAILED(hrcode) {
  function FAILED (line 1378) | FAILED(hrcode) {

FILE: src/disp.h
  type options_t (line 11) | enum options_t {
  function TypeInfoGetName (line 24) | inline bool TypeInfoGetName(ITypeInfo *info, DISPID dispid, BSTR *name) {
  function class (line 81) | class DispInfo {
  type std (line 99) | typedef std::shared_ptr<type_t> type_ptr;
  type std (line 100) | typedef std::map<DISPID, type_ptr> types_by_dispid_t;
  function Prepare (line 111) | void Prepare(IDispatch *disp) {
  function GetTypeInfo (line 125) | inline bool GetTypeInfo(const DISPID dispid, type_ptr &info) {
  function HRESULT (line 133) | HRESULT FindProperty(LPOLESTR name, DISPID *dispid) {
  type std (line 210) | typedef std::shared_ptr<DispInfo> DispInfoPtr;
  function class (line 212) | class DispObject: public NodeObject
  function class (line 311) | class VariantObject : public NodeObject
  function class (line 377) | class ConnectionPointObject : public NodeObject

FILE: src/main.cpp
  function NODE_MODULE_INIT (line 13) | NODE_MODULE_INIT(/*exports, module, context*/) {
  function BOOL (line 49) | BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ulReason, LPVOID lpReserve...

FILE: src/utils.cpp
  function GetScodeString (line 22) | void GetScodeString(HRESULT hr, wchar_t* buf, int bufSize)
  function GetWin32ErrorMessage (line 397) | Local<String> GetWin32ErrorMessage(Isolate *isolate, HRESULT hrcode, LPC...
  function Variant2Array (line 404) | Local<Value> Variant2Array(Isolate *isolate, const VARIANT &v) {
  function Variant2Array2 (line 424) | static Local<Value> Variant2Array2(Isolate *isolate, const VARIANT &v) {
  function Variant2Value (line 452) | Local<Value> Variant2Value(Isolate *isolate, const VARIANT &v, bool allo...
  function Variant2String (line 541) | Local<Value> Variant2String(Isolate *isolate, const VARIANT &v) {
  function Value2Variant (line 622) | void Value2Variant(Isolate *isolate, Local<Value> &val, VARIANT &var, VA...
  function Value2SafeArray (line 747) | void Value2SafeArray(Isolate *isolate, Local<Value> &val, VARIANT &var, ...
  function Value2Unknown (line 771) | bool Value2Unknown(Isolate *isolate, Local<Value> &val, IUnknown **unk) {
  function UnknownDispGet (line 779) | bool UnknownDispGet(IUnknown *unk, IDispatch **disp) {
  function VariantUnkGet (line 793) | bool VariantUnkGet(VARIANT *v, IUnknown **punk) {
  function VariantDispGet (line 807) | bool VariantDispGet(VARIANT *v, IDispatch **pdisp) {
  function HRESULT (line 831) | HRESULT STDMETHODCALLTYPE DispArrayImpl::GetIDsOfNames(REFIID riid, LPOL...
  function HRESULT (line 839) | HRESULT STDMETHODCALLTYPE DispArrayImpl::Invoke(DISPID dispIdMember, REF...
  function HRESULT (line 861) | HRESULT STDMETHODCALLTYPE DispEnumImpl::GetIDsOfNames(REFIID riid, LPOLE...
  function HRESULT (line 872) | HRESULT STDMETHODCALLTYPE DispEnumImpl::Invoke(DISPID dispIdMember, REFI...
  function HRESULT (line 922) | HRESULT STDMETHODCALLTYPE DispObjectImpl::GetIDsOfNames(REFIID riid, LPO...
  function HRESULT (line 934) | HRESULT STDMETHODCALLTYPE DispObjectImpl::Invoke(DISPID dispIdMember, RE...
  function FromOleDate (line 1043) | double FromOleDate(double oleDate) {
  function ToOleDate (line 1049) | double ToOleDate(double posixDate) {
  function DoEvents (line 1086) | void DoEvents()
  function GetTimeMs64 (line 1113) | long long GetTimeMs64()
  function WinaxSleep (line 1134) | void WinaxSleep(const FunctionCallbackInfo<Value>& args) {
  function HRESULT (line 1153) | HRESULT GetAccessibleObject(const wchar_t* pszWindowText, CComPtr<IUnkno...

FILE: src/utils.h
  function Local (line 45) | inline Local<String> v8str(Isolate *isolate, const char *text) {
  function Local (line 53) | inline Local<String> v8str(Isolate *isolate, const uint16_t *text) {
  function Local (line 61) | inline Local<String> v8str(Isolate *isolate, const wchar_t *text) {
  function class (line 79) | class CComVariant : public VARIANT {
  function Clear (line 105) | inline void Clear() {
  function Detach (line 110) | inline void Detach(VARIANT *dst) {
  function HRESULT (line 114) | inline HRESULT CopyTo(VARIANT *dst) {
  function ULONG (line 122) | inline ULONG ArrayLength() {
  function HRESULT (line 128) | inline HRESULT ArrayGet(LONG index, CComVariant &var) {
  function HRESULT (line 142) | inline HRESULT ArrayCreate(VARTYPE avt, ULONG cnt) {
  function HRESULT (line 149) | inline HRESULT ArrayResize(ULONG cnt) {
  function class (line 155) | class CComBSTR {
  function class (line 176) | class CComException: public EXCEPINFO {
  function Local (line 280) | inline Local<Value> DispErrorNull(Isolate *isolate) {
  function Local (line 284) | inline Local<Value> DispErrorInvalid(Isolate *isolate) {
  function Local (line 288) | inline Local<Value> TypeError(Isolate *isolate, const char *msg) {
  function Local (line 292) | inline Local<Value> InvalidArgumentsError(Isolate *isolate) {
  function Local (line 296) | inline Local<Value> Error(Isolate *isolate, const char *msg) {
  function class (line 302) | class NodeObject : public ObjectWrap
  function HRESULT (line 324) | inline HRESULT DispFind(IDispatch *disp, LPOLESTR name, DISPID *dispid) {
  function v8val2bool (line 406) | inline bool v8val2bool(Isolate *isolate, const Local<Value> &v, bool def) {
  function class (line 417) | class VarArguments {
  function class (line 441) | class NodeArguments {
  function virtual (line 471) | virtual ULONG __stdcall AddRef() {
  function virtual (line 475) | virtual ULONG __stdcall Release() {
  function class (line 486) | class DispArrayImpl : public UnknownImpl<IDispatch> {
  function class (line 498) | class DispEnumImpl : public UnknownImpl<IDispatch> {
  function class (line 518) | class DispObjectImpl : public UnknownImpl<IDispatch> {
  type std (line 564) | typedef std::map<std::wstring, item_type> map_type;

FILE: test/wscript.js
  function r (line 3) | function r(name) {

FILE: test/wscript/enumerator.js
  function isKnown (line 6) | function isKnown(file){

FILE: test/wscript/wmi.js
  function WMIDateStringToDate (line 84) | function WMIDateStringToDate(dtmDate)
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (199K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 829,
    "preview": "# https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-nodejs\n\nname: ci\no"
  },
  {
    "path": ".gitignore",
    "chars": 80,
    "preview": ".vscode\n.idea\nnode_modules\nbuild\nnpm-debug.log\ntest.js\n/data/PERSONS.DBF\n/debug*"
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2023 Yuri Dursin\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "NodeWScript.js",
    "chars": 10927,
    "preview": "#!/usr/bin/env node \nvar g_winax = require('./activex');\r\nvar path = require('path');\r\n\r\nif(process.argv.length<3)\r\n{\r\n "
  },
  {
    "path": "README.md",
    "chars": 11878,
    "preview": "# Name\n\n\tWindows C++ Node.JS addon, that implements COM IDispatch object wrapper, analog ActiveXObject on cscript.exe\n\n#"
  },
  {
    "path": "activex.js",
    "chars": 164,
    "preview": "var ActiveX = module.exports = require('./build/Release/node_activex.node');\n\nglobal.ActiveXObject = function(id, opt) {"
  },
  {
    "path": "binding.gyp",
    "chars": 312,
    "preview": "{\n  'targets': [\n    {\n      'target_name': 'node_activex',\n      'conditions': [\n        ['OS==\"win\"', {\n          'sou"
  },
  {
    "path": "examples/ado.js",
    "chars": 2119,
    "preview": "//-------------------------------------------------------------------------------------------------------\n// Project: no"
  },
  {
    "path": "examples/event.js",
    "chars": 1961,
    "preview": "var winax = require('..');\nvar interval = setInterval(function () {\n\twinax.peekAndDispatchMessages(); // allows ActiveX "
  },
  {
    "path": "examples/wbem.js",
    "chars": 494,
    "preview": "var ActiveX = require('../activex');\nvar conn = new ActiveX.Object('WbemScripting.SWbemLocator');\nvar svr = conn.Connect"
  },
  {
    "path": "include/node_activex.h",
    "chars": 261,
    "preview": "#pragma once\n\n// Include all the relevant definitions for use of the library code in a dependency.\n// Note that this doe"
  },
  {
    "path": "index.d.ts",
    "chars": 1500,
    "preview": "export class Object {\n  constructor(id: string, options?: ActiveXObjectOptions);\n  \n  // Define properties typically fou"
  },
  {
    "path": "index.js",
    "chars": 38,
    "preview": "module.exports = require('./activex');"
  },
  {
    "path": "lib_binding.gyp",
    "chars": 361,
    "preview": "{\n  'targets': [\n    {\n      'target_name': 'lib_node_activex',\n      'type': 'static_library',\n      'sources': [\n     "
  },
  {
    "path": "package.json",
    "chars": 984,
    "preview": "{\n  \"name\": \"winax\",\n  \"version\": \"3.6.9\",\n  \"description\": \"Windows COM bindings\",\n  \"homepage\": \"https://github.com/du"
  },
  {
    "path": "src/disp.cpp",
    "chars": 49591,
    "preview": "//-------------------------------------------------------------------------------------------------------\n// Project: No"
  },
  {
    "path": "src/disp.h",
    "chars": 16591,
    "preview": "//-------------------------------------------------------------------------------------------------------\n// Project: No"
  },
  {
    "path": "src/main.cpp",
    "chars": 2427,
    "preview": "//-------------------------------------------------------------------------------------------------------\n// Project: no"
  },
  {
    "path": "src/stdafx.h",
    "chars": 933,
    "preview": "#pragma once\n\n// To remove conflicts with recent v8 code std::numeric_limits<int>::max()\n#ifndef NOMINMAX\n#define NOMINM"
  },
  {
    "path": "src/utils.cpp",
    "chars": 47141,
    "preview": "//-------------------------------------------------------------------------------------------------------\n// Project: No"
  },
  {
    "path": "src/utils.h",
    "chars": 21803,
    "preview": "//-------------------------------------------------------------------------------------------------------\n// Project: No"
  },
  {
    "path": "test/ado.js",
    "chars": 3221,
    "preview": "var winax = require('../activex');\n\nvar path = require('path');\nconst assert = require('assert');\n\nvar data_path = path."
  },
  {
    "path": "test/excel.js",
    "chars": 4969,
    "preview": "require('../activex');\n\nvar path = require('path');\nconst assert = require('assert');\n\nvar template_filename = path.join"
  },
  {
    "path": "test/variant.js",
    "chars": 2377,
    "preview": "const winax = require('../activex');\n\nconst path = require('path');\nconst assert = require('assert');\nconst x64 = proces"
  },
  {
    "path": "test/wscript/README.md",
    "chars": 346,
    "preview": "# WScript JScript samples\n\nHere we have a number of .js files taken from available tutorials and samples just as is. Our"
  },
  {
    "path": "test/wscript/arguments.js",
    "chars": 222,
    "preview": "WScript.Echo(\"Same count: \" + (WScript.Arguments.length==WScript.Arguments.Count()) );\n\nobjArgs = WScript.Arguments\nWScr"
  },
  {
    "path": "test/wscript/enumerator.js",
    "chars": 1044,
    "preview": "//Reference: <https://groups.google.com/group/comp.lang.javascript/browse_thread/thread/684ad16518c837a2/67d00aa5dbe854c"
  },
  {
    "path": "test/wscript/mixed.js",
    "chars": 946,
    "preview": "// This test should have both node and WScript mixed\n\n// First, we get count of files using FSO\n\nvar fso = new ActiveXOb"
  },
  {
    "path": "test/wscript/nodewscript_test_cli.cmd",
    "chars": 260,
    "preview": "@ECHO off\nSETLOCAL\nCALL :find_dp0\n\nIF EXIST \"%dp0%\\node.exe\" (\n  SET \"_prog=%dp0%\\node.exe\"\n) ELSE (\n  SET \"_prog=node\"\n"
  },
  {
    "path": "test/wscript/wmi.js",
    "chars": 5773,
    "preview": "  var wbemFlagReturnImmediately = 0x10;\nvar wbemFlagForwardOnly = 0x20;\n\nvar arrComputers = new Array(\".\");\nfor (i = 0; "
  },
  {
    "path": "test/wscript.js",
    "chars": 626,
    "preview": "const assert = require('assert');\n\nfunction r(name) {\n    const {execSync} = require('child_process')\n    execSync(__dir"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the durs/node-activex GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (186.8 KB), approximately 50.5k tokens, and a symbol index with 103 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!