/******************************************************************************
 *                                                                         
 *               (C) COPYRIGHT Pacific MindWorks, Inc. 2001-2005
 *                          All rights reserved.
 *
 *****************************************************************************/

#ifndef __NTLIIVIDRIVERIMPL_INCLUDED
#define __NTLIIVIDRIVERIMPL_INCLUDED

#include <atlstr.h>
#include <atlrx.h>
#include <atlcoll.h>
#include <atlpath.h>
#include <stdarg.h>

template <class DriverClass>
class IIviDriverImpl
{
public:
	// IIviDriver methods
	STDMETHOD(Close)();
    STDMETHOD(get_DriverOperation)(IIviDriverOperation **pVal);
    STDMETHOD(get_Identity)(IIviDriverIdentity **pVal);
	STDMETHOD(Initialize)(BSTR bstrResourceDescriptor, VARIANT_BOOL bIdQuery, VARIANT_BOOL bReset, BSTR bstrOptionString);
    STDMETHOD(get_Initialized)(VARIANT_BOOL *pVal);
    STDMETHOD(get_Utility)(IIviDriverUtility **pVal);

	// IIviDriverUtility methods
	STDMETHOD(Disable)();
	STDMETHOD(ErrorQuery)(long* Code, BSTR* Message);
	STDMETHOD(LockObject)();
	STDMETHOD(Reset)();
	STDMETHOD(ResetWithDefaults)();
	STDMETHOD(SelfTest)(long* Result, BSTR* Message);
	STDMETHOD(UnlockObject)();

	// IIviComponentIdentity
	STDMETHOD(get_Description)(BSTR *pVal);
	STDMETHOD(get_Revision)(BSTR *pVal);
	STDMETHOD(get_Vendor)(BSTR *pVal);

	// IIviDriverIdentity
	STDMETHOD(get_GroupCapabilities)(BSTR *pVal);
	STDMETHOD(get_Identifier)(BSTR *pVal);
	STDMETHOD(get_InstrumentFirmwareRevision)(BSTR *pVal);
	STDMETHOD(get_InstrumentManufacturer)(BSTR *pVal);
	STDMETHOD(get_InstrumentModel)(BSTR *pVal);
	STDMETHOD(get_SpecificationMajorVersion)(long *pVal);
	STDMETHOD(get_SpecificationMinorVersion)(long *pVal);
	STDMETHOD(get_SupportedInstrumentModels)(BSTR *pVal);

	// IIviDriverOperation methods
	STDMETHOD(get_LogicalName)(BSTR *pVal);
	STDMETHOD(get_IoResourceDescriptor)(BSTR *pVal);
	STDMETHOD(get_Cache)(VARIANT_BOOL *pVal);
	STDMETHOD(put_Cache)(VARIANT_BOOL newVal);
	STDMETHOD(get_InterchangeCheck)(VARIANT_BOOL *pVal);
	STDMETHOD(put_InterchangeCheck)(VARIANT_BOOL newVal);
	STDMETHOD(get_QueryInstrumentStatus)(VARIANT_BOOL *pVal);
	STDMETHOD(put_QueryInstrumentStatus)(VARIANT_BOOL newVal);
	STDMETHOD(get_RangeCheck)(VARIANT_BOOL *pVal);
	STDMETHOD(put_RangeCheck)(VARIANT_BOOL newVal);
	STDMETHOD(get_RecordCoercions)(VARIANT_BOOL *pVal);
	STDMETHOD(put_RecordCoercions)(VARIANT_BOOL newVal);
	STDMETHOD(get_Simulate)(VARIANT_BOOL *pVal);
	STDMETHOD(put_Simulate)(VARIANT_BOOL newVal);
	STDMETHOD(get_DriverSetup)(BSTR *pVal);
	STDMETHOD(InvalidateAllAttributes)();
	STDMETHOD(ClearInterchangeWarnings)();
	STDMETHOD(GetNextInterchangeWarning)(BSTR* Warning);
	STDMETHOD(ResetInterchangeCheck)();
	STDMETHOD(GetNextCoercionRecord)(BSTR* Record);
	
	// IIviClassWrapper
	STDMETHOD(AttachToExistingCSession)(long ViSession);
	STDMETHOD(get_NativeCHandle)(long *pVal);

	// Developer-customizable functions
protected:
	virtual HRESULT ProcessInitOptionStringDriverSetup(const CString& strDriverSetupString);
	virtual HRESULT ProcessInitOptionStringInherentAttribute(const CString& strName, const CString& strValue);
	virtual HRESULT ProcessInitOptionStringDriverSetupAttribute(const CString& strName, const CString& strValue);
	virtual HRESULT ValidateInitOptionString(const CString& strOptionString);
	virtual HRESULT InitializeIdentification();
	virtual HRESULT InitializeIO();
	virtual HRESULT CloseIO();
	virtual HRESULT ResetDefaults();

	virtual bool GetInterchangeCheckingSupported();
	virtual CString GetIdentificationCommand();
	virtual CString GetErrorQueryCommand();
	virtual CString GetErrorClearCommand();
	virtual CString GetDisableCommand();
	virtual long GetDisableTimeout();
	virtual CString GetResetCommand();
	virtual long GetResetTimeout();
	virtual CString GetSelfTestCommand();
	virtual long GetSelfTestTimeout();

private:
	HRESULT LoadDriverConfiguration(IIviDriverSessionPtr spDriverSession);
	HRESULT LoadConfigurableInitialSettings(IIviDriverSessionPtr spDriverSession);
	HRESULT LoadConfigurableInitialSettings(IIviSoftwareModulePtr spSoftwareModule);
	HRESULT LoadConfigurableInitialSettings(IIviStructurePtr spConfigSettings);
	HRESULT ProcessConfigurableInitialSettings();
	HRESULT ProcessInitOptionString(const CString& strOptionString);
	HRESULT LoadDriverResourceString(UINT nID, BSTR* pbstrResourceString);
};

