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

July, 2008

  • Mobile Development - 'Support Side Story'

    Power-Efficient applications on Windows Mobile

    • 3 Comments

    Sometimes it happens that a developer asks for suggestions about how to design an application from the very beginning so that it'll be power-efficient, and therefore I now have a list of links\suggestions that may be interesting to share...

    DWORD PwrFlag, NameLength; 
    TCHAR StateName[64] = { 0 }; 
    GetSystemPowerState(StateName, NameLength, &PwrFlag); 
    if(POWER_STATE_ON != PwrFlag) 
       //perform action only if display is on.
       //for example, in case of Home plugins:
       InvalidateRect(hPlugInWnd, NULL, TRUE);
    • Or the application may listen on Activity Timers through the State&Notification Broker to perform tasks only when necessary.
    • In some cases you may want to turn off only the display when performing some actions - I knew a solution based on ExtEscape API (see old Alex Feinmain's managed sample) and thought that this was supported for OEMs only, however looking at the official doc it's for ISV Application Developers as well (in any case, note that "[...] The device capabilities this function accesses must be implemented by an OEM.", thus meaning that you can see different behaviors on different devices):
    HDC gdc = ::GetDC(NULL);
    VIDEO_POWER_MANAGEMENT vpm;
    vpm.Length = sizeof(VIDEO_POWER_MANAGEMENT);
    vpm.DPMSVersion = 0x0001;
    vpm.PowerState = VideoPowerOff;
     
    // Power off the display
    ExtEscape(gdc, SETPOWERMANAGEMENT, vpm.Length, (LPCSTR) &vpm, 0, NULL);
    Sleep(5000); //just for demonstration purposes
    vpm.PowerState = VideoPowerOn;
     
    // Power on the display
    ExtEscape(gdc, SETPOWERMANAGEMENT, vpm.Length, (LPCSTR) &vpm, 0, NULL);
    ::ReleaseDC(NULL, gdc);
    • You may want to have applications continue running when the device is suspended. This is simply not possible, as the processor is in idle state and doesn't offer any CPU cycle to be used by applications. Moreover, an application can't detect entering suspend state: "[...] While an application cannot detect the transition into SUSPEND mode, it is able to detect the transition from SUSPEND mode. It does this by calling the CeRunAppAtEvent function, which takes two parameters: a path and an event code" (from Power Management Features of Windows CE .NET). So if you really want to continue having the application running you can use CeRunAppAtTime() invoking SystemIdleTimerReset() every minute to prevent the device to suspend. You may turn the display off to save battery tough, at least. In such cases, I would encourage revisiting the architecture of the application. In many cases you can simply use CeAppRunAtTime() to perform a task: consider that the API will wake the device up if it's suspended. Note that if the device was suspended and waken up by CeRunAppAtTime it will be in a state between Power On and Suspend: you need to call SetSystemPowerState API to really turn the device on:
    SetSystemPowerState(NULL, POWER_STATE_ON, POWER_FORCE)

     

    I imagine there are many other techniques out there to save devices to drain battery... feel free to add whatever link or suggestions in a comment! Smile

     

    Cheers,

    ~raffaele

  • Mobile Development - 'Support Side Story'

    Silently install multiple CABs on Windows Mobile

    • 4 Comments

    - How to modify MulticabInstall SDK Sample

    - Notes about Security

    Recently I've been involved in an issue about silently installing mutiple CABs, so I think it may be worth mentioning its results here.

    The OS component invoked when installing CABs is WCELOAD.EXE: it's the same if the CAB is created for XML Provisioning (through makecab.exe) or for installing an application (thruogh CabWiz.exe). It's for example responsible to check for certificates when the Security Policies dictate that unsigned CAB are not allowed to run. The version for Windows Mobile (5.0\6) is a bit different from the one for Windows CE 5.0, maily regarding available command-line parameters, check out:

    Starting with Windows Mobile 5.0, only one running instance of WCELOAD.EXE is possible, therefore the nested CABs used with Windows Mobile 2003 are no longer possible. A solution to this is the MulticabInstall SDK sample: it basically allows the installation-chaining of CABs packaged into an UberCAB by looking at some registry keys. The sample contains a whitepaper that explains everything needed, I won't double it here.

    However, what if I want all those inner CABs to be SILENTLY installed? For a single CAB, the solution is programmatically launch WCELOAD.EXE with /silent option (on Windows Mobile). So I simply looked for the function in the MulticabInstall SDK sample responsible for executing the CAB and found:

    BOOL HostExec(LPCTSTR lpszFilePath, HANDLE *phProcess)
    {
        BOOL bRet;
        SHELLEXECUTEINFO sei = {0};
    
        sei.cbSize = sizeof(sei);
        sei.nShow = SW_SHOWNORMAL;
        sei.lpFile = lpszFilePath;
        //sei.lpParameters = TEXT("/silent");  //WRONG APPROACH!!
    
        bRet = ShellExecuteEx(&sei);
    
        if (bRet)
        {
            *phProcess = sei.hProcess;
        }
    
        return bRet;
    }

    It uses the ShellExecuteEx() API to launch the application on the OS associated to files with .cab extension. I tried to set the parameter lpParameters of the SHELLEXECUTEINFO structure to "/silent", however this didn't work... So I looked at documentation for the SHELLEXECUTEINFO structure and found: "[...] If the lpFile member specifies a document file, this member should be NULL.". At this point I remembered what that sentence means: lpParameters was basically ignored since my lpFile was NOT an .EXE! Embarrassed

    In order to continue using ShellExecuteEx (and not CreateProcess, for exampple), the code I came up with was simply:

    BOOL HostExec(LPCTSTR lpszFilePath, HANDLE *phProcess)
    {
        BOOL bRet;
        SHELLEXECUTEINFO sei = {0};
    
        sei.cbSize = sizeof(sei);
        sei.nShow = SW_SHOWNORMAL; 
        sei.lpFile = TEXT("\\Windows\\WCELOAD.EXE");
    
        TCHAR   szLaunchParms[MAX_PATH];
        StringCchPrintf(szLaunchParms,ARRAYSIZE(szLaunchParms), 
                TEXT("/silent \"%s\""), 
                lpszFilePath);
        sei.lpParameters = szLaunchParms;
    
        bRet = ShellExecuteEx(&sei);
    
        if (bRet)
        {
            *phProcess = sei.hProcess;
        }
    
        return bRet;
    }

     

    As a final note, consider that on WM5 there could be additional prompt regarding CAB installations, if the CAB is not signed with a certificate stored on the SPC store of the device.When using /noui (or /silent) then by default prompts are answered with 'Yes'. However, if the .cab file is unsigned (or signed with a certificate not stored on the device), any security-related prompts will default to 'No' for security reasons, and the installation might SILENTLY fail.

     

    Cheers,

    ~raffaele

  • Mobile Development - 'Support Side Story'

    Pocket Outlook: Empty Deleted Items folder on SERVER's Mailbox

    • 2 Comments

    - MAPI: the wrong approach (yet interesting sample code)

    - WebDAV: DELETE command via HTTP

    - Customize Pocket Outlook menu items

     

    One of the features that Windows Mobile users frequently asked to the Product Group had always been the native ability to empty the Deleted Items folder on the server which the device is sync-ed to. Finally Windows Mobile 6-based devices have such ability when synchronizing with Exchange 2007 (Menu\Tools\Empty Deleted Items):

    empty

    The truth is that it's still plenty of Windows Mobile 5.0 devices (and Exchange 2003) in the real world, and many of their users are looking for a way to reach the goal... Smile The only available way seemed to be to access OWA from the device's web browser and here empty the folder: this is not acceptable in many cases for real-world scenarios. In other cases you may have found software to be installed server-side exposing such function to device-based clients. ... I've been working with some colleagues on this and I think it's worth mentioning here what we found...

     

    - MAPI: the wrong approach (yet interesting sample code)

    When started researching about the goal, I came across the MAPI (Messaging API) IMAPIFolder::DeleteMessages. Wow! Precisely the function I need, I said to myself. Unfortunately this wasn't true (BECAUSE I NEEDED A DEVICE-SIDE APPLICATION), but at least I spent some time on MAPI programming, which is really powerful after you  understand how it works... (and admittedly it's not really user-friendly...). So I ended up with an application that empties the Deleted Items folder, but only the one ON THE DEVICE, because you can't synchronize back it to the Exchange server. Interestingly, I hadn't to develop the whole code because portions of it were already available on the web (see for example "Practical use of MAPI") or in the WM6 SDK Samples (specifically, windows mobile 6 sdk\Samples\Common\CPP\Win32\InboxMenuExtensibility, which I'm going to talk about in a bit). Note that if you use the same MAPI on a server-side application (e.g. a Web Service exposing a Web Method that devices can invoke), then you would reach the goal: that's basically how the Blackberry Exchange-connector works, for example, when invoked from the device.

    The working sample I came up with is the following: it's not thoughtfully tested and I can't say it's error-free (or that it's the best code to achieve the result), but it does what it was meant for -- empty the Deleted Items folder (in its case, ON THE DEVICE). I copied it to the end of the post.

     

    - WebDAV: DELETE command via HTTP

    As I said, since the Deleted Items folder is not sync-ed back and forth between server and device, the MAPI-based approach is not the correct one. The one that works, IF WEBDAV IS AN ALLOWED WEB SERVICE EXTENSION OF THE IIS Front-End Exchange SERVER, it's through WebDAV. The idea is very simple: allow a client application to access the mail-folders via HTTPS and send them some commands such as

        DELETE /pub2/folder1/folder2 HTTP/1.1
        Host: hostname
        Content-Length: 0
     

    Basically I'm talking about an empty HTTP request (programmatically managed through a HttpWebRequest object, for example, in a .NET application) whose command is DELETE and whose argument is the path of the Deleted%20Items folder. There are many sample codes available at the doc Deleting Items (WebDAV). I'm copying the C# code here to add few comments and to show how simple it is:

    using System;
    using System.Net;
    
    namespace ExchangeSDK.Snippets.CSharp
    {
       class DeletingItemsWebDAV
       {
          [STAThread]
          static void Main(string[] args)
          {
             System.Net.HttpWebRequest Request;
             System.Net.WebResponse Response;
             System.Net.CredentialCache MyCredentialCache;
             string strSourceURI = "http://server/public/TestFolder1/test.txt";
    
             //In this sample user credentials are hard-coded for didactic purposes
             string strUserName = "UserName";
             string strPassword = "!Password";
             string strDomain = "Domain";
    
             try
             {
                // Create a new CredentialCache object and fill it with the network
                // credentials required to access the server. 
                // we won't pass the credentials in clear-text...
                MyCredentialCache = new System.Net.CredentialCache();
                MyCredentialCache.Add( new System.Uri(strSourceURI),
                   "NTLM",
                   new System.Net.NetworkCredential(strUserName, strPassword, strDomain)
                   );
    
                // Create the HttpWebRequest object.
                Request = (System.Net.HttpWebRequest)HttpWebRequest.Create(strSourceURI);
    
                // Add the network credentials to the request.
                Request.Credentials = MyCredentialCache;
    
                // Specify the DELETE method.
                Request.Method = "DELETE";
    
                // Send the DELETE method request.
                Response = (System.Net.HttpWebResponse)Request.GetResponse();
    
                // Close the HttpWebResponse object.
                Response.Close();
    
                Console.WriteLine("Item successfully deleted.");
    
             }
             catch(Exception ex)
             {
                // Catch any exceptions. Any error codes from the DELETE
                // method request on the server will be caught here, also.
                Console.WriteLine(ex.Message);
             }
          }
       }
    }

     

    The power of WebDAV is that it's independent on the client, so for example you can run all your tests from a DESKTOP console. Moreover, to check if there's any server-side issue, you can use the Microsoft Download Microsoft Exchange Server Public Folder DAV-based Administration Tool.

    Exchange servers' administrators may argue to us (mobile developers Nerd) that WebDAV is less secure. Let them read for example the doc at Authentication and Security Using WebDAV: it states that by using SSL, WebDAV is as secure as OWA.

     

    - Customize Pocket Outlook menu items

    Ok, now that you know how to achieve the goal, you may wonder how to integrate this new functionality on the system's Pocket Outlook (tmail.exe). It may be a bit tricky, but there's a complete NATIVE sample showing that in the SDK (so you'll need to use the native sample code of the Deleting Items (WebDAV) page): windows mobile 6 sdk\Samples\Common\CPP\Win32\InboxMenuExtensibility. This sample needs to be modified in some parts and may require a bit of knowledge on COM programming, but it's fully documented. It's basically a DLL COM Server loaded by tmail.exe (so it may require to be signed with a Priv Certificate), which ultimately invokes InsertMenu API to insert the menu item you want. The sample is more complex than strictly required in our case, as it also shows how to modify the menu items collection depending on the current context (i.e. if a message is selected or viewed).

     

    Cheers!

    ~raffaele

     

    P.S. The MAPI-based approach:

    #include "stdafx.h"
    #include <windows.h>
    #include <commctrl.h>
    
    #include <initguid.h>
    #include <pimstore.h>
    #include <mapiutil.h>
    
    HRESULT hr = E_FAIL;
    LPMAPISESSION m_pSession;
    
    ////////////////////////////////////////////////////////////////////////////// 
    //Functions declarations
    HRESULT CreateEntryList(SRowSet *pRows, ENTRYLIST **ppList);
    HRESULT DeleteMessages(IMsgStore* pStore);
    HRESULT GetWastebasketForFolder(LPMAPIFOLDER pFolder, LPMAPIFOLDER* ppfldrWastebasket);
    ULONG CountMessagesInFolder(LPMAPIFOLDER pFolder);
    HRESULT CreateEntryList(SRowSet *pRows, ENTRYLIST **ppList);
    
    ////////////////////////////////////////////////////////////////////////////// 
    //Macros
    #define _ErrorLabel Error
    
    #define CHR(hResult) \
        if(FAILED(hResult)) { hr = (hResult); goto _ErrorLabel;} 
    
    #define CPR(pPointer) \
        if(NULL == (pPointer)) { hr = (E_OUTOFMEMORY); goto _ErrorLabel;} 
    
    #define CBR(fBool) \
        if(!(fBool)) { hr = (E_FAIL); goto _ErrorLabel;} 
    
    #define ARRAYSIZE(s) (sizeof(s) / sizeof(s[0]))
    
    #define RELEASE_OBJ(s)  \
        if (NULL != s)      \
        {                   \
            s->Release();   \
            s = NULL;       \
        }
    
    
    //////////////////////////////////////////////////////////////////////////////// 
    //MAIN
    int _tmain(int argc, _TCHAR* argv[])
    {
        SRowSet *prowset = NULL;
        IMAPITable* ptbl = NULL;
        IMsgStore* pStore = NULL;
    
        // Warn user that the messages will be permanently deleting 
        int iResult = ::MessageBox(GetActiveWindow(), 
                    TEXT("Are you sure you want to permanently delete all messages in Deleted Items ON THE DEVICE?"), 
                    TEXT("Permanently Delete Items"), MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2);
    
        // If IDYES - then continue / else exit method now
        hr = (iResult == IDYES) ? S_OK : E_FAIL;                        
        CHR(hr);
    
        //Initialize MAPI COM Server
        hr = MAPIInitialize(NULL);    
        CHR(hr);
    
        //Logon to MAPI and get a session pointer
        hr = MAPILogonEx(0, NULL, NULL, 0, (LPMAPISESSION *)&m_pSession);
        CHR(hr);
    
        static const SizedSPropTagArray (2, spta) = { 2, PR_DISPLAY_NAME, PR_ENTRYID };
            
        // Get the table of accounts
        hr = m_pSession->GetMsgStoresTable(0, &ptbl);
        CHR(hr);
    
        // set the columns of the table we will query
        hr = ptbl->SetColumns((SPropTagArray *) &spta, 0);
        CHR(hr);
    
        while (TRUE)
        {
            // Free the previous row
            FreeProws (prowset);
            prowset = NULL;
     
            hr = ptbl->QueryRows (1, 0, &prowset);
            if ((hr != S_OK) || (prowset == NULL) || (prowset->cRows == 0))
                break;
     
            ASSERT (prowset->aRow[0].cValues == spta.cValues);
            SPropValue *pval = prowset->aRow[0].lpProps;
     
            ASSERT (pval[0].ulPropTag == PR_DISPLAY_NAME);
            ASSERT (pval[1].ulPropTag == PR_ENTRYID);
     
            //Windows Movile has 2 MAPI Message Stores: "ActiveSync" and "SMS"
            if (!_tcscmp(pval[0].Value.lpszW, TEXT("ActiveSync")))
            {
                // Get the Message Store pointer
                hr = m_pSession->OpenMsgStore(0, pval[1].Value.bin.cb, (LPENTRYID)pval[1].Value.bin.lpb, 0, 0, &pStore);
                CHR(hr);
     
                //Empty Deleted Items folder
                hr = EmptyDeletedMessages(pStore);
                CHR(hr);
            }
        }
    
        //finishing off...
        hr = m_pSession->Logoff(0, 0, 0);
        
    Error:
        //make sure we don't leak memory
        FreeProws(prowset);
        RELEASE_OBJ(ptbl);
        RELEASE_OBJ(pStore);
        RELEASE_OBJ(m_pSession);   
    
        MAPIUninitialize();
    
        return hr;
    }
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // EmptyDeletedMessages - Takes the Message Store as argument and empties its 
    //  Deleted Items folder
    HRESULT EmptyDeletedMessages(IMsgStore* pStore)
    {
        hr = E_FAIL;
        ULONG cMsgCount = 0;
        IMAPIFolder* pFolder = NULL;
        IMAPIFolder* pWasteBasket = NULL;
        IMAPITable *pTable = NULL; 
        ULONG cbEntryId = 0;
        ENTRYID *pEntryId = NULL;
        ULONG ulObjType = 0;
        SRowSet *pRows = NULL;
        LPENTRYLIST lpmsgEntryList = NULL;
    
        // First retrieve the ENTRYID of the Inbox folder of the message store
        hr = pStore->GetReceiveFolder(NULL, MAPI_UNICODE, &cbEntryId, &pEntryId, NULL); 
        CHR(hr);
     
        // We have the entryid of the inbox folder, let's get the folder and messages in it
        hr = pStore->OpenEntry(cbEntryId, pEntryId, NULL, 0, &ulObjType, (LPUNKNOWN*)&pFolder);
        CHR(hr);
     
        // Be sure it's a folder
        ASSERT(ulObjType == MAPI_FOLDER);
    
        // Get the Deleted Items Folder (because it may be that current view is on wahtever folder
        hr = GetWastebasketForFolder(pFolder, &pWasteBasket);
        CHR(hr);
        CPR(pWasteBasket);
    
        // Get contents of the folder
        pWasteBasket->GetContentsTable(MAPI_UNICODE, &pTable);
    
        // We only care about the EntryID. 
        SizedSPropTagArray (1, spta) = {1, PR_ENTRYID};
        pTable->SetColumns((SPropTagArray *)&spta, 0);
    
        BOOL done = FALSE;
    
        // We need to know how many messages there are
        cMsgCount = CountMessagesInFolder(pWasteBasket);
        
        while(cMsgCount > 0)
        {
            // 10 is the max number of rows which QueryRows can return
            int cCurrentQuery = 0;
    
            if (cMsgCount > 10)
            {
                cCurrentQuery = 10;
            }
            else
            {
                cCurrentQuery = cMsgCount;
            }
            
            hr = pTable->QueryRows(cCurrentQuery, 0, &pRows);
            CHR(hr);
            
            // Did we hit the end of the table?
            if (pRows->cRows == 0)
            {   
                break;
            }
    
            // Since we are in a loop here and we are re-using lpmsgEntryList 
            // we may need to free the memory from the previous iteration first
            // In the case of the last iteration or Error - this will be freed at Error:
            MAPIFreeBuffer(lpmsgEntryList);
            lpmsgEntryList = NULL;
            
            // Otherwise - Get the List of EntryIDs from the RowSet as an EntryList
            //  Note: the resulting will EntryList be dependant on the RowSet so don't free until later
            hr = CreateEntryList(pRows, &lpmsgEntryList);
            CHR(hr);  
    
    
            // permanently delete the items 
            hr = pWasteBasket->DeleteMessages(lpmsgEntryList, NULL, NULL, NULL);
            CHR(hr);           
            
            cMsgCount -= pRows->cRows;
        }  
    
    Error:
        RELEASE_OBJ(pFolder);
        RELEASE_OBJ(pWasteBasket);
        RELEASE_OBJ(pTable);
        FreeProws(pRows);
        
        MAPIFreeBuffer(pEntryId);
        
        return hr;
    }
    
    
    ////////////////////////////////////////////////////////////
    // CountMessagesInFolder - takes a folder as argument and returns 
    //  the # of messages in it
    ULONG CountMessagesInFolder(LPMAPIFOLDER pFolder)
    {
        hr = E_FAIL;
        IMAPIProp *pProp = NULL; 
        ULONG rgTags[] = {2, PR_CONTENT_COUNT, PR_FOLDER_TYPE};
        ULONG cValues = 0;
        SPropValue *rgFolderProps= NULL;
    
        // Get an IMAPIProp Interface
        hr = pFolder->QueryInterface(IID_IMAPIProp, (LPVOID *) &pProp);
        CHR(hr);
        CPR(pProp);
    
        // Get the Folder PR_CONTENT_COUNT property
        hr = pProp->GetProps((LPSPropTagArray)rgTags, MAPI_UNICODE, &cValues, &rgFolderProps);
        CHR(hr);
        CBR(PR_CONTENT_COUNT == rgFolderProps[0].ulPropTag);            
        CBR(PR_FOLDER_TYPE == rgFolderProps[1].ulPropTag);            
        
    Error:
        // return the COUNT of messages in this folder
        return rgFolderProps[0].Value.ul;
    }
    
    
    ////////////////////////////////////////////////////////////
    // GetWastebasketForFolder
    //  Independently on the currently selected folder, it returns 
    //  the related Deleted Items folder
    HRESULT GetWastebasketForFolder(LPMAPIFOLDER pFolder, LPMAPIFOLDER* ppfldrWastebasket)
    {
        hr = E_FAIL;
        IMsgStore* pms = NULL;
        ULONG cItems;
        ULONG rgtagsFldr[] = { 1, PR_OWN_STORE_ENTRYID };
        ULONG rgtagsMsgStore[] = { 1, PR_IPM_WASTEBASKET_ENTRYID };
        LPSPropValue rgprops = NULL;
    
        // This method assumes that the CALLER already logged on to a MAPISession
        if (!m_pSession)
            CHR(E_FAIL);  
    
        // Now request the PR_OWN_STORE_ENTRYID on the folder.  This is the
        // ENTRYID of the message store that owns the folder object.
        hr = pFolder->GetProps((LPSPropTagArray)rgtagsFldr, MAPI_UNICODE, &cItems, &rgprops);
        CHR(hr);
    
        CBR(PR_OWN_STORE_ENTRYID == rgprops[0].ulPropTag);
    
        // Now open the message store object.
        hr = m_pSession->OpenEntry(rgprops[0].Value.bin.cb,
                (LPENTRYID)rgprops[0].Value.bin.lpb,
                NULL, 0, NULL, (LPUNKNOWN*)&pms);
        CHR(hr);
    
        MAPIFreeBuffer(rgprops);
        rgprops = NULL;
    
        // Get the ENTRYID of the wastebasket for the message store
        hr = pms->GetProps((LPSPropTagArray)rgtagsMsgStore, MAPI_UNICODE, &cItems, &rgprops);
        CHR(hr);
    
        // Now open the correct wastebasket and return it to the caller.
        CBR(PR_IPM_WASTEBASKET_ENTRYID == rgprops[0].ulPropTag);
    
        hr = m_pSession->OpenEntry(rgprops[0].Value.bin.cb,
                (LPENTRYID)rgprops[0].Value.bin.lpb,
                NULL, 0, NULL, (LPUNKNOWN*)ppfldrWastebasket);
        CHR(hr);
    
    Error:
        MAPIFreeBuffer(rgprops);
        RELEASE_OBJ(pms);
    
        return hr;
    }
    
    
    ////////////////////////////////////////////////////////////
    // CreateEntryList
    //  Needed because IMAPIFolder::DeleteMessages works on EntryLists
    HRESULT CreateEntryList(SRowSet *pRows, ENTRYLIST **ppList)
    {
        hr = E_FAIL;
        ENTRYLIST* plist = NULL;
        
        // How much space do we need to create this entry list?
        ULONG cbNeeded = sizeof(SBinaryArray) + (pRows->cRows * (sizeof(SBinary)));
    
        // Allocate one buffer to hold all the data for the list.
        hr = MAPIAllocateBuffer(cbNeeded, (LPVOID*)&plist);
        CHR(hr);
        CPR(plist);
    
        // Set the number of items in the EntryList 
        plist->cValues = pRows->cRows;
    
        // Set plist->lpbin to the place in the buffer where the array items will be
        // filled in
        BYTE* pb;
        pb = (BYTE*)plist;
        pb += sizeof(SBinaryArray);
        plist->lpbin = (SBinary*) pb;
    
        // Loop through the list setting the contents of the EntryList to the contents
        // of the incoming SRowSet
        for (int cItems = 0; cItems < (int)plist->cValues; cItems++)
        {
            plist->lpbin[cItems].cb = pRows->aRow[cItems].lpProps[0].Value.bin.cb;
            plist->lpbin[cItems].lpb = pRows->aRow[cItems].lpProps[0].Value.bin.lpb;
    
            // Track our memory usage 
            pb += sizeof(SBinary);
        }
    
        // Make sure that we didn't write off the end of our buffer...
        CBR(pb <= ((BYTE*)plist + cbNeeded));
    
    Error:
    
        if (FAILED(hr))
        {
            MAPIFreeBuffer(plist);
            plist = NULL;
        }
    
        *ppList = plist;
    
        return hr;
    }
Page 1 of 1 (3 items)