/* This file is the compacted single file version of the DispHelper COM helper library.
 * DispHelper allows you to call COM objects with an extremely simple printf style syntax.
 * DispHelper can be used from C++ or even plain C. It works with most Windows compilers
 * including Dev-CPP, Visual C++ and LCC-WIN32. Including DispHelper in your project
 * couldn't be simpler as it is available in a compacted single file version (this file).
 *
 * Included with DispHelper are over 20 samples that demonstrate using COM objects
 * including ADO, CDO, Outlook, Eudora, Excel, Word, Internet Explorer, MSHTML,
 * PocketSoap, Word Perfect, MS Agent, SAPI, MSXML, WIA, dexplorer and WMI.
 *
 * DispHelper is free open source software provided under the BSD license.
 *
 * Find out more, browse the readable version of the source code
 * and download DispHelper at:
 * http://sourceforge.net/projects/disphelper/
 * http://disphelper.sourceforge.net/
 */


/* To use DispHelper in your project, include this file(disphelper.c) and the
 * header (disphelper.h). For Visual C++, Borland C++ and LCC-Win32 import
 * libraries are included via pragma directives. For other compilers you may
 * need to add ole32, oleaut32 and uuid. To do this in Dev-CPP add
 * "-lole32 -loleaut32 -luuid" to the linker box under Project->Project Options->Parameters.
 */


/* If you are using Dev-CPP and get errors when compiling this file:
 * Make sure this file is set to compile as C and not C++ under
 * Project->Project Options->Files.
 */


#define DISPHELPER_INTERNAL_BUILD
#include "disphelper.h"
#include <math.h>
#include <assert.h>

/* ----- convert.h ----- */

HRESULT ConvertFileTimeToVariantTime(FILETIME * pft, DATE * pDate);
HRESULT ConvertVariantTimeToFileTime(DATE date, FILETIME * pft);

HRESULT ConvertVariantTimeToSystemTime(DATE date, SYSTEMTIME * pSystemTime);
HRESULT ConvertSystemTimeToVariantTime(SYSTEMTIME * pSystemTime, DATE * pDate);

HRESULT ConvertTimeTToVariantTime(time_t timeT, DATE * pDate);
HRESULT ConvertVariantTimeToTimeT(DATE date, time_t * pTimeT);

HRESULT ConvertAnsiStrToBStr(LPCSTR szAnsiIn, BSTR * lpBstrOut);
HRESULT ConvertBStrToAnsiStr(BSTR bstrIn, LPSTR * lpszOut);

/* ----- dh_create.c ----- */

HRESULT dhCreateObjectEx(LPCOLESTR szProgId, REFIID riid, DWORD dwClsContext,
			    COSERVERINFO * pServerInfo, void ** ppv)
{
	CLSID clsid;
	HRESULT hr;
	IClassFactory * pCf = NULL;

	DH_ENTER(L"CreateObjectEx");

	if (!szProgId || !riid || !ppv) return DH_EXIT(E_INVALIDARG, szProgId);

	if (L'{' == szProgId[0])
		hr = CLSIDFromString((LPOLESTR) szProgId, &clsid);
	else
		hr = CLSIDFromProgID(szProgId, &clsid);

	if (SUCCEEDED(hr)) hr = CoGetClassObject(&clsid, dwClsContext, pServerInfo, &IID_IClassFactory, (void **) &pCf);
	if (SUCCEEDED(hr)) hr = pCf->lpVtbl->CreateInstance(pCf, NULL, riid, ppv);

	if (pCf) pCf->lpVtbl->Release(pCf);

	return DH_EXIT(hr, szProgId);
}

HRESULT dhGetObjectEx(LPCOLESTR szPathName, LPCOLESTR szProgId, REFIID riid,
		         DWORD dwClsContext, LPVOID lpvReserved, void ** ppv)
{
	HRESULT hr;

	DH_ENTER(L"GetObjectEx");

	if ((!szProgId && !szPathName) || !riid || !ppv || lpvReserved) return DH_EXIT(E_INVALIDARG, szProgId);

	if (szPathName)
	{

		if (!szProgId)
		{
			hr = CoGetObject(szPathName, NULL, riid, ppv);
		}
		else
		{
			IPersistFile * ppf = NULL;

			hr = dhCreateObjectEx(szProgId, &IID_IPersistFile, dwClsContext, NULL, (void **) &ppf);

			if (SUCCEEDED(hr)) hr = ppf->lpVtbl->Load(ppf, szPathName, 0);
			if (SUCCEEDED(hr)) hr = ppf->lpVtbl->QueryInterface(ppf, riid, ppv);

			if (ppf) ppf->lpVtbl->Release(ppf);
		}
	}
	else
	{

		CLSID clsid;
		IUnknown * pUnk = NULL;

		if (L'{' == szProgId[0])
			hr = CLSIDFromString((LPOLESTR) szProgId, &clsid);
		else
			hr = CLSIDFromProgID(szProgId, &clsid);

		if (SUCCEEDED(hr)) hr = GetActiveObject(&clsid, NULL, &pUnk);
		if (SUCCEEDED(hr)) hr = pUnk->lpVtbl->QueryInterface(pUnk, riid, ppv);

		if (pUnk) pUnk->lpVtbl->Release(pUnk);
	}

	return DH_EXIT(hr, szProgId);
}

HRESULT dhCreateObject(LPCOLESTR szProgId, LPCWSTR szMachine, IDispatch ** ppDisp)
{
	COSERVERINFO si = { 0 };

	DH_ENTER(L"CreateObject");

	si.pwszName = (LPWSTR) szMachine;

	return DH_EXIT(dhCreateObjectEx(szProgId, &IID_IDispatch,
			  szMachine ? CLSCTX_REMOTE_SERVER : CLSCTX_LOCAL_SERVER|CLSCTX_INPROC_SERVER,
			  szMachine ? &si : NULL, (void **) ppDisp), szProgId);
}

