DOCLIENT.CPP

//-------------------------------------------------------------------- 
// Microsoft OLE DB ISAPI Sample
// Copyight(c) 1996 Microsoft Corporation. All Rights Reserved.
//
// @doc
//
// @module DOclient.CPP | OLE DB DO client, it connects to the provider,
// runs SQL SELECT statement, prints data onto a webpage
//
// @rev 1 | 06-11-96 | Created
//


//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <afxole.h>

#define DBINITCONSTANTS

#include <iostream.h>
#include <assert.h>
#include <oledb.h>
#include <msdasql.h>
#include <oledberr.h>
#include "DOclient.h" //Data Objects Client header file

#define NUMELEM(p1) (sizeof(p1) / sizeof(p1[0]))

//-----------------------------------------------------------------------------
// Global Variables
//-----------------------------------------------------------------------------

IMalloc* g_pIMalloc = NULL; //Memory allocator



//-----------------------------------------------------------------------------------
// ConnectDB
//
// @func HRESULT| ConnectDB | Instantiates Kagera, Initialize OLE DB, Create Session
//
// @rdesc HRESULT
// @flag E_FAIL | Failed
// @flag E_NOERROR | Success
//-----------------------------------------------------------------------------------
HRESULT ConnectDB
(
IDBCreateCommand **ppIDBCreateCommand_out,//@param OUT | Pointer to Create Command
LPCSTR pstrDNS, //@param IN | Login Inforamtion
LPCSTR pstrUserName, //@param IN | Login Inforamtion
LPCSTR pstrPassword, //@param IN | Login Inforamtion
CHttpServerContext* pCtxt//@param IN | HTML page context
)
{
ULONGcVar; //Utility Variables
HRESULThr = ResultFromScode(S_OK);

IDBProperties * pIDBProps = NULL; // IDBInitialize Object
IDBInitialize * pIDBInit = NULL; // IDBInitialize Object
IDBCreateSession *pIDBCreateSession= NULL;// IDBCreateSession
IDBCreateCommand *pIDBCreateCommand = NULL;// IDBCreateCommand

DBPROPSETrgPropertySet[1];// Array of property sets
DBPROPrgProperties[3];// Array of property values

// Strings for OLE DB DO (CHAR & WCHAR)
*ppIDBCreateCommand_out=NULL;

// Set up the Property Buffer
rgPropertySet[0].guidPropertySet=DBPROPSET_DBINIT;
rgPropertySet[0].rgProperties=rgProperties;
rgPropertySet[0].cProperties=NUMELEM(rgProperties);

if( FAILED(hr = SetInitializationData(pstrDNS, pstrUserName, pstrPassword,
rgPropertySet, pCtxt)) )
goto error;

// Initialize OLE
hr = CoInitialize( NULL );
if (FAILED(hr))
{
*pCtxt << "<p>Ole Failed</p>";
goto error;
}

// Retrieve the task memory allocator
hr = CoGetMalloc( MEMCTX_TASK, &g_pIMalloc );
if (FAILED(hr))
{
*pCtxt << "<p>CoGetMalloc Failed</p>";
goto error;
}

GUID ProviderClsid = CLSID_MSDASQL;
// Get IDBProperties Pointer
hr = CoCreateInstance( ProviderClsid, NULL,
CLSCTX_INPROC_SERVER, IID_IDBProperties, (LPVOID*)&pIDBProps );
if (FAILED(hr))
{
*pCtxt << "<p>CoCreateInstance Failed</p>";
*pCtxt << "<p>"<<hr<<"</p>";
goto error;
}

// Set DSO init properties.
hr = pIDBProps->SetProperties(NUMELEM(rgPropertySet), rgPropertySet);
if (FAILED(hr))
{
*pCtxt << "<p>Set properties.</p>";
*pCtxt << "<p>"<<hr<<"</p>";
goto error;
}

//Get IDBInitialize pointer.
hr = pIDBProps->QueryInterface (IID_IDBInitialize,(LPVOID*)&pIDBInit);
if (FAILED(hr))
{
*pCtxt << "<p>QI for IDBInit failed.</p>";
*pCtxt << "<p>"<<hr<<"</p>";
goto error;
}

//Initialize DSO.
hr = pIDBInit->Initialize();
if (FAILED(hr))
{
PrintErrorInfo (pIDBInit,IID_IDBInitialize,pCtxt);
*pCtxt<<"<p>Initialize Failed</p>";
*pCtxt<<"<h3>Check your login parameters</h3>";
goto error;
}

// from the DataSource Object get the Session Object
hr = pIDBInit->QueryInterface(IID_IDBCreateSession,
(void**)&pIDBCreateSession );
if (FAILED(hr))
{
PrintErrorInfo (pIDBInit,IID_IUnknown,pCtxt);
*pCtxt<<"<p>QI for Session Object failed </p>";
goto error;
}

// From the Session object, get a Command Object
hr = pIDBCreateSession->CreateSession(NULL, IID_IDBCreateCommand, (IUnknown**) ppIDBCreateCommand_out);
if (FAILED(hr))
{
PrintErrorInfo (pIDBCreateSession,IID_IDBCreateCommand,pCtxt);
*pCtxt<<"<p>Create Session failed !</p>";
goto error;
}

error :
//Release resources that are not essential anymore.
for (cVar=0; cVar<rgPropertySet[0].cProperties; cVar++)
VariantClear(&rgPropertySet[0].rgProperties[cVar].vValue);

if( pIDBCreateSession )
pIDBCreateSession->Release();
if( pIDBProps )
pIDBProps->Release();
if( pIDBInit )
pIDBInit->Release();

return hr;
} //ConnectDB


