Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CVariantArray

0.00/5 (No votes)
31 Aug 2002 2  
A Two-Dimensional Array of VARIANTs

Introduction

CVariantArray is a wrapper class to manage one and two-dimensional arrays of VARIANT structures. It is most commonly used in client/server distributed processing projects (such as database servers), and to pass data between Visual Basic and Visual C++. I created this array after spending tons of time searching the internet, reading technical documents, and just plain trial-and-error.

Overview

The class supports all elementary C data types plus DATE. struct tm, SYSTEMTIME, and strings that are char*, wchar_t*, string templates, CString, and UString. Internally, all strings are stored as UNICODE BSTRs. whether the class is compiled for UNICODE or not. The class does NOT support any of the VARIANT pointer types (e.g. IDispatch, IUnknown, etc), nor does it support nested arrays (SAFEARRAY). The only exception to this is BSTR. It has Put() and Get() methods for each supported data type, plus support for CString for those using the array in an MFC project, or string for STL.

Also included in the class are input/output operators for streams, FILE, and HANDLE. This allows you to save the entire array to a file or stream with one line of code. For example:

    CVariantArray array;
    // populate the array here.

    fstream os;
    os.open("somefile.dat",ios::out | ios::trunc);
    if(os.is_open())
    {
        os << array;
        os.close();
    }

The WinCE version does not support streams, FILE, and struct tm.

Implementation Details

Near the top of VariantArray.h are two defines that toggle CString and UString on and off. Uncomment the line that is appropriate for your project. If neither line is uncommented, then the string template class is included, unless _WIN32_WCE (for WinCE projects) is also defined.
//////////////////////////////////////////////////////////////////

//

// uncomment the next line if your are using MFC

//#define _MFC_

//

// uncomment the next line if you are using the DataReel library

//#define _DATAREEL_

//

//

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

Methods and Operators

CVariantArray();
virtual ~CVariantArray();
Notes:
Class Constructors/Destructors
int Create(long rows, long cols);
Parameters:
rows - number of zero-based rows in the array
cols - number of zero-based columns in the array. If cols = 0, then a a one dimension array (vector) is crated.

Return Value:
Returns 0 if successful, non-zero otherwise.

Notes:
Creating the array can be accomplished in one of two ways. The Create method will create the array with empty rows and columns, while the Attach will create the array with a previously-defined array.

int Attach(VARIANT* vt);
Parameters:
VARIANT* vt - pointer to a VARIANT that is a SAFEARRAY

Notes:
The VARIANT type must be (VT_VARIANT | VT_ARRAY). CVariantArray> will replace all the data in the CVariantArray class with the array in VARIANT.
bool IsValidArray();
Parameters:
None

Return Value:
true if the array is a valid array, or false otherwise.

Notes:
Verify the CVariantArray has been constructed as an array. It returns true if it is an array, or false if it is not an array.
VARIANT* Detach();
Parameters:
None

Return Value:
The VARIANT that is represented in CVariantArray

Notes:
The CVariantArray class is no longer valid after Detach method is called.
The calling function is must call VariantClear() to deallicate the detached array. Memory leaks will occur if this is omitted.
void Destroy()
Parameters:
None

Return Value:
None

Notes:
Deletes all the data in the array. Correctly destroys all string.
long GetRows();
Parameters:
None

Return Value:
Returns the number of rows in the array.
long GetCols();
Parameters:
None

Return Value:
Returns the number of columns in the array.
int Redim(long rows);
Parameters:
rows - the number of new rows in the array

Return Value:
Returns 0 if the array was successfully redimensioned, no non-zero if an error occurred.

Notes:
The number of columns can not be changed. If rows is less than the current number of rows, the data in the excess rows is correctly destroyed. If rows is greater than the current number of rows, the data type of all columns in the new rows are set to VT_NULL, indicating that the cell does not contain any valid data.
int GetAtString(UString& string, long row, long col = 0)
int GetAtString(CString& string, long row, long col = 0);
int GetAtString(string& string, long row, long col = 0);
Parameters:
row - zero-based row number of the desired cell
col - zero-based column number of the desired cell. Vectory (1d) arrays do not need to specify a column.
string - an STL string template object
UString - A string class defined in the DataReel library. _DATAREEL_ must be defined in CVariantArray.h CString - An MFC string class. _MFC_ must be defined in CVariantArray.h

