// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1993 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and Microsoft
// QuickHelp and/or WinHelp documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.


#include "stdafx.h"
#include <stdarg.h>

#ifdef AFX_OLE5_SEG
#pragma code_seg(AFX_OLE5_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// COleDispatchDriver constructors/destructors

COleDispatchDriver::COleDispatchDriver()
{
	m_lpDispatch = NULL;
	m_bAutoRelease = TRUE;
}

BOOL COleDispatchDriver::CreateDispatch(REFCLSID clsid, COleException* pError)
{
	ASSERT(m_lpDispatch == NULL);

	m_bAutoRelease = TRUE;  // good default is to auto-release

	// create an instance of the object
	LPUNKNOWN lpUnknown = NULL;
	HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_ALL, IID_IUnknown,
		(LPLP)&lpUnknown);
	if (FAILED(hr))
		goto Failed;

	// make sure it is running
	hr = OleRun(lpUnknown);
	if (FAILED(hr))
		goto Failed;

	// query for IDispatch interface
	hr = lpUnknown->QueryInterface(IID_IDispatch, (LPLP)&m_lpDispatch);
	if (FAILED(hr))
		goto Failed;

	ASSERT(m_lpDispatch != NULL);
	lpUnknown->Release();
	return TRUE;

Failed:
	_AfxRelease(&lpUnknown);
	if (pError != NULL)
		pError->m_sc = GetScode(hr);
	return FALSE;
}

BOOL COleDispatchDriver::CreateDispatch(LPCSTR lpszProgID,
	COleException* pError)
{
	ASSERT(m_lpDispatch == NULL);

	// map prog id to CLSID
	CLSID clsid;
	HRESULT hr = ::CLSIDFromProgID(lpszProgID, &clsid);
	if (FAILED(hr))
	{
		if (pError != NULL)
			pError->m_sc = GetScode(hr);
		return FALSE;
	}

	// create with CLSID
	return CreateDispatch(clsid, pError);
}

void COleDispatchDriver::AttachDispatch(LPDISPATCH lpDispatch,
	BOOL bAutoRelease)
{
	ASSERT(lpDispatch != NULL);

	ReleaseDispatch();  // detach previous
	m_lpDispatch = lpDispatch;
	m_bAutoRelease = bAutoRelease;
}

void COleDispatchDriver::ReleaseDispatch()
{
	if (m_lpDispatch != NULL)
	{
		if (m_bAutoRelease)
			m_lpDispatch->Release();
		m_lpDispatch = NULL;
	}
}

LPDISPATCH COleDispatchDriver::DetachDispatch()
{
	LPDISPATCH lpDispatch = m_lpDispatch;
	m_lpDispatch = NULL;    // detach without Release
	return lpDispatch;
}

/////////////////////////////////////////////////////////////////////////////
// COleDispatchDriver implementation

