Mobile Development - 'Support Side Story'

Broadcasting technical support to Windows Mobile\CE Application Developers to help realizing their potential

Broadcasting technical support to Windows Mobile\CE Application Developers to help realizing their potential

May, 2009

  • Mobile Development - 'Support Side Story'

    The right approach to get a Contact’s last communication (IItem’s PIMPR_SMARTPROP)

    • 1 Comments

    I've discussed about the “wrong” approach in a previous post of mine, where I also talked about why using PIMPR_SMARTPROP to retrieve the info about the last way a Windows Mobile-device user communicated with a given contact. Recently 2 MSDN Forums users asked for help about this (“How to get the information of a selected phonecall number?” and “How to read LastNumber in contact”) and therefore I wanted to invest some time for the Community, hoping this may help others as well! smile_shades (and also to play with POOM, since managed APIs wrapped so many properties making POOM _quite_ obsolete…)

    Before spreading the code, let me state a thing: the SmartProp property is not set for a contact until the first time the user
    explicitly changes the default contact method, or it is otherwise explicitly set by an application
    . I noticed this when coding this sample, and later I understood that this is the expected behavior.

    Ehy, remember that this is not production code: this is for testing\didactic purposes only… indeed the code is simply meant to dump out to a text-file only the First Name, Last Name and the string representing the last way user communicated with the selected contact. The code doesn’t even use the IPOutlookItemCollection::Find to get a specific contact, as this was not the goal here. Yet, I think it was worth sharing as it is, so that who wants can customize it… smile_regular enjoy!!

     

    NOTES:

    1. PIMPR_SMARTPROP is a property of the IItem, not of the IContact: that’s why for example the old POOM NETCF sample code didn’t contain it. So what we need is to retrieve the collection of Contacts and handle them as IItem.
    2. Once you have all the contacts as a collection (IPOutlookItemCollection), you can’t directly retrieve each item – even if IItem implements IDispatch, the right way is the one for example mentioned by my colleague Xiaoyun Li here, i.e. invoke IPOutlookItemCollection::Item by passing a IContact as IDispatch, then call IContact::get_Oid to retrieve the unique OID and finally use this with IPOutlookApp2::GetItemFromOidEx (see function DumpOutToText below).
    3. At this point you have an IItem object and can query its properties in the usual old way, considering that PIMPR_SMARTPROP will return the property id (e.g. PIMPR_MOBILE_TELEPHONE_NUMBER, PIMPR_SMS, etc); all the propIDs that can be returned as smart prop are listed in the documentation here.
    4. For each returned property, remember to check if it was really found (.wFlags != CEDB_PROPNOTFOUND) and if it’s of the expected data type (.propid == CEVT_LPWSTR).

     

    If you know of any smarter way I’ll be more than welcome on continuing this saga about PIMPR_SMARTPROP… smile_teeth

     

    #include "stdafx.h"
    #include <pimstore.h>
    
    // **************************************************************************
    // Globals
    IPOutlookApp2 * g_polApp = NULL;
    IUnknown * g_pUnknown = NULL;
    LPCTSTR g_pszFilename = TEXT("contacts.txt");
    
    // **************************************************************************
    //Functions
    HRESULT InitPoom(void);
    HRESULT GetPoomFolder(int nFolder, IFolder ** ppFolder);
    HRESULT FillFileWithContacts(void);
    HRESULT DumpOutToText(IPOutlookItemCollection * pItemCol);
    HRESULT WriteItemSmartProp(IItem *pItem);
    HRESULT Log(LPTSTR szLog);
    HRESULT LogToFile(LPTSTR szLog, LPCTSTR pszFilename);
    
    // **************************************************************************
    //MAIN
    int _tmain(int argc, _TCHAR* argv[])
    {
        HRESULT hr = E_FAIL;
    
        //Initialize POOM
        hr = InitPoom();
        CHR(hr);
    
        //Fill file with Contacts
        hr = FillFileWithContacts();
        CHR(hr);
        
        //Success
        MessageBox(NULL, TEXT("Done"), TEXT("Test"), MB_OK);
    
    Exit:
        return 0;
    }
    
    
    // ************************************************************************** 
    //InitPoom
    HRESULT InitPoom(void) 
    {
        HRESULT hr = E_FAIL;
    
        hr = CoInitializeEx(NULL, 0);
        CHR(hr);
    
        hr = CoCreateInstance(CLSID_Application, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void **)&g_pUnknown);
        CHR(hr);
    
        hr = g_pUnknown->QueryInterface(IID_IPOutlookApp, (void**)&g_polApp); 
        CHR(hr);
    
        hr = g_polApp->Logon(NULL);
        CHR(hr);
    
        //success
        hr = S_OK;
    
    Exit:
        RELEASE_OBJ(g_polApp);
        return hr;
    }
    
    
    // ************************************************************************** 
    //FillFileWithContacts
    HRESULT FillFileWithContacts(void)
    {
        HRESULT hr = E_FAIL;
        IFolder * pCurrFldr = NULL;
        IPOutlookItemCollection * pItemCol = NULL;
        
        // Get the Contacts folder and its items
        hr = GetPoomFolder(olFolderContacts, &pCurrFldr);
        CHR(hr);
            
        if (SUCCEEDED(pCurrFldr->get_Items(&pItemCol)))
        {
            //Dump out First Name, Last Name, SMARTPROP for each item
            hr = DumpOutToText(pItemCol);
            CHR(hr);
        }
    
        //success 
        hr = S_OK;
    
    Exit:
        RELEASE_OBJ(pItemCol);
        RELEASE_OBJ(pCurrFldr);
        return hr;
    }
    
    
    // **************************************************************************
    //GetPoomFolder
    HRESULT GetPoomFolder(int nFolder, IFolder ** ppFolder)
    {
        HRESULT hr = E_FAIL;
        if (SUCCEEDED(g_polApp->GetDefaultFolder(nFolder, ppFolder)))
        {
            hr = S_OK;
        }
    
        return hr;
    }
    
    
    // ************************************************************************** 
    //DumpOutToText
    HRESULT DumpOutToText(IPOutlookItemCollection * pItemCol)
    {
        HRESULT hr = E_FAIL;
        IContact * pContact = NULL;
        IItem * pItem = NULL;
        CEOID oid = 0;   
        int cItems = 0;
    
        //Count contacts
        pItemCol->get_Count(&cItems);    
        for (int i = 1; i <= cItems; i++)
        {
            //get the item as a IContact
            if (SUCCEEDED(pItemCol->Item (i, reinterpret_cast<IDispatch**>(&pContact))))
            {
                //convert the IContact into a IItem...
                if (SUCCEEDED(pContact->get_Oid((long*)&oid)))
                {
                    //... by using the Oid
                    if (SUCCEEDED(g_polApp->GetItemFromOidEx(oid, 0, &pItem)))
                    {
                        //Write item's properties to file
                        hr = WriteItemSmartProp(pItem);
                        
                        RELEASE_OBJ(pItem);
                        CHR(hr);
                    }
                }
            }
        }
    
        //success
        hr = S_OK;
    
    Exit:
        return hr;
    }
    
    
    // **************************************************************************
    //GetItemSmartProp
    HRESULT WriteItemSmartProp(IItem *pItem)
    {
        HRESULT hr = E_FAIL;
        int cProps = 20;
        CEPROPID rgPropId[20] = {0};
        CEPROPVAL *prgPropvalUser = NULL;
        ULONG cbBuffer = 0;
    
        // FROM http://msdn.microsoft.com/en-us/library/bb415504.aspx
        // The Smart Property (PIMPR_SMARTPROP) is the Contact property that contains the property ID of the default communication mode.
        rgPropId[0] = PIMPR_FIRST_NAME; //type: CEVT_LPWSTR
        rgPropId[1] = PIMPR_LAST_NAME;  //type: CEVT_LPWSTR
        rgPropId[2] = PIMPR_SMARTPROP;  //type: CEVT_UI4
    
        //all of the following are of type: CEVT_LPWSTR
        rgPropId[3] = PIMPR_IM2_ADDRESS;                    
        rgPropId[4] = PIMPR_ASSISTANT_TELEPHONE_NUMBER;        
        rgPropId[5] = PIMPR_BUSINESS_TELEPHONE_NUMBER;        
        rgPropId[6] = PIMPR_BUSINESS2_TELEPHONE_NUMBER;        
        rgPropId[7] = PIMPR_CAR_TELEPHONE_NUMBER;            
        rgPropId[8] = PIMPR_COMPANY_TELEPHONE_NUMBER;        
        rgPropId[9] = PIMPR_EMAIL1_ADDRESS;                    
        rgPropId[10] = PIMPR_EMAIL2_ADDRESS;                    
        rgPropId[11] = PIMPR_EMAIL3_ADDRESS;                
        rgPropId[12] = PIMPR_HOME_TELEPHONE_NUMBER;            
        rgPropId[13] = PIMPR_HOME2_TELEPHONE_NUMBER;        
        rgPropId[14] = PIMPR_IM1_ADDRESS;                                        
        rgPropId[15] = PIMPR_IM3_ADDRESS;                    
        rgPropId[16] = PIMPR_MOBILE_TELEPHONE_NUMBER;        
        rgPropId[17] = PIMPR_PAGER_NUMBER;                    
        rgPropId[18] = PIMPR_RADIO_TELEPHONE_NUMBER;        
        rgPropId[19] = PIMPR_SMS;                            
    
    
        //FROM: http://msdn.microsoft.com/en-us/library/ms859378.aspx
        // Allocate memory, then get item properties
        cbBuffer = 0;
        hr = pItem->GetProps(rgPropId, 0, cProps, &prgPropvalUser, &cbBuffer, NULL);
        if(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) == hr)
        {
            prgPropvalUser = (CEPROPVAL *) LocalAlloc(0, cbBuffer); 
        }
    
        // cbBuffer is set to the number of bytes required to hold the data.
        hr = pItem->GetProps(rgPropId, 0, cProps, (CEPROPVAL **)&prgPropvalUser, &cbBuffer, NULL);
    
        //better error-checking to do here...
        if(FAILED(hr) || 0 == cbBuffer)
        {
            goto Exit;
        }
    
        if(prgPropvalUser[0].wFlags!=CEDB_PROPNOTFOUND)   
        {   
            if(LOWORD(prgPropvalUser[0].propid) == CEVT_LPWSTR)
            {
                Log(prgPropvalUser[0].val.lpwstr);
                Log(TEXT("\t"));
            }
        }
    
        if(prgPropvalUser[1].wFlags!=CEDB_PROPNOTFOUND)   
        {   
            if(LOWORD(prgPropvalUser[1].propid) == CEVT_LPWSTR)  
            {
                Log(prgPropvalUser[1].val.lpwstr);
                Log(TEXT("\t"));
            }
        }
    
        if(prgPropvalUser[2].wFlags!=CEDB_PROPNOTFOUND)   
        {   
            if(LOWORD(prgPropvalUser[2].propid) == CEVT_UI4)   
            {
                switch (prgPropvalUser[2].val.ulVal)
                {
                    case PIMPR_IM2_ADDRESS:
                        if(prgPropvalUser[3].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[3].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[3].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_ASSISTANT_TELEPHONE_NUMBER:
                        if(prgPropvalUser[4].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[4].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[4].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_BUSINESS_TELEPHONE_NUMBER:    
                        if(prgPropvalUser[5].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[5].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[5].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_BUSINESS2_TELEPHONE_NUMBER:    
                        if(prgPropvalUser[6].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[6].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[6].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_CAR_TELEPHONE_NUMBER:        
                        if(prgPropvalUser[7].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[7].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[7].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_COMPANY_TELEPHONE_NUMBER:
                        if(prgPropvalUser[8].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[8].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[8].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_EMAIL1_ADDRESS:                
                        if(prgPropvalUser[9].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[9].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[9].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_EMAIL2_ADDRESS:                
                        if(prgPropvalUser[10].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[10].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[10].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_EMAIL3_ADDRESS:                
                        if(prgPropvalUser[11].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[11].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[11].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_HOME_TELEPHONE_NUMBER:        
                        if(prgPropvalUser[12].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[12].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[12].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_HOME2_TELEPHONE_NUMBER:        
                        if(prgPropvalUser[13].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[13].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[13].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_IM1_ADDRESS:                    
                        if(prgPropvalUser[14].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[14].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[14].val.lpwstr);                            
                            }
                        }
                        break;
                    case PIMPR_IM3_ADDRESS:                    
                        if(prgPropvalUser[15].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[15].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[15].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_MOBILE_TELEPHONE_NUMBER:        
                        if(prgPropvalUser[16].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[16].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[16].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_PAGER_NUMBER:                
                        if(prgPropvalUser[17].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[17].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[17].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_RADIO_TELEPHONE_NUMBER:        
                        if(prgPropvalUser[18].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[18].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[18].val.lpwstr);
                            }
                        }
                        break;
                    case PIMPR_SMS:                            
                        if(prgPropvalUser[19].wFlags!=CEDB_PROPNOTFOUND)   
                        {   
                            if(LOWORD(prgPropvalUser[19].propid) == CEVT_LPWSTR)   
                            {
                                Log(prgPropvalUser[19].val.lpwstr);
                            }
                        }
                        break;
                } //switch
            }            
        }
        Log(TEXT("\r\n"));
    
        //success
        hr = S_OK;
    
    Exit:
        LocalFree(prgPropvalUser); 
        return hr;
    }
     
     
    // ************************************************************************** 
    // Log 
    HRESULT Log(LPTSTR szLog)
    {
        HRESULT hr = E_FAIL;
        hr = LogToFile(szLog, g_pszFilename);
        CHR(hr);
    
    Exit:
        return hr;
    }
    
    
    // ************************************************************************** 
    // LogToFile 
    // Writes szLog into the file named pszFilename
    HRESULT LogToFile(LPTSTR szLog, LPCTSTR pszFilename)
    {
        HRESULT hr = E_FAIL;
        
        //Open the handle to the file (and create it if it doesn't exist
        HANDLE hFile = CreateFile(pszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
        if (INVALID_HANDLE_VALUE == hFile)
            goto Exit;
    
        //Set the pointer at the end so that we can append szLog
        DWORD dwFilePointer = SetFilePointer(hFile, 0, NULL, FILE_END);
        if (0xFFFFFFFF == dwFilePointer)
            goto Exit;
    
        //Write to the file
        DWORD dwBytesWritten = 0;
        BOOL bWriteFileRet = WriteFile(hFile, szLog, wcslen(szLog) * 2, &dwBytesWritten, NULL);
        if (!bWriteFileRet)
            goto Exit;
    
        //Flush the buffer
        BOOL bFlushFileBuffersRet = FlushFileBuffers(hFile);
        if (!bFlushFileBuffersRet)
            goto Exit;
    
        //Success
        hr = S_OK;
    
    Exit:
        if (NULL != hFile)
        CloseHandle(hFile);
    
        return hr;
    }
     

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    MAPI on Windows Mobile: Backup and Restore Mails

    • 4 Comments
    • Backup\Restore: is there a standard way?
    • How to programmatically mark messages so that they are downloaded at next sync
    • About linked attachments

    Yet another post about MAPI on Windows Mobile, it seems it became an common topic (not well covered over the web probably)… I recently worked with one of my colleagues in Redmond (thanks Alex! smile_regular) about the following topic: how to use MAPI, if possible, to backup and restore e-mails sync-ed with a backend Exchange server (so by using ActiveSync MAPI Store).

    After some investigation about the possible combination of properties, we understood that if the message is partially downloaded (only the header, or partially downloaded, or without downloading attachments) the is no standard way on how to retrieve the rest of information, such as a link or so to click for downloading, after restoring the messages from a backup file – simply because the server at that point will consider the message as different from the initial one.

    At the beginning we imagined the problem was only with message body… and you can programmatically state if a message hasn't fully downloaded by looking at its properties MSGSTATUS_HEADERONLY, MSGSTATUS_PARTIAL_DOWNLOAD or MSGSTATUS_PARTIAL. So… how to programmatically mark messages so that they are downloaded at next sync? To mark a message for download, you should close the messaging application (tmail.exe), add the property MSGSTATUS_REMOTE_DOWNLOAD to the message and then restart the messaging application (tmail.exe). Note that if you don't want the download to start immediately, then you should also modify the message's PR_CE_SUPPRESS_FETCH property in the same call to IItem::SetProps, accordingly to documentation. I’m talking about something like:

    SPropValue rgProps[2];
    
    rgProps[0].ulPropTag = PR_MSG_STATUS;
    rgProps[0].Value.ul  = ulMsgStatus | MSGSTATUS_REMOTE_DOWNLOAD | MSGSTATUS_REMOTE_DOWNLOAD_ATTACH;
    rgProps[1].ulPropTag = PR_CE_SUPPRESS_FETCH;
    rgProps[1].Value.ul  = 1;
    
    hr = pMsg->SetProps(2, &rgProps, NULL);
    CHR(hr);

    Note: MSGSTATUS_REMOTE_DOWNLOAD is not mentioned under the "Windows Mobile Defined MAPI Properties" documentation, but you can see it defined in the related SDK's header file ("C:\Program Files (x86)\Windows Mobile 6 SDK\PocketPC\Include\Armv4i\cemapi.h"), so you can use it – in other words, the documentation is not complete on that page.

    Note that this may not be a complete solution, accordingly to Jay Ongg's answer to a comment on a Windows Mobile Dev Team's blog post (here):

    "[...] Regarding MSGSTATUS_PARTIAL, downloading a whole message is actually more complicated than just setting a flag.  Depending on the transport, you have to think about downloading attachments, which is done different for HTML mail vs normal attachments, I believe.  With that said, this might work... try setting the MSGSTATUS_REMOTE_DOWNLOAD flag to start out, and MSGSTATUS_REMOTE_DOWNLOAD_ATTACH as well."

    Indeed, as reported by Jay, the approach above resolved the problem for message bodies, but not for attachments! smile_sad So… to recap: imagine you wish to copy a MAPI message that has a non-downloaded attachment into an unspecified backup storage; later, you copy the message from the backup store into the messaging store. What we expected was that the attachment to still be part of the message, but we found that the message's link to download the attachment is missing. Is there any supported way so that the attachment remains in the message after performing the backup and then retrieving the message? The answer is simply “no”. Trying to copy (backup) the email message with a non-downloaded attachment will not work. The link to the attachment will be lost, the attachment won’t be retrievable. The email and the non-downloaded attachment are associated with each other in Exchange, however the attachment is not actually a part of the message, so copying the message will not make a copy of the attachment. So the solution is to force the attachment to be downloaded before backing up the message (if user wants to maintain the attachment). Obviously saving the message and attachment on the device might not be a workable solution due to the storage space required… but, as usual, that depends on scenarios.

     

    Cheers!
    ~raffaele & Alex

  • Mobile Development - 'Support Side Story'

    Microsoft released a HotFix for NETCF v3.5 on Windows Mobile: now you can use Web Services over SSL (without worrying about empty packets)

    • 5 Comments
    • System.Net.WebException: Unable to read data from the transport connection (System.Net.Sockets.SocketException: Unknown error (0x0))
    • Use HotFix responsibly!

    Today my co-worker Carmelo (thanks for sharing this! smile_regular) found something that NETCF Developers were waiting since long time ago… a fixed version of NETCF CAB for Windows Mobile that would allow to address a bug about calling Web Services over SSL, which was greatly described by Andrew Arnott here some time ago. The problem was that NETCF couldn’t work with Web Services over SSL that respond with empty encryption packets.

    The link to the hotfix is “FIX: A System.Net.WebException occurs when you run an application to send HTTPS Web requests to a server in an embedded device”.

    Does this mean that also NETCF Developers can begin waiting for hotfixes for bugs? No, I personally think that this is a one-shot FIX coming from the NETCF Dev Team, because the problem was affecting so many customers around the globe… they simply deserve it. And, again, Technical Support is your friend!! I don’t know the story behind this fix, however to convince the Dev Team to produce it I’m sure that a “certain” number of customers made their voice be heard through Service Requests…

    WARNING! Remember that analogously to every other hotfix you can ask to Technical Support about whatever Microsoft’s product, you should install it only if you're sure it’s for the problem you're being affected by. This is because a hotfix is meant to address a specific issue and has been tested precisely for that: it didn't go through the whole regression testing that it's usually done when packaging many hotfixes into a Service Pack. So, you don’t have to install this hotfix if you’re not interested on Web Services over SSL (sending empty packets).

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    O Service Request (about NETCF Memory Leak…), where art thoU?

    • 1 Comments

    I’m losing my expertise on this!! smile_confused After blogging about it to spread out the usual suggestions I was used to give to ISVs with troubles around memory, I simply quitted handling this kind of cases! Either v3.5 addressed all known bugs, either NETCF developers are getting skilled about things to do to prevent memory leaks… and I hope I’ve been of some little help! smile_shades

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Yet Another Technical Support’s added value: Engagement of Dev Teams

    • 1 Comments
    • Some bloggers within Technical Support
    • Example of added value given by Technical Support: Devs’ engagement 
    • Comments about NETCF on ARMv6 and ARMv7

    I often have to do some marketing for Microsoft Technical Support, because it’s something everyone with questions or problems around Microsoft products must test out to understand its added value… Apart from myself smile_regular we’re plenty of great people on all technologies: to name just a few, the following are representatives of the Technical Support – watch out their blogs, as they’re so plenty of “knowledge”! smile_nerd

    There are many others I simply hadn’t yet the honor to work with, I simply couldn’t mention all of them!

    So, what is the added value I want to present today? This time I’d like to demonstrate that one of benefit of using Technical Support is that we may have a preferential engagement with the Product Groups, when necessary. This may differ in forms depending on the technology, but no doubt that we can engage the people who actually wrote the software. And an example of this happened to me some weeks ago…

    In that particular case who opened a Service Request was the OEM of a Windows CE 6.0-based device: you can say that I help ISVs, but this particular OEM was posing questions about developing a NETCF v3.5 application and wanted to receive support about Application Development rather than OS Customization through Platform Builder, therefore it fell in area of expertise. The device was based on a ARMv7 processor and was used as navigation system, hence it was heavily dependent on mathematical functions. On this processor, the OEM noticed that calling the same mathematical functions in a NETCF application required much more time than in a native application. The impression was that the native application leverages on the mathematical co-processor and asked if there’s any way to configure the NETCF Runtime to do the same either at application-layer or system-wide through an OS Customization.

    To make it short, in this case I had the pleasure to work directly with Abhinaba Basu, member of the NETCF Dev Team – see his blog posts as a result of this co-operation:

    Apart from the deep explanations given by Abhinaba in his blog, questions and answers were:

    1- Is this an expected result?
    NetCF 3.5 lacks Armv6/v7 specific optimizations as it is based on Armv4i.  In particular we don’t leverage the FPU (Floating Point Unit), as NETCF does only floating point emulation.  Hence NetCF math functions will be much slower than doing the same in a native C++ program, which in contrast could be compiled to use the hardware FPU.

    2- Is ARMv7 a fully-supported architecture by the NETCF Runtime?
    NetCF 3.5 is based on Armv4i and Armv7 is backward compatible with Armv4i.  Hence all of the functionality of NetCF3.5 should work fine though may not yield optimal performance.

    3- Is there any possible way to achieve similar results with NETCF?
    Math routines could be written in native and the same could be PInvoked from managed code. Obviously you need to ensure that the cost of P/Invoke marshalling is not offsetting the saving in computation perf. A good idea to do that is to reduce the number of P/Invoke calls by bulking the computation.

    In particular, in that specific case the solution proposed was to develop a native DLL that internally invoked native math functions and exposes some “math expressions” to a .NET client. I mean a native DLL exposing f(a,b,c) which internally is like {[sin(a) x sin(b)] / cos(c)}. Moreover, if for example you know that you need to do 3 of these operations then create a buffer of inputs and outputs and push that to the native dll so that it can do all 3 in one go.

     

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    MAPI GetOutgoingQueue not supported on Windows Mobile (so the Outbox?)

    • 1 Comments

    Recently I had to work on a sample code based on my previous post around MAPI that shows a possible way to extract the BODY of the mails in the Inbox folder of the ActiveSync account… this time I needed to grab a handle of the Outbox folder, and therefore I had to modify the function SaveMessagesFromStore of that post. Luckily the code was modularized enough… smile_regular so, once I had the LPMAPIFOLDER pointer to the Outbox I could continue using basically the same code. I thought there was a MAPI Function similar to IMsgStore::GetReceiveFolder, but found that quite many functions are not implemented in Windows Mobile 6 – see doc here. Luckily MAPI gives different ways to reach a goal, and in this particular case the code was based on the PR_IPM_OUTBOX_ENTRYID property of the MAPI store:

            // Get the Outbox folder
            ULONG rgTags[] = {1, PR_IPM_OUTBOX_ENTRYID};
            LPSPropValue rgprops = NULL;
            ULONG ulValues = 0;
            hr = pStore->GetProps((LPSPropTagArray) rgTags, MAPI_UNICODE, &ulValues, &rgprops);
            CHR(hr);
            
            cbEntryId = rgprops[0].Value.bin.cb;
            pEntryId = (LPENTRYID)rgprops[0].Value.bin.lpb;
            
            // We have the entryid of the Outbox folder, let's get the folder 
            hr = pStore->OpenEntry(cbEntryId, pEntryId, NULL, 0, &ulObjType, (LPUNKNOWN*)&pFolder); 
            CHR(hr); 
            
            //check 
            CBR(ulObjType == MAPI_FOLDER);
     

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    The wrong approach to get a Contact’s last communication (EDB)

    • 3 Comments

    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 (http://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

Page 1 of 1 (7 items)