Implementing a New Verification Point

prevnext

Fundamentals for Implementing a Verification Point


QualityArchitect provides a framework for implementing COM/DCOM verification points. This framework includes interfaces for all of the components you must implement, as well as a VPFramework component that provides much of the required implementation for all verification points.

Any verification point type that you implement should inherit the VPFramework component's implementation. Since COM does not support implementation inheritance, you acccomplish this task through COM containment. With COM containment, methods and properties inherited from the framework appear as part of your verification point type. This minimizes the amount of code that a test designer has to write to perform a simple verification point, plus it eliminates the need for a QueryInterface operation.

For more information, see Essential COM by Don Box.


Task Summary

To implement a new verification point type, you must implement the interfaces and components documented in the following sections:


Interface for Your Verification Point Component

Your verification point component's interface must contain properties or methods for defining the verification point. This interface must inherit from the IVerificationPoint interface -- for example:

[
	 object,
	 uuid(7C4870B0-6E1A-11D4-9A26-0010A4E86989),
	 dual,
	 helpstring("IDatabaseVP Interface"),
	 pointer_default(unique)
]
interface IDatabaseVP : IVerificationPoint
{
	 [propget, helpstring("property ConnectionString")] 
	 	 	 HRESULT ConnectionString([out, retval] BSTR *pVal);
	 [propput, helpstring("property ConnectionString")] 
	 	 	 HRESULT ConnectionString([in] BSTR newVal);
	 [propget, helpstring("property SQL")] HRESULT SQL([out, retval] 
	 	 	 BSTR *pVal);
	 [propput, helpstring("property SQL")] HRESULT SQL([in] BSTR newVal);
};

The Verification Point Component

Your specialized verification point must perform the following tasks:

To enable your specialized Verification Point component to perform these tasks, you must implement the following interfaces:

Here is an example class declaration for a Verification Point component:

class ATL_NO_VTABLE CDatabaseVP : 
	 public CComObjectRootEx<CComSingleThreadModel>,
	 public CComCoClass<CDatabaseVP, &CLSID_DatabaseVP>,
	 public IDispatchImpl<IDatabaseVP, &IID_IDatabaseVP,
	 	 	 &LIBID_RTCOMVPLib>,
	 public IPersistFile
{
public:
	 CDatabaseVP()
	 {
	 }

DECLARE_REGISTRY_RESOURCEID(IDR_DATABASEVP)
DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct();

BEGIN_COM_MAP(CDatabaseVP)
	 COM_INTERFACE_ENTRY(IDatabaseVP)
	 COM_INTERFACE_ENTRY(IVerificationPoint)
	 COM_INTERFACE_ENTRY2(IDatabaseVP, IDispatch)
	 COM_INTERFACE_ENTRY(IPersistFile)
END_COM_MAP()

// IVerificationPoint
public:
	 STDMETHOD(CodeFactoryGetConstructorInvocation)
	 	 	 	 (CTDScriptTypes Language, BSTR * Code);
	 STDMETHOD(CodeFactoryGetNumExternallizedInputs)
	 	 	 	 (CTDScriptTypes Language, short * NumInputs);
	 STDMETHOD(CodeFactoryGetExternalizedInputDecl)
	 	 	 	 (CTDScriptTypes Language, short InputNumber, BSTR * Code);
	 STDMETHOD(CodeFactoryGetExternalizedInputInit)(/*[in]*/
	 	 	 	 CTDScriptTypes Language, /*[in]*/ short InputNumber,
	 	 	 	 /*[out, retval]*/ BSTR *Code);
	 STDMETHOD(CodeFactoryGetNumPropertySet)(/*[in]*/ 
	 	 	 	 CTDScriptTypes Language, /*[out, retval]*/ 
	 	 	 	 short *NumProps);
	 STDMETHOD(CodeFactoryGetPropertySet)(/*[in]*/ 
	 	 	 	 CTDScriptTypes Language, /*[in]*/ short InputNumber,
	 	 	 	 /*[out, retval]*/ BSTR *Code);
	 STDMETHOD(DefineVP)();

// IDatabaseVP
public:
	 STDMETHOD(get_SQL)(/*[out, retval]*/ BSTR *pVal);
	 STDMETHOD(put_SQL)(/*[in]*/ BSTR newVal);
	 STDMETHOD(get_ConnectionString)(/*[out, retval]*/ BSTR *pVal);
	 STDMETHOD(put_ConnectionString)(/*[in]*/ BSTR newVal);

// IPersistFile
public:
	 STDMETHOD(GetCurFile)(LPOLESTR *ppszFileName);
	 STDMETHOD(SaveCompleted)(LPCOLESTR pszFileName);
	 STDMETHOD(Save)(LPCOLESTR pszFileName, BOOL fRemember);
	 STDMETHOD(Load)(LPCOLESTR pszFileName, DWORD dwMode);
	 STDMETHOD(IsDirty)();
	 STDMETHOD(GetClassID)(CLSID *pClassID);

// IVPFramework
public:
//	 STDMETHOD(InitializeFramework)(BSTR Name, BSTR VPComparator, 
	 	 	 BSTR VPData, BSTR VPDataProvider, BSTR VPDataRenderer);
	 STDMETHOD(PerformTest)(/*[in]*/ VARIANT Object, /*[optional, in]*/
	 	 	 VARIANT ExpectedData, /*[optional, in]*/ 
	 	 	 VARIANT ActualData, /*[out, retval]*/ enum VPResult *Result);
	 STDMETHOD(get_VPname)(/*[out, retval]*/ BSTR *pVal);
	 STDMETHOD(put_VPname)(/*[in]*/ BSTR newVal);
	 STDMETHOD(get_Options)(/*[out, retval]*/ VARIANT *pVal);
	 STDMETHOD(put_Options)(/*[in]*/ VARIANT newVal);
	 STDMETHOD(get_VP)( IDispatch ** ppVP );
	 STDMETHOD(put_VP)( IDispatch * pVP );
	 STDMETHOD(get_Plumbing)( IDispatch ** ppPlumbing );
	 STDMETHOD(put_Plumbing)( IDispatch * pPlumbing );
	 STDMETHOD(get_CodeFactorySuffix)( BSTR* pVal );
	 STDMETHOD(put_CodeFactorySuffix)( BSTR newVal );