void COleDispatchDriver::InvokeHelperV(DISPID dwDispID, WORD wFlags,
	VARTYPE vtRet, void* pvRet, const BYTE FAR* pbParamInfo, va_list argList)
{
	if (m_lpDispatch == NULL)
	{
		TRACE0("Warning: attempt to call Invoke with NULL m_lpDispatch\r\n");
		return;
	}

	DISPPARAMS dispparams;
	memset(&dispparams, 0, sizeof dispparams);

	// determine number of arguments
	if (pbParamInfo != NULL)
		dispparams.cArgs = lstrlen((LPCSTR)pbParamInfo);

	DISPID dispidNamed = DISPID_PROPERTYPUT;
	if (wFlags & DISPATCH_PROPERTYPUT)
	{
		ASSERT(dispparams.cArgs > 0);
		dispparams.cNamedArgs = 1;
		dispparams.rgdispidNamedArgs = &dispidNamed;
	}

	if (dispparams.cArgs != 0)
	{
		// allocate memory for all VARIANTARG parameters
		VARIANTARG* pArg = new VARIANTARG[dispparams.cArgs];
		ASSERT(pArg != NULL);   // should have thrown exception
		dispparams.rgvarg = pArg;
		memset(pArg, 0, sizeof(VARIANTARG) * dispparams.cArgs);

		// get ready to walk vararg list
		const BYTE FAR* pb = pbParamInfo;
		pArg += dispparams.cArgs - 1;   // params go in opposite order

		while (*pb != 0)
		{
			ASSERT(pArg >= dispparams.rgvarg);

			pArg->vt = *pb; // set the variant type
			if (pArg->vt & VT_MFCBYREF)
			{
				pArg->vt &= ~VT_MFCBYREF;
				pArg->vt |= VT_BYREF;
			}
			switch (pArg->vt)
			{
			case VT_I2:
				pArg->iVal = va_arg(argList, short);
				break;
			case VT_I4:
				pArg->lVal = va_arg(argList, long);
				break;
			case VT_R4:
				// Note: All float arguments to vararg functions are passed
				//  as doubles instead.  Thats why they are passed as VT_R8
				//  instead of VT_R4.
				pArg->vt = VT_R8;
				*(_AFXDOUBLE*)&pArg->dblVal = va_arg(argList, _AFXDOUBLE);
				break;
			case VT_R8:
				*(_AFXDOUBLE*)&pArg->dblVal = va_arg(argList, _AFXDOUBLE);
				break;
			case VT_DATE:
				*(_AFXDOUBLE*)&pArg->date = va_arg(argList, _AFXDOUBLE);
				break;
			case VT_CY:
				pArg->cyVal = *va_arg(argList, CY*);
				break;
			case VT_BSTR:
				pArg->bstrVal = ::SysAllocString(va_arg(argList, char*));
				if (pArg->bstrVal == NULL)
				{
					AfxThrowMemoryException();
					ASSERT(FALSE);
				}
				break;;
			case VT_DISPATCH:
				pArg->pdispVal = va_arg(argList, LPDISPATCH);
				break;
			case VT_ERROR:
				pArg->scode = va_arg(argList, SCODE);
				break;
			case VT_BOOL:
				pArg->bool = va_arg(argList, BOOL) ? -1 : 0;
				break;
			case VT_VARIANT:
				*pArg = *va_arg(argList, VARIANTARG*);
				break;
			case VT_UNKNOWN:
				pArg->punkVal = va_arg(argList, LPUNKNOWN);
				break;

			case VT_I2|VT_BYREF:
				pArg->piVal = va_arg(argList, short*);
				break;
			case VT_I4|VT_BYREF:
				pArg->plVal = va_arg(argList, long*);
				break;
			case VT_R4|VT_BYREF:
				pArg->pfltVal = va_arg(argList, float*);
				break;
			case VT_R8|VT_BYREF:
				pArg->pdblVal = va_arg(argList, double*);
				break;
			case VT_DATE|VT_BYREF:
				pArg->pdate = va_arg(argList, DATE*);
				break;
			case VT_CY|VT_BYREF:
				pArg->pcyVal = va_arg(argList, CY*);
				break;
			case VT_BSTR|VT_BYREF:
				pArg->pbstrVal = va_arg(argList, BSTR*);
				break;;
			case VT_DISPATCH|VT_BYREF:
				pArg->ppdispVal = va_arg(argList, LPDISPATCH*);
				break;
			case VT_ERROR|VT_BYREF:
				pArg->pscode = va_arg(argList, SCODE*);
				break;
			case VT_BOOL|VT_BYREF:
				{
					// Note: BOOL as VARIANT_BOOL.  Might not be portable.
					BOOL* pbool = va_arg(argList, BOOL*);
					*pbool = 0;
					pArg->pbool = (VARIANT_BOOL*)pbool;
				}
				break;
			case VT_VARIANT|VT_BYREF:
				pArg->pvarVal = va_arg(argList, VARIANTARG*);
				break;
			case VT_UNKNOWN|VT_BYREF:
				pArg->ppunkVal = va_arg(argList, LPUNKNOWN*);
				break;

			default:
				ASSERT(FALSE);  // unknown type!
				break;
			}

			--pArg; // get ready to fill next argument
			++pb;
		}
	}

	// initialize return value
	VARIANTARG vaResult;
	VariantInit(&vaResult);

	// initialize EXCEPINFO struct
	EXCEPINFO excepInfo;
	memset(&excepInfo, 0, sizeof excepInfo);

	UINT nArgErr = -1;  // initialize to invalid arg to

	// make the call
	HRESULT hr = m_lpDispatch->Invoke(dwDispID, IID_NULL, 0, wFlags,
		&dispparams, &vaResult, &excepInfo, &nArgErr);

	// cleanup any arguments that need cleanup
	if (dispparams.cArgs != 0)
	{
		VARIANTARG FAR* pArg = dispparams.rgvarg;
		const BYTE FAR* pb = pbParamInfo;
		while (*pb != 0)
		{
			switch ((VARTYPE)*pb)
			{
			case VT_BSTR:
				VariantClear(pArg);
				break;
			}
			++pArg;
			++pb;
		}
	}
	delete[] _AfxGetPtrFromFarPtr(dispparams.rgvarg);

	// throw exception on failure
	if (FAILED(hr))
	{
		VariantClear(&vaResult);
		if (GetScode(hr) != DISP_E_EXCEPTION)
		{
			// non-exception error code
			AfxThrowOleException(hr);
			ASSERT(FALSE);  // not reached
		}

		// make sure excepInfo is filled in
		if (excepInfo.pfnDeferredFillIn != NULL)
			excepInfo.pfnDeferredFillIn(&excepInfo);

		// allocate new exception, and fill it
		COleDispatchException* pException =
			new COleDispatchException(NULL, 0, 0);
		ASSERT(pException->m_wCode == excepInfo.wCode);
		if (excepInfo.bstrSource != NULL)
		{
			pException->m_strSource = excepInfo.bstrSource;
			SysFreeString(excepInfo.bstrSource);
		}
		if (excepInfo.bstrDescription != NULL)
		{
			pException->m_strDescription = excepInfo.bstrDescription;
			SysFreeString(excepInfo.bstrDescription);
		}
		if (excepInfo.bstrHelpFile != NULL)
		{
			pException->m_strHelpFile = excepInfo.bstrHelpFile;
			SysFreeString(excepInfo.bstrHelpFile);
		}
		pException->m_dwHelpContext = excepInfo.dwHelpContext;
		pException->m_scError = excepInfo.scode;

		// then throw the exception
		THROW(pException);
		ASSERT(FALSE);  // not reached
	}

	if (vtRet != VT_EMPTY)
	{
		// convert return value
		if (vtRet != VT_VARIANT)
		{
			HRESULT hr = VariantChangeType(&vaResult, &vaResult, 0, vtRet);
			if (FAILED(hr))
			{
				VariantClear(&vaResult);
				AfxThrowOleException(hr);
				ASSERT(FALSE);
			}
		}
		ASSERT(vtRet == vaResult.vt);

		// copy return value into return spot!
		switch (vtRet)
		{
		case VT_I2:
			*(short*)pvRet = vaResult.iVal;
			break;
		case VT_I4:
			*(long*)pvRet = vaResult.lVal;
			break;
		case VT_R4:
			*(_AFXFLOAT*)pvRet = *(_AFXFLOAT*)&vaResult.fltVal;
			break;
		case VT_R8:
			*(_AFXDOUBLE*)pvRet = *(_AFXDOUBLE*)&vaResult.fltVal;
			break;
		case VT_DATE:
			*(_AFXDOUBLE*)pvRet = *(_AFXDOUBLE*)&vaResult.fltVal;
			break;
		case VT_CY:
			*(CY*)pvRet = vaResult.cyVal;
			break;
		case VT_BSTR:
			if (vaResult.bstrVal == NULL)
				AfxThrowMemoryException();
			*(CString*)pvRet = vaResult.bstrVal;
			SysFreeString(vaResult.bstrVal);
			break;;
		case VT_DISPATCH:
			*(LPDISPATCH*)pvRet = vaResult.pdispVal;
			break;
		case VT_ERROR:
			*(SCODE*)pvRet = vaResult.scode;
			break;
		case VT_BOOL:
			*(BOOL*)pvRet = (vaResult.bool != 0);
			break;
		case VT_VARIANT:
			*(VARIANTARG*)pvRet = vaResult;
			break;
		case VT_UNKNOWN:
			*(LPUNKNOWN*)pvRet = vaResult.punkVal;
			break;

		default:
			ASSERT(FALSE);  // invalid return type specified
		}
	}
}