template <class DriverClass>
bool IIviDriverImpl<DriverClass>::GetInterchangeCheckingSupported()
{
	return false;
}

template <class DriverClass>
CString IIviDriverImpl<DriverClass>::GetIdentificationCommand()
{
	return _T("*IDN?");
}

template <class DriverClass>
CString IIviDriverImpl<DriverClass>::GetErrorQueryCommand()
{
	return _T("SYST:ERR?");
}

template <class DriverClass>
CString IIviDriverImpl<DriverClass>::GetErrorClearCommand()
{
	return _T("*CLS");
}

template <class DriverClass>
CString IIviDriverImpl<DriverClass>::GetDisableCommand()
{
	// *OPC? is sent to make sure that we hold off
	// until the reset has completed. This is needed
	// when communicating using serial bus.
	return _T("*RST;*OPC?");
}

template <class DriverClass>
long IIviDriverImpl<DriverClass>::GetDisableTimeout()
{
	return 30000;
}

template <class DriverClass>
CString IIviDriverImpl<DriverClass>::GetResetCommand()
{
	// *OPC? is sent to make sure that we hold off
	// until the reset has completed. This is needed
	// when communicating using serial bus.
	return _T("*RST;*OPC?");
}

template <class DriverClass>
long IIviDriverImpl<DriverClass>::GetResetTimeout()
{
	return 30000;
}

template <class DriverClass>
CString IIviDriverImpl<DriverClass>::GetSelfTestCommand()
{
	return _T("*TST?");
}

template <class DriverClass>
long IIviDriverImpl<DriverClass>::GetSelfTestTimeout()
{
	return 30000;
}