private:
	 HRESULT CalcIsDefined();
	 CComPtr<IVPFramework> m_pFramework;
	 LONG lOptions;
	 _bstr_t bstConnectionString;
	 _bstr_t bstSQL;
};

Implementing the IVerificationPoint Interface

This section describes the implementation of the following parts of the IVerificationPoint interface:


The DefineVP() Method

The DefineVP() method in the IVerificationPoint interface invokes a UI to capture the metadata definition of the verification point. When the tester dismisses the UI, this method should populate the verification point object with the metadata it captured.

The method should return S_OK if the metadata was successfully captured, and E_VP_UNDEFINED if it was not -- for example:

STDMETHODIMP CDatabaseVP::DefineVP()
{
	 ...

	 // Invoke some GUI to capture the VP's definition, in this case, 
	 // the QueryBuilder.

	 ...

	 pQB->DoModal();
	 pQB->get_Accepted( &vAccepted );
	 if ( vAccepted.vt == VT_BOOL && vAccepted.boolVal == VARIANT_TRUE )
	 {
	 	 pQB->get_Connection( &vConnection );
	 	 pQB->get_SQL( &vSQL );
	 	 if ( vConnection.vt == VT_BSTR && vSQL.vt == VT_BSTR )
	 	 {
	 	 	 bstConnectionString = vConnection.bstrVal;
	 	 	 bstSQL = vSQL.bstrVal;
	 	 }
	 	 else
	 	 {
	 	 	 bDefined = false;
	 	 }
	 	 if ( *((LPCWSTR)bstConnectionString) == L'\0'  ||
	 	 	 	 *((LPCWSTR)bstSQL) == L'\0' )
	 	 {
	 	 	 bDefined = false;
	 	 }
	 }
	 else // User canceled, or internal error in Query Builder component.
	 {
	 	 bDefined = false;
	 }

	 if ( bDefined == false )
	 	 return E_VP_UNDEFINED;
	 else
	 	 return S_OK;

}

The Code Factory Methods

The code factory methods generate source code that is capable of creating instances of your verification point type.

The code factory methods are similar in function to ActiveX controls that provide additional design-time behavior that integrates with the Visual C++ or Visual Basic environments.

The QualityArchitect code generator uses the code factory methods to insert verification points into generated test scripts. If a tester using QualityArchitect wants to insert one of your verification points into a generated test script, the QualityArchitect code generator creates an instance of your verification point, and then calls the DefineVP() method to present the tester with the UI you have created. Once the tester supplies the metadata through your UI, the code generator invokes the code factory methods to return source code. When the returned source code is inserted into the test script, a verification point is created from the metadata that the tester supplied.

For information about how the QualityArchitect code generator uses the code factory methods, see Integrating Your Verification Point with QualityArchitect.

Following are the code factory methods that you implement:

There are also CodeFactorySuffix property methods. The framework implements these methods -- you only pass them through. You use the suffix when constructing the externalized variables that are returned by the following methods:

The suffix ensures that externalized variable names from multiple verification points in the same scope are unique. If the QualityArchitect code generator sets the suffix, append the suffix to each externalized variable that is declared, initialized, and set by these methods. This allows the Quality Architect code generator to insert more than one verification point into a test script without risk of variable name conflicts.


Example of Code Factory Methods

The following code listing illustrates the use of the code factory methods:

STDMETHODIMP CDatabaseVP::get_CodeFactorySuffix(BSTR *pVal)
{
	 return m_pFramework->get_CodeFactorySuffix(pVal);
}

STDMETHODIMP CDatabaseVP::put_CodeFactorySuffix(BSTR newVal)
{
	 return m_pFramework->put_CodeFactorySuffix(newVal);
}


STDMETHODIMP 
CDatabaseVP::CodeFactoryGetConstructorInvocation(CTDScriptTypes 
Language, BSTR * Code)
{
	 if (Code == NULL)
	 	 return E_POINTER;

	 // Create a line of code which constructs this type of VP.
	 _bstr_t bsCode;
	 BSTR bsName = NULL;
	 BSTR bsSuffix = NULL;
	 
	 get_VPname(&bsName);
	 get_CodeFactorySuffix(&bsSuffix);

	 if ( bsName == NULL )
	 {
	 	 return E_INVALIDARG;
	 }

	 switch ( Language )
	 {
	 case CTD_SCRIPTTYPE_VB: // VB
	 	 bsCode = L"Dim ";
	 	 bsCode += bsName;
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L" As New DatabaseVP";
	 	 break;
	 
	 default:
	 	 return E_INVALIDARG;
	 }
	 
	 *Code = bsCode.copy();

	 return S_OK;
}

STDMETHODIMP 
CDatabaseVP::CodeFactoryGetNumExternallizedInputs(CTDScriptTypes
	 	 Language, short * NumInputs)
{
	 if (NumInputs == NULL)
	 	 return E_POINTER;

	 // Only VB is supported currently
	 if (Language != CTD_SCRIPTTYPE_VB)
	 	 return E_INVALIDARG;

	 if ( lOptions == 0 )
	 	 *NumInputs = 3;	 	 
	 else
	 	 *NumInputs = 4;

	 return S_OK;
}

STDMETHODIMP 
CDatabaseVP::CodeFactoryGetExternalizedInputDecl(CTDScriptTypes
	 	 Language, short InputNumber, BSTR * Code)
{
	 if (Code == NULL)
	 	 return E_POINTER;
	 
	 // Only VB is supported currently
	 if (Language != CTD_SCRIPTTYPE_VB)
	 	 return E_INVALIDARG;

	 _bstr_t bsCode;
	 BSTR bsSuffix = NULL;
	 
	 get_CodeFactorySuffix(&bsSuffix);

	 switch ( InputNumber )
	 {
	 case 1: // VPName
	 	 bsCode = L"Dim VPname";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L" As String";
	 	 break;
	 	 
	 case 2: // Connection String
	 	 bsCode = L"Dim VPConnectString";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L" As String";
	 	 break;

	 case 3: // SQL Statement
	 	 bsCode = L"Dim VPSQL";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L" As String";
	 	 break;

	 case 4: // Options
	 	 bsCode = L"Dim VPOptions";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L" As Integer";
	 	 break;

	 default:

	 	 break;
	 }
	 
	 *Code = bsCode.copy();
	 	 
	 return S_OK;
}