void COleDispatchDriver::InvokeHelper(DISPID dwDispID, WORD wFlags,
	VARTYPE vtRet, void* pvRet, const BYTE FAR* pbParamInfo, ...)
{
	va_list argList;
	va_start(argList, pbParamInfo);

	InvokeHelperV(dwDispID, wFlags, vtRet, pvRet, pbParamInfo, argList);

	va_end(argList);
}

void COleDispatchDriver::GetProperty(DISPID dwDispID, VARTYPE vtProp,
	void* pvProp) const
{
	((COleDispatchDriver*)this)->InvokeHelper(dwDispID,
		DISPATCH_PROPERTYGET, vtProp, pvProp, NULL);
}

void COleDispatchDriver::SetProperty(DISPID dwDispID, VARTYPE vtProp, ...)
{
	va_list argList;    // really only one arg, but...
	va_start(argList, vtProp);

	BYTE rgbParams[2];
	if (vtProp & VT_BYREF)
	{
		vtProp &= ~VT_BYREF;
		vtProp |= VT_MFCBYREF;
	}
	rgbParams[0] = (BYTE)vtProp;
	rgbParams[1] = 0;
	InvokeHelperV(dwDispID, DISPATCH_PROPERTYPUT, VT_EMPTY, NULL,
		rgbParams, argList);

	va_end(argList);
}

/////////////////////////////////////////////////////////////////////////////