// IIviDriver
template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::Initialize(BSTR bstrResourceDescriptor, VARIANT_BOOL bIdQuery, VARIANT_BOOL bReset, BSTR bstrOptionString)
{
	DriverClass* pT = static_cast<DriverClass*>(this);
	CComObject< CObjectModelRootNode<DriverClass> >* pNode = static_cast<CComObject< CObjectModelRootNode<DriverClass> >*>(this);

	if (bstrResourceDescriptor == NULL)
		return pT->ReportInvalidValueError(_T("Initialize"), _T("bstrResourceDescriptor"), (long)0);

	if (pT->m_bInitialized)
		return pT->ReportError(IDS_E_IVI_ALREADY_INITIALIZED);

	HRESULT hr = S_OK;

	try
	{
		CString strOptionString = COLE2T(bstrOptionString);
		pT->m_strInitOptionString = strOptionString;

		pT->m_strResourceDescriptor.Empty();
		pT->m_strClientExeName.Empty();
		pT->m_strLogicalName.Empty();
		pT->m_mapInherentOptions.RemoveAll();
		pT->m_mapDriverSetupOptions.RemoveAll();
		pT->m_mapConfigSettings.RemoveAll();
		pT->m_mapVirtualToPhysicalNames.RemoveAll();
		pT->m_mapPhysicalToVirtualNames.RemoveAll();
		pT->m_bIdentificationInitialized = false;

		IIviConfigStorePtr spConfigStore;
		hr = NtlLoadConfigStore(spConfigStore);
		if (SUCCEEDED(hr))
		{
			// Lookup the provided resource descriptor to see if it's a logical name
			IIviDriverSessionPtr spDriverSession = NULL;
			hr = NtlLoadDriverSession(spConfigStore, bstrResourceDescriptor, spDriverSession);
			if (SUCCEEDED(hr) && (spDriverSession != NULL))
			{
				// Found the associated driver session
				pT->m_strLogicalName = COLE2T(bstrResourceDescriptor);

				hr = LoadDriverConfiguration(spDriverSession);
				if (SUCCEEDED(hr))
				{
					hr = LoadConfigurableInitialSettings(spDriverSession);
					if (SUCCEEDED(hr))
					{
						// Values in the init option string override any found in the config store
						hr = ProcessInitOptionString(strOptionString);
					}
				}
			}
			else
			{
				// Resource descriptor is not a logical name
				pT->m_strResourceDescriptor = COLE2T(bstrResourceDescriptor);		

				//	Load the config settings from the software module
				_bstr_t bstrSoftwareModuleName = pT->GetIdentifier();
				IIviSoftwareModulePtr spSoftwareModule = NtlGetSoftwareModule(spConfigStore, bstrSoftwareModuleName);
				if (spSoftwareModule != NULL)
				{
					hr = LoadConfigurableInitialSettings(spSoftwareModule);
					if (SUCCEEDED(hr))
					{
						// Values in the init option string override any found in the config store
						hr = ProcessInitOptionString(strOptionString);
					}
				}
			}
		}
		else
		{
			// Couldn't create the ConfigStore object so parse the driver's configuration 
			// options from the provided option string
			pT->m_strResourceDescriptor = COLE2T(bstrResourceDescriptor);		
			hr = ProcessInitOptionString(strOptionString);
		}

		// Remember the original values of these options so that they can be restored if IIviDriver::ResetWithDefaults is called
		pT->m_bCacheOriginal = pT->m_bCache;
		pT->m_bInterchangeCheckOriginal = pT->m_bInterchangeCheck;
		pT->m_bQueryInstrStatusOriginal = pT->m_bQueryInstrStatus;
		pT->m_bRangeCheckOriginal = pT->m_bRangeCheck;
		pT->m_bRecordCoercionsOriginal = pT->m_bRecordCoercions;
		pT->m_bSimulateOriginal = pT->m_bSimulate;

		if (SUCCEEDED(hr))
		{
			hr = pT->InitializeTraceManager();
			if (SUCCEEDED(hr))
			{
				hr = InitializeIO();
			}
		}

		if (SUCCEEDED(hr))
		{
			pT->m_bInitialized = true;

			// Call to CObjectModelNode::FinalInitialize, NOT the DriverClass::FinalInitialize
			hr = pNode->FinalInitialize(bstrResourceDescriptor, bIdQuery, bReset, bstrOptionString);
		}

		if (SUCCEEDED(hr))
		{
			hr = pT->InitializeIdentification();
			if (SUCCEEDED(hr))
			{
				if (bIdQuery != VARIANT_FALSE || pT->m_bSimulate)
				{
					if (!pT->InstrumentSupported())
					{
						hr = pT->ReportError(IDS_E_IVI_ID_QUERY_FAILED);
					}
				}
			}
		}

		if (SUCCEEDED(hr) && (bReset != VARIANT_FALSE))
		{
			hr = ((IIviDriverUtility*)pT)->Reset();
			if (FAILED(hr))
			{
				hr = pT->ReportError(IDS_E_IVI_RESET_FAILED);
			}
		}

		if (SUCCEEDED(hr) && pT->m_bInterchangeCheck && !GetInterchangeCheckingSupported())
		{
			// If the user hasn't indicated interchangeability checking is supported, then we return an error
			// since NTL does not provide this capability by default.
			hr = pT->ReportErrorEx(IDS_E_IVI_BAD_OPTION_VALUE, _T("Interchange check is not supported by the driver."), _T("true"));
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::InitializeIO()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	// Developers can override this to do their own I/O
	// or initialize dependent DLLs instead of using DriverIOManager
	if (!pT->m_bSimulate)
	{
		hr = pT->InitializeIOManager(CComBSTR(pT->m_strResourceDescriptor));
	}

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::CloseIO()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	// Developers can override this to do their own I/O
	// or close dependent DLLs instead of using DriverIOManager
	hr = pT->CloseIOManager();
	ATLASSERT(SUCCEEDED(hr));

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::LoadDriverConfiguration(IIviDriverSessionPtr spDriverSession)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (spDriverSession == NULL)
		return pT->ReportInvalidValueError(_T("LoadDriverConfiguration"), _T("pDriverSession"), (long)0);

	HRESULT hr = S_OK;

	try
	{
		hr = pT->BuildVirtualNameMaps(spDriverSession);
		if (SUCCEEDED(hr))
		{
			pT->m_strResourceDescriptor = _T("");
			IIviHardwareAssetPtr spHardwareAsset = spDriverSession->HardwareAsset;
			if (spHardwareAsset != NULL)
			{
				_bstr_t bstrResourceDescriptor = spHardwareAsset->IOResourceDescriptor;
				pT->m_strResourceDescriptor = COLE2T(bstrResourceDescriptor);
			}
			pT->m_bCache = spDriverSession->Cache != VARIANT_FALSE;
			pT->m_bInterchangeCheck = spDriverSession->InterchangeCheck != VARIANT_FALSE;
			pT->m_bQueryInstrStatus = spDriverSession->QueryInstrStatus != VARIANT_FALSE;
			pT->m_bRangeCheck = spDriverSession->RangeCheck != VARIANT_FALSE;
			pT->m_bRecordCoercions = spDriverSession->RecordCoercions != VARIANT_FALSE;
			pT->m_bSimulate = spDriverSession->Simulate != VARIANT_FALSE;

			CString strDriverSetup = COLE2T(spDriverSession->DriverSetup);
			hr = ProcessInitOptionStringDriverSetup(strDriverSetup);
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::LoadConfigurableInitialSettings(IIviDriverSessionPtr spDriverSession)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (spDriverSession == NULL)
		return pT->ReportInvalidValueError(_T("LoadDriverConfiguration"), _T("spDriverSession"), (long)0);

	HRESULT hr = S_OK;

	try
	{
		IIviStructurePtr spConfigSettings = spDriverSession->DataComponents->Item["Configurable Initial Settings"];
		if (spConfigSettings != NULL)
		{
			hr = LoadConfigurableInitialSettings(spConfigSettings);
		}
	}
	catch (_com_error&)
	{
		// Not really an error => just means no configurable initial settings on driver session
	}

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::LoadConfigurableInitialSettings(IIviSoftwareModulePtr spSoftwareModule)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (spSoftwareModule == NULL)
		return pT->ReportInvalidValueError(_T("LoadConfigurableInitialSettings"), _T("spSoftwareModule"), (long)0);

	HRESULT hr = S_OK;

	try
	{
		IIviStructurePtr spConfigSettings = spSoftwareModule->DataComponents->Item["Configurable Initial Settings"];
		if (spConfigSettings != NULL)
		{
			hr = LoadConfigurableInitialSettings(spConfigSettings);
		}
	}
	catch (_com_error&)
	{
		// Not really an error => just no configuration initial settings on this software module
	}

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::LoadConfigurableInitialSettings(IIviStructurePtr spConfigSettings)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (spConfigSettings == NULL)
		return pT->ReportInvalidValueError(_T("LoadConfigurableInitialSettings"), _T("spConfigSettings"), (long)0);

	HRESULT hr = S_OK;

	try
	{
		IIviDataComponentCollectionPtr spDataComponents = spConfigSettings->DataComponents;
		if (spDataComponents != NULL)
		{
			for (int i = 0; i < spDataComponents->Count; i++)
			{
				IIviDataComponentPtr spConfigSetting = spDataComponents->Item[i+1];
				CString strName = COLE2T(spConfigSetting->Name);
				strName.Trim();
				strName.MakeLower();
				CString strType = COLE2T(spConfigSetting->Type);
				if (strType.CompareNoCase(_T("String")) == 0)
				{
					IIviStringPtr spString = spConfigSetting;
					pT->m_mapConfigSettings[strName] = CComVariant(spString->Value.GetBSTR());
				}
				else if (strType.CompareNoCase(_T("Integer")) == 0)
				{
					IIviIntegerPtr spInteger = spConfigSetting;
					pT->m_mapConfigSettings[strName] = CComVariant(spInteger->Value);
				}
				else if (strType.CompareNoCase(_T("Real")) == 0)
				{
					IIviRealPtr spReal = spConfigSetting;
					pT->m_mapConfigSettings[strName] = CComVariant(spReal->Value);
				}
				else if (strType.CompareNoCase(_T("Boolean")) == 0)
				{
					IIviBooleanPtr spBoolean = spConfigSetting;
					pT->m_mapConfigSettings[strName] = CComVariant(spBoolean->Value);
				}
				else if (strType.CompareNoCase(_T("Structure")) == 0)
				{
					// Skip structures
				}
				else if (strType.CompareNoCase(_T("APIReference")) == 0)
				{
					// Skip API references
				}
			}

			hr = ProcessConfigurableInitialSettings();
		}
	}
	catch (_com_error& e)
	{
		hr = e.Error();
	}

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::ProcessConfigurableInitialSettings()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	// The only configurable initial setting we currently support automatically is the simulated "model"
	CString strSimulatedModel;
	if (pT->GetConfigSetting(_T("model"), strSimulatedModel))
	{
		pT->m_strModel = strSimulatedModel;
	}

	return S_OK;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::ProcessInitOptionString(const CString& strOptionStringIn)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	pT->m_mapInherentOptions.RemoveAll();

	if (!strOptionStringIn.IsEmpty())
	{
		hr = ValidateInitOptionString(strOptionStringIn);
		if (SUCCEEDED(hr))
		{
			CString strOptionString = strOptionStringIn;
			CString strOptionStringUpper = strOptionString;
			strOptionStringUpper.MakeUpper();

			// Look for the DriverSetup information
			int nDriverSetupIndex = strOptionStringUpper.Find(_T("DRIVERSETUP="));
			if (nDriverSetupIndex >= 0)
			{
				int nLen = strOptionString.GetLength();
				CString strDriverSetupString = strOptionString.Right(nLen - nDriverSetupIndex - 12);
				strOptionString = strOptionString.Left(nDriverSetupIndex);
				hr = ProcessInitOptionStringDriverSetup(strDriverSetupString);
			}

			if (SUCCEEDED(hr))
			{
				CAtlMap<CString, CString> mapPairs;
				hr = ParseNameValuePairs(strOptionString, mapPairs);
				if (SUCCEEDED(hr))
				{
					POSITION pos = mapPairs.GetStartPosition();
					while ((pos != NULL) && SUCCEEDED(hr))
					{
						CString strName;
						CString strValue;
						mapPairs.GetNextAssoc(pos, strName, strValue);
						hr = ProcessInitOptionStringInherentAttribute(strName, strValue);
					}
				}
			}
		}
	}

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::ValidateInitOptionString(const CString& strOptionString)
{
	NTL_UNUSED(strOptionString);

	HRESULT hr = S_OK;

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::ProcessInitOptionStringDriverSetup(const CString& strDriverSetupString)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	pT->m_mapDriverSetupOptions.RemoveAll();
	pT->m_strDriverSetup = strDriverSetupString;

	// Try to parse out name-value pairs, if they exist
	CAtlMap<CString, CString> mapPairs;
	hr = ParseNameValuePairs(strDriverSetupString, mapPairs);
	if (SUCCEEDED(hr))
	{
		POSITION pos = mapPairs.GetStartPosition();
		while ((pos != NULL) && SUCCEEDED(hr))
		{
			CString strName;
			CString strValue;
			mapPairs.GetNextAssoc(pos, strName, strValue);
			hr = ProcessInitOptionStringDriverSetupAttribute(strName, strValue);
		}
	}

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::ProcessInitOptionStringDriverSetupAttribute(const CString& strName, const CString& strValue) 
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	CString strNameTemp = strName;
	strNameTemp.Trim();
	strNameTemp.MakeLower();

	if (strNameTemp.CompareNoCase(_T("Model")) == 0)
	{
		pT->m_strModel = strValue;
	}
	else if (strNameTemp.CompareNoCase(_T("Trace")) == 0)
	{
		if (strValue.CompareNoCase(_T("true")) == 0 || strValue.CompareNoCase(_T("1")) == 0)
		{
			hr = pT->m_pTracer->SetTraceEnabled(true);
		}
		else if (strValue.CompareNoCase(_T("false")) == 0 || strValue.CompareNoCase(_T("0")) == 0)
		{
			hr = pT->m_pTracer->SetTraceEnabled(false);
		}
		else
		{
			hr = pT->ReportError(IDS_E_IVI_BAD_OPTION_VALUE, LPCTSTR(strValue));
		}
	}
	else if (strNameTemp.CompareNoCase(_T("TraceName")) == 0)
	{
		hr = pT->m_pTracer->SetTraceName(strValue);
	}
	else if (strNameTemp.CompareNoCase(_T("TraceSizeMax")) == 0)
	{
		hr = pT->m_pTracer->SetTraceSizeMax(_ttol(LPCTSTR(strValue)));
	}
	else if (strNameTemp.CompareNoCase(_T("TraceArray")) == 0)
	{
		if (strValue.CompareNoCase(_T("true")) == 0 || strValue.CompareNoCase(_T("1")) == 0)
		{
			hr = pT->m_pTracer->SetTraceArray(true);
		}
		else if (strValue.CompareNoCase(_T("false")) == 0 || strValue.CompareNoCase(_T("0")) == 0)
		{
			hr = pT->m_pTracer->SetTraceArray(false);
		}
		else
		{
			hr = pT->ReportError(IDS_E_IVI_BAD_OPTION_VALUE, LPCTSTR(strValue));
		}
	}
	else if (strNameTemp.CompareNoCase(_T("TraceArraySizeMax")) == 0)
	{
		hr = pT->m_pTracer->SetTraceArraySizeMax(_ttol(LPCTSTR(strValue)));
	}
	else if (strNameTemp.CompareNoCase(_T("TraceSaveInterval")) == 0)
	{
		hr = pT->m_pTracer->SetTraceSaveInterval(_ttol(LPCTSTR(strValue)));
	}
	else if (strNameTemp.CompareNoCase(_T("TraceTransform")) == 0)
	{	
		hr = pT->m_pTracer->SetTraceTransformPath(CPath(strValue));
	}

	// Add the pair to the option/value map
	pT->m_mapDriverSetupOptions[strNameTemp] = strValue;

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::ProcessInitOptionStringInherentAttribute(const CString& strName, const CString& strValue) 
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (strName.IsEmpty())
		return pT->ReportInvalidValueError(_T("ProcessInitOption"), _T("strNameIn"), (long)0);

	if (strValue.IsEmpty())
		return pT->ReportInvalidValueError(_T("ProcessInitOption"), _T("strValueIn"), (long)0);

	HRESULT hr = S_OK;

	CString strNameTemp = strName;
	CString strValueTemp = strValue;

	strNameTemp.Trim();
	strValueTemp.Trim();

	strNameTemp.MakeLower();

	if (strNameTemp.CompareNoCase(_T("cache")) == 0)
	{
		if (strValueTemp.CompareNoCase(_T("true")) == 0 || strValueTemp.CompareNoCase(_T("1")) == 0)
		{
			pT->m_bCache = true;
		}
		else if (strValueTemp.CompareNoCase(_T("false")) == 0 || strValueTemp.CompareNoCase(_T("0")) == 0)
		{
			pT->m_bCache = false;
		}
		else
		{
			hr = pT->ReportError(IDS_E_IVI_BAD_OPTION_VALUE, LPCTSTR(strValue));
		}
	}
	else if (strNameTemp.CompareNoCase(_T("interchangecheck")) == 0)
	{
		if (strValueTemp.CompareNoCase(_T("true")) == 0 || strValueTemp.CompareNoCase(_T("1")) == 0)
		{
			pT->m_bInterchangeCheck = true;
		}
		else if (strValueTemp.CompareNoCase(_T("false")) == 0 || strValueTemp.CompareNoCase(_T("0")) == 0)
		{
			pT->m_bInterchangeCheck = false;
		}
		else
		{
			hr = pT->ReportError(IDS_E_IVI_BAD_OPTION_VALUE, LPCTSTR(strValue));
		}
	}
	else if (strNameTemp.CompareNoCase(_T("queryinstrstatus")) == 0)
	{
		if (strValueTemp.CompareNoCase(_T("true")) == 0 || strValueTemp.CompareNoCase(_T("1")) == 0)
		{
			pT->m_bQueryInstrStatus = true;
		}
		else if (strValueTemp.CompareNoCase(_T("false")) == 0 || strValueTemp.CompareNoCase(_T("0")) == 0)
		{
			pT->m_bQueryInstrStatus = false;
		}
		else
		{
			hr = pT->ReportError(IDS_E_IVI_BAD_OPTION_VALUE, LPCTSTR(strValue));
		}
	}
	else if (strNameTemp.CompareNoCase(_T("rangecheck")) == 0)
	{
		if (strValueTemp.CompareNoCase(_T("true")) == 0 || strValueTemp.CompareNoCase(_T("1")) == 0)
		{
			pT->m_bRangeCheck = true;
		}
		else if (strValueTemp.CompareNoCase(_T("false")) == 0 || strValueTemp.CompareNoCase(_T("0")) == 0)
		{
			pT->m_bRangeCheck = false;
		}
		else
		{
			hr = pT->ReportError(IDS_E_IVI_BAD_OPTION_VALUE, LPCTSTR(strValue));
		}
	}
	else if (strNameTemp.CompareNoCase(_T("recordcoercions")) == 0)
	{
		if (strValueTemp.CompareNoCase(_T("true")) == 0 || strValueTemp.CompareNoCase(_T("1")) == 0)
		{
			pT->m_bRecordCoercions = true;
		}
		else if (strValueTemp.CompareNoCase(_T("false")) == 0 || strValueTemp.CompareNoCase(_T("0")) == 0)
		{
			pT->m_bRecordCoercions = false;
		}
		else
		{
			hr = pT->ReportError(IDS_E_IVI_BAD_OPTION_VALUE, LPCTSTR(strValue));
		}
	}
	else if (strNameTemp.CompareNoCase(_T("simulate")) == 0)
	{
		if (strValueTemp.CompareNoCase(_T("true")) == 0 || strValueTemp.CompareNoCase(_T("1")) == 0)
		{
			pT->m_bSimulate = true;
		}
		else if (strValueTemp.CompareNoCase(_T("false")) == 0 || strValueTemp.CompareNoCase(_T("0")) == 0)
		{
			pT->m_bSimulate = false;
		}
		else
		{
			hr = pT->ReportError(IDS_E_IVI_BAD_OPTION_VALUE, LPCTSTR(strValue));
		}
	}

	if (SUCCEEDED(hr))
	{
		// Add the pair to the option/value map
		pT->m_mapInherentOptions[strNameTemp] = strValueTemp;
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::Close() 
{
	DriverClass* pT = static_cast<DriverClass*>(this);
	CComObject< CObjectModelRootNode<DriverClass> >* pNode = static_cast<CComObject< CObjectModelRootNode<DriverClass> >*>(this);

	if (!pT->GetInitialized())
		return pT->ReportError(IDS_E_IVI_NOT_INITIALIZED);

	HRESULT hr = S_OK;

	if (pT->m_pTracer->GetTraceEnabled())
	{
		hr = pT->m_pTracer->SaveTraceData();
		ATLASSERT(SUCCEEDED(hr));
	}

	hr = CloseIO();

	// It's important to delete all cache entries when closing, else there
	// will be an error if we try and re-initialize the same driver instance.
	pT->DestroyAllCacheEntries();

	pT->m_bInitialized = false;

	// Call to CObjectModelNode::FinalClose, NOT the DriverClass::FinalClose
	hr = pNode->FinalClose();

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::InitializeIdentification()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pT->m_bSimulate)
		return S_OK;

	if (pT->m_bIdentificationInitialized)
		return S_OK;

	CString strID;
	HRESULT hr = pT->InstrQuery(GetIdentificationCommand(), strID, 1024);
	if (SUCCEEDED(hr))
	{
		CString strIdentification = strID;
		CString strManufacturer;
		CString strModel;
		CString strSerialNumber;
		CString strFirmwareRevision;

		CString strToken;
		int curPos = 0;
		strManufacturer = strID.Tokenize(_T(","), curPos).Trim();
		if (curPos >= 0)
		{
			strModel = strID.Tokenize(_T(","), curPos).Trim();
		}

		if (curPos >= 0)
		{
			strSerialNumber = strID.Tokenize(_T(","), curPos).Trim();
		}

		if (curPos >= 0)
		{
			strFirmwareRevision = strID.Tokenize(_T(","), curPos).Trim();
		}

		pT->SetIdentificationInfo(strIdentification, strManufacturer, strModel, strSerialNumber, strFirmwareRevision);
	}
	
	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_DriverOperation(IIviDriverOperation** pVal)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pVal == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_DriverOperation"), _T("pVal"));
	*pVal = NULL;

	return pT->_InternalQueryInterface(__uuidof(*pVal), (void**)(pVal));
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Identity(IIviDriverIdentity** pVal)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pVal == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Identity"), _T("pVal"));
	*pVal = NULL;

	return pT->_InternalQueryInterface(__uuidof(*pVal), (void**)(pVal));
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Initialized(VARIANT_BOOL* pbInitialized)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbInitialized == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Initialized"), _T("pbInitialized"));

	*pbInitialized = pT->m_bInitialized ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Utility(IIviDriverUtility** pVal)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pVal == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Utility"), _T("pVal"));
	*pVal = NULL;

	return pT->_InternalQueryInterface(__uuidof(*pVal), (void**)(pVal));
}


// IIviDriverUtility
template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::Disable()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	if (!pT->GetSimulate())
	{
		long lResult = 0;
		hr = pT->InstrQuery(GetDisableCommand(), &lResult, GetDisableTimeout());
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::ErrorQuery(long* pCode, BSTR* pMessage)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pMessage == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("ErrorQuery"), _T("pMessage"));
	if ((*pMessage) != NULL)
		::SysFreeString(*pMessage);
	*pMessage = NULL;

	if (pCode == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("ErrorQuery"), _T("pCode"));
	*pCode = 0;

	HRESULT hr = S_OK;

	if (!pT->GetSimulate())
	{
		CString str;
		hr = pT->InstrQuery(GetErrorQueryCommand(), str, 1023);
		if (SUCCEEDED(hr))
		{
			wchar_t mess[1024];
			if (swscanf(CT2W(str), OLESTR("%d,\"%[^'\"']\""), pCode, mess) == 2)
			{
				*pMessage = ::SysAllocString(mess);

				hr = S_OK;
			}
			else
			{
				hr = pT->ReportError(IDS_E_IVI_UNEXPECTED_RESPONSE);
			}
		}
	}
	else
	{
		*pCode = 0;
		*pMessage = CComBSTR("No error.").Detach();
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::LockObject()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	return pT->ReportError(IDS_E_IVI_METHOD_NOT_SUPPORTED, _T("LockObject"));
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::Reset()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	if (!pT->GetSimulate())
	{
		long lResult = 0;
		hr = pT->InstrQuery(GetResetCommand(), &lResult, GetResetTimeout());
	}

	pT->InternalResetObjectState();

	pT->InvalidateCacheAll();

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::ResetWithDefaults()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	if (!pT->GetSimulate())
	{
		long lResult = 0;
		hr = pT->InstrQuery(GetResetCommand(), &lResult, GetResetTimeout());
	}

	pT->InternalResetObjectState();

	pT->InvalidateCacheAll();

	if (SUCCEEDED(hr))
	{
		hr = ResetDefaults();
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::SelfTest(long* pResult, BSTR* pMessage)
{
	DriverClass* pT = static_cast<DriverClass*>(this);
	
	if (pMessage == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("SelfTest"), _T("pResult"));
	if ((*pMessage) != NULL)
		::SysFreeString(*pMessage);
	*pMessage = NULL;
	
	if (pResult == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("SelfTest"), _T("pMessage"));
	*pResult = 0;
	
	HRESULT hr = S_OK;

	if (!pT->GetSimulate())
	{
		hr = pT->InstrQuery(GetSelfTestCommand(), pResult, GetSelfTestTimeout());
		if (SUCCEEDED(hr))
		{
			if (*pResult == 0)
			{
				*pMessage = ::SysAllocString(OLESTR("Selftest passed"));
			}
			else
			{
				*pMessage = ::SysAllocString(OLESTR("Selftest failed"));
			}
		}
	}
	else
	{
		*pResult = 0;
		*pMessage = CComBSTR("Selftest passed").Detach();
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::UnlockObject()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	return pT->ReportError(IDS_E_IVI_METHOD_NOT_SUPPORTED, _T("UnlockObject"));
}

// IIviComponentIdentity
template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Description(BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Description"), _T("pbstr"));
	*pbstr = NULL;

	CString str = pT->GetDescription();

	*pbstr = ::SysAllocString(CT2OLE(str));

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Revision(BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Revision"), _T("pbstr"));
	*pbstr = NULL;

	CString str = pT->GetRevision();

	*pbstr = ::SysAllocString(CT2OLE(str));

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Vendor(BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Vendor"), _T("pbstr"));
	*pbstr = NULL;

	CString str = pT->GetVendor();

	*pbstr = ::SysAllocString(CT2OLE(str));

	return S_OK;
}

// IIviDriverIdentity
template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_GroupCapabilities(BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_GroupCapabilities"), _T("pbstr"));
	*pbstr = NULL;

	CString str = pT->GetGroupCapabilities();

	*pbstr = ::SysAllocString(CT2OLE(str));

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Identifier(BSTR* pVal)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pVal == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Identifier"), _T("pVal"));
	*pVal = NULL;

	CString str = pT->GetIdentifier();

	*pVal = ::SysAllocString(CT2OLE(str));

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_InstrumentFirmwareRevision(BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_InstrumentFirmwareRevision"), _T("pbstr"));
	*pbstr = NULL;

	HRESULT hr = S_OK;

	if (pT->GetSimulate())
	{
		// When simulating, the instrument firmware revision is the driver revision
		CString strFirmwareRevision =  CString(_T("Sim")) + pT->GetRevision();
		*pbstr = strFirmwareRevision.AllocSysString();
	}
	else
	{
		*pbstr = pT->m_strFirmwareRevision.AllocSysString();
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_InstrumentManufacturer(BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_InstrumentManufacturer"), _T("pbstr"));
	*pbstr = NULL;

	HRESULT hr = S_OK;

	if (pT->GetSimulate())
	{
		// When simulating, the instrument manufacturer is the driver vendor
		*pbstr = pT->GetVendor().AllocSysString();
	}
	else
	{
		*pbstr = pT->m_strManufacturer.AllocSysString();
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_InstrumentModel(BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_InstrumentModel"), _T("pbstr"));
	*pbstr = NULL;

	HRESULT hr = S_OK;

	*pbstr = pT->m_strModel.AllocSysString();

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_SpecificationMajorVersion(long* plVer)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (plVer == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_SpecificationMajorVersion"), _T("plVer"));
	*plVer = 0;

	*plVer = pT->GetSpecificationMajorVersion();

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_SpecificationMinorVersion(long* plVer)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (plVer == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_SpecificationMinorVersion"), _T("plVer"));
	*plVer = 0;

	*plVer = pT->GetSpecificationMinorVersion();

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_SupportedInstrumentModels(BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_SupportedInstrumentModels"), _T("pbstr"));
	*pbstr = NULL;

	// Returns a comma-separated list of names of instrument models with which the 
	// IVI specific driver is compatible. The string has no white space except 
	// possibly embedded in the instrument model names. (IVI 3.2)
	CString strModels;
	const InstrModelMap* pModelMap = CInstrumentModelManager::GetModelMap();	
	POSITION pos = pModelMap->GetStartPosition();
	while (pos != NULL)
	{
		CString strModel = pModelMap->GetNextKey(pos);
		strModels += strModel + _T(",");
	}
	strModels.TrimRight();
	strModels.TrimRight(_T(','));

	*pbstr = CComBSTR(LPCTSTR(strModels)).Detach();

	return S_OK;
}

// IIviDriverOperation
template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_LogicalName(BSTR* pbstrName)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstrName == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_LogicalName"), _T("pbstrName"));

	*pbstrName = ::SysAllocString(CT2OLE(LPCTSTR(pT->m_strLogicalName)));

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_IoResourceDescriptor(BSTR* pbstrName)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstrName == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_IoResourceDescriptor"), _T("pbstrName"));

	*pbstrName = ::SysAllocString(CT2OLE(LPCTSTR(pT->m_strResourceDescriptor)));

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Cache(VARIANT_BOOL* pbCache)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbCache == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Cache"), _T("pbCache"));

	*pbCache = pT->m_bCache ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::put_Cache(VARIANT_BOOL bCache)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	pT->m_bCache = (bCache != VARIANT_FALSE);

	if (bCache == VARIANT_FALSE)
	{
		InvalidateAllAttributes();
	}

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_InterchangeCheck(VARIANT_BOOL* pbInterchangeCheck)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbInterchangeCheck == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_InterchangeCheck"), _T("pbInterchangeCheck"));

	*pbInterchangeCheck = pT->m_bInterchangeCheck ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::put_InterchangeCheck(VARIANT_BOOL bInterchangeCheck)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if ((bInterchangeCheck != VARIANT_FALSE) && !GetInterchangeCheckingSupported())
		return pT->ReportError(IDS_E_IVI_VALUE_NOT_SUPPORTED, _T("VARIANT_TRUE"), _T("bInterchangeCheck"), _T("put_InterchangeCheck"));

	pT->m_bInterchangeCheck = (bInterchangeCheck != VARIANT_FALSE);

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_QueryInstrumentStatus(VARIANT_BOOL* pbQueryInstrStatus)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbQueryInstrStatus == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_QueryInstrumentStatus"), _T("pbQueryInstrStatus"));

	*pbQueryInstrStatus = pT->m_bQueryInstrStatus ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::put_QueryInstrumentStatus(VARIANT_BOOL bQueryInstrStatus)
{
	HRESULT hr = S_OK;

	DriverClass* pT = static_cast<DriverClass*>(this);

	pT->m_bQueryInstrStatus = (bQueryInstrStatus != VARIANT_FALSE);

	if (pT->m_bQueryInstrStatus)
	{
		if (!pT->GetSimulate())
		{
			// Clear any old errors that may be in the queue.
			// We do this to prevent the next driver method/property to
			// from finding errors in the error queue that could have been
			// generated by previous methods/properties when QueryInstrStatus was off.
			hr = pT->InstrWrite(GetErrorClearCommand());
		}
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_RangeCheck(VARIANT_BOOL* pbRangeCheck)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbRangeCheck == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_RangeCheck"), _T("pbRangeCheck"));

	*pbRangeCheck = pT->m_bRangeCheck ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::put_RangeCheck(VARIANT_BOOL bRangeCheck)
{
	DriverClass* pT = static_cast<DriverClass*>(this);
	
	pT->m_bRangeCheck = (bRangeCheck != VARIANT_FALSE);

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_RecordCoercions(VARIANT_BOOL* pbRecordCoercions)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbRecordCoercions == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_RecordCoercions"), _T("pbRecordCoercions"));

	*pbRecordCoercions = pT->m_bRecordCoercions ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::put_RecordCoercions(VARIANT_BOOL bRecordCoercions)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	pT->m_bRecordCoercions = (bRecordCoercions != VARIANT_FALSE);

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_Simulate(VARIANT_BOOL* pbSimulate)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbSimulate == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_Simulate"), _T("pbSimulate"));

	*pbSimulate = pT->m_bSimulate ? VARIANT_TRUE : VARIANT_FALSE;

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::put_Simulate(VARIANT_BOOL bSimulate)
{
	HRESULT hr = S_OK;

	DriverClass* pT = static_cast<DriverClass*>(this);

	// If the IVI specific driver is initialized with Simulate set to True, the 
	// specific driver shall return the Cannot Change Simulation State error if 
	// the user attempts to set Simulate to False prior to calling the Close 
	// function. (IVI 3.2)
	if (pT->m_bSimulateOriginal && (bSimulate == VARIANT_FALSE))
		return pT->ReportError(IDS_E_IVI_CANNOT_CHANGE_SIMULATION_STATE);
	
	pT->m_bSimulate = (bSimulate != VARIANT_FALSE);

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_DriverSetup(BSTR* pVal)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pVal == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_DriverSetup"), _T("pVal"));
	*pVal = NULL;

	HRESULT hr = S_OK;

	*pVal = ::SysAllocString(CT2OLE(LPCTSTR(pT->m_strDriverSetup)));

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::InvalidateAllAttributes()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	pT->InvalidateCacheAll();

	return S_OK;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::ClearInterchangeWarnings()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	return pT->ReportError(IDS_E_IVI_METHOD_NOT_SUPPORTED, _T("ClearInterchangeWarnings"));
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::GetNextInterchangeWarning(BSTR* pbstrWarning)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstrWarning == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("GetNextInterchangeWarning"), _T("pbstrWarning"));
	*pbstrWarning = NULL;

	return pT->ReportError(IDS_E_IVI_METHOD_NOT_SUPPORTED, _T("GetNextInterchangeWarning"));
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::ResetInterchangeCheck()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	return pT->ReportError(IDS_E_IVI_METHOD_NOT_SUPPORTED, _T("ResetInterchangeCheck"));
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::GetNextCoercionRecord(BSTR* pbstrRecord)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstrRecord == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("GetNextCoercionRecord"), _T("pbstrRecord"));
	*pbstrRecord = NULL;

	if (pT->GetRecordCoercions())
	{
		*pbstrRecord = ::SysAllocString(CT2OLE(LPCTSTR(pT->GetNextCoercionRecordAndRemove())));
	}
	else
	{
		*pbstrRecord = CComBSTR("").Detach();
	}

	return S_OK;
}

// IIviClassWrapper
template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::AttachToExistingCSession(long ViSession)
{
	NTL_UNUSED(ViSession);

	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;
	if (pT->m_pWrappedDriver != NULL)
	{
		ATLASSERT(!"Not Implemented.");
	}
	else
	{
		hr = pT->ReportError(IDS_E_IVI_METHOD_NOT_SUPPORTED, _T("AttachToExistingCSession"));
	}

	return hr;
}

template <class DriverClass>
STDMETHODIMP IIviDriverImpl<DriverClass>::get_NativeCHandle(long* pVal)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pVal == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("get_NativeCHandle"), _T("pVal"));
	*pVal = 0;

	HRESULT hr = S_OK;
	if (pT->m_pWrappedDriver != NULL)
	{
		UINT nSession = pT->m_pWrappedDriver->GetVisaSession();
		*pVal = (long)nSession;
	}
	else
	{
		hr = pT->ReportError(IDS_E_IVI_PROPERTY_NOT_SUPPORTED, _T("get_NativeCHandle"));
	}

	return hr;
}


template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::LoadDriverResourceString(UINT nID, BSTR* pbstr)
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	if (pbstr == NULL)
		return pT->ReportError(IDS_E_IVI_NULL_POINTER, _T("LoadDriverResourceString"), _T("pbstr"));
	*pbstr = NULL;

	HRESULT hr = ::LoadDriverResourceString(nID, pbstr);

	return hr;
}

template <class DriverClass>
HRESULT IIviDriverImpl<DriverClass>::ResetDefaults()
{
	DriverClass* pT = static_cast<DriverClass*>(this);

	HRESULT hr = S_OK;

	// NOTE: It is very important to call put_Cache, etc. rather than simply setting the member variables directly.
	// This is because CPersonalityHost overrides these functions to pass the new settings on to the personalities.
	if (SUCCEEDED(hr))
	{
		hr = put_Cache(pT->m_bCacheOriginal ? VARIANT_TRUE : VARIANT_FALSE);
	}

	if (SUCCEEDED(hr))
	{
		hr = put_InterchangeCheck(pT->m_bInterchangeCheckOriginal ? VARIANT_TRUE : VARIANT_FALSE);
	}

	if (SUCCEEDED(hr))
	{
		hr = put_QueryInstrumentStatus(pT->m_bQueryInstrStatusOriginal ? VARIANT_TRUE : VARIANT_FALSE);
	}

	if (SUCCEEDED(hr))
	{
		hr = put_RangeCheck(pT->m_bRangeCheckOriginal ? VARIANT_TRUE : VARIANT_FALSE);
	}

	if (SUCCEEDED(hr))
	{
		hr = put_RecordCoercions(pT->m_bRecordCoercionsOriginal ? VARIANT_TRUE : VARIANT_FALSE);
	}

	if (SUCCEEDED(hr))
	{
		hr = put_Simulate(pT->m_bSimulateOriginal ? VARIANT_TRUE : VARIANT_FALSE);
	}

	return hr;
}

#endif // __NTLIIVIDRIVERIMPL_INCLUDED