//-----------------------------------------------------------------------------------
// SetAndExecute
//
// @func HRESULT| SetAndExecute | Sets the query, executes and sets up the ground
// for data retrieval
//
// @rdesc HRESULT
// @flag E_FAIL | Failed
// @flag E_NOERROR | Success
//-----------------------------------------------------------------------------------

HRESULT SetAndExecute
(
WCHAR *wszSQLCommand,
IDBCreateCommand *pIDBCreateCommand,
IRowset**ppIRowset_out,
DBCOLUMNINFO **ppColumnInfo_out,
WCHAR **ppStringBuffer_out,
ULONG *pcCol,
DBBINDING **pprgBind,
ULONG *pcBind,
HACCESSOR *phAccessor,
ULONG *pcbMaxRowSize,
CHttpServerContext*pCtxt
)
{
HRESULThr = S_OK;

//Column Info Variables
ULONGcCol;
DBCOLUMNINFO *pColumnInfo;
WCHAR*pStringBuffer; //It is needed for printing column names
ULONG cBind;
HACCESSOR hAccessor= NULL;
IAccessor*pIAccessor= NULL;
IColumnsInfo*pIColumnsInfo= NULL;
ICommandText * pICommandText = NULL; //
IRowset * pIRowset = NULL; //
ULONG cbMaxRowSize;



hr = pIDBCreateCommand->CreateCommand(NULL,IID_ICommandText,(IUnknown**)&pICommandText);
if (FAILED(hr))
{
PrintErrorInfo (pIDBCreateCommand,IID_ICommand,pCtxt); //?????
*pCtxt << "<h3>ERROR : Create Command Text Failed</h3>";
goto error;
}
else
{
hr = pICommandText->SetCommandText (DBGUID_DBSQL,wszSQLCommand);
if (FAILED (hr))
{
PrintErrorInfo (pIDBCreateCommand,IID_ICommandText,pCtxt);
*pCtxt <<"<h3> ERROR : Could not set command </h3>";
goto error;
}
}

//Execute Query
hr = pICommandText->Execute (
NULL,
IID_IRowset,
NULL,
NULL,
(IUnknown**)&pIRowset);

if (FAILED(hr))
{
PrintErrorInfo (pICommandText,IID_ICommandText,pCtxt);
*pCtxt<<"<h3>ERROR : Execution failed, Check your SQL statement.</h3>";
goto error;
}

//Get column Info
hr = pIRowset->QueryInterface(IID_IColumnsInfo,(void **) &pIColumnsInfo);
if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

hr = pIColumnsInfo->GetColumnInfo(&cCol,&pColumnInfo,&pStringBuffer);
if (FAILED(hr))
{
PrintErrorInfo (pIColumnsInfo,IID_IRowset,pCtxt);
goto error;
}


//Allocate array of Bindings
(*pprgBind) = new DBBINDING [cCol];
if (*pprgBind==NULL)
{
*pCtxt << "<p>Could not allocate required number of columns.\n<p>";
*pCtxt << "<p>Out of memory.\n<p>";
goto error;
}

if (cCol==0)
{
*pCtxt << "<p>No Data (no columns found). \n<p>";
goto error;
}

hr = SetupBindings(cCol,pColumnInfo,*pprgBind,&cBind,&cbMaxRowSize);
if (FAILED(hr))
{
*pCtxt<<"<h3>ERROR : Could not setup bindings</h3>";
goto error;
}

// Get an accessor for our bindings from the rowset, via IAccessor
hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );

