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… smile_sad 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:

  1. 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.
  2. 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