STDMETHODIMP CDatabaseVP::CodeFactoryGetExternalizedInputInit(/*[in]*/
	 	 CTDScriptTypes Language, /*[in]*/ short InputNumber, 
	 	 /*[out,retval]*/ BSTR *Code)
{
	 if (Code == NULL)
	 	 return E_POINTER;
	 
	 // Only VB is supported currently
	 if (Language != CTD_SCRIPTTYPE_VB)
	 	 return E_INVALIDARG;

	 _bstr_t bsCode;
	 BSTR bsName = NULL;
	 BSTR bsSuffix = NULL;
	 
	 get_VPname(&bsName);
	 get_CodeFactorySuffix(&bsSuffix);

	 if ( bsName == NULL )
	 {
	 	 // TODO: Enum this error condition!
	 	 return E_INVALIDARG;
	 }

	 switch ( InputNumber )
	 {
	 case 1: // VPname
	 	 bsCode = L"VPname";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L" = \"";
	 	 bsCode += bsName;
	 	 bsCode += L"\"";
	 	 break;
	 	 
	 case 2: // Connection String
	 	 bsCode = L"VPConnectString";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L"= \"";
	 	 bsCode += bstConnectionString;
	 	 bsCode += L"\"";
	 	 break;

	 case 3: // SQL Statement
	 	 bsCode = L"VPSQL";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L" = \"";
	 	 bsCode += bstSQL;
	 	 bsCode += L"\"";
	 	 break;

	 case 4: // Options
	 	 bsCode = L"VPOptions";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L" = ";
	 	 bsCode += L"VPOptions";
	 	 break;

	 default:

	 	 break;
	 }

	 *Code = bsCode.copy();
	 	 
	 return S_OK;

}


STDMETHODIMP CDatabaseVP::CodeFactoryGetNumPropertySet(/*[in]*/
	 	 CTDScriptTypes Language, /*[out, retval]*/ short *NumProps)
{
return CodeFactoryGetNumExternallizedInputs(Language, NumProps);
}


STDMETHODIMP CDatabaseVP::CodeFactoryGetPropertySet(/*[in]*/
	 	 CTDScriptTypes Language, /*[in]*/ short InputNumber, 
	 	 /*[out, retval]*/ BSTR *Code)
{
	 if (Code == NULL)
	 	 return E_POINTER;
	 
	 // Only VB is supported currently
	 if (Language != CTD_SCRIPTTYPE_VB)
	 	 return E_INVALIDARG;

	 _bstr_t bsCode;
	 BSTR bsName = NULL;
	 BSTR bsSuffix = NULL;
	 
	 get_VPname(&bsName);
	 get_CodeFactorySuffix(&bsSuffix);

	 if ( bsName == NULL )
	 {
	 	 // TODO: Enum this error condition!
	 	 return E_INVALIDARG;
	 }

	 switch ( InputNumber )
	 {
	 case 1: // VPname
	 	 bsCode = bsName;
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L".VPname = ";
	 	 bsCode += L"VPname";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 break;
	 	 
	 case 2: // Connection String
	 	 bsCode = bsName;
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L".ConnectionString = ";
	 	 bsCode += L"VPConnectString";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 break;

	 case 3: // SQL Statement
	 	 bsCode = bsName;
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L".SQL = ";
	 	 bsCode += L"VPSQL";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 break;

	 case 4: // Options
	 	 bsCode = bsName;
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 bsCode += L".Options = ";
	 	 bsCode += L"VPOptions";
	 	 if ( bsSuffix ) bsCode += bsSuffix;
	 	 break;

	 default:

	 	 break;
	 }

	 *Code = bsCode.copy();
	 	 
	 return S_OK;

}

Sample Code Factory Output

The following example illustrates the output produced by calling the code factory methods for a database verification point on a fully specified database verification point object.

In this example, the caller performed these preliminary steps:

This is consistent with the behavior you see with the QualityArchitect code generator. In the QueryBuilder invoked by the DefineVP() method, the caller specified an ODBC data source called COFFEEBREAK, and built the SQL statement "SELECT * FROM COFFEES".

The following samples show what the code factory methods return when they are invoked for this database verification point object:


The Options Property

Several flags are predefined for all verification points (see the property table in Summary), but you can add additional flags for your new verification point type. If you do so, use a single bit to represent each option, with the first available bit being the fourth bit (0x8).

The Options property is defined in the IVerificationPoint interface as a Variant, but it is implemented as a Long bitfield.

The following example shows a mechanism for creating new options:

STDMETHODIMP CDatabaseVP::get_Options(/*[out, retval]*/ VARIANT *pVal)
{
	 pVal->vt = VT_I4;
	 pVal->lVal = lOptions;
	 return S_OK;

}

STDMETHODIMP CDatabaseVP::put_Options(/*[in]*/ VARIANT newVal)
{
	 switch (newVal.vt)
	 {
	 case VT_I4:
	 	 lOptions = newVal.lVal;
	 	 break;
	 case VT_I2:
	 	 lOptions = (LONG) newVal.iVal;
	 	 break;
	 case VT_UI4:
	 	 lOptions = (LONG) newVal.ulVal;
	 	 break;
	 case VT_UI2:
	 	 lOptions = (LONG) newVal.uiVal;
	 	 break;
	 case VT_UINT:
	 	 lOptions = (LONG) newVal.uintVal;
	 	 break;
	 case VT_INT:
	 	 lOptions = (LONG) newVal.intVal;
	 	 break;

	 default:
	 	 return S_FALSE;
	 }

	 return S_OK;
}

Implementing the Methods in Your Verification Point Interface

Your verification point component's interface contains properties or methods for defining the verification point's metadata-- for example:

STDMETHODIMP CDatabaseVP::get_ConnectionString(BSTR *pVal)
{
	 if (pVal == NULL)
	 	 return E_POINTER;
	 *pVal = SysAllocString(bstConnectionString);
	 return S_OK;
}

STDMETHODIMP CDatabaseVP::put_ConnectionString(BSTR newVal)
{
	 bstConnectionString = newVal;
	 CalcIsDefined();
	 return S_OK;
}

STDMETHODIMP CDatabaseVP::get_SQL(BSTR *pVal)
{
	 if (pVal == NULL)
	 	 return E_POINTER;
	 *pVal = SysAllocString(bstSQL);
	 return S_OK;
}