if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

hr = pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA,
cBind,
*pprgBind,
0,
&hAccessor,
NULL);

if (FAILED(hr))
{
PrintErrorInfo (pIAccessor,IID_IAccessor,pCtxt);
goto error;
}


//Assign out parameters
error :
if( SUCCEEDED(hr) )
{
*ppIRowset_out = pIRowset;
pIRowset = NULL;
*ppColumnInfo_out = pColumnInfo;
pColumnInfo = NULL;
*ppStringBuffer_out = pStringBuffer;
pStringBuffer = NULL;
*pcCol = cCol;
*pcBind = cBind;
*phAccessor = hAccessor;
*pcbMaxRowSize = cbMaxRowSize;
}
else
{
*ppIRowset_out = NULL;
*ppColumnInfo_out = NULL;
*ppStringBuffer_out = NULL;
*pcCol = 0;
*pcBind = 0;
*phAccessor = NULL;
*pcbMaxRowSize = 0;
}

if( pStringBuffer )
g_pIMalloc->Free(pStringBuffer);
if( pIRowset )
pIRowset->Release();
if( pIColumnsInfo )
pIColumnsInfo->Release();
if( pIAccessor )
pIAccessor->Release();
if( pICommandText )
pICommandText->Release();
if( pIDBCreateCommand )
pIDBCreateCommand->Release();

return hr;
} //Set And Execute


//-----------------------------------------------------------------------------------
// GetAndWrite
//
// @func HRESULT| GetAndWrite | Gets data from the rowset.
//
// @rdesc HRESULT
// @flag E_FAIL | Failed
// @flag E_NOERROR | Success
//-----------------------------------------------------------------------------------
HRESULT GetAndWrite
(
IRowset*pIRowset, //@param IN | Pointer to set of rows
ULONG cMaxRowSize,//@param IN | MaxRowSize
HACCESSOR hAccessor,//@param IN | Accessor Handle
DBBINDING*rgBind, //@param IN | Array of bindings
ULONGcBind, //@param IN | Number of bindings
DBCOLUMNINFO*pColumnInfo,//@param IN | Points to an array of DBCOLUMNIFO structures
ULONGcCol,//@param IN | Number of columns
CHttpServerContext* pCtxt//@param IN | Output place
)

