//----------------------------------------------------------------------------- // Microsoft OLE DB RowsetViewer // Copyright (C) 1994 - 1999 By Microsoft Corporation. // // @doc // // @module COMMON.CPP // //----------------------------------------------------------------------------------- ///////////////////////////////////////////////////////////////////////////// // Defines // ///////////////////////////////////////////////////////////////////////////// #define DBINITCONSTANTS #define INITGUID ///////////////////////////////////////////////////////////////////////////// // Include // ///////////////////////////////////////////////////////////////////////////// #include "Headers.h" #include //ERANGE ///////////////////////////////////////////////////////////////////////////// // Externs // ////////////////////////////////////////////////////////////////////////////// CComPtr g_spDataConvert; ///////////////////////////////////////////////////////////////////////////// // IsUnicodeOS // ///////////////////////////////////////////////////////////////////////////// BOOL IsUnicodeOS() { static BOOL fInitialized = FALSE; static BOOL fUnicode = TRUE; //NOTE: Don't call another other helper functions from within this function //that might also use the IsUnicodeOS flag, otherwise this will be an infinite loop... if(!fInitialized) { HKEY hkJunk = HKEY_CURRENT_USER; // Check to see if we have win95's broken registry, thus we do not have Unicode support if((RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE", 0, KEY_READ, &hkJunk) == S_OK) && hkJunk == HKEY_CURRENT_USER) { // Try the ANSI version if((RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE", 0, KEY_READ, &hkJunk) == S_OK) && (hkJunk != HKEY_CURRENT_USER)) { fUnicode = FALSE; } } if(hkJunk != HKEY_CURRENT_USER) RegCloseKey(hkJunk); fInitialized = TRUE; } return fUnicode; } ///////////////////////////////////////////////////////////////////////////// // WCHAR* StringCopy // ///////////////////////////////////////////////////////////////////////////// WCHAR* StringCopy(WCHAR* pwszDest, LPCWSTR pwszSource, DBLENGTH cMaxLen) { //Safe guard against overflow... if(cMaxLen) { //copy at most cMaxLen-1 letters, //pwszDest is null terminated when function returns wcsncpy_s(pwszDest, cMaxLen, pwszSource, cMaxLen-1); } return pwszDest; } ///////////////////////////////////////////////////////////////////////////// // WCHAR* StackFormat // ///////////////////////////////////////////////////////////////////////////// WCHAR* StackFormat(LPCWSTR pwszFmt, ...) { va_list marker; ASSERT(pwszFmt); static WCHAR wszBuffer[MAX_QUERY_LEN]; wszBuffer[0] = wEOL; // Use format and arguements as input //This version will not overwrite the stack, since it only copies //upto the max size of the array va_start(marker, pwszFmt); _vsnwprintf_s(wszBuffer, NUMELE(wszBuffer), _TRUNCATE, pwszFmt, marker); va_end(marker); //Make sure there is a NULL Terminator, vsnwprintf will not copy //the terminator if length==MAX_QUERY_LEN wszBuffer[NUMELE(wszBuffer)-1] = wEOL; return wszBuffer; } ///////////////////////////////////////////////////////////////////////////// // WCHAR* StringFormat // ///////////////////////////////////////////////////////////////////////////// WCHAR* StringFormat(WCHAR* pwszDest, DBLENGTH ulStrLen, LPCWSTR pwszFmt, ...) { ASSERT(pwszDest); ASSERT(ulStrLen); ASSERT(pwszFmt); va_list marker; // Use format and arguements as input //This version will not overwrite the stack, since it only copies //upto the max size of the array va_start(marker, pwszFmt); _vsnwprintf_s(pwszDest, ulStrLen, _TRUNCATE, pwszFmt, marker); va_end(marker); //Make sure there is a NULL Terminator, vsnwprintf will not copy //the terminator if length==MAX_QUERY_LEN pwszDest[ulStrLen-1] = wEOL; return pwszDest; } ///////////////////////////////////////////////////////////////////////////// // StringCompare // ///////////////////////////////////////////////////////////////////////////// BOOL StringCompare(LPCWSTR pwsz1, LPCWSTR pwsz2) { //Safe guard against NULL... if(pwsz1 && pwsz2) return wcscmp(pwsz1, pwsz2)==0; return pwsz1 == pwsz2; } ///////////////////////////////////////////////////////////////////////////// // StringCompareI // ///////////////////////////////////////////////////////////////////////////// BOOL StringCompareI(LPCWSTR pwsz1, LPCWSTR pwsz2) { //Safe guard against NULL... if(pwsz1 && pwsz2) return _wcsicmp(pwsz1, pwsz2)==0; return pwsz1 == pwsz2; } ///////////////////////////////////////////////////////////////////////////// // CHAR* StringCopy // ///////////////////////////////////////////////////////////////////////////// CHAR* StringCopy(CHAR* pszDest, LPCSTR pszSource, DBLENGTH cMaxLen) { //Safe guard against overflow... if(cMaxLen) { //copy at most cMaxLen-1 letters, //pwszDest is null terminated when function returns strncpy_s(pszDest, cMaxLen, pszSource, cMaxLen-1); } return pszDest; } ///////////////////////////////////////////////////////////////////////////// // ConvertToMBCS // ///////////////////////////////////////////////////////////////////////////// INT ConvertToMBCS(LPCWSTR pwsz, CHAR* psz, INT cchDst) { return ConvertToMBCS(pwsz, -1, psz, cchDst); } ///////////////////////////////////////////////////////////////////////////// // INT ConvertToMBCS // ///////////////////////////////////////////////////////////////////////////// INT ConvertToMBCS(LPCWSTR pwsz, INT cchSrc, CHAR* psz, INT cchDst) { //NOTE: The user can call this function with the Destination as NULL to indicate //just provide the length of the required conversion //No source if(!pwsz && psz) { //Conveience for the caller: //always make sure the result is null-terminated - if their is enough room if(cchDst >= 1) { psz[0] = EOL; return 1; } return 0; } //NOTE: WideCharToMultiByte destination buffer is really in (cb) count of bytes, //but to make things simplier since MultiByteToWideChar is opposite for the output //buffer, we will operate everything in cch - this is not a perf hit either since we don't //need a conversion since for CHAR -> cch == cb ASSERT(1/*cch*/ == sizeof(CHAR)/*cb*/); //Convert the string to MBCS INT cchWritten = WideCharToMultiByte(CP_ACP, 0, pwsz, cchSrc/*cch*/, psz, cchDst/*cb*/, NULL, NULL); //Since the buffer we passed in may be smaller than the actual data returned //we may not actually get to copy the NULL termintor, so make sure there is one at least //at the end of the buffer... if(cchDst && (cchDst < cchSrc)) { cchWritten = cchDst; psz[cchWritten-1] = '\0'; } return cchWritten; } ///////////////////////////////////////////////////////////////////////////// // ConvertToMBCS // Dynamically allocated memory ///////////////////////////////////////////////////////////////////////////// CHAR* ConvertToMBCS(LPCWSTR pwsz) { HRESULT hr = S_OK; //no-op case if(!pwsz) return NULL; //Determine the space required for the conversion INT iLen = ConvertToMBCS(pwsz, -1, NULL, 0); //Allocate space for the string CHAR* pszBuffer = NULL; SAFE_ALLOC(pszBuffer, CHAR, iLen+1); //Now convert the string ConvertToMBCS(pwsz, pszBuffer, iLen+1); CLEANUP: return pszBuffer; } ///////////////////////////////////////////////////////////////////////////// // ConvertToWCHAR // ///////////////////////////////////////////////////////////////////////////// INT ConvertToWCHAR(LPCSTR psz, WCHAR* pwsz, INT cchDst) { //Delegate return ConvertToWCHAR(psz, -1, pwsz, cchDst); } ///////////////////////////////////////////////////////////////////////////// // ConvertToWCHAR // ///////////////////////////////////////////////////////////////////////////// INT ConvertToWCHAR(LPCSTR psz, INT cchSrc, WCHAR* pwsz, INT cchDst) { //NOTE: The user can call this function with the Destination as NULL to indicate //just provide the length of the required conversion //No source if(!psz && pwsz) { //Conveience for the caller: //always make sure the result is null-terminated - if their is enough room if(cchDst >= 1) { pwsz[0] = wEOL; return 1; } return 0; } //NOTE: WideCharToMultiByte destination buffer is really in (cb) count of bytes, //but to make things simplier since MultiByteToWideChar is opposite for the output //buffer, we will operate everything in cch - this is not a perf hit either since we don't //need a conversion since for CHAR -> cch == cb ASSERT(1/*cch*/ == sizeof(CHAR)/*cb*/); //Convert the string to MBCS INT cchWritten = MultiByteToWideChar(CP_ACP, 0, psz, cchSrc/*cb*/, pwsz, cchDst/*cch*/); //Since the buffer we passed in may be smaller than the actual data returned //we may not actually get to copy the NULL termintor, so make sure there is one at least //at the end of the buffer... if(cchDst && (cchDst < cchSrc)) { cchWritten = cchDst; pwsz[cchWritten-1] = L'\0'; } return cchWritten; } ///////////////////////////////////////////////////////////////////////////// // HRESULT ConvertToWCHAR // Dynamically allocated memory ///////////////////////////////////////////////////////////////////////////// WCHAR* ConvertToWCHAR(LPCSTR psz) { HRESULT hr = S_OK; //no-op case if(!psz) return NULL; //Determine the space required for the conversion INT iLen = ConvertToWCHAR(psz, -1, NULL, 0); //Allocate space for the string WCHAR* pwszBuffer = NULL; SAFE_ALLOC(pwszBuffer, WCHAR, iLen+1); //Now convert the string ConvertToWCHAR(psz, pwszBuffer, iLen+1); CLEANUP: return pwszBuffer; } ///////////////////////////////////////////////////////////////////////////// // HRESULT PostProcessString // ///////////////////////////////////////////////////////////////////////////// HRESULT PostProcessString(WCHAR* pwsz, DBLENGTH ulMaxSize, DWORD dwFlags) { ASSERT(pwsz); ASSERT(ulMaxSize); WCHAR* pwszStop = NULL; LONG lValue = 0; //Currently only deals with HEX or DECIMAL ASSERT(dwFlags & CONV_HEX || dwFlags & CONV_DECIMAL); //Convert String to integer if(!ConvertToLONG(pwsz, &lValue, LONG_MIN, LONG_MAX, 0/*0 base indicates WinAPI to interpret*/)) return E_FAIL; //Convert Integer back to requested Format if(dwFlags & CONV_HEX) StringFormat(pwsz, ulMaxSize, L"0x%x", lValue); else StringFormat(pwsz, ulMaxSize, L"%d", lValue); return S_OK; } ///////////////////////////////////////////////////////////////////////////// // HRESULT DataConvert // ///////////////////////////////////////////////////////////////////////////// HRESULT DataConvert ( DBSTATUS dbSrcStatus, // @parm IN | source data status DBLENGTH cbSrcLength, // @parm IN | size of source data DBLENGTH cbSrcMaxLength, // @parm IN | max size of input buffer DBTYPE wSrcType, // @parm IN | type of source data void* pSrcValue, // @parm IN | ptr to source data BYTE bPrecision, // @parm IN | precision to use for dst BYTE bScale, // @parm IN | scale to use for dst DBTYPE wDstType, // @parm IN | type of output data DBSTATUS* pdbDstStatus, // @parm OUT | ptr to output data status DBLENGTH* pcbDstLength, // @parm OUT | ptr to size of output data void* pDstValue, // @parm OUT | ptr to output data DBLENGTH cbDstMaxLength, // @parm IN | max size of output buffer DWORD dwConvFlags // @parm IN | Conversion flags ) { HRESULT hr = S_OK; if(pdbDstStatus) *pdbDstStatus = DBSTATUS_S_OK; if(pcbDstLength) *pcbDstLength = 0; //NOTE: cbSrcLength passed in is the actual length of the data (untruncated). //cbSrcMaxLength is the maximum size of the source (read) buffer thats valid. This is useful to //seperate into two seperate items, since for String data this doesn't include the null terminator //and for fixed length it is the maximum. But all data source is bound by cbSrcMaxLength... cbSrcLength = (wSrcType & DBTYPE_BYREF) ? cbSrcLength : min(cbSrcLength, cbSrcMaxLength); //STATUS... switch(dbSrcStatus) { case DBSTATUS_S_ISNULL: case DBSTATUS_S_DEFAULT: case DBSTATUS_S_IGNORE: hr = S_OK; goto CLEANUP; case DBSTATUS_S_TRUNCATED: hr = S_OK; break; case DBSTATUS_S_OK: hr = S_OK; break; default: hr = DB_E_CANTCONVERTVALUE; goto CLEANUP; }; //LENGTH... //VALUE... if(!pSrcValue || !pDstValue) goto CLEANUP; //Can we convert this "locally" without bringing in DC? if(!(dwConvFlags & CONV_MSDADC_ONLY)) { switch(DBTYPE_SRC_DST(wSrcType, wDstType)) { case DBTYPE_SRC_DST(DBTYPE_WSTR, DBTYPE_WSTR): { //cbSrcLength - doesn't account for Null terminator DBLENGTH cbSrcLengthPlusTerm = min(cbSrcLength + sizeof(WCHAR), cbSrcMaxLength); //VALUE //NOTE: We don't use wcscpy since user data could have embedded NULLs memcpy(pDstValue, pSrcValue, (size_t)(min(cbSrcLengthPlusTerm, cbDstMaxLength))); //Since the buffer we passed in may be smaller than the actual data returned //we may not actually get to copy the NULL termintor, so make sure there is one at least //at the end of the buffer... if(cbDstMaxLength && (cbDstMaxLength < cbSrcLengthPlusTerm)) ((WCHAR*)pDstValue)[cbDstMaxLength/sizeof(WCHAR)-1] = L'\0'; //LENGTH //NOTE: This doesn't include the NULL terminator if(pcbDstLength) *pcbDstLength = min(cbSrcLength, cbDstMaxLength); break; } case DBTYPE_SRC_DST(DBTYPE_STR, DBTYPE_WSTR): { //cbSrcLength - doesn't account for Null terminator DBLENGTH cbSrcLengthPlusTerm = min(cbSrcLength + sizeof(CHAR), cbSrcMaxLength); //VALUE INT chWritten = ConvertToWCHAR((CHAR*)pSrcValue, (INT)cbSrcLengthPlusTerm/sizeof(CHAR), (WCHAR*)pDstValue, (INT)cbDstMaxLength/sizeof(WCHAR)); //NOTE: LENGTH doesn't include the NULL terminator if(pcbDstLength && chWritten) *pcbDstLength = (chWritten * sizeof(WCHAR)) - sizeof(WCHAR); break; } case DBTYPE_SRC_DST(DBTYPE_WSTR, DBTYPE_STR): { //cbSrcLength - doesn't account for Null terminator DBLENGTH cbSrcLengthPlusTerm = min(cbSrcLength + sizeof(WCHAR), cbSrcMaxLength); //VALUE INT chWritten = ConvertToMBCS((WCHAR*)pSrcValue, (INT)cbSrcLengthPlusTerm/sizeof(WCHAR), (CHAR*)pDstValue, (INT)cbDstMaxLength/sizeof(CHAR)); //NOTE: LENGTH doesn't include the NULL terminator if(pcbDstLength && chWritten) *pcbDstLength = (chWritten * sizeof(CHAR)) - sizeof(CHAR); break; } case DBTYPE_SRC_DST(DBTYPE_VARIANT, DBTYPE_WSTR): { hr = VariantToString((VARIANT*)pSrcValue, (WCHAR*)pDstValue, cbDstMaxLength/sizeof(WCHAR), dwConvFlags); //LENGTH //NOTE: This doesn't include the NULL terminator if(pcbDstLength) *pcbDstLength = wcslen((WCHAR*)pDstValue)*sizeof(WCHAR); break; } case DBTYPE_SRC_DST(DBTYPE_WSTR, DBTYPE_VARIANT): { VARIANT vVariant; V_VT(&vVariant) = VT_BSTR; V_BSTR(&vVariant) = SysAllocString((WCHAR*)pSrcValue); memcpy(pDstValue, &vVariant, sizeof(VARIANT)); //LENGTH if(pcbDstLength) *pcbDstLength = sizeof(VARIANT); break; } case DBTYPE_SRC_DST(DBTYPE_HCHAPTER, DBTYPE_WSTR): { //TODO IUnknown* pIUnknown = *(IUnknown**)pSrcValue; StringFormat((WCHAR*)pDstValue, cbDstMaxLength/sizeof(WCHAR), L"0x%p", pIUnknown); //LENGTH //NOTE: This doesn't include the NULL terminator if(pcbDstLength) *pcbDstLength = wcslen((WCHAR*)pDstValue)*sizeof(WCHAR); break; } case DBTYPE_SRC_DST(DBTYPE_WSTR, DBTYPE_HCHAPTER): { //TODO - I have no clue how to update a hChapter columns? //But since we allow binding in the Accessor as native HCHAPTER we //need to support the SetData case, otherwise it will assert... //LENGTH if(pcbDstLength) *pcbDstLength = sizeof(HCHAPTER); break; } default: { if(wSrcType & DBTYPE_VECTOR && wDstType == DBTYPE_WSTR) { hr = VectorToString((DBVECTOR*)pSrcValue, wSrcType, (WCHAR*)pDstValue, cbDstMaxLength/sizeof(WCHAR)); //LENGTH if(pcbDstLength) *pcbDstLength = wcslen((WCHAR*)pDstValue)*sizeof(WCHAR); break; } else if(wDstType & DBTYPE_VECTOR && wSrcType == DBTYPE_WSTR) { hr = StringToVector((WCHAR*)pSrcValue, wDstType, (DBVECTOR*)pDstValue); //LENGTH if(pcbDstLength) *pcbDstLength = sizeof(DBVECTOR); break; } else if(wSrcType & DBTYPE_ARRAY && wDstType == DBTYPE_WSTR) { hr = SafeArrayToString(*(SAFEARRAY**)pSrcValue, wSrcType, (WCHAR*)pDstValue, cbDstMaxLength/sizeof(WCHAR)); //LENGTH if(pcbDstLength) *pcbDstLength = wcslen((WCHAR*)pDstValue)*sizeof(WCHAR); break; } else if(wDstType & DBTYPE_ARRAY && wSrcType == DBTYPE_WSTR) { hr = StringToSafeArray((WCHAR*)pSrcValue, wDstType, (SAFEARRAY**)pDstValue); //LENGTH if(pcbDstLength) *pcbDstLength = sizeof(DBVECTOR); break; } else { //Unhandled Conversion? hr = DB_E_CANTCONVERTVALUE; } } }; } //Otherwise use the DataConversion Library... //Either we didn't support the conversion, or the user explcitly requested DC if((dwConvFlags & CONV_MSDADC_ONLY) || FAILED(hr)) { //Obtain DataConversion library if(!g_spDataConvert) TESTC(hr = g_spDataConvert.CoCreateInstance(CLSID_OLEDB_CONVERSIONLIBRARY)); //Unable to obtain DC or not allowed to use it... if(!g_spDataConvert) TESTC(hr = DB_E_CANTCONVERTVALUE); //We need to convert from the consumers type to ours... hr = g_spDataConvert->DataConvert( wSrcType, // wSrcType wDstType, // wDstType cbSrcLength, // cbSrcLength pcbDstLength, // pcbDstLength pSrcValue, // pSrc pDstValue, // pDst cbDstMaxLength, // cbDstMaxLength dbSrcStatus, // dbsSrcStatus pdbDstStatus, // pdbsStatus bPrecision, // bPrecision bScale, // bScale 0L ); // dwFlags } CLEANUP: return hr; } /////////////////////////////////////////////////////////////// // CVariant::CVariant // /////////////////////////////////////////////////////////////// CVariant::CVariant() { Init(); } /////////////////////////////////////////////////////////////// // CVariant::~CVariant // /////////////////////////////////////////////////////////////// CVariant::~CVariant() { Clear(); } /////////////////////////////////////////////////////////////// // CVariant::Init // /////////////////////////////////////////////////////////////// HRESULT CVariant::Init() { return VariantInitFast(this); } /////////////////////////////////////////////////////////////// // CVariant::CVariant // /////////////////////////////////////////////////////////////// HRESULT CVariant::Clear() { return VariantClearFast(this); } /////////////////////////////////////////////////////////////// // CVariant::Attach // /////////////////////////////////////////////////////////////// HRESULT CVariant::Attach(VARIANT* pSrc) { ASSERT(pSrc); //Clear the previous value HRESULT hr = Clear(); if(SUCCEEDED(hr)) { memcpy(this, pSrc, sizeof(VARIANT)); pSrc->vt = VT_EMPTY; } return hr; } /////////////////////////////////////////////////////////////// // CVariant::Detach // /////////////////////////////////////////////////////////////// HRESULT CVariant::Detach(VARIANT* pDest) { ASSERT(pDest); //Clear the previous value HRESULT hr = VariantClearFast(pDest); if(SUCCEEDED(hr)) { memcpy(pDest, this, sizeof(VARIANT)); vt = VT_EMPTY; } return hr; } /////////////////////////////////////////////////////////////// // CVariant::Copy // /////////////////////////////////////////////////////////////// HRESULT CVariant::Copy(VARIANT* pSrc) { ASSERT(pSrc); return VariantCopyFast(pSrc, this); } /////////////////////////////////////////////////////////////// // CVariant::ChangeType // /////////////////////////////////////////////////////////////// HRESULT CVariant::ChangeType(VARTYPE vt, VARIANT* pSrc) { //If NULL, we are doing an inplace conversion if(!pSrc) pSrc = this; return VariantChangeFast(this, pSrc, ULONG_MAX, 0, vt); } ///////////////////////////////////////////////////////////////////////////// // HRESULT VariantInitFast // ///////////////////////////////////////////////////////////////////////////// HRESULT VariantInitFast(VARIANT* pVariant) { ASSERT(pVariant); V_VT(pVariant) = VT_EMPTY; return S_OK; } ///////////////////////////////////////////////////////////////////////////// // HRESULT VariantClearFast // ///////////////////////////////////////////////////////////////////////////// HRESULT VariantClearFast(VARIANT* pVariant) { ASSERT(pVariant); HRESULT hr = S_OK; switch(V_VT(pVariant)) { case VT_EMPTY: break; case VT_NULL: case VT_I8: V_VT(pVariant) = VT_EMPTY; break; default: //Nice to know if this fails, display error XTESTC(hr = VariantClear(pVariant)); break; } CLEANUP: return hr; } ///////////////////////////////////////////////////////////////////////////// // HRESULT VariantCopyFast // ///////////////////////////////////////////////////////////////////////////// HRESULT VariantCopyFast(VARIANT* pVarDest, VARIANT* pVarSrc) { ASSERT(pVarDest); ASSERT(pVarSrc); HRESULT hr = S_OK; //First Free the Destination. //NOTE: This is the same symmantics as VariantCopy. We do this always since //we know how to clear other types as well... TESTC(hr = VariantClearFast(pVarDest)); //Now do the copy... switch(V_VT(pVarSrc)) { case VT_EMPTY: case VT_NULL: V_VT(pVarDest) = V_VT(pVarSrc); break; case VT_I8: V_VT(pVarDest) = V_VT(pVarSrc); pVarDest->ullVal = pVarSrc->ullVal; break; default: //Nice to know if this fails, display error XTESTC(hr = VariantCopy(pVarDest, pVarSrc)); break; }; CLEANUP: return hr; } ///////////////////////////////////////////////////////////////////////////// // VariantChangeFast // ///////////////////////////////////////////////////////////////////////////// HRESULT VariantChangeFast(VARIANT* pVarDest, VARIANT* pVarSrc, LCID lcid, USHORT wFlags, VARTYPE vt) { HRESULT hr = S_OK; static LCID lcidDef = GetSystemDefaultLCID(); //Delegate... TESTC(hr = VariantChangeTypeEx( pVarDest, // Destination (convert in place) pVarSrc, // Source lcid != ULONG_MAX ? lcid : lcidDef, // LCID wFlags, // dwFlags vt )); CLEANUP: return hr; } ///////////////////////////////////////////////////////////////////////////// // HRESULT VariantToString // ///////////////////////////////////////////////////////////////////////////// HRESULT VariantToString(VARIANT* pVariant, WCHAR* pwsz, DBLENGTH ulMaxSize, DWORD dwFlags) { //No-op if(!ulMaxSize) return S_OK; //Convert a VARIANT to a WCHAR ASSERT(pVariant); ASSERT(pwsz); pwsz[0] = EOL; //Find the VariantType DBTYPE wType = V_VT(pVariant); CVariant cVariant; HRESULT hr = S_OK; //VT_ARRAY is not handled by VariantChangeType if(wType & VT_ARRAY) { TESTC(hr = SafeArrayToString(V_ARRAY(pVariant), wType, pwsz, ulMaxSize)); goto CLEANUP; } switch(wType) { case VT_NULL: case VT_EMPTY: //pwsz is already set to null terminator above break; case VT_BOOL: if(V_BOOL(pVariant) == VARIANT_TRUE) StringFormat(pwsz, ulMaxSize, L"%s", dwFlags & CONV_VARBOOL ? L"VARIANT_TRUE" : L"True"); else if(V_BOOL(pVariant) == VARIANT_FALSE) StringFormat(pwsz, ulMaxSize, L"%s", dwFlags & CONV_VARBOOL ? L"VARIANT_FALSE" : L"False"); else StringFormat(pwsz, ulMaxSize, L"%d", V_BOOL(pVariant)); break; case VT_ERROR: StringFormat(pwsz, ulMaxSize, L"%d", V_ERROR(pVariant)); break; case VT_I8: { //TODO64: OLEAUT doesn't have support for this type yet, so we have to special case... // StringFormat(pwsz, ulMaxSize, L"%ld", V_I8(pVariant)); StringFormat(pwsz, ulMaxSize, L"%lld", pVariant->ullVal); break; } default: { TESTC(hr = VariantChangeFast( &cVariant, // Destination (convert not in place) pVariant, // Source ULONG_MAX, // LCID 0, // dwFlags VT_BSTR )); StringCopy(pwsz, V_BSTR(&cVariant), ulMaxSize); //May need Hex postprocessing if(dwFlags & CONV_HEX) { switch(V_VT(pVariant)) { case DBTYPE_I4: //"0x" + 10 characters if(ulMaxSize >= 12) StringFormat(pwsz, ulMaxSize, L"0x%x", V_I4(pVariant)); break; } } } }; CLEANUP: pwsz[ulMaxSize ? ulMaxSize-1 : 0] = wEOL; return hr; } ///////////////////////////////////////////////////////////////////////////// // HRESULT StringToVariant // ///////////////////////////////////////////////////////////////////////////// HRESULT StringToVariant(WCHAR* pwsz, VARTYPE vt, VARIANT* pVariant, DWORD dwFlags) { //Convert a VARIANT to a WCHAR ASSERT(pVariant); HRESULT hr = S_OK; //Assign the type... V_VT(pVariant) = vt; //VT_ARRAY is not handled by VariantChangeType if(vt & VT_ARRAY) { SAFEARRAY* pSafeArray = NULL; TESTC(hr = StringToSafeArray(pwsz, vt, &pSafeArray)); V_ARRAY(pVariant) = pSafeArray; goto CLEANUP; } //VariantChangeType seems to handle most types, //except the following cases... switch(vt) { case VT_NULL: case VT_EMPTY: break; case VT_BOOL: { //This type requires a string if(!pwsz) TESTC(hr = E_INVALIDARG); if(dwFlags & CONV_VARBOOL && StringCompareI(pwsz, L"VARIANT_TRUE")) V_BOOL(pVariant) = VARIANT_TRUE; else if(dwFlags & CONV_VARBOOL && StringCompareI(pwsz, L"VARIANT_FALSE")) V_BOOL(pVariant) = VARIANT_FALSE; else if(dwFlags & CONV_ALPHABOOL && StringCompareI(pwsz, L"True")) V_BOOL(pVariant) = VARIANT_TRUE; else if(dwFlags & CONV_ALPHABOOL && StringCompareI(pwsz, L"False")) V_BOOL(pVariant) = VARIANT_FALSE; else { LONG lValue = 0; if(!ConvertToLONG(pwsz, &lValue, SHRT_MIN, SHRT_MAX, 0/*Base*/)) { hr = E_FAIL; goto CLEANUP; } V_BOOL(pVariant) = (VARIANT_BOOL)lValue; } break; } case VT_I4: case VT_ERROR: case VT_UI4: { //This type requires a string if(!pwsz) TESTC(hr = E_INVALIDARG); //We handle this case seperatly since we want to handle HEX values WCHAR* pwszStop = NULL; BOOL fSigned = (vt != VT_UI4); errno = 0; //Convert ULONG ulValue = vt==VT_UI4 ? wcstoul(pwsz, &pwszStop, 0) : wcstol(pwsz, &pwszStop, 0); //Function stopped converting prematurely... if(pwszStop==NULL || pwszStop[0]!=wEOL || errno==ERANGE) TESTC(hr = E_FAIL); V_UI4(pVariant) = ulValue; break; } case VT_I8: { //This type requires a string if(!pwsz) TESTC(hr = E_INVALIDARG); //TODO64: OLEAUT doesn't have support for this type yet, so we have to special case... LONGLONG llValue = _wtoi64(pwsz); // V_I8(pVariant) = ulValue; pVariant->ullVal = llValue; break; } case VT_VARIANT: { //Place the string into the BSTR of the VARIANT V_VT(pVariant) = VT_BSTR; V_BSTR(pVariant) = SysAllocString(pwsz); break; } default: { //Place the string into the BSTR of the VARIANT V_VT(pVariant) = VT_BSTR; V_BSTR(pVariant) = SysAllocString(pwsz); //Now delegate to VariantChangeType... TESTC(hr = VariantChangeFast( pVariant, // Destination (convert in place) pVariant, // Source ULONG_MAX, // LCID 0, // dwFlags vt )); } }; CLEANUP: return hr; } ///////////////////////////////////////////////////////////////////////////// // HRESULT SafeArrayToString // ///////////////////////////////////////////////////////////////////////////// HRESULT SafeArrayToString(SAFEARRAY* pSafeArray, DBTYPE wType, WCHAR* pwszBuffer, DBLENGTH ulMaxSize) { ASSERT(pSafeArray); ASSERT(pwszBuffer); //No-op if(!ulMaxSize) return S_OK; //This method is capable of handling N-Dimenstions of Data!! //We need to take N-Dimensions of Data and convert it to a string //For example: We have a 3D data, where z-dim has 2 elements, y-Dim has 3 ele //and x-dim has 2 elements we end up with the following matrix of points // (z, y, x) => value [[[ // (1, 1, 1) => 1 // (1, 1, 2) => 2 // (1, 2, 1) => 3 ][ // (1, 2, 2) => 4 // (1, 3, 1) => 5 ][ // (1, 3, 2) => 6 // (2, 1, 1) => 7 ]][[ // (2, 1, 2) => 8 // (2, 2, 1) => 9 ][ // (2, 2, 2) => A // (2, 3, 1) => B ][ // (2, 3, 2) => C // ]]] //So what we need to generate is a string of: // [ [[1,2][3,4][5,6]] [[7,8][9,A][B,C]] ] //The algorythm we are using is bascially based upon a "ripple" counter. //We keep a counter for each dimension. So when CounterDim[0] hits the Upper //Limit, we increment CounterDim[1] and set CounterDim[0] back to LowerLimit. //This continues until all have reached the upper limit together: //{CounterDim[n-1]==UpperLimt, ... Dim[0]==UpperLimit} //The braces are determined by rising/falling edges of the ripple counter. //Everytime a dimension restarts its value from Upper->Lower we see a "][". //So we have a pre and a post set of - "[[[" braces "]]]" for the number of dimensions. //You'll notices the set of braces in the above example on the rising/falling //edges of the ripple counter.... HRESULT hr = S_OK; CVariant cVariant; WCHAR* pwsz = pwszBuffer; WCHAR* pwszEnd = pwsz + ulMaxSize; ULONG i,iDim, ulInnerElements = 0; ULONG cDimensions = SafeArrayGetDim(pSafeArray); BOOL bDone = FALSE; //No-op if(!cDimensions) return E_FAIL; //Make sure there is no Array in the type wType &= ~VT_ARRAY; pwsz[0] = wEOL; LONG* rgIndices = NULL; SAFE_ALLOC(rgIndices, LONG, cDimensions); //Loop over all dimenstions and fill in "pre" info... for(iDim=0; iDimrgsabound[0].cElements; while(!bDone && (pwsz < pwszEnd)) { //Dimension[0] always goes through a complete cycle every time //Just do this part of the ripple counter seperately... for(i=0; i0 && (pwsz < pwszEnd); j--) { *pwsz = L'['; pwsz++; } break; } //Otherwise reset this one and move onto the //next Dimension (ie: Don't break...) else if(iDim != cDimensions-1) { LONG lLowerBound = 0; SafeArrayGetLBound(pSafeArray, iDim+1, &lLowerBound); rgIndices[iDim] = lLowerBound; if(pwsz < pwszEnd) { *pwsz = L']'; pwsz++; } } //If we have hit the last Dimension and its over the value //This means were done... else { bDone = TRUE; } } } //Display Right outer braces for(iDim=0; iDim value [[[ // (1, 1, 1) => 1 // (1, 1, 2) => 2 // (1, 2, 1) => 3 ][ // (1, 2, 2) => 4 // (1, 3, 1) => 5 ][ // (1, 3, 2) => 6 // (2, 1, 1) => 7 ]][[ // (2, 1, 2) => 8 // (2, 2, 1) => 9 ][ // (2, 2, 2) => A // (2, 3, 1) => B ][ // (2, 3, 2) => C // ]]] //So we could be passed a string of: // [ [[1,2][3,4][5,6]] [[7,8][9,A][B,C]] ] //The algorythm we are using is bascially based upon a "ripple" counter. //We keep a counter for each dimension. So when CounterDim[0] hits the Upper //Limit, we increment CounterDim[1] and set CounterDim[0] back to LowerLimit. //This continues until all have reached the upper limit together: //{CounterDim[n-1]==UpperLimt, ... Dim[0]==UpperLimit} //The braces are determined by rising/falling edges of the ripple counter. //Everytime a dimension restarts its value from Upper->Lower we see a "][". //So we have a pre and a post set of - "[[[" braces "]]]" for the number of dimensions. //You'll notices the set of braces in the above example on the rising/falling //edges of the ripple counter.... HRESULT hr = S_OK; CVariant cVariant; WCHAR wszBuffer[MAX_COL_SIZE]; wszBuffer[0] = wEOL; WCHAR* pwsz = pwszBuffer; ULONG i,iDim, ulInnerElements = 0; //Determine the Number of Dimensions... ULONG cDimensions = 0; while(pwsz[0]==L'[') { cDimensions++; pwsz++; wcscat_s(wszBuffer, NUMELE(wszBuffer), L"]"); } //Find the End of the Data (where everever "]...") is... WCHAR* pwszNext = pwsz; WCHAR* pwszEnd = pwsz; WCHAR* pwszCurEnd = wcschr(pwsz, L']'); //No-op if(cDimensions < 1) return E_FAIL; //Create SafeArray SAFEARRAY* pSafeArray = NULL; *ppSafeArray = NULL; LONG* rgIndices = NULL; SAFEARRAYBOUND* rgSafeArrayBounds = NULL; //Indices array... SAFE_ALLOC(rgIndices, LONG, cDimensions); memset(rgIndices, 0, cDimensions * sizeof(LONG)); //SafeArrayBounds array... SAFE_ALLOC(rgSafeArrayBounds, SAFEARRAYBOUND, cDimensions); memset(rgSafeArrayBounds, 0, cDimensions*sizeof(SAFEARRAYBOUND)); //Need to find out how many elements are in Dim[0] //NOTE: <= 'Allow empty string safearrays' while(pwszNext && pwszNext <= pwszCurEnd) { ulInnerElements++; rgIndices[0]++; pwszNext = wcschr(pwszNext, L','); if(pwszNext) pwszNext++; } //Now from the [] notation find out how many elements in the other dimenstions // [ [[1,2][3,4][5,6]] [[7,8][9,A][B,C]] ] //The algorythm we will use is: //Everytime we see a "]" we need to increment the next Dimension elements //Everytime we see a "[" we need to reset the previous Dimension elements //TODO //Currently this algorythm only handles 1 dimension. //No reason it couldn't use the above method and work for more, just on //a time constraint, and the only provider we have supports max 1 dim... ASSERT(cDimensions == 1); //Create the SafeArrayBounds for(iDim=0; iDimpwszCurEnd) pwszEnd = pwszCurEnd; //Setup rgIndicaes rgIndices[0] = i; //Convert Value to a Variant void* pValue = NULL; StringCopy(wszBuffer, pwszNext, pwszEnd-pwszNext+1); TESTC(hr = StringToVariant(wszBuffer, wType, &cVariant, CONV_NONE)); //Add this Value to the SafeArray //NOTE: According to the spec for SafeArrayPutElement //VT_DISPATCH, VT_UNKNOWN, and VT_BSTR are pointers, and do not require another level of indirection. switch(wType) { case VT_DISPATCH: case VT_UNKNOWN: case VT_BSTR: pValue = V_UNKNOWN(&cVariant); break; case VT_VARIANT: pValue = &cVariant; break; case VT_DECIMAL: //DECIMAL is not part of the VARIANT union pValue = &V_DECIMAL(&cVariant); break; default: pValue = &V_I4(&cVariant); break; }; //Add this Value to the SafeArray TESTC(hr = SafeArrayPutElement(pSafeArray, rgIndices, pValue)); XTEST(cVariant.Clear()); //Incement to next value... pwszNext = wcschr(pwszNext, L','); if(pwszNext) pwszNext += 1; } //Everything complete successfully... *ppSafeArray = pSafeArray; CLEANUP: SAFE_FREE(rgIndices); SAFE_FREE(rgSafeArrayBounds); return hr; } ///////////////////////////////////////////////////////////////////////////// // HRESULT VectorToString // ///////////////////////////////////////////////////////////////////////////// HRESULT VectorToString(DBVECTOR* pVector, DBTYPE wType, WCHAR* pwszBuffer, DBLENGTH ulMaxSize) { ASSERT(pVector); ASSERT(pwszBuffer); HRESULT hr = S_OK; //No-op if(!ulMaxSize) return S_OK; VARIANT Variant; VARIANT* pVariant = NULL; WCHAR* pwsz = pwszBuffer; WCHAR* pwszEnd = pwsz + ulMaxSize; pwsz[0] = wEOL; //Make sure we are dealing with the base type... wType &= ~DBTYPE_VECTOR; //Loop over the vector... for(ULONG iEle=0; iElesize; iEle++) { //Initialize Variant pVariant = &Variant; pVariant->vt = VT_EMPTY; //NOTE: The pVariant is really just a pointer to the data. We don't free the data //since the vector data doesn't belong to us, we are just convering the given data to //a string. The simplest way to do this is to dump into a variant and let our helper //function VariantToString deal with this... //Obtain the data from the vector... switch(wType) { case VT_EMPTY: case VT_NULL: V_VT(pVariant) = wType; break; case VT_I2: case VT_I4: case VT_R4: case VT_R8: case VT_CY: case VT_DATE: case VT_BSTR: case VT_DISPATCH: case VT_ERROR: case VT_BOOL: case VT_UNKNOWN: case VT_I1: case VT_UI1: case VT_UI2: case VT_UI4: case VT_I8: case VT_UI8: case VT_INT: case VT_UINT: { DBLENGTH ulTypeSize = 0; if(SUCCEEDED(GetDBTypeMaxSize(wType, &ulTypeSize))) { V_VT(pVariant) = wType; memcpy(&V_I4(pVariant), (BYTE*)pVector->ptr + (ulTypeSize*iEle), (size_t)ulTypeSize); } break; } case VT_VARIANT: //just place directly into our variant. pVariant = (VARIANT*)((BYTE*)pVector->ptr + (sizeof(VARIANT)*iEle)); break; case VT_DECIMAL: //DECIMAL is not part of the VARIANT union V_VT(pVariant) = wType; V_DECIMAL(pVariant) = *(DECIMAL*)((BYTE*)pVector->ptr + (sizeof(DECIMAL)*iEle)); break; default: //Unable to handle this type... TESTC(hr = E_FAIL); } //Convert VARIANT To String TESTC(hr = VariantToString(pVariant, pwsz, pwszEnd - pwsz, CONV_NONE)); pwsz += wcslen(pwsz); //Vector Seperator if(iElesize-1 && (pwsz < pwszEnd)) { *pwsz = L','; pwsz++; } } CLEANUP: return hr; } ///////////////////////////////////////////////////////////////////////////// // HRESULT StringToVector // ///////////////////////////////////////////////////////////////////////////// HRESULT StringToVector(WCHAR* pwszBuffer, DBTYPE wType, DBVECTOR* pVector) { ASSERT(pVector); if(!pwszBuffer) return E_INVALIDARG; //Make sure we are dealing with the base type... wType &= ~DBTYPE_VECTOR; HRESULT hr = S_OK; VARIANT Variant; VariantInitFast(&Variant); WCHAR wszBuffer[MAX_COL_SIZE] = {0}; ULONG iEle = 0; //Determine the Number of Elements... pVector->size = 1; WCHAR* pwsz = pwszBuffer; while(pwsz = wcschr(pwsz, L',')) { pVector->size++; pwsz++; } //Obtain the size of each element DBLENGTH ulTypeSize = 0; if(FAILED(GetDBTypeMaxSize(wType, &ulTypeSize))) return hr; //Alloc the vector... SAFE_ALLOC(pVector->ptr, BYTE, ulTypeSize*pVector->size); pwsz = pwszBuffer; for(iEle=0; iElesize; iEle++) { //Obtain the start and end of the value WCHAR* pwszEnd = wcschr(pwsz, L','); void* pElement = (BYTE*)pVector->ptr + (ulTypeSize*iEle); //Convert Value to a Variant StringCopy(wszBuffer, pwsz, pwszEnd ? pwszEnd-pwsz+1 : MAX_COL_SIZE); TESTC(hr = StringToVariant(wszBuffer, wType, &Variant, CONV_NONE)); //Add this to the vector... switch(wType) { case VT_EMPTY: case VT_NULL: break; case VT_I2: *(SHORT*)pElement = V_I2(&Variant); break; case VT_I4: case VT_R4: case VT_R8: case VT_CY: case VT_DATE: case VT_BSTR: case VT_DISPATCH: case VT_ERROR: case VT_BOOL: case VT_UNKNOWN: case VT_I1: case VT_UI1: case VT_UI2: case VT_UI4: case VT_I8: case VT_UI8: case VT_INT: case VT_UINT: memcpy(pElement, &V_I4(&Variant), (size_t)ulTypeSize); break; case VT_VARIANT: //just place directly into our variant. *(VARIANT*)pElement = Variant; break; case VT_DECIMAL: *(DECIMAL*)pElement = V_DECIMAL(&Variant); break; default: //Unable to handle this type... TESTC(hr = E_NOTIMPL); }; //Incement to next value... pwsz = wcschr(pwsz, L','); if(pwsz) pwsz += 1; } CLEANUP: return hr; } ///////////////////////////////////////////////////////////////////////////// // CHAR* strDuplicate // ///////////////////////////////////////////////////////////////////////////// CHAR* strDuplicate(LPCSTR psz) { HRESULT hr = S_OK; //no-op case if(!psz) return NULL; size_t cLen = strlen(psz); //Allocate space for the string CHAR* pszBuffer = NULL; SAFE_ALLOC(pszBuffer, CHAR, cLen+1); //Now copy the string strcpy_s(pszBuffer, cLen+1, psz); CLEANUP: return pszBuffer; } ///////////////////////////////////////////////////////////////////////////// // WCHAR* wcsDuplicate // ///////////////////////////////////////////////////////////////////////////// WCHAR* wcsDuplicate(LPCWSTR pwsz) { HRESULT hr = S_OK; //no-op case if(!pwsz) return NULL; size_t cLen = wcslen(pwsz); //Allocate space for the string WCHAR* pwszBuffer = NULL; SAFE_ALLOC(pwszBuffer, WCHAR, cLen+1); //Now copy the string StringCopy(pwszBuffer, pwsz, cLen+1); CLEANUP: return pwszBuffer; } ///////////////////////////////////////////////////////////////////////////// // HRESULT DBIDToString // ///////////////////////////////////////////////////////////////////////////// HRESULT DBIDToString(const DBID* pDBID, WCHAR* pwsz, ULONG ulMaxLen) { //No-op if(!ulMaxLen) return S_OK; ASSERT(pwsz); WCHAR wszBuffer[MAX_NAME_LEN] = {0}; pwsz[0] = wEOL; //No-op if(pDBID == NULL) { StringCopy(pwsz, L"NULL", MAX_NAME_LEN); return S_OK; } //ColumnID (SubItem) DBID switch(pDBID->eKind) { case DBKIND_NAME: StringFormat(pwsz, ulMaxLen, L"{\"%s\"}", pDBID->uName.pwszName); break; case DBKIND_PROPID: StringFormat(pwsz, ulMaxLen, L"{%ld}", pDBID->uName.ulPropid); break; case DBKIND_GUID: StringFromGUID2(pDBID->uGuid.guid, pwsz, ulMaxLen); break; case DBKIND_GUID_NAME: StringFromGUID2(pDBID->uGuid.guid, wszBuffer, MAX_NAME_LEN); StringFormat(pwsz, ulMaxLen, L"{%s,\"%s\"}", wszBuffer, pDBID->uName.pwszName); break; case DBKIND_GUID_PROPID: { //Special Case: Maybe this is a defined RowCol DBID WCHAR* pwszColName = GetRowColName(pDBID); if(pwszColName) { StringCopy(pwsz, pwszColName, ulMaxLen); } else { StringFromGUID2(pDBID->uGuid.guid, wszBuffer, MAX_NAME_LEN); StringFormat(pwsz, ulMaxLen, L"{%s,%ld}", wszBuffer, pDBID->uName.ulPropid); } break; } case DBKIND_PGUID_NAME: if(pDBID->uGuid.pguid) StringFromGUID2(*pDBID->uGuid.pguid, wszBuffer, MAX_NAME_LEN); StringFormat(pwsz, ulMaxLen, L"{&%s,\"%s\"}", wszBuffer, pDBID->uName.pwszName); break; case DBKIND_PGUID_PROPID: if(pDBID->uGuid.pguid) StringFromGUID2(*pDBID->uGuid.pguid, wszBuffer, MAX_NAME_LEN); StringFormat(pwsz, ulMaxLen, L"{&%s,%ld}", wszBuffer, pDBID->uName.ulPropid); break; default: ASSERT(!"Unhandled Type!"); break; }; //Make sure the resulting string is NULL terminated pwsz[ulMaxLen ? ulMaxLen-1 : 0] = wEOL; return S_OK; } ///////////////////////////////////////////////////////////////////////////// // DBIDFree // ///////////////////////////////////////////////////////////////////////////// void DBIDFree(DBID* pDBID) { if(pDBID) { switch(pDBID->eKind) { case DBKIND_PGUID_NAME: SAFE_FREE(pDBID->uGuid.pguid); SAFE_FREE(pDBID->uName.pwszName); break; case DBKIND_GUID_NAME: case DBKIND_NAME: SAFE_FREE(pDBID->uName.pwszName); break; case DBKIND_PGUID_PROPID: SAFE_FREE(pDBID->uGuid.pguid); break; }; } } ///////////////////////////////////////////////////////////////////////////// // HRESULT DBIDCopy // ///////////////////////////////////////////////////////////////////////////// HRESULT DBIDCopy(DBID* pDst, const DBID* pSrc) { ASSERT(pDst && pSrc); HRESULT hr = S_OK; //Simple case, no out-of-line data... memcpy(pDst, pSrc, sizeof(DBID)); //Now copy any outofline data... switch(pDst->eKind) { case DBKIND_PGUID_PROPID: pDst->uGuid.pguid = NULL; if(pSrc->uGuid.pguid) { SAFE_ALLOC(pDst->uGuid.pguid, GUID, 1); memcpy(pDst->uGuid.pguid, pSrc->uGuid.pguid, sizeof(GUID)); } break; case DBKIND_PGUID_NAME: pDst->uGuid.pguid = NULL; if(pSrc->uGuid.pguid) { SAFE_ALLOC(pDst->uGuid.pguid, GUID, 1); memcpy(pDst->uGuid.pguid, pSrc->uGuid.pguid, sizeof(GUID)); } pDst->uName.pwszName = wcsDuplicate(pDst->uName.pwszName); break; case DBKIND_GUID_NAME: case DBKIND_NAME: pDst->uName.pwszName = wcsDuplicate(pDst->uName.pwszName); break; }; CLEANUP: return hr; } ///////////////////////////////////////////////////////////////////////////// // HRESULT DBIDEqual // ///////////////////////////////////////////////////////////////////////////// BOOL DBIDEqual(const DBID* pDst, const DBID* pSrc) { ASSERT(pDst && pSrc); BOOL bEqual = FALSE; if(pDst->eKind == pSrc->eKind) { //Now compare sub-parts... switch(pDst->eKind) { case DBKIND_PGUID_NAME: if(bEqual = pDst->uGuid.pguid && pSrc->uGuid.pguid ? memcmp(pDst->uGuid.pguid, pSrc->uGuid.pguid, sizeof(GUID))==0 : pDst->uGuid.pguid == pSrc->uGuid.pguid) bEqual = StringCompare(pDst->uName.pwszName, pSrc->uName.pwszName); break; case DBKIND_PGUID_PROPID: if(bEqual = pDst->uGuid.pguid && pSrc->uGuid.pguid ? memcmp(pDst->uGuid.pguid, pSrc->uGuid.pguid, sizeof(GUID))==0 : pDst->uGuid.pguid == pSrc->uGuid.pguid) bEqual = (pDst->uName.ulPropid == pSrc->uName.ulPropid); break; case DBKIND_PROPID: bEqual = (pDst->uName.ulPropid == pSrc->uName.ulPropid); break; case DBKIND_GUID_PROPID: bEqual = (pDst->uName.ulPropid == pSrc->uName.ulPropid); break; case DBKIND_GUID: bEqual = memcmp(&pDst->uGuid.guid, &pSrc->uGuid.guid, sizeof(GUID))==0; break; case DBKIND_GUID_NAME: if(bEqual = memcmp(&pDst->uGuid.guid, &pSrc->uGuid.guid, sizeof(GUID))==0) bEqual = StringCompare(pDst->uName.pwszName, pSrc->uName.pwszName); break; case DBKIND_NAME: bEqual = StringCompare(pDst->uName.pwszName, pSrc->uName.pwszName); break; default: break; }; } //Otherwise return bEqual; } ///////////////////////////////////////////////////////////////////////////// // IsVariableType // ///////////////////////////////////////////////////////////////////////////// BOOL IsVariableType(DBTYPE wType) { //According to OLE DB Spec Appendix A (Variable-Length Data Types) switch(wType) { case DBTYPE_STR: case DBTYPE_WSTR: case DBTYPE_BYTES: case DBTYPE_VARNUMERIC: return TRUE; } return FALSE; } ///////////////////////////////////////////////////////////////////////////// // IsFixedType // ///////////////////////////////////////////////////////////////////////////// BOOL IsFixedType(DBTYPE wType) { return !IsVariableType(wType); } ///////////////////////////////////////////////////////////////////////////// // IsNumericType // ///////////////////////////////////////////////////////////////////////////// BOOL IsNumericType(DBTYPE wType) { //According to OLE DB Spec Appendix A (Numeric Data Types) switch(wType) { case DBTYPE_I1: case DBTYPE_I2: case DBTYPE_I4: case DBTYPE_I8: case DBTYPE_UI1: case DBTYPE_UI2: case DBTYPE_UI4: case DBTYPE_UI8: case DBTYPE_R4: case DBTYPE_R8: case DBTYPE_CY: case DBTYPE_DECIMAL: case DBTYPE_NUMERIC: return TRUE; } return FALSE; } ///////////////////////////////////////////////////////////////////////////// // HRESULT GetDBTypeMaxSize // ///////////////////////////////////////////////////////////////////////////// HRESULT GetDBTypeMaxSize(DBTYPE wType, DBLENGTH* pulMaxSize, BYTE* pbPrecision, BYTE* pbScale) { HRESULT hr = S_OK; //On failure we will set the column size to the max, so callers //don't have to condition this for normal unknown types... DBLENGTH ulMaxSize = MAX_COL_SIZE; BYTE bPrecision = 0; BYTE bScale = 0; //Values taken from OLE DB Spec, Appendix A //In some situations we need to know ulMaxSize, Prec, Scale, as defaults or creating //an accessor before we actually have ColumnsInfo. Useful info, but mainly used //only for defaults for dialogs... switch(wType) { case DBTYPE_EMPTY: case DBTYPE_NULL: ulMaxSize = 0; break; case DBTYPE_STR: case DBTYPE_WSTR: case DBTYPE_BYTES: case DBTYPE_VARNUMERIC: ulMaxSize = MAX_COL_SIZE; break; case DBTYPE_BSTR: ulMaxSize = sizeof(BSTR); break; case DBTYPE_I1: case DBTYPE_UI1: ulMaxSize = 1; bPrecision = 3; break; case DBTYPE_I2: case DBTYPE_UI2: ulMaxSize = 2; bPrecision = 5; break; case DBTYPE_I4: case DBTYPE_UI4: ulMaxSize = 4; bPrecision = 10; break; case DBTYPE_I8: ulMaxSize = 8; bPrecision = 19; break; case DBTYPE_UI8: ulMaxSize = 8; bPrecision = 20; break; case DBTYPE_R4: ulMaxSize = sizeof(float); bPrecision = 7; break; case DBTYPE_R8: ulMaxSize = sizeof(double); bPrecision = 16; break; case DBTYPE_CY: ulMaxSize = 8; bPrecision = 19; break; case DBTYPE_NUMERIC: ulMaxSize = sizeof(DB_NUMERIC); bPrecision = 38; break; case DBTYPE_DATE: ulMaxSize = sizeof(double); break; case DBTYPE_BOOL: ulMaxSize = 2; break; case DBTYPE_VARIANT: ulMaxSize = sizeof(VARIANT); break; case DBTYPE_IDISPATCH: case DBTYPE_IUNKNOWN: ulMaxSize = sizeof(IUnknown*); break; case DBTYPE_GUID: ulMaxSize = sizeof(GUID); break; case DBTYPE_ERROR: ulMaxSize = sizeof(SCODE); break; case DBTYPE_DBDATE: ulMaxSize = sizeof(DBDATE); break; case DBTYPE_DBTIME: ulMaxSize = sizeof(DBTIME); break; case DBTYPE_DBTIMESTAMP: ulMaxSize = sizeof(DBTIMESTAMP); break; case DBTYPE_DECIMAL: ulMaxSize = sizeof(DECIMAL); bPrecision = 28; break; default: //DBTYPE_BYREF if(wType & DBTYPE_BYREF) { ulMaxSize = sizeof(void*); } //DBTYPE_ARRAY else if(wType & DBTYPE_ARRAY) { ulMaxSize = sizeof(SAFEARRAY*); } //DBTYPE_VECTOR else if(wType & DBTYPE_VECTOR) { ulMaxSize = sizeof(DBVECTOR*); } else { //On failure we will set the column size to the max (already done above), so callers //don't have to condition this for normal unknown types... hr = E_FAIL; } break; }; if(pulMaxSize) *pulMaxSize = ulMaxSize; if(pbPrecision) *pbPrecision = bPrecision; if(pbScale) *pbScale = bScale; return hr; } ///////////////////////////////////////////////////////////////////////////// // WCHAR* GetDBTypeName // ///////////////////////////////////////////////////////////////////////////// WCHAR* GetDBTypeName(DBTYPE wType) { return GetBitMapName(wType, g_cDBTypes, g_rgDBTypes, DBTYPE_VECTOR | DBTYPE_ARRAY | DBTYPE_BYREF | DBTYPE_RESERVED/*dwBitStart*/); } ///////////////////////////////////////////////////////////////////////////// // DBTYPE GetDBType // ///////////////////////////////////////////////////////////////////////////// DBTYPE GetDBType(WCHAR* pwsz) { return (DBTYPE)GetMapName(pwsz, g_cDBTypes, g_rgDBTypes); } ///////////////////////////////////////////////////////////////////////////// // WCHAR* GetVariantTypeName // ///////////////////////////////////////////////////////////////////////////// WCHAR* GetVariantTypeName(VARTYPE vt) { return GetBitMapName(vt, g_cVariantTypes, g_rgVariantTypes, VT_VECTOR | VT_ARRAY | VT_BYREF | VT_RESERVED/*dwBitStart*/); } ///////////////////////////////////////////////////////////////////////////// // WCHAR* GetVariantTypeName // ///////////////////////////////////////////////////////////////////////////// DBTYPE GetVariantType(WCHAR* pwsz) { return (DBTYPE)GetMapName(pwsz, g_cVariantTypes, g_rgVariantTypes); } /////////////////////////////////////////////////////////////// // FreeData // /////////////////////////////////////////////////////////////// HRESULT FreeData(DBTYPE wType, DBLENGTH cData, void* rgData) { //no-op if(!rgData) return S_OK; //Loop over all elements of the array for(ULONG i=0; isize, pVector->ptr); //Free the block of memory SAFE_FREE(pVector->ptr); } //DBTYPE_ARRAY else if(wType & DBTYPE_ARRAY) { SAFEARRAY** ppSafeArray = &((SAFEARRAY**)rgData)[i]; if(*ppSafeArray) SafeArrayDestroy(*ppSafeArray); *ppSafeArray = NULL; } break; } }; } return S_OK; } /////////////////////////////////////////////////////////////// // FreeBindingData // /////////////////////////////////////////////////////////////// HRESULT FreeBindingData(DBCOUNTITEM cBindings, const DBBINDING* rgBindings, void* pData, BOOL fSetData) { //Need to walk the array and free any other alloc memory for(ULONG i=0; ipObject) { SAFE_ALLOC(pDest->pObject, DBOBJECT, 1); memcpy(pDest->pObject, pSource->pObject, sizeof(DBOBJECT)); } CLEANUP: return hr; } /////////////////////////////////////////////////////////////// // FreeBindings // /////////////////////////////////////////////////////////////// HRESULT FreeBindings(DBCOUNTITEM* pcBindings, DBBINDING** prgBindings) { ASSERT(pcBindings); ASSERT(prgBindings); //Need to walk the array and free any other alloc memory for(ULONG i=0; i<*pcBindings; i++) { //Free any pObjects if(*prgBindings) SAFE_FREE((*prgBindings)[i].pObject); } //Now we can free the outer struct *pcBindings = 0; SAFE_FREE(*prgBindings); return S_OK; } /////////////////////////////////////////////////////////////// // FreeColAccess // /////////////////////////////////////////////////////////////// HRESULT FreeColAccess(ULONG* pcColAccess, DBCOLUMNACCESS** prgColAccess, void** ppData) { ASSERT(pcColAccess); ASSERT(prgColAccess); //Now we can free the outer struct *pcColAccess = 0; SAFE_FREE(*prgColAccess); SAFE_FREE(*ppData); return S_OK; } /////////////////////////////////////////////////////////////// // FreeConstraintDesc // /////////////////////////////////////////////////////////////// HRESULT FreeConstraintDesc( DBORDINAL* pcConsDesc, DBCONSTRAINTDESC** prgConsDesc, BOOL fFree ) { DBORDINAL indexCol; DBCONSTRAINTDESC *rgConsDesc; ASSERT(pcConsDesc); ASSERT(prgConsDesc); rgConsDesc = *prgConsDesc; for(DBORDINAL index = 0; index<*pcConsDesc; index++) { // release the current element DBIDFree(rgConsDesc[index].pConstraintID); SAFE_FREE(rgConsDesc[index].pConstraintID); DBIDFree(rgConsDesc[index].pReferencedTableID); SAFE_FREE(rgConsDesc[index].pReferencedTableID); SAFE_FREE(rgConsDesc[index].pwszConstraintText); for (indexCol = 0; indexCol < rgConsDesc[index].cColumns; indexCol++) DBIDFree(&rgConsDesc[index].rgColumnList[indexCol]); SAFE_FREE(rgConsDesc[index].rgColumnList); for (indexCol = 0; indexCol < rgConsDesc[index].cForeignKeyColumns; indexCol++) DBIDFree(&rgConsDesc[index].rgForeignKeyColumnList[indexCol]); SAFE_FREE(rgConsDesc[index].rgForeignKeyColumnList); } //Now we can free the outer struct *pcConsDesc = 0; if(fFree) SAFE_FREE(*prgConsDesc); return S_OK; } ///////////////////////////////////////////////////////////////// // WCHAR* GetColName // ///////////////////////////////////////////////////////////////// WCHAR* GetColName(const DBCOLUMNINFO* pColInfo) { static WCHAR* pwszNullName = L""; static WCHAR* pwszEmptyName = L""; static WCHAR* pwszBookmarkName = L""; //Providers are not required to return a Column Name for any column //Useful information, but some columns (ie: Bookmarks) and others //May not have a column name. So this method will either just return //whatever the provider returned or will generate an appropiate one if not... //We really need a column name to help for the GUI headers if(pColInfo == NULL) return NULL; WCHAR* pwszColName = pColInfo->pwszName; if(pColInfo->iOrdinal == 0) { //Bookmark if(!pwszColName || !pwszColName[0]) pwszColName = pwszBookmarkName; } else { //otherwise if(!pwszColName || !pwszColName[0]) { //If this is a DBID defined column if(pColInfo->columnid.eKind == DBKIND_GUID_PROPID) pwszColName = GetRowColName(&pColInfo->columnid); } } //Handle Boundaries if(!pwszColName) pwszColName = pwszNullName; else if(!pwszColName[0]) pwszColName = pwszEmptyName; ASSERT(pwszColName); return pwszColName; } ///////////////////////////////////////////////////////////////// // GetMaxDisplaySize // ///////////////////////////////////////////////////////////////// DBLENGTH GetMaxDisplaySize(DBTYPE wBindingType, DBTYPE wBackendType, DBLENGTH ulColumnSize, DBLENGTH ulMaxVarSize) { //Obtain the correct number of bytes to respresent the data in the wBindingType format. //NOTE: For variable types, the bytes will be bounded by ulMaxVarSize (cbMaxLen), for //fixed length type bindings, the size will be bounded by the type, (ie: NOT ulMaxVarSize), //since the provider ignores cbMaxLen for fixed length types and assumes their is enough //storage for the type... DBLENGTH cbMaxLen = 0; //May need to adjust the MaxLen, depending upon what the BindingType is switch(wBindingType) { //Strings are kind of a pain. Although we get the luxury of //Having the provider coerce the type, we need to allocate a buffer //large enough for the provider to store the type in "string" format case DBTYPE_STR: case DBTYPE_WSTR: { switch(wBackendType) { case DBTYPE_NULL: case DBTYPE_EMPTY: //Don't need much room for these... cbMaxLen = 0 + 1; break; case DBTYPE_I1: case DBTYPE_I2: case DBTYPE_I4: case DBTYPE_UI1: case DBTYPE_UI2: case DBTYPE_UI4: case DBTYPE_R4: case DBTYPE_HCHAPTER: //All of the above fit well into 15 characters of display size cbMaxLen = 15 + 1; break; case DBTYPE_I8: case DBTYPE_UI8: case DBTYPE_R8: case DBTYPE_CY: case DBTYPE_ERROR: case DBTYPE_BOOL: //All of the above fit well into 25 characters of display size cbMaxLen = 25 + 1; break; case DBTYPE_DECIMAL: case DBTYPE_NUMERIC: case DBTYPE_DATE: case DBTYPE_DBDATE: case DBTYPE_DBTIMESTAMP: case DBTYPE_GUID: //All of the above fit well into 50 characters of display size cbMaxLen = 50 + 1; break; case DBTYPE_BYTES: //Bytes -> String, 1 byte = 2 Ascii chars. (0xFF == "FF") cbMaxLen = min(ulColumnSize, ulMaxVarSize) * 2 + 1; break; case DBTYPE_STR: case DBTYPE_WSTR: //String -> String //ulColumnSize already contains the length cbMaxLen = min(ulColumnSize, ulMaxVarSize) + 1; break; case DBTYPE_IUNKNOWN: case DBTYPE_IDISPATCH: cbMaxLen = sizeof(IUnknown*); break; default: //For everything else //Just default to our largest buffer size cbMaxLen = ulMaxVarSize; break; }; //Adjust for Unicode strings if(wBindingType == DBTYPE_WSTR) cbMaxLen *= 2; //Make sure variable types are bounded by the maximum variable size, //since we are binding this is a string (STR/WSTR) cbMaxLen = min(cbMaxLen, ulMaxVarSize); break; } default: { //Otherwise were not binding it as an inline string, so we don't need to know the //total formating string length, we can simply specify the size of the type... //NOTE: On failure this function already sets the maxlen to the max for unrecognized types. GetDBTypeMaxSize(wBindingType, &cbMaxLen); //Make sure variable types are bounded by the maximum variable size. //We can't do this for fixed length types since the provider assumes this memory //is at least the size of the data type... if(IsVariableType(wBindingType)) cbMaxLen = min(cbMaxLen, ulMaxVarSize); break; } }; return cbMaxLen; } ///////////////////////////////////////////////////////////////////// // Helper Functions // ///////////////////////////////////////////////////////////////////// void* SetThis(HWND hWnd, void* pThis) { ASSERT(hWnd); void* pPrev = (void*)SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pThis); //NOTE: We need to help prevent the case of overwriting the "this" pointer //in a nested senario. If you hit this assert a subwindow has subclassed the window //as well. By replacing the this pointer, the previous window will lose the correct //class assoicted with it. ASSERT(!pPrev || !pThis || pPrev == pThis); //Return the current value... return pThis; } void* GetThis(HWND hWnd) { //Return the current value... ASSERT(hWnd); return (void*)GetWindowLongPtr(hWnd, GWLP_USERDATA); } ////////////////////////////////////////////////////////////////// // SyncSibling // ////////////////////////////////////////////////////////////////// void SyncSibling(HWND hToWnd, HWND hFromWnd) { ASSERT(hToWnd && hFromWnd); //Make both windows synched, //Get the current selection from the Source INDEX iItem = (INDEX)SendMessage(hFromWnd, LVM_GETNEXTITEM, (WPARAM)-1, (LPARAM)LVNI_SELECTED); //Tell the Target to select the same selection if(iItem != LVM_ERR) { //Get the current selection from the Target and Unselect it INDEX iOldItem = (INDEX)SendMessage(hToWnd, LVM_GETNEXTITEM, (WPARAM)-1, (LPARAM)LVNI_SELECTED); if(iItem != iOldItem) { //Unselect previous one LV_SetItemState(hToWnd, iOldItem, 0, 0, LVIS_SELECTED); //Select the new one LV_SetItemState(hToWnd, iItem, 0, LVIS_SELECTED, LVNI_SELECTED); //Ensure that it is visible SendMessage(hToWnd, LVM_ENSUREVISIBLE, (WPARAM)iItem, (LPARAM)FALSE); } } } typedef struct _SynchInfo { WNDPROC wpProc; HWND hWndSynch; } SYNCHINFO; //////////////////////////////////////////////////////////////// // SynchSubProc // ///////////////////////////////////////////////////////////////// INT_PTR WINAPI SynchSubProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { SYNCHINFO* pSynchInfo = (SYNCHINFO*)GetThis(hWnd); static BOOL fInsideSend = FALSE; switch(message) { case WM_INITDIALOG: { //SubClass pSynchInfo = new SYNCHINFO; pSynchInfo->wpProc = (WNDPROC)GetWindowLongPtr(hWnd, GWLP_WNDPROC); pSynchInfo->hWndSynch = (HWND)lParam; SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)SynchSubProc); SetThis(hWnd, (void*)pSynchInfo); return 0; } case WM_DESTROY: SetThis(hWnd, NULL); SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pSynchInfo->wpProc); SAFE_DELETE(pSynchInfo); break; case WM_VSCROLL: if(!fInsideSend) { //Send a synch message to the other control fInsideSend = TRUE; SendMessage(pSynchInfo->hWndSynch, message, wParam, lParam); fInsideSend = FALSE; } break; } if(pSynchInfo && pSynchInfo->wpProc) return CallWindowProc(pSynchInfo->wpProc, hWnd, message, wParam, lParam); return TRUE; } ////////////////////////////////////////////////////////////////// // InternalTrace // ////////////////////////////////////////////////////////////////// void InternalTrace(LPCWSTR pwszText) { //NOTE: We have a fixed argument version and a variable version (...), since //we have problems with calls to a variable length version and trying to use the '%' //operator as a real character and not as a format specifier... if(pwszText) { if(IsUnicodeOS()) { OutputDebugStringW(pwszText); } else { //Output to the DebugWindow CHAR szBuffer[MAX_QUERY_LEN]; ConvertToMBCS(pwszText, szBuffer, MAX_QUERY_LEN); OutputDebugStringA(szBuffer); } } } ////////////////////////////////////////////////////////////////// // InternalTraceFmt // ////////////////////////////////////////////////////////////////// void InternalTraceFmt(LPCWSTR pwszFmt, ...) { va_list marker; WCHAR wszBuffer[MAX_QUERY_LEN]; // Use format and arguements as input //This version will not overwrite the stack, since it only copies //upto the max size of the array va_start(marker, pwszFmt); _vsnwprintf_s(wszBuffer, MAX_QUERY_LEN, _TRUNCATE, pwszFmt, marker); va_end(marker); //Make sure there is a NULL Terminator, vsnwprintf will not copy //the terminator if length==MAX_QUERY_LEN wszBuffer[MAX_QUERY_LEN-1] = wEOL; //Delegate InternalTrace(wszBuffer); } ////////////////////////////////////////////////////////////////// // CenterDialog // ////////////////////////////////////////////////////////////////// BOOL CenterDialog(HWND hdlg) { RECT rcParent; // Parent window client rect RECT rcDlg; // Dialog window rect int nLeft, nTop; // Top-left coordinates int cWidth, cHeight; // Width and height HWND hwnd; // Get frame window client rect in screen coordinates hwnd = GetParent(hdlg); if(hwnd == NULL || hwnd == GetDesktopWindow()) { rcParent.top = rcParent.left = 0; rcParent.right = GetSystemMetrics(SM_CXFULLSCREEN); rcParent.bottom = GetSystemMetrics(SM_CYFULLSCREEN); } else { GetWindowRect(hwnd, &rcParent); } // Determine the top-left point for the dialog to be centered GetWindowRect(hdlg, &rcDlg); cWidth = rcDlg.right - rcDlg.left; cHeight = rcDlg.bottom - rcDlg.top; nLeft = rcParent.left + (((rcParent.right - rcParent.left) - cWidth ) / 2); nTop = rcParent.top + (((rcParent.bottom - rcParent.top ) - cHeight) / 2); if (nLeft < 0) nLeft = 0; if (nTop < 0) nTop = 0; // Place the dialog return MoveWindow(hdlg, nLeft, nTop, cWidth, cHeight, TRUE); } ////////////////////////////////////////////////////////////////// // MoveWindow // ////////////////////////////////////////////////////////////////// BOOL MoveWindow(HWND hWnd, ULONG x, ULONG y) { RECT rcParent; // Parent window client rect RECT rcDlg; // Dialog window rect int nLeft, nTop; // Top-left coordinates int cWidth, cHeight; // Width and height HWND hWndParent; // Get frame window client rect in screen coordinates hWndParent = GetParent(hWnd); if(hWndParent == NULL || hWndParent == GetDesktopWindow()) { rcParent.top = rcParent.left = 0; rcParent.right = GetSystemMetrics(SM_CXFULLSCREEN); rcParent.bottom = GetSystemMetrics(SM_CYFULLSCREEN); } else GetWindowRect(hWndParent, &rcParent); // Determine the top-left point for the dialog to be centered GetWindowRect(hWnd, &rcDlg); cWidth = rcDlg.right - rcDlg.left; cHeight = rcDlg.bottom - rcDlg.top; nLeft = rcParent.left + x; nTop = rcParent.top + y; // Place the dialog return MoveWindow(hWnd, nLeft, nTop, cWidth, cHeight, TRUE); } ////////////////////////////////////////////////////////////////// // GetWindowPos // ////////////////////////////////////////////////////////////////// BOOL GetWindowPos(HWND hWnd, POINTS* pPTS) { ASSERT(pPTS); RECT rect; if(GetWindowRect(hWnd, &rect)) { pPTS->x = (SHORT)rect.left; pPTS->y = (SHORT)rect.top; } return FALSE; } ////////////////////////////////////////////////////////////////// // GetWindowSize // ////////////////////////////////////////////////////////////////// SIZE GetWindowSize(HWND hWnd) { RECT rect = { 0,0,0,0 }; SIZE size = { 0,0 }; //Obtain window cordinates. if(GetWindowRect(hWnd, &rect)) { //Fillin SIZE struct size.cx = rect.right - rect.left; size.cy = rect.bottom - rect.top; } return size; } ////////////////////////////////////////////////////////////////// // GetClientSize // ////////////////////////////////////////////////////////////////// SIZE GetClientSize(HWND hWnd) { RECT rect = { 0,0,0,0 }; SIZE size = { 0,0 }; //Obtain window cordinates. if(GetClientRect(hWnd, &rect)) { //Fillin SIZE struct size.cx = rect.right - rect.left; size.cy = rect.bottom - rect.top; } return size; } ////////////////////////////////////////////////////////////////// // GetClientCoords // ////////////////////////////////////////////////////////////////// RECT GetClientCoords(HWND hWndParent, HWND hWnd) { //Need the Boundary Rectangle in Client Coordinates, with //respect to the parent window... RECT rect, rectClientCoords; GetWindowRect(hWnd, &rect); POINT ptTopLeft = { rect.left, rect.top }; ScreenToClient(hWndParent, &ptTopLeft); POINT ptBottomRight = { rect.right, rect.bottom }; ScreenToClient(hWndParent, &ptBottomRight); rectClientCoords.top = ptTopLeft.y; rectClientCoords.left = ptTopLeft.x; rectClientCoords.bottom = ptBottomRight.y; rectClientCoords.right = ptBottomRight.x; return rectClientCoords; } ////////////////////////////////////////////////////////////////// // wMessageBox // ////////////////////////////////////////////////////////////////// INT wMessageBox( HWND hwnd, // Parent window for message display UINT uiStyle, // Style of message box WCHAR* pwszTitle, // Title for message WCHAR* pwszFmt, // Format string ... // Substitution parameters ) { va_list marker; WCHAR wszBuffer[MAX_QUERY_LEN]; // Use format and arguements as input //This version will not overwrite the stack, since it only copies //upto the max size of the array va_start(marker, pwszFmt); _vsnwprintf_s(wszBuffer, MAX_QUERY_LEN, _TRUNCATE, pwszFmt, marker); va_end(marker); //Make sure there is a NULL Terminator, vsnwprintf will not copy //the terminator if length==MAX_QUERY_LEN wszBuffer[MAX_QUERY_LEN-1] = wEOL; //Unicode version is supported on both Win95/WinNT return MessageBoxW(hwnd, wszBuffer, pwszTitle, uiStyle); } ////////////////////////////////////////////////////////////////// // wSendMessage // ////////////////////////////////////////////////////////////////// LRESULT wSendMessage(HWND hWnd, UINT Msg, WPARAM wParam, WCHAR* pwszText) { LRESULT lResult = 0; HRESULT hr = S_OK; if(IsUnicodeOS()) { //WinNT - Unicode, no need for conversions... lResult = SendMessageW(hWnd, Msg, (WPARAM)wParam, (LPARAM)pwszText); } else { //Win95 - Unicode not supported... CHAR szBuffer[MAX_QUERY_LEN] = { 0, 0, 0, 0 }; ULONG ulDestSize = MAX_QUERY_LEN; CHAR* psz = szBuffer; //All messages that have [input] strings switch(Msg) { case EM_GETLINE: ((WORD*)psz)[0] = MAX_QUERY_LEN; break; case WM_GETTEXT: //We may need to dynamically allocate this... if(wParam > MAX_QUERY_LEN) SAFE_ALLOC(psz, CHAR, wParam); break; case EM_GETSELTEXT: ASSERT(wParam && "wSendMessage EM_GETSELTEXT requires a length so the conversion buffer so it doesn't overflow..."); ulDestSize = (ULONG)wParam; //We may need to dynamically allocate this... if(wParam > MAX_QUERY_LEN) SAFE_ALLOC(psz, CHAR, wParam); wParam = 0; break; case SB_SETTEXTW: Msg = SB_SETTEXT; case WM_SETTEXT: case EM_REPLACESEL: case CB_FINDSTRINGEXACT: case CB_FINDSTRING: case CB_ADDSTRING: case LB_ADDSTRING: case SB_SETTEXTA: //Convert to ANSI before sending //We may need to dynamically allocate this... if(!ConvertToMBCS(pwszText, psz, MAX_QUERY_LEN)) psz = ConvertToMBCS(pwszText); break; }; //Send the message with an ANSI Buffer lResult = SendMessageA(hWnd, Msg, (WPARAM)wParam, (LPARAM)psz); //All messages that have [output] messages (return strings) switch(Msg) { case WM_GETTEXT: //Now convert the result into the users WCHAR buffer //wParam indicates the max length ConvertToWCHAR(psz, pwszText, (INT)wParam); break; case EM_GETLINE: //Now convert the result into the users WCHAR buffer ASSERT(pwszText); ConvertToWCHAR(psz, pwszText, ((WORD*)pwszText)[0]); break; case CB_GETLBTEXT: case EM_GETSELTEXT: case LB_GETTEXT: //Now convert the result into the users WCHAR buffer ConvertToWCHAR(psz, pwszText, ulDestSize); break; }; //Free any dynamically allocated memory if(psz != szBuffer) SAFE_FREE(psz); } CLEANUP: return lResult; } ////////////////////////////////////////////////////////////////// // wSendMessageFmt // ////////////////////////////////////////////////////////////////// LRESULT wSendMessageFmt(HWND hWnd, UINT Msg, WPARAM wParam, WCHAR* pwszFmt, ...) { ASSERT(pwszFmt); ASSERT(wParam != WM_GETTEXT); va_list marker; WCHAR wszBuffer[MAX_QUERY_LEN]; // Use format and arguements as input //This version will not overwrite the stack, since it only copies //upto the max size of the array va_start(marker, pwszFmt); _vsnwprintf_s(wszBuffer, MAX_QUERY_LEN, _TRUNCATE, pwszFmt, marker); va_end(marker); //Make sure there is a NULL Terminator, vsnwprintf will not copy //the terminator if length==MAX_QUERY_LEN wszBuffer[MAX_QUERY_LEN-1] = wEOL; //Delegate return wSendMessage(hWnd, Msg, wParam, wszBuffer); } ////////////////////////////////////////////////////////////////// // WCHAR wGetWindowText // ////////////////////////////////////////////////////////////////// WCHAR* wGetWindowText(HWND hWnd) { //Dynamic Memory Version of the the WinAPI GetWindowText WCHAR* pwsz = NULL; HRESULT hr = S_OK; //First obtain the length of the string LRESULT iLength = SendMessage(hWnd, WM_GETTEXTLENGTH, 0, 0); if(iLength>0) { LRESULT iActLength = 0; //Allocate our string SAFE_ALLOC(pwsz, WCHAR, iLength+1); //Now get the text from the window... iActLength = wSendMessage(hWnd, WM_GETTEXT, iLength+1, pwsz); //Ensure NULL terminated... pwsz[iActLength>0 ? min(iActLength, iLength) : 0] = wEOL; } CLEANUP: return pwsz; } ////////////////////////////////////////////////////////////////// // ConvertToLONG // ////////////////////////////////////////////////////////////////// BOOL ConvertToLONG(LPCWSTR pwszText, LONG* plValue, LONG lMin, LONG lMax, INT iBase) { ASSERT(plValue); if(!pwszText || !pwszText[0]) return FALSE; errno = 0; WCHAR* pwszEnd = NULL; //Convert to LONG LONG lValue = wcstol(pwszText, &pwszEnd, iBase); if(errno==ERANGE || !pwszText[0] || lValuelMax || pwszEnd==NULL || pwszEnd[0]!=wEOL) return FALSE; //Only change the input value on success... *plValue = lValue; return TRUE; } ////////////////////////////////////////////////////////////////// // GetEditBoxValue // ////////////////////////////////////////////////////////////////// BOOL GetEditBoxValue(HWND hEditWnd, LONG* plValue, LONG lMin, LONG lMax, BOOL fAllowEmpty) { ASSERT(hEditWnd); ASSERT(plValue); WCHAR wszBuffer[MAX_QUERY_LEN] = {0}; //Get the EditText wSendMessage(hEditWnd, WM_GETTEXT, MAX_QUERY_LEN, wszBuffer); if(wszBuffer[0] || !fAllowEmpty) { //Delegate if(!wszBuffer[0] || !ConvertToLONG(wszBuffer, plValue, lMin, lMax)) { wMessageBox(hEditWnd, MB_TASKMODAL | MB_ICONERROR | MB_OK, wsz_ERROR, wsz_INVALID_VALUE_, wszBuffer, lMin, lMax); SetFocus(hEditWnd); return FALSE; } } return TRUE; } ///////////////////////////////////////////////////////////////// // SetMenuData // ///////////////////////////////////////////////////////////////// BOOL SetMenuData(HMENU hMenu, UINT uItem, BOOL fByPosition, ULONG_PTR dwItemData) { //Setup our Menu Structure... //NOTE: HBITMAP was added in IE5, and may not work on older controls... MENUITEMINFOA menuinfo = { offsetof(MENUITEMINFOA, cch) + sizeof(UINT), MIIM_DATA, 0, 0, 0, NULL, NULL, NULL, dwItemData, NULL, 0}; #ifndef _WIN64 //Set this menu's item data //NOTE: HBITMAP was added in IE5, and may not work on older controls... //So we will first try with the smaller size, since this should be backward compatible... if(SetMenuItemInfoA(hMenu, uItem, fByPosition, &menuinfo)) return TRUE; #endif //_WIN64 //Retry Logic //On some OS's, (Win64), it only assumes the full NT5 structure size //thus for a new OS doesn't have backward compatability issues... menuinfo.cbSize = sizeof(MENUITEMINFOA); return SetMenuItemInfoA(hMenu, uItem, fByPosition, &menuinfo); } ///////////////////////////////////////////////////////////////////////////// // SetSubMenuData // ///////////////////////////////////////////////////////////////////////////// BOOL SetSubMenuData(HMENU hMenu, UINT uItem, BOOL fByPosition, ULONG_PTR dwItemData) { //NOTE: Having every menu item in all objects menu have unique ids, makes adding the same interface //to another object very painful, and causes manually editing the menu resources, making sure its //added to OnUpdateCommand for enabling/disabling the menu item, as well as adding another //case in OnCommand for the same interface! //Instead we have devised a design where we can intitally set the item data of every menu //to contain the object identifier. This way we can always obtain the stored data of the menu //and instantly know the type of object this interface/method is called from. Saves hundreads //of lines of code, and almost ellimiates all places to remember to add code to... //First set this menu's item data if(SetMenuData(hMenu, 0, TRUE/*fByPosition*/, dwItemData)) { //Now that we have successfully set the data, we should loop through any child //menus and also set that data, since there is no current way to traverse backward in a menu... INT iItems = GetMenuItemCount(hMenu); for(INT i=0; i= %lu and <= %lu.";