STDMETHODIMP CDatabaseVP::put_SQL(BSTR newVal)
{
	 bstSQL = newVal;
	 CalcIsDefined();
	 return S_OK;
}

Implementing the IPersistFile Interface

The framework uses the following two IPersistFile interface methods to serialize your verification point's metadata. The other IPersistFile methods can simply return E_NOTIMPL:

The following example illustrates metadata serialization:

STDMETHODIMP CDatabaseVP::GetClassID(CLSID *pClassID)
{
	 return E_NOIMPL;
}

STDMETHODIMP CDatabaseVP::IsDirty()
{
	 return E_NOIMPL;
}

STDMETHODIMP CDatabaseVP::SaveCompleted(LPCOLESTR pszFileName)
{
	 return E_NOIMPL;
}

STDMETHODIMP CDatabaseVP::GetCurFile(LPOLESTR *ppszFileName)
{
	 return E_NOIMPL;
}

STDMETHODIMP CDatabaseVP::Load(LPCOLESTR pszFileName, DWORD dwMode)
{
	 TCHAR szBuffer[4096];
	 _bstr_t bstFile(pszFileName);
	 long lReadOptions = 0;

	 GetPrivateProfileString(_T("Definition"), _T("Case ID"), _T("\n"),	 
	 	 	 szBuffer, 4096, (LPCTSTR) bstFile );

	 // Verify that the file exists and that there is at least a VP name.
	 if ( _tcscmp(szBuffer, _T("\n")) == 0 )
	 	 return E_VP_FILENOTFOUND;
	 else
	 	 put_VPname(_bstr_t(szBuffer));

	 GetPrivateProfileString(_T("Definition"), 
	 	 	 _T(	 	 "Verification 	 	 	 Method"), _T("\n"),  szBuffer, 4096,
	 	 	 (LPCTSTR) bstFile );

	 // Check to see if comparison is Case Insensitive
	 if ( _tcscmp(szBuffer, _T("CaseInsensitive")) == 0 )
	 	 lReadOptions |= VPOPTION_COMPARE_CASEINSENSITIVE;

	 GetPrivateProfileString(_T("Definition"), _T("Expected Result"),
	 	 	 _T("\n"),  szBuffer, 4096, (LPCTSTR) bstFile );

	 // Check to see if expected result is failure
	 if ( _tcscmp(szBuffer, _T("Failure")) == 0 )
	 	 lReadOptions |= VPOPTION_EXPECT_FAILURE;
	 
	 GetPrivateProfileString(_T("DatabaseVP"), _T("Connection String"),
	 	 	 _T("\n"),  szBuffer, 4096, (LPCTSTR) bstFile );

	 // Connection String is a required field.
	 if ( _tcscmp(szBuffer, _T("\n")) == 0 )
	 	 return E_VP_BADFILE;
	 else
	 	 put_ConnectionString(_bstr_t(szBuffer));

	 GetPrivateProfileString(_T("DatabaseVP"), _T("SQL"), _T("\n"), 
	 	 	 szBuffer, 4096, (LPCTSTR) bstFile );

	 // SQL is a required field.
	 if ( _tcscmp(szBuffer, _T("\n")) == 0 )
	 	 return E_VP_BADFILE;
	 else
	 	 put_SQL(_bstr_t(szBuffer));

	 GetPrivateProfileString(_T("DatabaseVP"), _T("Trim Whitespace"),
	 	 	 _T("\n"),  szBuffer, 4096, (LPCTSTR) bstFile );

	 // Check to see if we should trim whitespace
	 if ( _tcscmp(szBuffer, _T("1")) == 0 )
	 	 lReadOptions |= DATABASEVPOPTION_TRIM_WHITESPACE;

	 lOptions = lReadOptions;

	 CalcIsDefined();

	 return S_OK;
}

STDMETHODIMP CDatabaseVP::Save(LPCOLESTR pszFileName, BOOL fRemember)
{
	 BSTR bsName = NULL;

	 get_VPname( &bsName );
	 _bstr_t bstName(bsName);
	 _bstr_t bstFile(pszFileName);

	 // Write [Definition] section
	 WritePrivateProfileString(_T("Definition"), NULL, NULL,
	 	 	 (LPCTSTR)bstFile);
	 WritePrivateProfileString(_T("Definition"), _T("Case ID"),
	 	 	 (LPCTSTR)bstName, (LPCTSTR)bstFile);
	 WritePrivateProfileString(_T("Definition"), _T("Type"), 
	 	 	 _T("Object Data"), (LPCTSTR)bstFile);
	 WritePrivateProfileString(_T("Definition"), _T("Data Test"),
	 	 	 _T("Contents"), (LPCTSTR)bstFile);

	 if ( lOptions & VPOPTION_COMPARE_CASEINSENSITIVE )
	 	 WritePrivateProfileString(_T("Definition"), 
	 	 	 _T("Verification Method"), _T("CaseInsensitive"),
	 	 	 (LPCTSTR)bstFile);
	 else
	 	 WritePrivateProfileString(_T("Definition"), 
	 	 	 _T("Verification Method"), _T("CaseSensitive"),
	 	 	 (LPCTSTR)bstFile);

	 if ( lOptions & VPOPTION_EXPECT_FAILURE )
	 	 WritePrivateProfileString(_T("Definition"), 
	 	 	 _T("Expected Result"), _T("Failure"), (LPCTSTR)bstFile);

	 // Write [DatabaseVP] section
	 WritePrivateProfileString(_T("DatabaseVP"), NULL, NULL,
	 	 	 (LPCTSTR)bstFile);
	 WritePrivateProfileString(_T("DatabaseVP"), 
	 	 	 _T("Connection String"), (LPCTSTR)bstConnectionString,
	 	 	 (LPCTSTR)bstFile);
	 WritePrivateProfileString(_T("DatabaseVP"), _T("SQL"),
	 	 	 (LPCTSTR)bstSQL, (LPCTSTR)bstFile);

	 if ( lOptions & DATABASEVPOPTION_TRIM_WHITESPACE )
	 	 WritePrivateProfileString(_T("DatabaseVP"), 
	 	 	 _T("Trim Whitespace"), _T("1"), (LPCTSTR)bstFile);

	 return S_OK;
}

Implementing the IVPFramework Interface