HRESULT dhGetObject(LPCOLESTR szPathName, LPCOLESTR szProgId, IDispatch ** ppDisp)
{
	DH_ENTER(L"GetObject");

	return DH_EXIT(dhGetObjectEx(szPathName, szProgId, &IID_IDispatch,
			  CLSCTX_LOCAL_SERVER|CLSCTX_INPROC_SERVER, NULL, (void **) ppDisp), szProgId);
}

HRESULT dhCallMethod(IDispatch * pDisp, LPCOLESTR szMember, ... )
{
	HRESULT hr;
	va_list marker;

	DH_ENTER(L"CallMethod");

	va_start(marker, szMember);

	hr = dhCallMethodV(pDisp, szMember, &marker);

	va_end(marker);

	return DH_EXIT(hr, szMember);
}

HRESULT dhPutValue(IDispatch * pDisp, LPCOLESTR szMember, ...)
{
	HRESULT hr;
	va_list marker;

	DH_ENTER(L"PutValue");

	va_start(marker, szMember);

	hr = dhPutValueV(pDisp, szMember, &marker);

	va_end(marker);

	return DH_EXIT(hr, szMember);
}

HRESULT dhPutRef(IDispatch * pDisp, LPCOLESTR szMember, ...)
{
	HRESULT hr;
	va_list marker;

	DH_ENTER(L"PutRef");

	va_start(marker, szMember);

	hr = dhPutRefV(pDisp, szMember, &marker);

	va_end(marker);

	return DH_EXIT(hr, szMember);
}

HRESULT dhGetValue(LPCWSTR szIdentifier, void * pResult, IDispatch * pDisp, LPCOLESTR szMember, ...)
{
	HRESULT hr;
	va_list marker;

	DH_ENTER(L"GetValue");

	va_start(marker, szMember);

	hr = dhGetValueV(szIdentifier, pResult, pDisp, szMember, &marker);

	va_end(marker);

	return DH_EXIT(hr, szMember);
}

HRESULT dhInvoke(int invokeType, VARTYPE returnType, VARIANT * pvResult, IDispatch * pDisp, LPCOLESTR szMember, ...)
{
	HRESULT hr;
	va_list marker;

	DH_ENTER(L"Invoke");

	va_start(marker, szMember);

	hr = dhInvokeV(invokeType, returnType, pvResult, pDisp, szMember, &marker);

	va_end(marker);

	return DH_EXIT(hr, szMember);
}

/* ----- dh_core.c ----- */

BOOL dh_g_bIsUnicodeMode;