Notes:
Each of these functions converts the cell's data type to a stirng. Numberic data types are converted by"%d", and float or double by "%f". DATE types are converted to the format "mm/dd/yyyy hh:mm:ss"
int GetAt(VARIANT& vt, long row, long col = 0);
int GetAt(char** string, long row, long col= 0);
int GetAt(char* string, long stringlen, long row, long col = 0);
int GetAt(UString& string, long row, long col = 0 );
int GetAt(CString& string, long row, long col = 0);
int GetAt(char& val, long row, long col = 0);
int GetAt(short& val, long row, long col = 0);
int GetAt(unsigned short& val, long row, long col = 0);
int GetAt(int& val, long row, long col = 0);
int GetAt(unsigned int& val, long row, long col = 0);
int GetAt(long& val, long row, long col = 0);
int GetAt(unsigned long& val, long row, long col = 0);
int GetAt(float& val, long row, long col = 0);
int GetAt(double& val, long row, long col = 0);
int GetAt(struct tm& tm, long row, long col = 0);
int GetAtDate(DATE& val, long row, long col = 0); 

Parameters:
row - zero-based row number of the desired cell
col - zero-based column number of the desired cell. Vectory (1d) arrays do not need to specify a column.

Notes:

  1. Each of these functions modify the third parameter to contain the value of the cell when the cell is of the same data type. An error occurs if the cell is not the same data type.
  2. The version with the third argument of char **string, the method will allocate enough memory to hold the null-terminated string. The calling function must use the delete operator to release the memory.
  3. In the version with the third argument is long stringlen, if stringlen is not large enough to hold the string, the function returns the length of the required length (plus the null terminator). In that case, the calling function should reallicate its buffer and call the function again with the new values.
  4. Since DATE and double are the same data types, use GetAtDATE for DATE data types.
int SetAt(VARIANT& vt, long row, long col = 0);
int SetAt(const char* string, long row, long col = 0);
int SetAt(const wchar_t* string, long row, long col = 0);
int SetAt(const UString& string, long row, long col = 0);
int SetAt(const CString& string, long row, long col = 0);
int SetAt(const string& string, long row, long col = 0);
int SetAt(const char val, long row, long col = 0);
int SetAt(const unsigned char val, long row, long col = 0);
int SetAt(const short val, long row, long col = 0);
int SetAt(const unsigned short val, long row, long col = 0);
int SetAt(const int val, long row, long col = 0);
int SetAt(const unsigned int val, long row, long col = 0);
int SetAt(const long val, long row, long col = 0);
int SetAt(const unsigned long val, long row, long col = 0);
int SetAt(const double val, long row, long col = 0);
int SetAt(const struct tm& tm, long row, long col = 0);
int SetAt(const SYSTEMTIME& tm, long row, long col = 0);
int SetAtZuluDateTime(long row, long col = 0);
int SetAtLocalDateTime(long row, long col = 0);    
int SetAtDate(const DATE val, long row, long col = 0);
Parameters:
row - zero-based row number of the desired cell
col - zero-based column number of the desired cell. Vectory (1d) arrays do not need to specify a column.

Notes:
Each of these functions properly destroys the current contents of the cell, then modifies it with the specified data type.
int Sort(long lKeyCol, bool bAscending = true);
Parameters:
lKeyCol - the column number that will be used to perform the sort bAscending - true sorts the rows in ascending order, or false in descent

Notes:
This function sorts by the specified column all the rows in either ascending or descending order using a quicker sort algorithm by Dr. Robert Sedgewick http://www.yendor.com/programming/sort/
long Find(long lKeyCol, CVariantObj& key)
Parameters:
lKeyCol - the column number that contains the desired value
key - a VARIANT that contains the value to search for.

Return Value:
The zero-based row number where key was found, or -1 if key was not found.
Notes:
Performs a linear search of the array for the key value.
const VARIANT* GetArray()
Parameters:
None

Return Value:
Returns the VARIANT represented in the CVariantArray class. The VARIANT is NOT copied, so any changes the calling functions may make will be reflected in the CVariantArray class. Calling functions must not call InitVariant with the returned value.

Examples

CVariantArray is very simple to use as the client/server demo project shows. It only requires including VariantArray.h and VariantArray.cpp in your project, for example:

#include "VariantArray.h"

On the server side, to attach the array to the CVariantArray class:

int foo(VARIANT* vt)
{
    CVariantArray array;
    array.Attach(vt)
}

Then to insert a data item into the array:

    array.SetAt("something", row,col);
    long lItem = 123;
    array.SetAt(lItem, row,col);

A Detach() method is available in the class, but it is not necessary to use it before the server automation method returns. The CVariantArray destructure does that for you.

The client side is similar to the server side

int foo(VARIANT* vt)
{
    CVariantArray array;
    VARIANT vt;
    // call server to get the data 

    array.Attach(&vt)
}