IVPFramework is the base class for IVerificationPoint, and IVerificationPoint is the base class for your verification point's interface. Consequently, you must provide entry points for all of the methods in the IVPFramework interface.

Because the implementation of the IVPFramework methods is provided in the VPFramework component, your class must simply construct a VPFramework object, and then pass each of the IVPFramework methods to that VPFramework object.

This form of implementation inheritance is called containment, and it is illustrated in the following example:

STDMETHODIMP CDatabaseVP::get_VPname(BSTR *pVal)
{
	 return m_pFramework->get_VPname(pVal);
}
STDMETHODIMP CDatabaseVP::put_VPname(BSTR newVal)
{
	 return m_pFramework->put_VPname(newVal);
}

STDMETHODIMP CDatabaseVP::get_VP( IDispatch ** ppVP )
{
	 return m_pFramework->get_VP(ppVP);
}

STDMETHODIMP CDatabaseVP::put_VP( IDispatch * pVP )
{
	 return m_pFramework->put_VP(pVP);
}

STDMETHODIMP CDatabaseVP::get_Plumbing( IDispatch ** ppPlumbing )
{
	 return m_pFramework->get_Plumbing(ppPlumbing);
}

STDMETHODIMP CDatabaseVP::put_Plumbing( IDispatch * pPlumbing )
{
	 return m_pFramework->put_Plumbing(pPlumbing);
}

STDMETHODIMP CDatabaseVP::get_CodeFactorySuffix(BSTR *pVal)
{
	 return m_pFramework->get_CodeFactorySuffix(pVal);
}

STDMETHODIMP CDatabaseVP::put_CodeFactorySuffix(BSTR newVal)
{
	 return m_pFramework->put_CodeFactorySuffix(newVal);
}

STDMETHODIMP CDatabaseVP::PerformTest(/*[in]*/ VARIANT Object, 
	 	 /*[optional, in]*/ VARIANT ExpectedData, /*[optional, in] */
	 	 VARIANT ActualData, /*[out, retval]*/ enum VPResult *Result) 
{
	 return m_pFramework->PerformTest(Object, ExpectedData, 
	 	 	 ActualData, Result);
}

Other Responsibilities of the Verification Point Component

In addition to the implementation tasks already described in this section, your verification point component must also:

These tasks are described in the following subsections:


Creating the FinalConstruct() Method

You create a FinalConstruct() method to initialize your verification point objects. The FinalConstruct() method must be defined with a DECLARE_PROTECT_FINAL_CONSTRUCT() statement in the class header file, as illustrated in the example in the section The Verification Point Component.

The FinalConstruct() method must perform the following tasks:

The following is an example of a FinalConstruct() method:

HRESULT CDatabaseVP::FinalConstruct()
{
	 HRESULT hrRetVal =
	 	 	 m_pFramework.CoCreateInstance(L"RTComVP.VPFramework");
	 CComQIPtr<IVPPlumbing, &IID_IVPPlumbing> plumbing;
	 LPDISPATCH pTemp;
	 _bstr_t bsComparator(L"RTComVP.DatabaseVPComparator");
	 _bstr_t bsData(L"RTComVP.DatabaseVPData");
	 _bstr_t bsDataProvider(L"RTComVP.DatabaseVPDataProvider");
	 _bstr_t bsDataRenderer(L"RTComVP.DatabaseVPDataRenderer");

	 m_pFramework->get_Plumbing(&pTemp);
	 plumbing = pTemp;

	 m_pFramework->put_VP(this);
	 plumbing->InitializeFramework(bsComparator, bsData, 
	 	 	 bsDataProvider, bsDataRenderer);

	 lOptions = 0;
	 bstConnectionString = "";
	 bstSQL = "";

	 _com_error e(hrRetVal);
	 _bstr_t bsError = e.ErrorMessage();

	 return S_OK;
}

Maintaining the IsDefined Flag

The VPPlumbing class contains the Boolean property IsDefined. The framework uses this property to determine if a verification point's metadata is fully specified when PerformTest() is invoked. If IsDefined is set to VARIANT_FALSE, the framework calls DefineVP() to prompt the tester for the missing metadata.

Your verification point implementation is responsible for coordinating the value of this property with the state of the metadata in your verification point object. The Load() method and the property-set methods should update the IsDefined value if they result in a change in the verification point's definition (that is, the verification point's metadata becomes fully specified or becomes no longer fully specified).

Note that the DatabaseVP component implements a private method named CalcIsDefined(). This method determines the state of the verification point's metadata and sets the IsDefined flag accordingly. All methods that might change the state of a verification point's metadata can invoke CalcIsDefined().

Here is an example of IsDefined flag maintenance:

HRESULT CDatabaseVP::CalcIsDefined()
{
	 CComQIPtr<IVPPlumbing, &IID_IVPPlumbing> plumbing;
	 LPDISPATCH pTemp;

	 m_pFramework->get_Plumbing(&pTemp);
	 plumbing = pTemp;

	 BSTR bsName;
	 get_VPname(&bsName);
	 _bstr_t bstName(bsName);

	 BSTR bsConn;
	 get_ConnectionString(&bsConn);
	 _bstr_t bstConnectionString(bsConn);

	 BSTR bsSQL;
	 get_SQL(&bsSQL);
	 _bstr_t bstSQL(bsSQL);


	 if ( bstName.length() != 0 && bstConnectionString.length() != 0 &&
	 	 	 	 bstSQL.length() != 0 )
	 	 plumbing->put_IsDefined(VARIANT_TRUE);
	 else
	 	 plumbing->put_IsDefined(VARIANT_FALSE);

	 return S_OK;
}

Interface for Your Verification Point Data Component

You must define an interface for your verification point data component. This interface must inherit from IVerificationPointData.

Your verification point data component that implements this interface contains a snapshot of the data being verified. That data can be either expected data or actual data.

The test designer should be able to use this interface to populate a Verification Point Data component for use with dynamic or manual verification points (for information, see Types of Verification Points).

The following is an example of an implementation of your verification point data component:

[
	 object,
	 uuid(7C4870B3-6E1A-11D4-9A26-0010A4E86989),
	 dual,
	 helpstring("IDatabaseVPData Interface"),
	 pointer_default(unique)
]
interface IDatabaseVPData : IVerificationPointData
{
	 [propget, helpstring("property NumCols")] HRESULT NumCols(
	 	 	 [out, retval] long *pVal);
	 [propput, helpstring("property NumCols")] HRESULT NumCols(
	 	 	 [in] long newVal);
	 [propget, helpstring("property NumRows")] HRESULT NumRows(
	 	 	 [out, retval] long *pVal);
	 [propput, helpstring("property NumRows")] HRESULT NumRows(
	 	 	 [in] long newVal);
	 [propget, helpstring("property Columns")] HRESULT Columns(
	 	 	 [out, retval] VARIANT *pVal);
	 [propput, helpstring("property Columns")] HRESULT Columns(
	 	 	 [in] VARIANT newVal);
	 [propget, helpstring("property Row")] HRESULT Row([in] long Index,
	 	 	 [out, retval] VARIANT *pVal);
	 [propput, helpstring("property Row")] HRESULT Row([in] long Index,
	 	 	 [in] VARIANT newVal);
};

The Verification Point Data Component

Your verification point data component implements the methods defined in your verification point data interface -- for example:

STDMETHODIMP CDatabaseVPData::get_NumCols(long *pVal)
{
	 *pVal = lCols;
	 return S_OK;
}

STDMETHODIMP CDatabaseVPData::put_NumCols(long newVal)
{
	 lCols = newVal;
	 return S_OK;
}

STDMETHODIMP CDatabaseVPData::get_NumRows(long *pVal)
{
	 *pVal = lRows;
	 return S_OK;
}

STDMETHODIMP CDatabaseVPData::put_NumRows(long newVal)
{
	 lRows = newVal;
	 return S_OK;
}

STDMETHODIMP CDatabaseVPData::get_Columns(VARIANT *pVal)
{

	 VariantInit(pVal);
	 pVal->vt = (VT_ARRAY | VT_BYREF | VT_BSTR);

	 ...
	 // Copy the array and return it to the caller.
	 ...
	 return S_OK;
}

STDMETHODIMP CDatabaseVPData::put_Columns(VARIANT newVal)
{
	 pvColumns = new VARIANT;
	 VariantInit(pvColumns);
	 pvColumns->vt = (VT_ARRAY | VT_BSTR);
	 SafeArrayCopy(newVal.parray, &(pvColumns->parray));
	 put_NumCols(newVal.parray->rgsabound[0].cElements);

	 return S_OK;
}

STDMETHODIMP CDatabaseVPData::get_Row(long Index, VARIANT *pVal)
{
	 VariantInit(pVal);
	 pVal->vt = (VT_ARRAY | VT_BYREF | VT_BSTR);

	 if ( paRows != NULL )
	 {
	 	 ...
	 	 // Copy the row into the output array
	 	 ...
	 }
	 return S_OK;
}

STDMETHODIMP CDatabaseVPData::put_Row(long Index, VARIANT newVal)
{
	 ...
	 // Copy this row into our data structure
	 ...

	 return S_OK;
}

In addition, your verification point data component must implement:

The following sections describe these tasks.


Implementing the IPersistFile Interface

Your verification point class must implement its own serialization to a verification point data file by implementing the IPersistFile interface.

As with your verification point class, you only need to implement the Load() and Save() methods, and you may return E_NOTIMPL for the other methods.

Because the Grid Comparator in the current version of the product also accesses your metadata and data files, you must use a .ini metafile format and a .csv data file format. In a future release, you will be able to create your own comparator applications to read from and write to files of any data file format you choose.

The following is an example of data file serialization:

STDMETHODIMP CDatabaseVPData::GetClassID(CLSID *pClassID)
{
	 return E_NOTIMPL;
}

STDMETHODIMP CDatabaseVPData::IsDirty()
{
	 return E_NOTIMPL ;
}

STDMETHODIMP CDatabaseVPData::SaveCompleted(LPCOLESTR pszFileName)
{
	 return E_NOTIMPL ;
}

STDMETHODIMP CDatabaseVPData::GetCurFile(LPOLESTR *ppszFileName)
{
	 return E_NOTIMPL;
}

STDMETHODIMP CDatabaseVPData::Load(LPCOLESTR pszFileName, 
	 	 	 DWORD dwMode)
{
	 SAFEARRAY *psaColumns = NULL;
	 SAFEARRAY *psaRow = NULL;
	 wstring buffer;
	 HRESULT hr = S_OK;
	 wchar_t delim = L'\n';
	 long lNumVals = 0;

	 _bstr_t bsFile = pszFileName; 
	 wifstream stream(bsFile);

	 getline(stream, buffer, delim);
	 if ( buffer.empty() != true )
	 {
	 	 lNumVals = GetNumElementsFromCSVString( buffer.c_str() );

	 	 hr = BuildBSTRSafeArrayFromCSVString( buffer.c_str(),
	 	 	 	 &psaColumns, lNumVals, 0 );
	 	 if ( hr == S_OK )
	 	 {
	 	 	 pvColumns = new VARIANT;
	 	 	 VariantInit(pvColumns);

	 	 	 pvColumns->vt = (VT_ARRAY | VT_BSTR);
	 	 	 pvColumns->parray = psaColumns;
	 	 	 put_NumCols(lNumVals);
	 	 }
	 	 else
	 	 {
	 	 	 return E_INVALIDARG;
	 	 }
	 }
	 else
	 {
	 	 put_NumCols(0);
	 	 put_NumRows(0);
	 	 return S_OK;
	 }

	 getline(stream, buffer, delim);
	 for ( long l = 0; true != buffer.empty(); l++ )
	 {
	 	 hr = BuildBSTRSafeArrayFromCSVString( buffer.c_str(), &psaRow,
	 	 	 	 lNumVals, 0 );
	 	 if ( hr == S_OK )
	 	 {
	 	 	 VARIANT vRow;
	 	 	 VariantInit(&vRow);
	 	 	 vRow.vt = (VT_ARRAY | VT_BSTR);
	 	 	 vRow.parray = psaRow;
	 	 	 put_Row(l, vRow);
	 	 	 VariantClear( &vRow );
	 	 }
	 	 else
	 	 {
	 	 	 return E_INVALIDARG;
	 	 }
	 	 getline(stream, buffer, delim);
	 }

	 return S_OK;
}