{
ULONG cRowsObtained; // Number of rows obtained
ULONGiRow; // Row Count
BYTE*pRowData = NULL; // Memory for Data
HROW rghRows[NUMROWS_CHUNK]; // Row Handles
HROW*pRows = &rghRows[0]; // Pointer to the Row Handles
HRESULT hr=S_OK; // HRESULT

ULONG ulCount; //Counter
CHAR szBuffer[MAXBUFLEN+1];

// Asserts
assert(pIRowset != NULL);
assert(rgBind != NULL);
assert(pColumnInfo != NULL);


// Create a buffer for row data, big enough to hold the biggest row
pRowData = (BYTE *) malloc( cMaxRowSize );
if (!pRowData)
goto error;

//Initialize table output
*pCtxt << "<CENTER>\n";
*pCtxt << "<table>\n";
*pCtxt << "<TABLE BORDER>\n";
*pCtxt << "<caption> <h3> The data you requested: <h3> </caption>\n";

//Print Column Names
*pCtxt << "<TR>";
for (ulCount=0;ulCount<cCol;ulCount++)
{
WideCharToMultiByte(CP_ACP, 0, pColumnInfo[ulCount].pwszName, -1, szBuffer, MAXBUFLEN+1, NULL, NULL);
*pCtxt << "<TH><B>"<<szBuffer<<"</B></TH>";
}
*pCtxt << "</TR>";


// Process all the rows, NUMROWS_CHUNK rows at a time
while (1)
{
hr = pIRowset->GetNextRows(
0,// cbChapter
0,// cRowsToSkip
NUMROWS_CHUNK,// cRowsDesired
&cRowsObtained,// cRowsObtained
&pRows );// filled in w/ row handles

if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

// All done, no more rows left to get
if ( cRowsObtained == 0 )
break;

// Loop over rows obtained, getting data for each
for ( iRow=0; iRow < cRowsObtained; iRow++ )
{
hr = pIRowset->GetData(
rghRows[iRow],
hAccessor,
pRowData );

if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}


// Print to web page
*pCtxt <<"<tr>\n";
DumpRow( rgBind, cBind,pRowData,pCtxt);
*pCtxt <<"</tr>\n";
} //for


// Release row handles
hr = pIRowset->ReleaseRows( cRowsObtained, rghRows, NULL, NULL, NULL );

if (FAILED(hr))
{
PrintErrorInfo (pIRowset,IID_IRowset,pCtxt);
goto error;
}

}// end while
*pCtxt << "</table>"; //End of table (no more data)

error:
if( pRowData )
free( pRowData );

return hr;
} //GetAndWrite

//-----------------------------------------------------------------------------------
// FreeEnvironment
//
// @func void| FreeEnvironment | Deallocates resources
//
// @rdesc NONE
//-----------------------------------------------------------------------------------
void FreeEnvironment
(
IRowset*pIRowset, //@param IN | Pointer to set of rows
HACCESSOR hAccessor,//@param IN | Accessor Handle
DBCOLUMNINFO*pColumnInfo, //@param IN | DBCOLUMNINFO array pointer
WCHAR * pStringBuffer //@param IN | String Buffer contining the info about columns
)
{
HRESULT hr;
IAccessor*pIAccessor = NULL;// Pointer to an Accessor

// Tell the rowset object it can release the accessor, via IAccessor
if (pIRowset)
hr = pIRowset->QueryInterface( IID_IAccessor, (void**)&pIAccessor );
else
goto error;

if (FAILED(hr))
goto error;

hr = pIAccessor->ReleaseAccessor( hAccessor,NULL );
if (FAILED(hr))
goto error;

error :
if( pIAccessor )
pIAccessor->Release();
if( pIRowset )
pIRowset->Release();

if (pStringBuffer!=NULL)
{
g_pIMalloc->Free(pStringBuffer);
pStringBuffer = NULL;
}

if (pColumnInfo!=NULL)
{
g_pIMalloc->Free(pColumnInfo);
pColumnInfo = NULL;
}

if (g_pIMalloc)
g_pIMalloc->Release();

SetErrorInfo(0, NULL);

CoUninitialize();

} //Free Environment


//-----------------------------------------------------------------------------------
//Functions that are not called directly by MainFunction but are used by
//functions described above.
//-----------------------------------------------------------------------------------