The server project in the demo has only one method exposed to automation objects - ExecuteSQL(). It simulates retrieving some data from a database, creating the array, populating the array with the data received from the database, and returning the data back to the client. In the demo, there are no real database calls. To keep things simple, the data is hard-coded in the program.

STDMETHODIMP CDatabaseObj::GetArray(long nDims, VARIANT *array)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState())
	if( nDims < 1 || nDims > 2)
		return S_FALSE;

	// create a 2-dimensional array with six columns and three rows

	long nRows = 3;
	long nCols = nDims == 1 ? 1 : 6;
	CVariantArray ay;
	ay.Attach(array);
	// Create the array

	ay.Create(nRows,nCols);
	// populate the array with the column headers.

	// Column headerings are not required by

	// the CVariantArray class.  It knows nothing about

	// them.

	for(long i= 0; i < nCols; i++)
		ay.SetAt(names[i], 0,i);
	// populate rows 1 and 2 with actional data.  In this example

	// we are using only char* data, but it can be any elementry

	// data type.

	for(long col = 0; col < nCols; col++)
	{
		ay.SetAt(row1[col],1,col);
	}

	for(col = 0; col < nCols; col++)
	{
		ay.SetAt(row2[col], 2,col);
	}

	return S_OK;
}

The client demo is an MFC dialog project that supports automation. When the "GetData" button is pressed, it calls CoCreateIntance() to create the client/server link, calls the ExecuteSQL() method, then displays the returned data in a grid.

Below is the code to get the data from the server.

void CClientMFCDlg::OnButon2DArray() 
//

// Create the com object, get the data from

// the server, release the com obmect, and 

// finally display the data.

{
	HRESULT hr = 0;
	IDatabaseObj* pObj;
	hr = CoCreateInstance(CLSID_DatabaseObj, 
				NULL, 
				CLSCTX_INPROC_SERVER,
				IID_IDatabaseObj, 
				(void**) &pObj);
	if(hr == S_OK)
	{
		VARIANT vt;
		VariantInit(&vt);
		hr = pObj->GetArray(2,&vt);
		pObj->Release();
		DisplayResults(vt);

	}
	
}


void CClientMFCDlg::OnButton1DArray() 
{
	HRESULT hr = 0;
	IDatabaseObj* pObj;
	hr = CoCreateInstance(CLSID_DatabaseObj, 
				NULL, 
				CLSCTX_INPROC_SERVER,
				IID_IDatabaseObj, 
				(void**) &pObj);
	if(hr == S_OK)
	{
		VARIANT vt;
		VariantInit(&vt);
		hr = pObj->GetArray(1,&vt);
		pObj->Release();
		DisplayResults(vt);

	}
	
}

This function retrieves the data from the array and displays it in the grid.

void CClientMFCDlg::DisplayResults(VARIANT& vt)
// Create the CVariantArray object and attached the

// VARIANT array that was received from the server.

// Then populate the grid control with the data.

{
	CVariantArray ay;
	// attach the VARIANT to the class

	ay.Attach(&vt);
	// get number of rows and columns in

	// the array

	long nRows = ay.GetRows();
	long nCols = ay.GetCols();
	// set up the grid with the number of

	// rows and columns in the array.

	m_Grid.SetCols(nCols);
	m_Grid.SetRows(nRows+1);
	m_Grid.SetFixedRows(1);
	m_Grid.SetFixedCols(0);
#if defined(_MFC_)
	CString data;
#elif defined(_DATAREEL_)
	UString data;
#else
	string data;
#endif
	m_Grid.SetRow(0);
	// reset the width of each column in the grid

	for(long i = 0; i < nCols; i++)
	{
		long width = m_Grid.GetColWidth(i);
		width += 80;
		m_Grid.SetColWidth(i,width);
	}
	// Get the data from each column row using a CString 

	// data type.

	for(long row = 0; row < nRows; row++)
	{
		for(long col = 0; col < nCols; col++)
		{
			// get a string 

			ay.GetAt(data,row,col);
			// put it in the grid

#if defined(_MFC_)
			m_Grid.SetTextMatrix(row,col,data);
#else
			m_Grid.SetTextMatrix(row,col,data.c_str());
#endif
		}
	}

}

History

Version 1.1 adds support for string template class for non-MFC programs.

Version 2.0
  • Major upgrade to support 1d vectors. Parameters were rearranged so that the column number could be optional for 1d arrays.
  • The demo project includes both a Win32 and a WinCE demo. MSVC++ 6.0 is required for the Win32 demo, and eVC++ 3.0 compiler is required for the WinCE demo.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here