HRESULT dhInvokeArray(int invokeType, VARIANT * pvResult, UINT cArgs,
                         IDispatch * pDisp, LPCOLESTR szMember, VARIANT * pArgs)
{
	DISPPARAMS dp       = { 0 };
	EXCEPINFO excep     = { 0 };
	DISPID dispidNamed  = DISPID_PROPERTYPUT;
	DISPID dispID;
	UINT uiArgErr;
	HRESULT hr;

	DH_ENTER(L"InvokeArray");

	if(!pDisp || !szMember || (cArgs != 0 && !pArgs)) return DH_EXIT(E_INVALIDARG, szMember);

	hr = pDisp->lpVtbl->GetIDsOfNames(pDisp, &IID_NULL, (LPOLESTR *) &szMember, 1, LOCALE_USER_DEFAULT, &dispID);

	if(FAILED(hr)) return DH_EXITEX(hr, TRUE, szMember, szMember, NULL, 0);

	if (pvResult != NULL) VariantInit(pvResult);

	dp.cArgs  = cArgs;
	dp.rgvarg = pArgs;

	if(invokeType & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
	{
		dp.cNamedArgs = 1;
		dp.rgdispidNamedArgs = &dispidNamed;
	}

	hr = pDisp->lpVtbl->Invoke(pDisp, dispID, &IID_NULL, LOCALE_USER_DEFAULT, (WORD) invokeType, &dp, pvResult, &excep, &uiArgErr);

	return DH_EXITEX(hr, TRUE, szMember, szMember, &excep, uiArgErr);
}

HRESULT dhCallMethodV(IDispatch * pDisp, LPCOLESTR szMember, va_list * marker)
{
	DH_ENTER(L"CallMethodV");

	return DH_EXIT(dhInvokeV(DISPATCH_METHOD, VT_EMPTY, NULL, pDisp, szMember, marker), szMember);
}

HRESULT dhPutValueV(IDispatch * pDisp, LPCOLESTR szMember, va_list * marker)
{
	DH_ENTER(L"PutValueV");

	return DH_EXIT(dhInvokeV(DISPATCH_PROPERTYPUT, VT_EMPTY, NULL, pDisp, szMember, marker), szMember);
}

HRESULT dhPutRefV(IDispatch * pDisp, LPCOLESTR szMember, va_list * marker)
{
	DH_ENTER(L"PutRefV");

	return DH_EXIT(dhInvokeV(DISPATCH_PROPERTYPUTREF, VT_EMPTY, NULL, pDisp, szMember, marker), szMember);
}

HRESULT dhGetValueV(LPCWSTR szIdentifier, void * pResult, IDispatch * pDisp, LPCOLESTR szMember, va_list * marker)
{
	VARIANT vtResult;
	VARTYPE returnType;
	HRESULT hr;

	DH_ENTER(L"GetValueV");

	if (!pResult || !szIdentifier) return DH_EXIT(E_INVALIDARG, szMember);

	if (*szIdentifier == L'%') szIdentifier++;

	switch(*szIdentifier)
	{
		case L'd': returnType = VT_I4;       break;
		case L'u': returnType = VT_UI4;      break;
		case L'e': returnType = VT_R8;       break;
		case L'b': returnType = VT_BOOL;     break;
		case L'v': returnType = VT_EMPTY;    break;
		case L'B': returnType = VT_BSTR;     break;
		case L'S': returnType = VT_BSTR;     break;
		case L's': returnType = VT_BSTR;     break;
		case L'T': returnType = VT_BSTR;     break;
		case L'o': returnType = VT_DISPATCH; break;
		case L'O': returnType = VT_UNKNOWN;  break;
		case L't': returnType = VT_DATE;     break;
		case L'W': returnType = VT_DATE;     break;
		case L'f': returnType = VT_DATE;     break;
		case L'D': returnType = VT_DATE;     break;
#ifndef _WIN64
		case L'p': returnType = VT_I4;       break;
#else
		case L'p': returnType = VT_I8;       break;
#endif
		default:
			DEBUG_NOTIFY_INVALID_IDENTIFIER(*szIdentifier);
			return DH_EXIT(E_INVALIDARG, szMember);
	}

	hr = dhInvokeV(DISPATCH_PROPERTYGET|DISPATCH_METHOD, returnType, &vtResult, pDisp, szMember, marker);
	if (FAILED(hr)) return DH_EXIT(hr, szMember);

	switch(*szIdentifier)
	{
		case L'd':
			*((LONG *) pResult) = V_I4(&vtResult);
			break;

		case L'u':
			*((ULONG *) pResult) = V_UI4(&vtResult);
			break;

		case L'e':
			*((DOUBLE *) pResult) = V_R8(&vtResult);
			break;

		case L'b':
			*((BOOL *) pResult) = V_BOOL(&vtResult);
			break;

		case L'v':
			*((VARIANT *) pResult) = vtResult;
			break;

		case L'B':
			*((BSTR *) pResult) = V_BSTR(&vtResult);
			break;

		case L'S':
			*((LPWSTR *) pResult) = V_BSTR(&vtResult);
			break;

		case L's':
			hr = ConvertBStrToAnsiStr(V_BSTR(&vtResult), (LPSTR *) pResult);
			SysFreeString(V_BSTR(&vtResult));
			break;

		case L'T':
			if (dh_g_bIsUnicodeMode)
			{
				*((LPWSTR *) pResult) = V_BSTR(&vtResult);
			}
			else
			{
				hr = ConvertBStrToAnsiStr(V_BSTR(&vtResult), (LPSTR *) pResult);
				SysFreeString(V_BSTR(&vtResult));
			}
			break;

		case L'o':
			*((IDispatch **) pResult) = V_DISPATCH(&vtResult);
			if (V_DISPATCH(&vtResult) == NULL) hr = E_NOINTERFACE;
			break;

		case L'O':
			*((IUnknown **) pResult) = V_UNKNOWN(&vtResult);
			if (V_UNKNOWN(&vtResult) == NULL) hr = E_NOINTERFACE;
			break;

		case L't':
			hr = ConvertVariantTimeToTimeT(V_DATE(&vtResult), (time_t *) pResult);
			break;

		case L'W':
			hr = ConvertVariantTimeToSystemTime(V_DATE(&vtResult), (SYSTEMTIME *) pResult);
			break;

		case L'f':
			hr = ConvertVariantTimeToFileTime(V_DATE(&vtResult), (FILETIME *) pResult);
			break;

		case L'D':
			*((DATE *) pResult) = V_DATE(&vtResult);
			break;

		case L'p':
#ifndef _WIN64
			*((LPVOID *) pResult) = (LPVOID) V_I4(&vtResult);
#else
			*((LPVOID *) pResult) = (LPVOID) V_I8(&vtResult);
#endif
			break;
	}

	return DH_EXIT(hr, szMember);
}

/* ----- dh_invoke.c ----- */

static HRESULT TraverseSubObjects(IDispatch ** ppDisp, LPWSTR * lpszMember, va_list * marker);
static HRESULT CreateArgumentArray(LPWSTR szTemp, VARIANT * pArgs, BOOL * pbFreeList, UINT * pcArgs, va_list * marker);
static HRESULT InternalInvokeV(int invokeType, VARTYPE returnType, VARIANT * pvResult, IDispatch * pDisp, LPOLESTR szMember, va_list * marker);
static HRESULT ExtractArgument(VARIANT * pvArg, WCHAR chIdentifier, BOOL * pbFreeArg, va_list * marker);

HRESULT dhInvokeV(int invokeType, VARTYPE returnType, VARIANT * pvResult,
                     IDispatch * pDisp, LPCOLESTR szMember, va_list * marker)
{
	WCHAR szCopy[DH_MAX_MEMBER];
	LPWSTR szTemp                  = szCopy;
	SIZE_T cchDest                 = ARRAYSIZE(szCopy);
	HRESULT hr;

	DH_ENTER(L"InvokeV");

	if (!pDisp || !szMember || !marker) return DH_EXIT(E_INVALIDARG, szMember);

	do
	{
		if (cchDest-- == 0) return DH_EXIT(E_INVALIDARG, szMember);
	}
	while( (*szTemp++ = *szMember++) );

	szTemp = szCopy;

	hr = TraverseSubObjects(&pDisp, &szTemp, marker);

	if (SUCCEEDED(hr))
	{
		hr = InternalInvokeV(invokeType, returnType, pvResult, pDisp, szTemp, marker);

		pDisp->lpVtbl->Release(pDisp);
	}

	return DH_EXIT(hr, szMember);
}

static HRESULT TraverseSubObjects(IDispatch ** ppDisp, LPWSTR * lpszMember, va_list * marker)
{
	LPWSTR szSeperator, szTemp;
	VARIANT vtObject;
	HRESULT hr;

	DH_ENTER(L"TraverseSubObjects");

	if (**lpszMember == L'.') (*lpszMember)++;

	(*ppDisp)->lpVtbl->AddRef(*ppDisp);

	szSeperator = wcschr(*lpszMember, L'.');

	if (szSeperator == NULL) return DH_EXIT(NOERROR, *lpszMember);

	szTemp = *lpszMember;

	do
	{
		*szSeperator = L'\0';

		hr = InternalInvokeV(DISPATCH_METHOD|DISPATCH_PROPERTYGET, VT_DISPATCH,
		                     &vtObject, *ppDisp, szTemp, marker);

		if (!vtObject.pdispVal && SUCCEEDED(hr)) hr = E_NOINTERFACE;

		(*ppDisp)->lpVtbl->Release(*ppDisp);

		if (FAILED(hr)) break;

		*ppDisp = vtObject.pdispVal;

		szTemp = szSeperator + 1;

	}
	while ( (wcschr(szTemp, L'.') ) != NULL);

	*lpszMember = szTemp;

	return DH_EXIT(hr, *lpszMember);
}

static HRESULT InternalInvokeV(int invokeType, VARTYPE returnType, VARIANT * pvResult,
                               IDispatch * pDisp, LPOLESTR szMember, va_list * marker)
{
	VARIANT vtArgs[DH_MAX_ARGS];
	BOOL bFreeList[DH_MAX_ARGS];
	HRESULT hr;
	UINT cArgs, iArg;

	DH_ENTER(L"InternalInvokeV");

	hr = CreateArgumentArray(szMember, vtArgs, bFreeList, &cArgs, marker);

	if (SUCCEEDED(hr))
	{
		hr = dhInvokeArray(invokeType, pvResult, cArgs, pDisp, szMember, &vtArgs[DH_MAX_ARGS - cArgs]);

		for (iArg = DH_MAX_ARGS - cArgs;iArg < DH_MAX_ARGS;iArg++)
		{
			if (bFreeList[iArg]) VariantClear(&vtArgs[iArg]);
		}

		if (SUCCEEDED(hr) && pvResult != NULL &&
	            pvResult->vt != returnType && returnType != VT_EMPTY)
		{
			hr = VariantChangeType(pvResult, pvResult, 16 , returnType);
			if (FAILED(hr)) VariantClear(pvResult);
		}
	}

	return DH_EXIT(hr, szMember);
}

static HRESULT CreateArgumentArray(LPWSTR szMember, VARIANT * pArgs, BOOL * pbFreeList,
				   UINT * pcArgs, va_list * marker)
{
	HRESULT hr        = NOERROR;
	INT iArg          = DH_MAX_ARGS;
	BOOL bInArguments = FALSE;

	DH_ENTER(L"CreateArgumentArray");

	while (*szMember)
	{
		if (!bInArguments &&
                   (*szMember == L'(' || *szMember == L' ' || *szMember == L'=') )
		{
			bInArguments = TRUE;

			*szMember = L'\0';
		}
		else if  (*szMember == L'%')
		{
			if (!bInArguments)
			{
				bInArguments = TRUE;
				*szMember = L'\0';
			}

			iArg--;

			if (iArg == -1) { hr = E_INVALIDARG; break; }

			szMember++;

			hr = ExtractArgument(&pArgs[iArg], *szMember, &pbFreeList[iArg], marker);

			if (FAILED(hr)) break;
		}

		szMember++;
	}

	*pcArgs = DH_MAX_ARGS - iArg;

	if (FAILED(hr))
	{
		for (++iArg;iArg < DH_MAX_ARGS; iArg++)
		{
			if (pbFreeList[iArg]) VariantClear(&pArgs[iArg]);
		}
	}

	return DH_EXIT(hr, szMember);
}

static HRESULT ExtractArgument(VARIANT * pvArg, WCHAR chIdentifier, BOOL * pbFreeArg, va_list * marker)
{
	HRESULT hr = NOERROR;

	*pbFreeArg = FALSE;

	if (chIdentifier == L'T') chIdentifier = (dh_g_bIsUnicodeMode ? L'S' : L's');

	switch (chIdentifier)
	{
		case L'd':
			V_VT(pvArg)  = VT_I4;
			V_I4(pvArg)  = va_arg(*marker, LONG);
			break;

		case L'u':
			V_VT(pvArg)  = VT_UI4;
			V_UI4(pvArg) = va_arg(*marker, ULONG);
			break;

		case L'e':
			V_VT(pvArg)  = VT_R8;
			V_R8(pvArg)  = va_arg(*marker, DOUBLE);
			break;

		case L'b':
			V_VT(pvArg)   = VT_BOOL;
			V_BOOL(pvArg) = ( va_arg(*marker, BOOL) ? VARIANT_TRUE : VARIANT_FALSE );
			break;

		case L'v':
			*pvArg  = *va_arg(*marker, VARIANT *);
			break;

		case L'm':
			V_VT(pvArg)    = VT_ERROR;
			V_ERROR(pvArg) = DISP_E_PARAMNOTFOUND;
			break;

		case L'B':
			V_VT(pvArg)   = VT_BSTR;
			V_BSTR(pvArg) = va_arg(*marker, BSTR);
			break;

		case L'S':
		{
			LPOLESTR szTemp = va_arg(*marker, LPOLESTR);

			V_VT(pvArg)   = VT_BSTR;
			V_BSTR(pvArg) = SysAllocString(szTemp);

			if (V_BSTR(pvArg) == NULL && szTemp != NULL) hr = E_OUTOFMEMORY;

			*pbFreeArg = TRUE;
			break;
		}

		case L's':
			V_VT(pvArg) = VT_BSTR;
			hr = ConvertAnsiStrToBStr(va_arg(*marker, LPSTR), &V_BSTR(pvArg));
			*pbFreeArg = TRUE;
			break;

		case L'o':
			V_VT(pvArg)       = VT_DISPATCH;
			V_DISPATCH(pvArg) = va_arg(*marker, IDispatch *);
			break;

		case L'O':
			V_VT(pvArg)      = VT_UNKNOWN;
			V_UNKNOWN(pvArg) = va_arg(*marker, IUnknown *);
			break;

		case L'D':
			V_VT(pvArg)   = VT_DATE;
			V_DATE(pvArg) = va_arg(*marker, DATE);
			break;

		case L't':
			V_VT(pvArg) = VT_DATE;
			hr = ConvertTimeTToVariantTime(va_arg(*marker, time_t), &V_DATE(pvArg));
			break;

		case L'W':
			V_VT(pvArg) = VT_DATE;
			hr = ConvertSystemTimeToVariantTime(va_arg(*marker, SYSTEMTIME *), &V_DATE(pvArg));
			break;

		case L'f':
			V_VT(pvArg) = VT_DATE;
			hr = ConvertFileTimeToVariantTime(va_arg(*marker, FILETIME *), &V_DATE(pvArg));
			break;

		case L'p':
#ifndef _WIN64
			V_VT(pvArg) = VT_I4;
			V_I4(pvArg) = (LONG) va_arg(*marker, LPVOID);
#else
			V_VT(pvArg) = VT_I8;
			V_I8(pvArg) = (LONGLONG) va_arg(*marker, LPVOID);
#endif
			break;

		default:
			hr = E_INVALIDARG;
			DEBUG_NOTIFY_INVALID_IDENTIFIER(chIdentifier);
			break;
	}

	return hr;
}

/* ----- dh_enum.c ----- */

HRESULT dhEnumBeginV(IEnumVARIANT ** ppEnum, IDispatch * pDisp, LPCOLESTR szMember, va_list * marker)
{
	DISPPARAMS dp    = { 0 };
	EXCEPINFO excep  = { 0 };
	VARIANT vtResult;
	IDispatch * pDispObj;
	HRESULT hr;

	DH_ENTER(L"EnumBeginV");

	if (!ppEnum || !pDisp) return DH_EXIT(E_INVALIDARG, szMember);

	if (szMember != NULL)
	{
		hr = dhGetValueV(L"%o", &pDispObj, pDisp, szMember, marker);
		if (FAILED(hr)) return DH_EXIT(hr, szMember);
	}
	else
	{
		pDispObj = pDisp;
	}

	hr = pDispObj->lpVtbl->Invoke(pDispObj, DISPID_NEWENUM, &IID_NULL, LOCALE_USER_DEFAULT,
				 DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &vtResult, &excep, NULL);

	if (szMember != NULL) pDispObj->lpVtbl->Release(pDispObj);

	if (FAILED(hr)) return DH_EXITEX(hr, TRUE, L"_NewEnum", szMember, &excep, 0);

	if (vtResult.vt == VT_DISPATCH)
		hr = vtResult.pdispVal->lpVtbl->QueryInterface(vtResult.pdispVal, &IID_IEnumVARIANT, (void **) ppEnum);
	else if (vtResult.vt == VT_UNKNOWN)
		hr = vtResult.punkVal->lpVtbl->QueryInterface(vtResult.punkVal, &IID_IEnumVARIANT, (void **) ppEnum);
	else
		hr = E_NOINTERFACE;

	VariantClear(&vtResult);

	return DH_EXIT(hr, szMember);
}

HRESULT dhEnumNextVariant(IEnumVARIANT * pEnum, VARIANT * pvResult)
{
	DH_ENTER(L"EnumNextVariant");

	if (!pEnum || !pvResult) return DH_EXIT(E_INVALIDARG, L"Enumerator");

	return DH_EXIT(pEnum->lpVtbl->Next(pEnum, 1, pvResult, NULL), L"Enumerator");
}

HRESULT dhEnumNextObject(IEnumVARIANT * pEnum, IDispatch ** ppDisp)
{
	VARIANT vtResult;
	HRESULT hr;

	DH_ENTER(L"EnumNextObject");

	if (!pEnum || !ppDisp) return DH_EXIT(E_INVALIDARG, L"Enumerator");

	hr = pEnum->lpVtbl->Next(pEnum, 1, &vtResult, NULL);

	if (hr == S_OK)
	{
		if (vtResult.vt == VT_DISPATCH)
		{
			*ppDisp = vtResult.pdispVal;
		}
		else
		{
			hr = VariantChangeType(&vtResult, &vtResult, 0, VT_DISPATCH);
			if (SUCCEEDED(hr)) *ppDisp = vtResult.pdispVal;
			else VariantClear(&vtResult);
		}
	}

	return DH_EXIT(hr, L"Enumerator");
}

HRESULT dhEnumBegin(IEnumVARIANT ** ppEnum, IDispatch * pDisp, LPCOLESTR szMember, ...)
{
	HRESULT hr;
	va_list marker;

	DH_ENTER(L"EnumBegin");

	va_start(marker, szMember);

	hr = dhEnumBeginV(ppEnum, pDisp, szMember, &marker);

	va_end(marker);

	return DH_EXIT(hr, szMember);
}

/* ----- convert.c ----- */

static const LONGLONG FILE_TIME_ONE_DAY           = 864000000000;

static const LONGLONG FILE_TIME_VARIANT_DAY0      = 94353120000000000;

static const ULONGLONG FILE_TIME_VARIANT_OVERFLOW  = 2650467744000000000;

static const DATE      VARIANT_FILE_TIME_DAY0      = -109205;

static const DATE      VARIANT_TIMET_DAY0          = 25569;

static const LONG      TIMET_ONE_DAY               = 86400;

#ifndef _WIN64
static const DATE      VARIANT_TIMET_MAX           = 50424.13480;
#else
static const time_t    TIMET_VARIANT_OVERFLOW      = 253402300800;
#endif

HRESULT ConvertFileTimeToVariantTime(FILETIME * pft, DATE * pDate)
{
	ULONGLONG ftScalar;

	if (!pft || !pDate) return E_INVALIDARG;

	ftScalar = *((ULONGLONG *) pft) + 500;

	if (ftScalar >= FILE_TIME_VARIANT_OVERFLOW) return E_INVALIDARG;
	*pDate = (LONGLONG) (ftScalar - FILE_TIME_VARIANT_DAY0) / (double) FILE_TIME_ONE_DAY;
	if (*pDate < 0) *pDate = floor(*pDate) + (floor(*pDate) - *pDate);

	return NOERROR;
}

HRESULT ConvertVariantTimeToFileTime(DATE date, FILETIME * pft)
{
	ULONGLONG ftScalar;

	if (!pft) return E_INVALIDARG;

	if (date < 0) date = ceil(date) + (ceil(date) - date);

	if (date < VARIANT_FILE_TIME_DAY0) return E_INVALIDARG;
	ftScalar = (ULONGLONG) ((date * FILE_TIME_ONE_DAY) + FILE_TIME_VARIANT_DAY0);

	*pft = *((FILETIME *) &ftScalar);

	return NOERROR;
}

HRESULT ConvertVariantTimeToSystemTime(DATE date, SYSTEMTIME * pSystemTime)
{
	HRESULT hr;
	FILETIME fileTime;

	if (!pSystemTime) return E_INVALIDARG;
	if (FAILED(hr = ConvertVariantTimeToFileTime(date, &fileTime))) return hr;
	return (FileTimeToSystemTime(&fileTime, pSystemTime) ? NOERROR : HRESULT_FROM_WIN32( GetLastError() ));
}

HRESULT ConvertSystemTimeToVariantTime(SYSTEMTIME * pSystemTime, DATE * pDate)
{
	FILETIME fileTime;

	if (!pSystemTime || !pDate) return E_INVALIDARG;
	if (!SystemTimeToFileTime(pSystemTime, &fileTime)) return HRESULT_FROM_WIN32( GetLastError() );
	return ConvertFileTimeToVariantTime(&fileTime, pDate);
}

HRESULT ConvertVariantTimeToTimeT(DATE date, time_t * pTimeT)
{
	struct tm * ptm;

	if (!pTimeT) return E_INVALIDARG;

#ifndef _WIN64
	if (date < VARIANT_TIMET_DAY0 || date > VARIANT_TIMET_MAX) return E_INVALIDARG;
#else
	if (date < VARIANT_TIMET_DAY0) return E_INVALIDARG;
#endif

	*pTimeT = (time_t) (((date - VARIANT_TIMET_DAY0) * TIMET_ONE_DAY) + 0.5);

	if ( (ptm = gmtime(pTimeT)) == NULL || !(ptm->tm_isdst = -1) ||
	     (*pTimeT = mktime(ptm)) == (time_t) -1 ) return E_FAIL;

	return NOERROR;
}

HRESULT ConvertTimeTToVariantTime(time_t timeT, DATE * pDate)
{
	struct tm localtm, utctm, * ptm;
	time_t timeTLocal, timeTUtc;

	if (!pDate) return E_INVALIDARG;

	if ( (ptm = localtime(&timeT)) == NULL) return E_FAIL;
	localtm = *ptm;

	if ( (ptm = gmtime(&timeT)) == NULL) return E_FAIL;
	utctm = *ptm;

	localtm.tm_isdst = 0;
	utctm.tm_isdst   = 0;

	if ( (timeTLocal = mktime(&localtm)) == (time_t) -1 ||
	     (timeTUtc   = mktime(&utctm))   == (time_t) -1) return E_FAIL;

	timeT += timeTLocal - timeTUtc;

#ifdef _WIN64
	if (timeT >= TIMET_VARIANT_OVERFLOW) return E_INVALIDARG;
#endif
	*pDate = (DATE)  (timeT / (double) TIMET_ONE_DAY) + VARIANT_TIMET_DAY0;

	return NOERROR;
}

HRESULT ConvertAnsiStrToBStr(LPCSTR szAnsiIn, BSTR * lpBstrOut)
{
	DWORD dwSize;

	if (lpBstrOut == NULL) return E_INVALIDARG;
	if (szAnsiIn == NULL) { *lpBstrOut = NULL; return NOERROR; }

	dwSize = MultiByteToWideChar(CP_ACP, 0, szAnsiIn, -1, NULL, 0);
	if (dwSize == 0) return HRESULT_FROM_WIN32( GetLastError() );

	*lpBstrOut = SysAllocStringLen(NULL, dwSize - 1);
	if (*lpBstrOut == NULL) return E_OUTOFMEMORY;

	if ( !MultiByteToWideChar(CP_ACP, 0, szAnsiIn, -1, *lpBstrOut, dwSize) )
	{
		SysFreeString(*lpBstrOut);
		return HRESULT_FROM_WIN32( GetLastError() );
	}

	return NOERROR;
}

HRESULT ConvertBStrToAnsiStr(BSTR bstrIn, LPSTR * lpszOut)
{
	DWORD dwSize;

	if (lpszOut == NULL) return E_INVALIDARG;
	if (bstrIn == NULL) { *lpszOut = NULL; return NOERROR; }

	dwSize = WideCharToMultiByte(CP_ACP, 0, bstrIn, -1, NULL, 0, NULL, NULL);
	if (dwSize == 0) return HRESULT_FROM_WIN32( GetLastError() );

	*lpszOut = (LPSTR) SysAllocStringByteLen(NULL, dwSize - 1);
	if (*lpszOut == NULL) return E_OUTOFMEMORY;

	if ( !WideCharToMultiByte(CP_ACP, 0, bstrIn, -1, *lpszOut, dwSize, NULL, NULL) )
	{
		SysFreeString((BSTR) *lpszOut);
		return HRESULT_FROM_WIN32( GetLastError() );
	}

	return NOERROR;
}

/* ----- dh_exceptions.c ----- */

#ifndef DISPHELPER_NO_EXCEPTIONS

static DH_EXCEPTION_OPTIONS g_ExceptionOptions;

static LONG  f_lngTlsInitBegin = -1, f_lngTlsInitEnd = -1;
static DWORD f_TlsIdxStackCount, f_TlsIdxException;

#define SetStackCount(nStackCount)   TlsSetValue(f_TlsIdxStackCount, (LPVOID) (nStackCount))
#define SetExceptionPtr(pException)  TlsSetValue(f_TlsIdxException, pException);
#define GetStackCount()       (UINT) TlsGetValue(f_TlsIdxStackCount)
#define GetExceptionPtr()            TlsGetValue(f_TlsIdxException)
#define CheckTlsInitialized()        if (f_lngTlsInitEnd != 0) InitializeTlsIndexes();

static void hlprStringCchCopyW(LPWSTR pszDest, SIZE_T cchDest, LPCWSTR pszSrc)
{
	assert(cchDest > 0);

	do
	{
		if (--cchDest == 0) break;
	}
	while ((*pszDest++ = *pszSrc++));

	*pszDest = L'\0';
}

static void InitializeTlsIndexes(void)
{
	if (0 == InterlockedIncrement(&f_lngTlsInitBegin))
	{
		f_TlsIdxStackCount = TlsAlloc();
		f_TlsIdxException  = TlsAlloc();
		f_lngTlsInitEnd    = 0;
	}
	else
	{
		while (f_lngTlsInitEnd != 0) Sleep(5);
	}
}

void dhEnter(void)
{
	CheckTlsInitialized();
	SetStackCount(GetStackCount() + 1);
}

HRESULT dhExitEx(HRESULT hr, BOOL bDispatchError, LPCWSTR szMember, LPCWSTR szCompleteMember,
                 EXCEPINFO * pExcepInfo, UINT iArgError, LPCWSTR szFunctionName)
{
	UINT nStackCount = GetStackCount();

	SetStackCount(nStackCount - 1);

	if (FAILED(hr) && !g_ExceptionOptions.bDisableRecordExceptions)
	{
		PDH_EXCEPTION pException = GetExceptionPtr();

		if (!pException)
		{
			pException = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DH_EXCEPTION));
			if (!pException) return hr;
			SetExceptionPtr(pException);
		}
		else if (pException->bOld)
		{
			SysFreeString(pException->szDescription);
			SysFreeString(pException->szSource);
			SysFreeString(pException->szHelpFile);
			ZeroMemory(pException, sizeof(DH_EXCEPTION));
		}

		if (pException->hr == 0)
		{
			pException->hr              = hr;
			pException->iArgError       = iArgError;
			pException->szErrorFunction = szFunctionName;
			pException->bDispatchError  = bDispatchError;

			if (szMember) hlprStringCchCopyW(pException->szMember, ARRAYSIZE(pException->szMember), szMember);

			if (pExcepInfo && hr == DISP_E_EXCEPTION)
			{
				if (pExcepInfo->pfnDeferredFillIn &&
				    !IsBadCodePtr((FARPROC) pExcepInfo->pfnDeferredFillIn)) pExcepInfo->pfnDeferredFillIn(pExcepInfo);

				pException->szDescription = pExcepInfo->bstrDescription;
				pException->szSource      = pExcepInfo->bstrSource;
				pException->szHelpFile    = pExcepInfo->bstrHelpFile;
				pException->dwHelpContext = pExcepInfo->dwHelpContext;
				pException->swCode        = (pExcepInfo->wCode ? pExcepInfo->wCode : pExcepInfo->scode);
			}
		}

		if (nStackCount == 1)
		{
			pException->bOld              = TRUE;
			pException->szInitialFunction = szFunctionName;

			if (szCompleteMember) hlprStringCchCopyW(pException->szCompleteMember, ARRAYSIZE(pException->szCompleteMember), szCompleteMember);

			if (g_ExceptionOptions.bShowExceptions)
				dhShowException(pException);

			if (g_ExceptionOptions.pfnExceptionCallback)
				g_ExceptionOptions.pfnExceptionCallback(pException);
		}
	}
	else if (hr == DISP_E_EXCEPTION && pExcepInfo)
	{
		SysFreeString(pExcepInfo->bstrDescription);
		SysFreeString(pExcepInfo->bstrSource);
		SysFreeString(pExcepInfo->bstrHelpFile);
	}

	return hr;
}

