//-------------------------------------------------------------------------------------------------------
// 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));
}