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 BSTR
s. 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;
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.
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:
- 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.
- 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.
- 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.
- 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;
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;
long nRows = 3;
long nCols = nDims == 1 ? 1 : 6;
CVariantArray ay;
ay.Attach(array);
ay.Create(nRows,nCols);
for(long i= 0; i < nCols; i++)
ay.SetAt(names[i], 0,i);
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()
{
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)
{
CVariantArray ay;
ay.Attach(&vt);
long nRows = ay.GetRows();
long nCols = ay.GetCols();
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);
for(long i = 0; i < nCols; i++)
{
long width = m_Grid.GetColWidth(i);
width += 80;
m_Grid.SetColWidth(i,width);
}
for(long row = 0; row < nRows; row++)
{
for(long col = 0; col < nCols; col++)
{
ay.GetAt(data,row,col);
#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.