HRESULT dhShowException(PDH_EXCEPTION pException)
{
	WCHAR szMessage[512];

	dhFormatExceptionW(pException, szMessage, ARRAYSIZE(szMessage), FALSE);

	MessageBoxW(g_ExceptionOptions.hwnd, szMessage, g_ExceptionOptions.szAppName,
	            MB_ICONSTOP | MB_SETFOREGROUND);

	return NOERROR;
}

HRESULT dhFormatExceptionW(PDH_EXCEPTION pException, LPWSTR szBuffer, UINT cchBufferSize, BOOL bFixedFont)
{
	HRESULT hr;
	UINT cch = 0;
#	define DESCRIPTION_LENGTH 255

	if (!szBuffer && cchBufferSize) return E_INVALIDARG;

	if (!pException)
	{
		dhGetLastException(&pException);
		if (!pException)
		{
			if (cchBufferSize != 0)
			{
				_snwprintf(szBuffer, cchBufferSize, L"No error information available.");
				szBuffer[cchBufferSize - 1] = L'\0';
			}

			return NOERROR;
		}
	}

	hr = (pException->hr == DISP_E_EXCEPTION && pException->swCode ?
			pException->swCode : pException->hr);

	if (!pException->szSource)
	{
		if (pException->bDispatchError)
			pException->szSource = SysAllocString(L"IDispatch Interface");
		else
			pException->szSource = SysAllocString(L"Application");
	}

	if (!pException->szDescription)
	{
		pException->szDescription = SysAllocStringLen(NULL, DESCRIPTION_LENGTH);

		if (pException->szDescription)
		{
			switch (hr)
			{
				case E_NOINTERFACE:
					_snwprintf(pException->szDescription, DESCRIPTION_LENGTH, L"Object required");
					break;

				case DISP_E_UNKNOWNNAME:
				case DISP_E_MEMBERNOTFOUND:
					_snwprintf(pException->szDescription, DESCRIPTION_LENGTH, L"Object doesn't support this property or method: '%s'", pException->szMember);
					break;

				case DISP_E_TYPEMISMATCH:
					if (pException->szMember[0])
					{
						_snwprintf(pException->szDescription, DESCRIPTION_LENGTH, L"Type mismatch: '%s'. Argument Index: %d", pException->szMember, pException->iArgError);
						break;
					}

				default:
				{
#ifndef UNICODE
					CHAR szDescription[DESCRIPTION_LENGTH];
#else
					LPWSTR szDescription = pException->szDescription;
#endif
					cch = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
					             NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
					             szDescription, DESCRIPTION_LENGTH, NULL);

					if (!cch) wcscpy(pException->szDescription, L"Unknown runtime error");
#ifndef UNICODE
					else MultiByteToWideChar(CP_ACP, 0, szDescription, -1, pException->szDescription, DESCRIPTION_LENGTH);
#endif
				}
			}
		}
	}

	if (pException->szDescription)
	{

		if (!cch) cch = wcslen(pException->szDescription);

		if (cch >= 2 && pException->szDescription[cch - 2] == L'\r')
			pException->szDescription[cch - 2] = L'\0';
		else if (cch >= 1 && pException->szDescription[cch - 1] == L'\n')
			pException->szDescription[cch - 1] = L'\0';
	}

	if (cchBufferSize)
	{
		if (!bFixedFont)
		{
			_snwprintf(szBuffer, cchBufferSize, L"Member:\t  %s\r\nFunction:\t  %s\t\t\r\nError In:\t  %s\r\nError:\t  %s\r\nCode:\t  %x\r\nSource:\t  %s",
				pException->szCompleteMember,
				pException->szInitialFunction, pException->szErrorFunction,
				pException->szDescription, hr,
				pException->szSource);
		}
		else
		{
			_snwprintf(szBuffer, cchBufferSize, L"Member:   %s\r\nFunction: %s\r\nError In: %s\r\nError:    %s\r\nCode:     %x\r\nSource:   %s",
				pException->szCompleteMember,
				pException->szInitialFunction, pException->szErrorFunction,
				pException->szDescription, hr,
				pException->szSource);
		}

		szBuffer[cchBufferSize - 1] = L'\0';
	}

	return NOERROR;
}