HRESULT SetInitializationData
(
LPCSTR pstrDNS,//@param IN | DNS Name
LPCSTR pstrUserName,//@param IN | User Name
LPCSTR pstrPassword,//@param IN | Passwd
DBPROPSET rgPropertySet[],//@param OUT | Array of propery sets (only one)
CHttpServerContext* pCtxt//@param IN | Web page context
)
{
HRESULT hr = ResultFromScode(S_OK);

CHARszDBName[MAXBUFLEN+1];// DSN
CHARszUserName[MAXBUFLEN+1]; // User Name
CHARszPassword[MAXBUFLEN+1]; // Password

WCHARwszDBName[MAXBUFLEN+1];// DSN String (WCHAR)
WCHARwszUserName[MAXBUFLEN+1];// User Name (WCHAR)
WCHARwszPassword[MAXBUFLEN+1];// Password (WCHAR)

strcpy (szDBName,pstrDNS);
strcpy (szUserName,pstrUserName);
strcpy (szPassword,pstrPassword);

//Reset the out parameter(s)
//*****************************************************************
// Identify Property Set
// Initialize property values
for(ULONG ul=0; ul<rgPropertySet[0].cProperties; ul++)
{
VariantInit(&rgPropertySet[0].rgProperties[ul].vValue);

// Set Common structure values
rgPropertySet[0].rgProperties[ul].dwOptions=DBPROPOPTIONS_REQUIRED;
rgPropertySet[0].rgProperties[ul].colid=DB_NULLID;
rgPropertySet[0].rgProperties[ul].vValue.vt=VT_BSTR;
}

// Fill in Data Source
rgPropertySet[0].rgProperties[0].dwPropertyID=DBPROP_INIT_DATASOURCE;
if (0==MultiByteToWideChar(CP_ACP, 0, szDBName, -1, wszDBName, MAXBUFLEN+1))
{
*pCtxt << "MultiByteToWideChar szDBName, wszDBName FAILED";
hr = E_FAIL;
goto error;
}
rgPropertySet[0].rgProperties[0].vValue.bstrVal=SysAllocString(wszDBName);
if (rgPropertySet[0].rgProperties[0].vValue.bstrVal==NULL)
{
*pCtxt << "Error during string allocation #0 (ConnectDB)\n";
hr = E_OUTOFMEMORY;
goto error;
}

// Fill in User Name
rgPropertySet[0].rgProperties[1].dwPropertyID=DBPROP_AUTH_USERID;
if (0==MultiByteToWideChar(CP_ACP, 0, szUserName, -1, wszUserName, MAXBUFLEN+1))
{
*pCtxt << "MultiByteToWideChar szDBName, wszDBName FAILED";
hr = E_FAIL;
goto error;
}
rgPropertySet[0].rgProperties[1].vValue.bstrVal=SysAllocString(wszUserName);
if (rgPropertySet[0].rgProperties[1].vValue.bstrVal==NULL)
{
*pCtxt << "Error during string allocation #1 (ConnectDB)\n";
hr = E_OUTOFMEMORY;
goto error;
}

// Fill in Password
rgPropertySet[0].rgProperties[2].dwPropertyID=DBPROP_AUTH_PASSWORD;
if (0==MultiByteToWideChar(CP_ACP, 0, szPassword, -1, wszPassword, MAXBUFLEN+1))
{
*pCtxt << "MultiByteToWideChar szDBName, wszDBName FAILED";
hr = E_FAIL;
goto error;
}
rgPropertySet[0].rgProperties[2].vValue.bstrVal=SysAllocString(wszPassword);
if (rgPropertySet[0].rgProperties[2].vValue.bstrVal==NULL)
{
*pCtxt << "Error during string allocation #2(ConnectDB)\n";
hr = E_OUTOFMEMORY;
goto error;
}

error :
return hr;
} //SetInitializationData


