After migrating an MFC application from Visual Studio 2008 to Visual Studio 2012 I run into an unexpected error: the application was having problem fetching data from the SQL Server database. After debugging it turned out that function CDatabase::GetConnect that I was using to retrieve the connection string after opening the database (for different purposes) was suddenly returning an empty string. It turned out this was a known MFC bug, reported on Microsoft Connect. The cause of the problem is that CDatabase encrypts the connection string, and then empties it. Here is a snippet of OpenEx:
BOOL CDatabase::OpenEx(LPCTSTR lpszConnectString, DWORD dwOptions) { ENSURE_VALID(this); ENSURE_ARG(lpszConnectString == NULL || AfxIsValidString(lpszConnectString)); ENSURE_ARG(!(dwOptions & noOdbcDialog && dwOptions & forceOdbcDialog)); // Exclusive access not supported. ASSERT(!(dwOptions & openExclusive)); m_bUpdatable = !(dwOptions & openReadOnly); TRY { m_strConnect = lpszConnectString; DATA_BLOB connectBlob; connectBlob.pbData = (BYTE *)(LPCTSTR)m_strConnect; connectBlob.cbData = (DWORD)(AtlStrLen(m_strConnect) + 1) * sizeof(TCHAR); if (CryptProtectData(&connectBlob, NULL, NULL, NULL, NULL, 0, &m_blobConnect)) { SecureZeroMemory((BYTE *)(LPCTSTR)m_strConnect, m_strConnect.GetLength() * sizeof(TCHAR)); m_strConnect.Empty(); }
Though Microsoft promised to be solved the bug in the next major version I needed a fix now. So here is my fix.
CDatabase has a protected member m_strConnect which is supposed to keep the connection string in plain text, and one called m_blobConnect that represents the encrypted connection string. However, there is no method in CDatabase to return this blob. Getting the blob would allow you to decrypt it and get the actual connection string. So the solution is to derive CDatabase, provide a method to return the connection string, and in your code replace CDatabase with this derived class and the call to GetConnect() to this new method.
class CDatabaseEx : public CDatabase { public: CString GetConnectEx() { CString strConnect = m_strConnect; if (strConnect.GetLength() == 0) { DATA_BLOB connectBlob; if (CryptUnprotectData(&m_blobConnect, NULL, NULL, NULL, NULL, 0, &connectBlob)) { strConnect = (LPTSTR)connectBlob.pbData; LocalFree(connectBlob.pbData); } } return strConnect; } };
The line
if (CryptUnprotectData(&m_blobConnect, NULL, NULL, NULL, NULL, 0, &connectBlob))
is returnng the error for me
error C2664: ‘CryptUnprotectData’ : cannot convert parameter 1 from ‘const DATA_BLOB *’ to ‘DATA_BLOB *’
If I modify the code with a simple data cast, however, everything is fine…
if (CryptUnprotectData((DATA_BLOB*)(&m_blobConnect), NULL, NULL, NULL, NULL, 0, &connectBlob))
m_blocConnect is not const, so I have no idea why you get that error. It compiles perfectly fine for me.
Thank you very much. You are a genius!
u saved my friday :- )
I found this blog since I encountered CDatabase::OpenEx() StackHash crash under pressure test, and tried to looking for if any known bug or solution.
My environment is using VS2005 SP1 + SQLServer native client 10.0 + MSSQL 2008.
According to VS2005 IDE call stack window, at the crash point, it is a little odd that there were about 20 levels of call stack from CDatabase::OpenEx to crash function in sqlncli10.dll.
I doubt if that a known problem?
Best regards,
Alan