HRESULT dhFormatExceptionA(PDH_EXCEPTION pException, LPSTR szBuffer, UINT cchBufferSize, BOOL bFixedFont)
{
	WCHAR szBufferW[1024];

	dhFormatExceptionW(pException, szBufferW, ARRAYSIZE(szBufferW), bFixedFont);

	if (0 == WideCharToMultiByte(CP_ACP, 0, szBufferW, -1, szBuffer, cchBufferSize, NULL, NULL))
		return HRESULT_FROM_WIN32( GetLastError() );

	return NOERROR;
}

HRESULT dhGetLastException(PDH_EXCEPTION * ppException)
{
	if (!ppException) return E_INVALIDARG;

	CheckTlsInitialized();
	*ppException = GetExceptionPtr();

	return NOERROR;
}

HRESULT dhToggleExceptions(BOOL bShow)
{
	g_ExceptionOptions.bShowExceptions = bShow;
	if (bShow) g_ExceptionOptions.bDisableRecordExceptions = FALSE;

	return NOERROR;
}

HRESULT dhSetExceptionOptions(PDH_EXCEPTION_OPTIONS pExceptionOptions)
{
	if (!pExceptionOptions) return E_INVALIDARG;

	g_ExceptionOptions.hwnd                     = pExceptionOptions->hwnd;
	g_ExceptionOptions.szAppName                = pExceptionOptions->szAppName;
	g_ExceptionOptions.bShowExceptions          = pExceptionOptions->bShowExceptions;
	g_ExceptionOptions.bDisableRecordExceptions = pExceptionOptions->bDisableRecordExceptions;
	g_ExceptionOptions.pfnExceptionCallback     = pExceptionOptions->pfnExceptionCallback;

	return NOERROR;
}