STDMETHODIMP CDatabaseVPData::Save(LPCOLESTR pszFileName, 
	 	 	 BOOL fRemember)
{
	 SAFEARRAY *psaColumns = NULL;
	 SAFEARRAY *psaRow = NULL;
	 FILE *pfOut = NULL;

	 // Validate parameters
	 if ( pszFileName == NULL )
	 	 return E_POINTER;

	 if ( *pszFileName == L'\0' )
	 	 return E_INVALIDARG;

	 if ( pvColumns == NULL || pvColumns->vt != (VT_ARRAY | VT_BSTR) )
	 	 return E_INVALIDARG;

	 psaColumns = pvColumns->parray;
	 
    // If there's nothing to write -- don't write anything...
	 lCols = psaColumns->rgsabound[0].cElements;

    if ( lCols == 0 )
        return S_OK;
	 
	 // Open the file
	 pfOut = _wfopen(pszFileName, L"wt");
	 
	 if  ( pfOut == NULL )	 	 	 
	 	 return E_INVALIDARG;
	 
	 // Write out the file!
	 // First print out a line with all the column names.
	 WriteBSTRSafeArrayToCSVFile(pfOut, psaColumns);

    for ( long l=0; l < lRows; l++ )
    {
	 	 psaRow = paRows[l].parray;
	 	 
	 	 if ( psaRow != NULL) 
	 	 	 WriteBSTRSafeArrayToCSVFile(pfOut, psaRow);
	 	 	 	 
    }
    
	 fclose(pfOut);
	 return S_OK;
}

Implementing the FileExtension() Property Methods

Your verification point data component must implement the following FileExtension() property methods:

In the current release of QualityArchitect, set this property to csv. In a future release, this property should contain the file extension associated with the data file format used by your verification point data component -- for example, csv, dat, or xml.

The verification point framework creates a unique data file name and passes it to the Load() and Save() methods. The FileExtension() property method tells the framework the file extension to use for this verification point data type.


The Verification Point Data Comparator Component

Your specialized Verification Point Data Comparator component must implement the IVerificationPointComparator interface. This interface has only one method, Compare().

The Compare() method compares an expected data object and an actual data object, both of type IVerificationPointData, and determines whether the test succeeds or fails.

The following is an example of a data comparison:

