The wrong approach to get a Contact’s last communication (EDB)
Have you ever played with EDB? I hadn’t… till the moment when I thought that what I needed was not implemented by the POOM and therefore I had to play with the “Contacts Database” contained in pim.vol... Unfortunately I understood only later that I was wrong… And luckily this was precisely the same query raised by in the MSDN Forum ‘How to get the information of a selected phonecall number?’ which I answered having fresh mind on the topic.
Basically when opening a Contact summary card, you can see 2 info:
- Firstly, what is the last time you called this contact by phone, and by using what phone# (mobile, work, home, etc): this is the item at the top, which is removed when user clears the Call Log history (off-topic: have you ever tried to do programmatically clear the call log? ), and therefore no longer retrievable by the Phone Functions like PhoneOpenCallLog, PhoneGetCallLogEntry, etc -- masterly wrapped by the SDF's OpenNetcf.Phone.CallLog if you’re using a managed application.
- Secondly, what is the last way user communicated with that contact: this is the selected item in the listview and can be phone\sms\mail etc, not necessarily phone. Well, this info is maintained also if user clears the call log history because it’s a property of the “Contacts Database” not of the “CLOG.EDB” database, and accordingly to what I wrote in the MSDN Forum post above, it's something you can retrieve by using POOM, related to the PIMPR_SMARTPROP property -- see doc here, ‘Remarks’ section: “The Smart Property (PIMPR_SMARTPROP) is the Contact property that contains the property ID of the default communication mode. This becomes the phone number or address displayed on the second line of the two-line display in the Contact list view, and highlighted in the Contact summary tab. ”.
So why on the earth did I mess up with EDB Functions against the “Contacts Database”? Purely because I wasn’t aware of such ad-hoc property!! And I ended up with a code that I want to share in case anyone is approaching to EDB Functions on Windows Mobile as it shows some basic functionalities… as usual it’s provided as-is for didactic purposes and doesn’t contain enough error-check for example.
DWORD dwError = ERROR_SUCCESS;
CEGUID guid;
DWORD dwBufSize, dwIndex;
BOOL fOk = FALSE;
HANDLE hSession, hDatabase;
WORD wNumProps;
CEOID oid = 0, ceoid;
TCHAR szBuffer[MAXBUFFERSIZE] = {0};
PCEPROPVAL lpProp;
//Used for CEVT_STREAM:
HANDLE hStream;
DWORD cbStream;
LPBYTE pBuffer;
DWORD cbActualRead;
//1- Mount DB
if (!CeMountDBVolEx(&guid, TEXT("\\pim.vol"), NULL, OPEN_ALWAYS))
{
dwError = GetLastError();
goto Exit;
}
//2- Open Session
hSession = CeCreateSession(&guid);
if (hSession == INVALID_HANDLE_VALUE)
{
dwError = GetLastError();
goto Exit;
}
//3- Open Database
hDatabase = CeOpenDatabaseInSession(hSession, &guid, &oid, TEXT("Contacts Database"), NULL, 0, NULL);
if (hDatabase == INVALID_HANDLE_VALUE)
{
dwError = GetLastError();
goto Exit;
}
//4- Iterate through records (there are other ways apart from waiting for ERROR_SEEK...)
dwIndex = 0;
BOOL bFound = FALSE;
while (!bFound)
{
//4.1- Set index into db
ceoid = CeSeekDatabaseEx(hDatabase, CEDB_SEEK_BEGINNING, dwIndex, 0, NULL);
if (ceoid == 0)
{
dwError = GetLastError();
goto Exit;
}
//4.2- Read records at index
wNumProps = 0;
ceoid = CeReadRecordPropsEx(hDatabase, CEDB_ALLOWREALLOC, &wNumProps, NULL, (LPBYTE*)&lpProp, &dwBufSize, NULL);
if (ceoid == 0)
{
dwError = GetLastError();
//if (dwError == 122) //ERR_INSUFFICIENT_BUFFER
//e.g. increase buffer and re-try
goto Exit;
}
//4.3- Iterate through columns
for( int i = 0; i < wNumProps; i++ )
{
//4.4- switch based on datatype (https://msdn.microsoft.com/en-us/library/aa917573.aspx)
switch( TypeFromPropID(lpProp[i].propid) )
{
case CEVT_I2:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s"), dwIndex, i, _T("Data Type"), _T("CEVT_I2") );
OutputDebugString(szBuffer);
_stprintf(szBuffer, _T("\t%d \r\n"), lpProp[i].val.iVal );
OutputDebugString(szBuffer);
break;
case CEVT_UI2:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s"), dwIndex, i, _T("Data Type"), _T("CEVT_UI2") );
OutputDebugString(szBuffer);
_stprintf(szBuffer, _T("\t%d \r\n"), lpProp[i].val.uiVal );
OutputDebugString(szBuffer);
break;
case CEVT_I4:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s"), dwIndex, i, _T("Data Type"), _T("CEVT_I4") );
OutputDebugString(szBuffer);
_stprintf(szBuffer, _T("\t%d \r\n"), lpProp[i].val.lVal );
OutputDebugString(szBuffer);
break;
case CEVT_UI4:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s"), dwIndex, i, _T("Data Type"), _T("CEVT_UI4") );
OutputDebugString(szBuffer);
_stprintf(szBuffer, _T("\t%d \r\n"), lpProp[i].val.ulVal );
OutputDebugString(szBuffer);
break;
case CEVT_LPWSTR:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s\t"), dwIndex, i, _T("Data Type"), _T("CEVT_LPWSTR") );
OutputDebugString(szBuffer);
OutputDebugString(lpProp[i].val.lpwstr);
OutputDebugString(_T("\r\n"));
break;
case CEVT_BLOB:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s \r\n"), dwIndex, i, _T("Data Type"),_T("CEVT_BLOB") );
OutputDebugString(szBuffer);
_stprintf(szBuffer, _T("\t%s: %li \r\n"), _T("Size in bytes"), lpProp[i].val.blob.dwCount );
OutputDebugString(szBuffer);
_stprintf(szBuffer, _T("\t%s: 0x%x \r\n"), _T("Buffer Address") ,lpProp[i].val.blob.lpb );
OutputDebugString(szBuffer);
break;
case CEVT_BOOL:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s \r\n"), dwIndex, i, _T("Data Type"),_T("CEVT_BOOL") );
OutputDebugString(szBuffer);
break;
case CEVT_R8:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s \r\n"), dwIndex, i, _T("Data Type"),_T("CEVT_R8") );
OutputDebugString(szBuffer);
break;
case CEVT_STREAM:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s \r\n"), dwIndex, i, _T("Data Type"),_T("CEVT_STREAM") );
OutputDebugString(szBuffer);
//OPEN STREAM
hStream = CeOpenStream(hDatabase, lpProp[i].propid, GENERIC_READ);
cbStream = sizeof(hStream);
if (hStream == INVALID_HANDLE_VALUE )
{
dwError = GetLastError();
goto Exit;
}
//SET SEEK POSITION AT BEGINNING
if (!CeStreamSeek(hStream, 0, STREAM_SEEK_SET, NULL))
{
dwError = GetLastError();
goto Exit;
}
//READ STREAM
pBuffer = new BYTE[cbStream];
if (!CeStreamRead(hStream, pBuffer, cbStream, &cbActualRead))
{
dwError = GetLastError();
delete [] pBuffer;
goto Exit;
}
_stprintf(szBuffer, _T("\tSTREAM: %s\r\n"), (LPTSTR)(pBuffer));
OutputDebugString(szBuffer);
break;
case CEVT_RECID:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s \r\n"), dwIndex, i, _T("Data Type"),_T("CEVT_RECID") );
OutputDebugString(szBuffer);
break;
case CEVT_AUTO_I4:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s"), dwIndex, i, _T("Data Type"),_T("CEVT_AUTO_I4") );
OutputDebugString(szBuffer);
_stprintf(szBuffer, _T("\t%d \r\n"), lpProp[i].val.lVal );
OutputDebugString(szBuffer);
break;
case CEVT_AUTO_I8:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s"), dwIndex, i, _T("Data Type"),_T("CEVT_AUTO_I8") );
OutputDebugString(szBuffer);
_stprintf(szBuffer, _T("\t%d \r\n"), lpProp[i].val.lVal );
OutputDebugString(szBuffer);
break;
default:
_stprintf(szBuffer, _T("%d\t %d\t %s: %s \r\n"), dwIndex, i, _T("Data Type"),_T("Unknown") );
OutputDebugString(szBuffer);
//lpProp[i].val ??
break;
} //switch
} //for
//move to next record
dwIndex++;
} //while
//5- Unmount db
if (!CeUnmountDBVol(&guid))
{
dwError = GetLastError();
goto Exit;
}
Exit:
if (NULL != hDatabase) CloseHandle(hDatabase);
if (NULL != hSession) CloseHandle(hSession);
if (dwError == ERROR_SEEK) dwError = ERROR_SUCCESS; //ERROR_SEEK is expected to exit the while loop
return dwError;
Cheers,
~raffaele
Comments
Anonymous
May 04, 2009
PingBack from http://asp-net-hosting.simplynetdev.com/the-wrong-approach-to-get-a-contact%e2%80%99s-last-communication-edb/Anonymous
May 28, 2009
I've discussed about the “wrong” approach in a previous post of mine , where I also talked about whyAnonymous
August 17, 2009
One advantage of this over managed WindowsMobile.PocketOutlook is speed. When dealing with a large collection of contacts the managed library slows to a crawl.