//-----------------------------------------------------------------------------------
// PrintErrorInfo
//
// @func void| PrintErrorInfo | Uses error collection data to issue an error.
//
// @rdesc HRESULT
//-----------------------------------------------------------------------------------
void PrintErrorInfo
(
IUnknown * pBadObject, //@param IN | Pointer to the object where error happened
REFIID IID_BadIntereface, //@param IN | Interface that caused the error
CHttpServerContext* pCtxt//@param IN | Output place
)
{
HRESULT hr = S_OK;
IErrorInfo * pErrorInfo= NULL;
IErrorInfo * pErrorInfoRec= NULL;
IErrorRecords * pErrorRecords= NULL;
ISupportErrorInfo * pSupportErrorInfo= NULL;
ULONG i,ulNumErrorRecs;
ERRORINFO ErrorInfo;
BSTRbstrDescriptionOfError = NULL;
BSTRbstrSourceOfError = NULL;

CHAR szBuffer [MAXBUFLEN+1]; //Error Info Buffer
DWORD MYLOCALEID;


MYLOCALEID = GetSystemDefaultLCID();

hr = pBadObject->QueryInterface (IID_ISupportErrorInfo,
(LPVOID FAR*)&pSupportErrorInfo);

if (SUCCEEDED(hr))
{
hr = pSupportErrorInfo->InterfaceSupportsErrorInfo(IID_BadIntereface);
if( hr == S_OK )
{
//Get Error Object. Return if no object Exists
GetErrorInfo (0,&pErrorInfo);
if( !pErrorInfo )
goto error;

//Get the IErrorRecord interface and get the count of error recs.
pErrorInfo->QueryInterface (IID_IErrorRecords,(LPVOID FAR*)&pErrorRecords);
pErrorRecords->GetRecordCount (&ulNumErrorRecs);

//Go through and print messages
for (i=0;i<ulNumErrorRecs;i++)
{
pErrorRecords->GetBasicErrorInfo(i,&ErrorInfo);
pErrorRecords->GetErrorInfo (i,MYLOCALEID,&pErrorInfoRec);

hr = pErrorInfoRec->GetDescription (&bstrDescriptionOfError);
if (FAILED(hr))
{
*pCtxt << "pErrorInfoRec->GetDescription FAILED!!! \n";
goto error;
}

hr = pErrorInfoRec->GetSource (&bstrSourceOfError);
if (FAILED(hr))
{
*pCtxt << "pErrorInfoRec->GetSource FAILED!!! \n";
goto error;
}


if (0==WideCharToMultiByte(CP_ACP, 0, bstrDescriptionOfError, -1, szBuffer, MAXBUFLEN+1, NULL, NULL))
{
*pCtxt << "WideChar -> Multi Conversion #1 failed. ERROR in PrintErrorInfo. \n";
goto error;
}

*pCtxt << "<p> Error Description : </p>";
*pCtxt <<"<p>" << szBuffer<< "</p>\n";
if (0==WideCharToMultiByte(CP_ACP, 0, bstrSourceOfError, -1, szBuffer, MAXBUFLEN+1, NULL, NULL))
{
*pCtxt << "WideChar -> Multi Conversion #2 failed. ERROR in PrintErrorInfo. \n";
goto error;
}

*pCtxt << "<p> Error Source : </p>";
*pCtxt <<"<p>" << szBuffer<< "</p>\n";


if( bstrDescriptionOfError )
{
SysFreeString (bstrDescriptionOfError);
bstrDescriptionOfError = NULL;
}
if( bstrSourceOfError )
{
SysFreeString (bstrSourceOfError);
bstrSourceOfError = NULL;
}

pErrorInfoRec->Release();
pErrorInfoRec = NULL;
}

pErrorInfo->Release();
pErrorInfo = NULL;

pErrorRecords->Release();
pErrorRecords = NULL;
} //S_OK
} //if (SUCCEEDED)

error :
if( bstrDescriptionOfError )
SysFreeString (bstrDescriptionOfError);
if( bstrSourceOfError )
SysFreeString (bstrSourceOfError);
if( pErrorInfo )
pErrorInfo->Release();
if( pErrorRecords )
pErrorRecords->Release();
if( pErrorInfoRec )
pErrorInfoRec->Release();
if( pSupportErrorInfo )
pSupportErrorInfo->Release();
} //PrintErrorInfo