HRESULT dhGetExceptionOptions(PDH_EXCEPTION_OPTIONS pExceptionOptions)
{
	if (!pExceptionOptions) return E_INVALIDARG;

	pExceptionOptions->hwnd                     = g_ExceptionOptions.hwnd;
	pExceptionOptions->szAppName                = g_ExceptionOptions.szAppName;
	pExceptionOptions->bShowExceptions          = g_ExceptionOptions.bShowExceptions;
	pExceptionOptions->bDisableRecordExceptions = g_ExceptionOptions.bDisableRecordExceptions;
	pExceptionOptions->pfnExceptionCallback     = g_ExceptionOptions.pfnExceptionCallback;

	return NOERROR;
}

void dhCleanupThreadException(void)
{
	PDH_EXCEPTION pException;

	CheckTlsInitialized();
	pException = GetExceptionPtr();

	if (pException)
	{
		SysFreeString(pException->szDescription);
		SysFreeString(pException->szSource);
		SysFreeString(pException->szHelpFile);

		HeapFree(GetProcessHeap(), 0, pException);

		SetExceptionPtr(NULL);
	}
}

#endif

/* ----- dh_init.c ----- */

HRESULT dhInitializeImp(BOOL bInitializeCOM, BOOL bUnicode)
{
	dh_g_bIsUnicodeMode = bUnicode;

	if (bInitializeCOM) return CoInitialize(NULL);

	return NOERROR;
}

void dhUninitialize(BOOL bUninitializeCOM)
{
#ifndef DISPHELPER_NO_EXCEPTIONS
	dhCleanupThreadException();
#endif
	if (bUninitializeCOM) CoUninitialize();
}