STDMETHODIMP CDatabaseVPComparator::Compare(
	 	 /*[in]*/ IVerificationPointData *ExpectedData, 
	 	 /*[in]*/ IVerificationPointData *ActualData, 
	 	 /*[in]*/ VARIANT Options, /*[out]*/ BSTR *FailureDescription,
	 	 /*[out, retval]*/  VARIANT_BOOL *Result)
{
	 CComQIPtr<IDatabaseVPData, &IID_IDatabaseVPData> vpdExpected;
	 CComQIPtr<IDatabaseVPData, &IID_IDatabaseVPData> vpdActual;

	 vpdExpected = ExpectedData;
	 vpdActual = ActualData;
	 *Result = VARIANT_FALSE;
	 long lNumCols = 0;
	 bool bCaseInsensative = false;
	 BSTR bsFoo;

	 // Allow for NULL FailureDescription
	 if ( FailureDescription == NULL )
	 {
	 	 // assign to throwaway local
	 	 FailureDescription = &bsFoo;
	 }

	 // First compare the column names.
	 VARIANT vExpColumns;
	 VARIANT vActColumns;
	 SAFEARRAY *psaExpColumns;
	 SAFEARRAY *psaActColumns;

	 vpdExpected->get_Columns(&vExpColumns);
	 vpdActual->get_Columns(&vActColumns);
	 
	 if ( vExpColumns.vt == (VT_ARRAY | VT_BYREF | VT_BSTR) &&
	 	 	 	  vActColumns.vt == (VT_ARRAY | VT_BYREF | VT_BSTR))
	 {
	 	 psaExpColumns = *(vExpColumns.pparray);
	 	 psaActColumns = *(vActColumns.pparray);

	 	 lNumCols = psaExpColumns->rgsabound[0].cElements;
	 	 if ((unsigned long)lNumCols !=
	 	 	 	 	 	 psaActColumns->rgsabound[0].cElements)
	 	 {
	 	 	 *FailureDescription = SysAllocString(	 	 	 	 	 L"Expected and Actual
	 	 	 	 	 resultsets had different number 	 	 	 	 	 of columns.");
	 	 	 return S_OK;
	 	 }
	 	 
	 	 
	 	 if ( !CompareBstrSafeArray(psaExpColumns, psaActColumns, 
	 	 	 	 NULL, bCaseInsensative))
	 	 {
	 	 	 *FailureDescription = SysAllocString(L"Expected and Actual
	 	 	 	 	 resultsets had different column names.");
	 	 	 return S_OK;
	 	 }
	 }


	 // Now loop over each of the adjacent rows and compare them.

	 long lNumExpRows = 0;
	 long lNumActRows = 0;
	 vpdExpected->get_NumRows(&lNumExpRows);
	 vpdActual->get_NumRows(&lNumActRows);

	 if ( lNumExpRows != lNumActRows )
	 {
	 	 *FailureDescription = SysAllocString(L"Expected and Actual
	 	 	 	 resultsets had different number of rows.");
	 	 return S_OK;
	 }


	 VARIANT vExpRow;
	 VARIANT vActRow;
	 SAFEARRAY *psaExpRow;
	 SAFEARRAY *psaActRow;

	 for ( long lIndex = 0; lIndex < lNumExpRows; lIndex++ )
	 {

	 	 vpdExpected->get_Row(lIndex, &vExpRow);
	 	 vpdActual->get_Row(lIndex, &vActRow);


	 	 if ( vExpRow.vt == (VT_ARRAY | VT_BYREF | VT_BSTR) &&
	 	 	  vActRow.vt == (VT_ARRAY | VT_BYREF | VT_BSTR))
	 	 {
	 	 	 psaExpRow = *(vExpRow.pparray);
	 	 	 psaActRow = *(vActRow.pparray);

	 	 	 if ( !CompareBstrSafeArray(psaExpRow, psaActRow,
	 	 	 	 	 FailureDescription, bCaseInsensative))
	 	 	 {
	 	 	 	 // FailureDescription filled in by CompareBstr rountine.
	 	 	 	 return S_OK;
	 	 	 }

	 	 }
	 	 else
	 	 {
	 	 	 ...
	 	 	 // Problem with data objects -- handle error
	 	 	 ...
	 	 }

	 }

	 *Result = VARIANT_TRUE;
	 return S_OK;
}

The Verification Point Data Provider Component

Your specialized Verification Point Data Provider component must implement the IVerificationPointDataProvider interface. This interface has only one method, CaptureData().

The CaptureData() method reads the verification point's definition from the supplied verification point object, captures the data required by the verification point, and returns the data in a new IVerificationPointData object.

The following example illustrates an implementation of the IVerificationPointDataProvider interface:

STDMETHODIMP CDatabaseVPDataProvider::CaptureData (
	 	 /*[in]*/ VARIANT Object, 	 /*[in]*/ IVerificationPoint *VP, 
	 	 /*[out, retval]*/ IVerificationPointData **Data)
{

	 //  QI for DatabaseVP interface
	 ...

	 dbVP->get_ConnectionString(&bsConnection);
	 _bstr_t bstConnection(bsConnection, false);
	 dbVP->get_SQL(&bsSQL);
	 _bstr_t bstSQL(bsSQL, false);


	 // Attempt to connect to the OLE DB using the connection string
	 // stored in the VP object.
	 ...	 

	 long lNumCols = rs->Fields->Count;

	 if ( lNumCols > 0 )
	 {
	 	 CComQIPtr<IDatabaseVPData, &IID_IDatabaseVPData> dataReturn;
	 	 _bstr_t bstDataProgID = "rtComVP.DatabaseVPData";
	 	 dataReturn.CoCreateInstance(bstDataProgID);
	 	 
	 	 
	 	 VARIANT v;
	 	 v.vt = VT_I4;

	 	 SAFEARRAYBOUND rgsaBound[1];
	 	 rgsaBound[0].lLbound = 0;
	 	 rgsaBound[0].cElements = lNumCols;
	 	 SAFEARRAY *psaColumns = SafeArrayCreate( VT_BSTR, 1, rgsaBound );

	 	 for ( long l=0; l < lNumCols; l++ )
	 	 {
	 	 	 v.lVal = l;
	 	 	 BSTR bsColumn = SysAllocString(rs->Fields->Item[v]->Name);
	 	 	 SafeArrayPutElement( psaColumns, &l, bsColumn);
	 	 }

	 	 VARIANT vColumns;
	 	 VariantInit(&vColumns);
	 	 vColumns.vt = (VT_ARRAY | VT_BSTR);
	 	 vColumns.parray = psaColumns;

	 	 dataReturn->put_Columns(vColumns);

	 	 rs->MoveLast();
	 	 long lNumRows = rs->RecordCount;
	 	 rs->MoveFirst();

	 	 dataReturn->put_NumRows(lNumRows);

	 	 for ( l=0; !rs->EOF; l++,rs->MoveNext() )
	 	 {
	 	 	 SAFEARRAY *psaRow = SafeArrayCreate( VT_BSTR, 1, rgsaBound );
	 	 	 for ( long j = 0; j < lNumCols; j++ )
	 	 	 {
	 	 	 	 v.lVal = j;
	 	 	 	 _bstr_t Temp = rs->Fields->Item[v]->Value;
	 	 	 	 BSTR bsData = SysAllocString(Temp);
	 	 	 	 SafeArrayPutElement( psaRow, &j, bsData );
	 	 	 }

	 	 	 VARIANT vRow;
	 	 	 VariantInit(&vRow);
	 	 	 vRow.vt = (VT_ARRAY | VT_BSTR);
	 	 	 vRow.parray = psaRow;
	 	 	 dataReturn->put_Row(l, vRow);
	 	 }

	 	 dataReturn->QueryInterface(IID_IVerificationPointData, 
	 	 	 	 (void **) Data);
	 	 (*Data)->AddRef();
	 	 
	 }

	 // Clean up memory
	 ...

	 return S_OK;

}

The Verification Point Data Renderer Component

Your specialized Verification Point Data Renderer component must implement the IVerificationPointDataRenderer interface. This interface has only one method, DisplayAndValidateData().

DisplayAndValidateData() displays the data in an IVerificationPointData object, allowing the tester to accept or reject that data as being correct.

The framework calls this method when both of the following conditions exist:

If the tester accepts the displayed data, the data is stored as the expected data for the static verification point. If the tester rejects the data, no expected data is stored, and the process is repeated the next time the verification point is executed.

The following example illustrates an implementation of the IVerificationPointDataRenderer interface:

STDMETHODIMP CDatabaseVPDataRenderer::DisplayAndValidateData(
	 	 /*[in, out]*/ IVerificationPointData **Data, 
	 	 /*[out, retval]*/ VARIANT_BOOL *Valid)
{
	 
	 CComQIPtr<_clsDatabaseVPDataRenderer,
	 	 	 &IID__clsDatabaseVPDataRenderer> pRend;
	 CComQIPtr<IDatabaseVPData, &IID_IDatabaseVPData> pDBdata;
	 VARIANT vCols;
	 VARIANT vRow;
	 VARIANT vAccepted;

	 // Create an instance of the GUI data renderer.
	 pRend.CoCreateInstance(L"rtCOMVpGui.clsDatabaseVPDataRenderer");

	 // Get a DatabaseVPData COM pointer.
	 pDBdata = *Data;

	 if ( pDBdata == NULL || pRend == NULL)
	 {
	 	 return S_FALSE;
	 }
	 
	 // Put the columns from the data object into the GUI.
	 pDBdata->get_Columns(&vCols);
	 pRend->put_Columns(vCols);

	 long lNumRows;
	 pDBdata->get_NumRows(&lNumRows);

	 // Put the rows from the data object into the GUI.
	 for ( long l = 0; l < lNumRows; l++ )
	 {
	 	 pDBdata->get_Row( l, &vRow );
	 	 pRend->put_Row( vRow );
	 }
	 
	 // Invoke the GUI dialog.
	 pRend->DoModal();

	 // Pass back the result.
	 pRend->get_Accepted(&vAccepted);

	 if ( vAccepted.vt == VT_BOOL && vAccepted.boolVal == VARIANT_TRUE )
	 {
	 	 *Valid = VARIANT_TRUE;
	 }
	 else
	 {
	 	 *Valid = VARIANT_FALSE;
	 }

	 return S_OK;
}

prevnext


Rational Test Script Services for Visual Basic Rational Software Corporation
Copyright (c) 2003, Rational Software Corporation http://www.rational.com
support@rational.com
info@rational.com