//-----------------------------------------------------------------------------------
// SetupBindings
//
// @func HRESULT| SetupBindings | Recycle
//
// @rdesc HRESULT
// @flag E_FAIL | Failed
// @flag E_NOERROR | Success
//-----------------------------------------------------------------------------------
HRESULT SetupBindings
(
ULONG cCol,
DBCOLUMNINFO*pColumnInfo,
DBBINDING*rgBind_out,
ULONG*pcBind_out,
ULONG*pcMaxRowSize_out
)
{
ULONG dwOffset;// Length of a Row
ULONG iCol;// Column Count
ULONG iBind;// Binding Index

// Asserts
assert(pColumnInfo!= NULL);
assert(rgBind_out != NULL);
assert(pcBind_out != NULL);
assert(pcMaxRowSize_out != NULL);

// Create bindings.
// Bind everything as a string just to keep things simple.
dwOffset = 0;
iBind=0;
for (iCol=0; iCol < cCol; iCol++)
{
// Binding Structure
rgBind_out[iBind].dwPart = DBPART_VALUE | DBPART_LENGTH |
DBPART_STATUS;
rgBind_out[iBind].eParamIO = DBPARAMIO_OUTPUT;
rgBind_out[iBind].iOrdinal = pColumnInfo[iCol].iOrdinal;
rgBind_out[iBind].wType = DBTYPE_STR;
rgBind_out[iBind].pTypeInfo = NULL;
rgBind_out[iBind].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
rgBind_out[iBind].obValue = dwOffset + offsetof(COLUMNDATA,bData);
rgBind_out[iBind].obLength = dwOffset + offsetof(COLUMNDATA,dwLength);
rgBind_out[iBind].obStatus = dwOffset + offsetof(COLUMNDATA,wStatus);
rgBind_out[iBind].cbMaxLen = pColumnInfo[iCol].wType == DBTYPE_STR ?
pColumnInfo[iCol].ulColumnSize + 1 : DEFAULT_CBMAXLENGTH;

// LONG DATA hack
if(rgBind_out[iBind].cbMaxLen > MAX_ROW_SIZE)
rgBind_out[iBind].cbMaxLen = MAX_ROW_SIZE;

dwOffset += rgBind_out[iBind].cbMaxLen + offsetof( COLUMNDATA, bData );
dwOffset = ROUND_UP( dwOffset, COLUMN_ALIGNVAL );
iBind++;
}

// Return Values
*pcBind_out = iBind;
*pcMaxRowSize_out = dwOffset;

return S_OK;
}


//-----------------------------------------------------------------------------------
// DumpRow
//
// @func void | DumpRow | Puts data onto the webpage
//
//-----------------------------------------------------------------------------------

void DumpRow
(
DBBINDING* rgBind, //@param IN | Column bindings
ULONGcBind, //@param IN | Count of bindings
BYTE* pData, //@param IN | Data Pointer
CHttpServerContext* pCtxt //@param IN | Web Server context
)
{
ULONG iBind;// Binding Count
COLUMNDATA*pColumn;// Data Structure
CHARszDispBuffer[MAXBUFLEN+1];//Display Buffer


// Print each column we're bound to.
szDispBuffer[0] = '\0';
for (iBind=0; iBind < cBind; iBind++)
{
pColumn = (COLUMNDATA *) (pData + rgBind[iBind].obLength);

// Check Status for NULL / OK / CANTCONVERT.
switch (pColumn->wStatus)
{
case DBSTATUS_S_ISNULL:
strcpy(szDispBuffer, (LPSTR)"<b>NULL</b>\n");
break;
case DBSTATUS_S_OK:
pColumn->bData[MAXBUFLEN] = '\0'; //Long data problem solved??!
if (NULL==lstrcpy(szDispBuffer, (LPSTR)pColumn->bData))
*pCtxt << "lstrcpy ERROR. ";
break;
case DBSTATUS_E_CANTCONVERTVALUE:
strcpy(szDispBuffer,(LPSTR)"<b>can't convert</b>\n");
break;
default:
strcpy(szDispBuffer,(LPSTR)"<b>unknown status</b>\n");
break;
}

*pCtxt<<"<td>"<<szDispBuffer<<"</td>\n";
}

}//DumpRow