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

  • Mobile Development - 'Support Side Story'

    Footprint on the Mobile Developer Community

    • 0 Comments

    "Hello world!" It's been a really long time I don't post anything on this blog... for multiple reasons actually, the main one probably being that Windows CE\Mobile is such a mature technology that "quite" every problem has been found and addressed! Winking Another reason is that we provide support for Windows Phone 7 through our App Hub Forums community (http://forums.create.msdn.com), even if for complex scenarios Technical Support is always available through regular Service Requests (last couple of weeks I repeteadly hit my head on a WP7.5 application accessing WCF Services on a IIS accepting Basic Authentication only, over SSL...). Anyway looking at blog-statistics I can see that even if I've not been posting, this blog is still visited by many and that lets me hope it has been having a good impact to the Mobile Developer Community during these 3+ years. There are posts more read than others, and in some cases that surprises me - indeed, what I consider one of my best posts is not the most read one Sad Basics on Windows CE\Mobile: an alternative way to approach the Memory Monster. Actually, the most read ones are the following (why the one on GPS? Probably for some keywords...):

    1. GPS Programming Tips for Windows Mobile - Part 1
    2. Microsoft released a HotFix for NETCF v3.5 on Windows Mobile 6.1.4 onwards, to address basic functionalities of WebBrowser control
    3. NETCF: Memory leak... now what??
    4. Supporting Kiosk-Applications on Windows Mobile (Technically achievable vs. supported)
    5. Silverlight for Windows Embedded CE 6.0: First sample codes (stress and multiple instances of a control in separate DLL)
    6. MAPI on Windows Mobile 6: Programmatically retrieve mail BODY (sample code)
    7. Establishing GPRS Connection on Windows CE and Windows Mobile: Sample Codes
    8. Wireless Programming on Windows Mobile: supported or not supported?
    9. Remote Desktop Mobile (RDP Client) disconnects after 10 minutes of inactivity
    10. Basics on Windows CE\Mobile: an alternative way to approach the Memory Monster

    I truly hope these have helped Mobile Developers out there!

    Cheers,
    ~raffaele

    Good luck is another name for tenacity of purpose.

  • Mobile Development - 'Support Side Story'

    #WP7: “The importance of being…” SUPPORTED!

    • 0 Comments

    Another non-technical one, but really proud of… Winking From http://live.visitmix.com/Resources, under “Windows Phone 7 Series” category:

    wp7sup

     

    Some interesting resources:

     

    Cheers!
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Windows Mobile 6.5 Certifications

    • 0 Comments

    Certifications for Windows Mobile 6.5 are finishing their beta-phase… so I can now share my current medals: Happy

    MCTS(rgb)_524_523_1343_1344 

    Details about the exams on:

     

    HTH!
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Fix released for WM6.x devices with HTC and Samsung drivers about UI slowdown

    • 0 Comments

    A very quick post: if you’re using devices powered with HTC or Samsung drivers (such as also the Sony-Ericsson Xperia X1, for example) and noticed periodic slowdowns of the UI, then you may benefit of the following fix: Some GUI interactions no longer work correctly on Windows Mobile 6.1 devices and Windows Mobile 6.5 devices. This is not strictly required, so make sure to read the Symptoms section. And please remember to fill out the feedback at the bottom of the page! Happy

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Silverlight for Windows Embedded CE 6.0: First sample codes (stress and multiple instances of a control in separate DLL)

    • 7 Comments
    • Intro about SWE (Silverlight for Windows Embedded)
    • Sample code for simple starting stress-test
    • Sample code showing multiple instances of a control, implemented as separate DLL

    sweUIs 

    Part of my job as Support Engineer is to handle requests coming from ISVs developing applications for Windows CE: this is not always an easy task because contrarily to Windows Mobile, for which OEMs must stick to some specifications (otherwise their OS would fail the “Windows Mobile Logo Test Kit”), when it comes to Windows CE (or “Windows Embedded CE”, you know what I mean…) the OEMs have the complete freedom of developing whatever module or driver to be included in their OS, so in some cases the only option to understand a problem’s cause or to reach a specific goal programmatically, is to engage the OEM. I surely have already talked about this in the past… Happy

    So, requests from ISVs targeting Windows CE are rare compared to the ones for Windows Mobile, but after the release of Windows Embedded CE 6.0 R3 (“Cashmere”) Microsoft did on late September 2009, I’ve started handling some very interesting cases about Silverlight for Windows Embedded (“SWE” from now on). During past months many have discussed about this topic and how “particular” it seems to be programming for Silverlight in C++ instead that in .NET… so I’d like to share some info and sample code that I’ve personally been using so far.

    Before doing that, even just because this is the very first post about SWE I’m writing, let me digress a bit so that we can all be on the same page. Above all, if you have no idea what SWE is at all about, then I think that nothing else than watching it in action would help on describing it, and apart from some other videos from latest conferences we may benefit of a 15' minutes-long demo recorded by Mike Hall back in June 2009 together with Jeff MacDuff and Todd Segal, available thru his post Creating compelling user interfaces for embedded devices (even if Jeff and Todd paid attention not to disclose the “name” of the underlying technology, we now know it’s SWE! Hot).

    So, the idea is very simple: just divide the job done by designers and by developers and let everyone do what is good at. Designers have their tools to develop the UI (for example, Expression Blend) and developers have theirs to develop the Business Logic (for example, Visual Studio). Developers won’t even need to know how the UI looks like in fact (my sample code below are an example for that). More about this idea is available through the document Introduction to Application Development with Silverlight for Windows Embedded.

    The important thing is that those out-of-browser applications are possible through a powerful engine, i.e. the XAML Runtime, that takes care of glueing the UI to the code-behind by supporting a subset of Silverlight 2 XAML. The object model to programmaticaly interact with such runtime is native.

    Other introductive videos, as reported by Olivier Bloch in his post:

    Now: imagine you’re a Windows Mobile developer or in any case not a CE6.0 OEM – this means you don’t have the tools to even start investigating how programming for SWE looks like. If that’s the case, remember that precisely as it was for its predecessors, also Windows Embedded CE 6.0 R3 has its own evaluation edition that you can freely download and that is fully functional for 6 months. Note that this is no longer a separate IDE as it was for Windows CE 5.0: it’s an addin that integrates with VS2005 SP1 – follow all the steps here. The evaluation copy includes the software tool for interaction design, Expression Blend 2.0 and gives you the ability to create a so-called “Private SDK” related to the XAML Runtime-powered CE image (the required component is SYSGEN_XAML_RUNTIME) and containing a Device Emulator image targetable from VS2008 directly.

    Still about tools, Olivier Bloch explained here why there’s a lack of really tied integration between the UI and the code-behind: “[…] Well here is the answer: there has been some decisions made to make this technology available as is because our customers were in urgent need of this technology.”. I’ve not yet personally tested it, but this missing functionality seems to be provided by the (Italian! Winking) Microsoft MVP Walter Minute through his XAML2CPP tool.

    So, after this quick intro, what I’d like to share here is:

    1. A starting sample code useful for testing purposes
    2. A sample showing how to use multiple instances of a SWE Custom User Control library (DLL separate from the SWE Application)

    The sample is very easy to read and I used it as starting poing to run specific tests, for example about automating resources-stressing. The code is based on the documentation Create a Silverlight for Windows Embedded Application:

    //global vars
    IXRStackPanelPtr m_pStackPanel; 
    IXRApplicationPtr m_pApp;
     
     
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
    {
        int exitCode                    = 0;
        HRESULT hr                      = S_FALSE;
        IXRVisualHostPtr vhost          = NULL;
        IXRFrameworkElementPtr root     = NULL;
     
        //Initialize Xaml Runtime
        if (!XamlRuntimeInitialize())
            return -1;
     
        //Get XRAppInstance
        hr = GetXRApplicationInstance(&m_pApp);
        CHR(hr);
     
        XRWindowCreateParams wp;
        ZeroMemory(&wp, sizeof(XRWindowCreateParams));
        wp.Style       = WS_OVERLAPPED | WS_SYSMENU;
        wp.pTitle      = L"SWE Testing";
        wp.Left        = 0;
        wp.Top         = 0;
     
        //Retrieve resource associated to the PAGE.XAML file
        XRXamlSource xamlsrc;
        xamlsrc.SetResource(hInstance,TEXT("XAML"), MAKEINTRESOURCE(IDR_XAML_PAGE));
     
        //Create host
        hr = m_pApp->CreateHostFromXaml(&xamlsrc, &wp, &vhost);
        CHR(hr);
     
        //Get Root Element of the XAML
        hr = vhost->GetRootElement(&root);
        CHR(hr);
        
        //Get the Stack Panel
        hr = root->FindName(TEXT("LayoutRoot"), &m_pStackPanel); //Grid = StackPanel
        CHR(hr);
     
        //TODO: Add test code here
        //For automation purposes, create a Timer
        //Automation testing code in TimerProc function
        Timer = SetTimer(0, 0, 250, TimerProc);
     
        UINT exitcode;
        hr = vhost->StartDialog(&exitcode);
        CHR(hr);
     
    Exit:
        XamlRuntimeUninitialize();
     
        return (int)hr;
    }
     
     
    void CALLBACK TimerProc(HWND hwnd, UINT, UINT, DWORD dwTime)
    {
        //TODO: Automation code here
        //... 
     
        //Just to see the UI refreshed
        InvalidateRect(GetActiveWindow(), NULL, TRUE);
    }

    In another case the ISV was interested on maintaining main application (or main page) separate from the control DLLs, so that he could re-use the control separately by other applications. Also, he was interested on having multiple instances of the same control within the same application.

    What I could learn through this Service Request is that the XAML Runtime takes care of (quite) everything for you! Love Struck I left many comments in the sample code (for me at least!) but probably the most important bit here is that a control class is derived from XRCustomUserControlImpl<Class [,Interface]>, therefore it’s an abstract class hence can’t be instantiated and needs to have static functions only. This means that you can’t really talk about “visual controls hosted in an application” – what happens instead is that the XAML Runtime executes and renders different bits of XAML code. So, look at “controls” as “chunks of XAML” (this is not really object-oriented…). This also means that before using the control, you need to initialize it becasue before calling IXRApplication::CreateHostFromXaml from the application EXE code (note closely where CreateHostFromXaml is called in WinMain), the XAML Runtime must know all the associations between Classes and XAML Namespaces.

    In other words, the “initialization” of a control should be done in2 steps:

    1. Set up the global var for the application instance so that Application’s EXE and Control’s DLL share the same and secondly invoke .Register on the control so that the XAML Runtime knows how to associate XAML Namespace and control class [in the sample, this is MyControl::MyControlPreInitialize]
    2. Initialization of the control, for example in terms of delegates associated to its events [in the sample, this is MyControl::MyControlInitialize]
    //CONTROL CLASS DECLARATION 
    class __declspec(uuid("{1F8F37C1-CA69-4894-A6AA-8667F9387E2E}")) //Change GUID!
    MyControl : public XRCustomUserControlImpl<MyControl>
    {
        public:
            static HRESULT GetXamlSource(XRXamlSource* pXamlSource);
            static HRESULT Register(HINSTANCE hInstance);
     
            //Added these functions so that they can be exported through a .LIB (created by adding a .DEF to the project)
            //Any exe application using this control will need to link the .LIB
            static HRESULT MyControlInitialize(IXRFrameworkElementPtr root, LPCTSTR pszControlClassName);
            static HRESULT MyControlPreInitialize(HINSTANCE hInstance);
    };
     
     
    //*************************************************************************************
     
    //Standard implementation of XRCustomUserControlImpl<>.GetXamlSource
    HRESULT MyControl::GetXamlSource(XRXamlSource* pXamlSource)
    {
        //Retrieve the resource (the XAML file) from the DLL Module
        pXamlSource->SetResource((HINSTANCE)GetModuleHandle(lpThisModule), TEXT("XAML"), MAKEINTRESOURCE(IDR_XAML_MYCTRL));
        return S_OK;
    }
     
    //Standard implementation of XRCustomUserControlImpl<>.Register
    HRESULT MyControl::Register(HINSTANCE hInstance)
    {
        HRESULT hr = S_FALSE;
        hr = XRCustomUserControlImpl::Register(__uuidof(MyControl), L"MyControl", L"clr-namespace:SLForEmbedded");
        CHR(hr);
     
    Exit:
        return hr;
    }
     
     
    // Setup the global var for the application instance and invoke .Register on the control.
    HRESULT MyControl::MyControlPreInitialize(HINSTANCE hInstance)
    {
        s_appInstance = hInstance;
     
        HRESULT hr = S_FALSE;
     
        /*LEFT FOR COMMENTING PURPOSES, IN CASE THE .XAML WILL CONTAIN IMAGE or FONT <Application.Resources>
        ////Initialize Xaml Runtime --> If needed, then add MyControl::UnInitialize
        //if (!XamlRuntimeInitialize())
        //    return -1;
        ////1. Get XRAppInstance 
        //IXRApplicationPtr app;
        //hr = GetXRApplicationInstance(&app);
        //CHR(hr);
        //2. Add control resource 
        /*This is needed when there are resources within the XAML of type XAML_RESOURCE
        //hr = app->AddResourceModule(hInstance);
        //CHR(hr);
        
        // ONLY IF <Application.Resources> IN THE APPLICATION's XAML:
        //The following code shows an example of XAML markup that must be parsed 
        //into a resource dictionary:
        //<Application.Resources>
        //    <SolidColorBrush x:Key="Text_DefaultColor" Color="#FF292F33"/>
        //</Application.Resources>
        //
        //IXRResourceDictionary* pResourceDictionary;
        //XRXamlSource Source;
        //Source.SetFile(MAKEINTRESOURCE(IDR_XAML_MYCONTROL));
        //hr = app->LoadResourceDictionary(&Source, &pResourceDictionary);
        //CHR(hr);
        //hr = app->GetResourceDictionary(&pResourceDictionary);
        //CHR(hr);
        */
     
     
        //3. Register control's Namespace
        hr = MyControl::Register(hInstance);
        CHR(hr);
     
    Exit:
        return hr;
    }
     
     
    //Initialize the control and event handlers
    HRESULT MyControl::MyControlInitialize(IXRFrameworkElementPtr root, LPCTSTR pszControlClassName)
    {
        HRESULT hr = S_FALSE;
     
        IXRControlPtr m_myUserControl;
        BtnEventHandler m_btnUserControlHandler;
        IXRDelegate<XRMouseButtonEventArgs>* m_btnUserControlDelegate;
        IXRButtonBasePtr m_btnUserControl;
     
        //hr = root->FindName(TEXT("MyUserControl"), &m_myUserControl);
        hr = root->FindName(pszControlClassName, &m_myUserControl);
        CHR(hr);
     
        //m_btnUserControlHandler.m_StoryboardPaused = FALSE;
        //BtnEventHandler::m_StoryboardPaused = FALSE;
        m_StoryboardPaused = FALSE;
     
        hr = m_myUserControl->FindName(TEXT("btnUserControl"), &m_btnUserControl);
        CHR(hr);
     
        hr = CreateDelegate(&m_btnUserControlHandler, &BtnEventHandler::btnUserControlOnClick,&m_btnUserControlDelegate);
        CHR(hr);
     
        hr = m_btnUserControl->AddClickEventHandler(m_btnUserControlDelegate);
        CHR(hr);
     
    Exit:
        return hr;
    }
     
     
    //TODO: Add event handler code
    class BtnEventHandler
    {
    public:
        HRESULT btnUserControlOnClick(IXRDependencyObject* pSender, XRMouseButtonEventArgs* pArgs)
        {
            MessageBox(GetActiveWindow(), L"User Control button clicked.", L"Event Handler", MB_OK | MB_ICONINFORMATION);
            return S_OK;
        }
    };

    For the application:

    //Application's MAIN Entry Pointint WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
    {
        int exitCode                    = 0;
        HRESULT hr                      = S_FALSE;
        IXRApplicationPtr app           = NULL;
     
        //(let's keep it simple here...)This is not needed when the XAML has not <Application.Resources>
        //IXRResourceManager* pResourceMgr = NULL;
        IXRVisualHostPtr vhost          = NULL;
        IXRFrameworkElementPtr root     = NULL;
        IXRButtonBasePtr btn            = NULL;
     
        //showing a page with its own button control
        //<Button Margin="200,200,200,200" Content="Button" x:Name="MyButton"/>
        IXRDelegate<XRMouseButtonEventArgs>* clickdelegate = NULL;
        PageBtnEventHandler btnHandler;
     
     
        //Initialize Xaml Runtime
        if (!XamlRuntimeInitialize())
            return -1;
     
        //Get XRAppInstance
        hr = GetXRApplicationInstance(&app);
        CHR(hr);
     
        ////An application should register an IXRResourceManager object for Silverlight
        ////before the application executes any functionality related to loading resources.
        ////<Image Source=foo.jpg" />
        ////<TextBlock x:Name="GridTextBlock10" Grid.Row="1" Grid.Column="0" FontSize="14" FontFamily="castelar.ttf#Castellar" FontStyle='Italic'  Text='Hello'/>
        // -->
        ////IXRResourceManager* pResourceMgr;
        //hr = app->RegisterResourceManager(pResourceMgr);
        //CHR(hr);
     
        //This is needed when there are resources within the XAML of type XAML_RESOURCE
        //hr = app->AddResourceModule(hInstance);
        //CHR(hr);
     
        XRWindowCreateParams wp;
        ZeroMemory(&wp, sizeof(XRWindowCreateParams));
        wp.Style       = WS_OVERLAPPED | WS_SYSMENU;
        wp.pTitle      = L"SWE Testing";
        wp.Left        = 0;
        wp.Top         = 0;
     
        //Retrieve resource associated to the PAGE.XAML file
        XRXamlSource xamlsrc;
        xamlsrc.SetResource(hInstance,TEXT("XAML"), MAKEINTRESOURCE(IDR_XAML_PAGE));
     
        //Register control with application instance
        //This method added so that the control code can do init and call Register on the control
        //so that the control's class is tied to the XAML Runtime's namespace
        //and this is done in control's DLL code
        hr = MyControl::MyControlPreInitialize(hInstance);
        CHR(hr);
     
        //Create host
        hr = app->CreateHostFromXaml(&xamlsrc, &wp, &vhost);
        CHR(hr);
     
        //Get Root Element of the XAML
        hr = vhost->GetRootElement(&root);
        CHR(hr);
     
        //MyButton is a control directly defined and used by the application
        //defined in PAGE.XAML, look at x.Name of the <Button> element
        //left here just to show a control example inside the same application
        hr = root->FindName(TEXT("MyButton"), &btn);
        CHR(hr);
        hr = CreateDelegate(&btnHandler,&PageBtnEventHandler::OnClick,&clickdelegate);
        CHR(hr);
        hr = btn->AddClickEventHandler(clickdelegate);
        CHR(hr);
     
     
        //MyControl is the control exported by SilverlightControl DLL project
        //deriving from XRCustomUserControlImpl<MyControl>, it's an abstract class
        //i.e. can't be instantiated and needs to have STATIC functions only
        hr = MyControl::MyControlInitialize(root, TEXT("MyUserControl"));
        CHR(hr);
     
        hr = MyControl::MyControlInitialize(root, TEXT("MyUserControl2"));
        CHR(hr);
     
     
        UINT exitcode;
        hr = vhost->StartDialog(&exitcode);
        CHR(hr);
     
    Exit:
        RELEASE_OBJ(clickdelegate);
        XamlRuntimeUninitialize();
     
        return (int)hr;
    }
     
     
    //Handler of a button included directly in the EXE
    class PageBtnEventHandler
    {
        public:
            HRESULT OnClick(IXRDependencyObject* source,XRMouseButtonEventArgs* args)
            {
                MessageBox(NULL,TEXT("Click!"),TEXT("Button"),MB_OK);
                return S_OK;
            }
    };

    Note how straightforward the XAML for the page (main application) is – quite intuitively, it just contains one line for each control:

    <UserControl
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="SLForEmbedded.Page"
        xmlns:SLForEmbedded="clr-namespace:SLForEmbedded"
        Width="640" Height="480">
     
      <Grid x:Name="LayoutRoot" Background="White">
        <Button Margin="200,200,200,200" Content="Button" x:Name="MyButton"/>
        <SLForEmbedded:MyControl HorizontalAlignment="Left" VerticalAlignment="Top" x:Name="MyUserControl"/>
        <SLForEmbedded:MyControl HorizontalAlignment="Right" VerticalAlignment="Top" x:Name="MyUserControl2"/>
      </Grid>
    </UserControl>

    The XAML for the control may be useless for this post, however for the sake of completeness:

    <UserControl
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        x:Class="SLForEmbedded.MyControl">
        <Grid x:Name="LayoutRoot">
            <Button Height="25" Width="125" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10,10,0,0" Content="Click User Control" x:Name="btnUserControl"/>
        </Grid>
    </UserControl>

    As usual, macros were:

    #pragma once
     
    #ifndef MACROS
    #define MACROS
     
    #define _ExitLabel Exit
    #define _ExitCode exitCode
     
    #define CHR(hResult) \
        if(S_OK != hResult) { hr = (hResult); goto _ExitLabel;} 
     
    #define RELEASE_OBJ(s)  \
        if (NULL != s)      \
        {                   \
            s->Release();   \
            s = NULL;       \
        }
    #endif

     

     

     

     

    Hope this may help out developers learning SWE (like myself… Winking)

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Windows Mobile: programmatically intercepting phone calls

    • 3 Comments
    • Supported?? Expectations about cprog.exe
    • How Communicator Mobile works with “Work Phone calls
    • Sample code to “extend” the State&Notification Broker for IGNORED phone calls

    I recently worked with some ISVs asking support about this, then also answered to a MSDN Forum thread about the same, so I’d say it may be worth to mention it here as well for Community reference. This happens to be one of those common questions that fall under the umbrella of the “not supported as OEM-dependent” or “not supported as it’s not meant to work that way”. I’ve talked about this some times on my blog, for example regarding Wireless programming, Drivers, Kiosk applications, Hooks

    The issue is about “intercepting” incoming phone calls and prevent the native UI dialog provided by OS application cprog.exe to take care of it: there are some OEMs that customized the user interface for this phone application, so ISVs may think this could be a common task to achieve? One could say: “Wouldn’t be sufficient to kill cprog.exe?”. Or, in any case, is there a way for ISV Application Developers to programmatically control incoming and outgoing calls *before* or *instead* the Operating System itself?

    The answer is no, but you may possibly find “creative” ways to achieve the same visual effect your application’s final users may look for. The main point is that interfering with the OS Native Phone application (cprog.exe) is not supported for ISV Application Developers, contrarily to Device Manufacturers (OEMs) who actually build their own OS based on the Windows Mobile platform and therefore can develop and include whatever they want on their Operating Systems, as long as they fulfill the “Windows Mobile Logo Test Kit” (so that their OS is certified as “Windows Mobile”).

    As an example, a thing that the Windows Mobile platform doesn’t test or even consider is killing cprog.exe, which is the process underlying the Phone UI Application. Once the device is booted, cprog.exe executes and even when there is a Out-Of-Memory condition (so that the Shell starts asking every application to shut down, accordingly to this old post from WinMo Dev Team), cprog.exe is not terminated. So the current OS design does not expect it to be killed forcefully.

    In some cases, to meet this requirement you would need to cooperate with the Device OEM\OEMs so that he\they can sponsor you as their “Windows Mobile In-ROM ISV”. By doing so, you would have access to all the tools a Device Manufacturer has, including the knowledge on the RIL Driver (Radio Interface Layer), developed by the OEM, that is responsible to handle every radio-related communication of the device (phone call, GPRS data session, …). So, to intercept phone calls before cprog.exe does it, you should do it at RIL Driver level, and Microsoft has no idea about how the OEM implemented it.

    In other cases, you can let cprog.exe do its work, and play with Windows APIs to hide its window and take control of the phone call via TAPI (Telephony API): that is the “supported” way.

     

    You may ask: then how does Microsoft’s Communicator Mobile 2007 R2 implement "Work Phone calls”? How can it intercept phone calls, if this is OEM-dependent? Well, actually it doesn’t. CoMo is not working with the RIL to intercept phone-voice calls. Documentation is available through the following Microsoft Office Communications Server 2007 R2 – Technical Reference (section “Outside Voice Control”):

    […] For inbound calls, Office Communications Server 2007 R2 sends a SIP Invite to all registered SIP endpoints of the user including the user’s Communicator Mobile (2007 R2 release) client running on the phone, over the data channel.   Office Communications Server 2007 R2 subsequently initiates an outbound PSTN/mobile network call through Office Communications Server 2007 R2 Mediation Server to the user’s mobile phone number.

    So as you can see, when dealing with “Communicator phone-calls” we’re not precisely working with “Voice-Phone calls”: it’s about SIP requests sent over the Data Channel, not the Voice-one.

     

    Among the “supported” ways to deal with phone calls, developers can rely on the State&Notification Broker states like PhoneCallTalking and PhoneIncomingCall: a state that may be missing is about the last call being ignored. Here it is a managed sample code showing a possible way to achieve this (to be tested… as usual! Smile):

     

    public Form1()
    {
        InitializeComponent();
        _PhoneIncomingCall.Changed += new ChangeEventHandler(_PhoneIncomingCall_Changed);
    }
     
    bool bTalking = false;
    bool bIncoming = false;
    bool bIgnoring = false;
     
    SystemState _PhoneCallTalking = new SystemState(SystemProperty.PhoneCallTalking);
    SystemState _PhoneIncomingCall = new SystemState(SystemProperty.PhoneIncomingCall);
     
     
    void _PhoneCallTalking_Changed(object sender, ChangeEventArgs args)
    {
        bTalking = (0 != (int)args.NewValue);
     
        //_PhoneIncomingCall.Changed += new ChangeEventHandler(_PhoneIncomingCall_Changed);
        _PhoneCallTalking.Changed -= _PhoneCallTalking_Changed;
    }
     
    void _PhoneIncomingCall_Changed(object sender, ChangeEventArgs args)
    {
        bIncoming = (0 != (int)args.NewValue);
        if (bIncoming)
        {
            //_PhoneIncomingCall.Changed -= _PhoneIncomingCall_Changed;
            _PhoneCallTalking.Changed += new ChangeEventHandler(_PhoneCallTalking_Changed);
        }
        else
        {
            //answered: bTalking = true
            //ignored: bTalking = false
            bIgnoring = !bTalking;
            MessageBox.Show(string.Format("IgnoredCall: {0}", bIgnoring.ToString()));
     
            //reset vars
            bIncoming = false;
            bIgnoring = false;
            bTalking = false;
        }
    }

     

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Windows Mobile 6 SDK TEST-ONLY Certificates: new CAB and XML Provisioning

    • 0 Comments

    SDK Test Certificates expired last December 31 and the Product Group provided a new set of Test Certificates (and accordingly to mainly this MSDN Forum thread many ISVs had found alternatives in the meantime). The aim of this post if to provide an addendum to that notification, specifically related to the following sentence: “[…] If you’re a tester, you can follow these instructions to create a provisioning file for a device, so that you don’t need Visual Studio to push certificates onto test platforms.”. In other words, the certificates you can download from here don’t contain a Certs.XML and a Certs.CAB files, contrarily to a standard installation of Windows Mobile 6 SDKs. I have simply created them by using the known technique and made them available through this post (click here to download the zip), hoping they may help “testers” saving time in case “developers” use the new certs. Note that they can also cohexist with expired certificates on a test device\emulator.

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Get help now! A Vademecum about requesting Technical Support from Microsoft

    • 1 Comments

      Get Help Now
      Contact a Support Professional by E-mail, Online, or Phone

     

    I often receive questions related to how to open Service Requests at Microsoft Customer Care & Technical Support, the last of which was in this MSDN Forum thread regarding the NETCF v3.5’s FIX about the WebBrowser control (which I’ve discussed about here). So I’d say it could be a good investment to describe in detail how you can ask to be contacted by a Support Professional: I’ve already done it once some time ago (wow, that was nearly 2 years ago!), but the following is a kind of updated process..

    Well, above all: are you using Visual Studio 2008 and need support? As a starting point, apart from MSDN Forums, have you ever clicked on Visual Studio’s menu Help\Technical Support? Open-mouthed

    Untitled 

    That leads to a documentation page containing details and .doc files relevant to Developers\IT Professionals\Value Add Partners, Resellers, Consultants\Microsoft Certified Partners\OEMs. The same page contains (800)-* phone numbers to open a Service Request in the US:

    Untitled2 

    And to know international phone numbers you can use to open Service Requests at Microsoft Customer Care & Technical Support over the phone, you can start from the International Support page.

     

    However, opening the Service Request over the phone is not the only option: you can do it also online. For example, let’s assume you have a copy of Visual Studio 2008 Professional Edition and you need help regarding a Smart Device application, which falls precisely under my area of specialty... Well, you can start from the Visual Studio 2008 Solution Center, which also contains useful info to address the issue on your own, and apart from that contains a link to:

     

      Get Help Now
      Contact a Support Professional by E-mail, Online, or Phone

     

    That links to the same page as Assisted Support Options, which lets you begin a step-by-step procedure to gather details of your eligibility of support, along with details about the product and the problem. Here it comes.. remember that developers programming Smart Device application don’t ask for support about the products “Windows Mobile” or “Windows CE”: they’re asking for support about the product “Visual Studio”!

    1- Quick product finder:

    Untitled3

     

    2- Select a product:

    Untitled4

     

    3- Select a support topic:

    Untitled5

     

    4- Provide product ID:

    Untitled6

     

    Note the words “NO-CHARGE SUPPORT”: they’re what I meant “what you’ve already paid” in my post get what you paid for!! The Product ID for Developer Tools can be retrived by:

    1. Start the program.
    2. On the menu bar, click Help, and then click About Microsoft product name.
    3. The Product ID (PID) is under This product is licensed to.

    Or you can let Microsoft locate the Product ID by allowing the control to run through Internet Explorer.

    5- At this point, if you have an “ACCESS ID” related to a Technical Support contract (MSDN Subscription, Partner, Premier, Professional 5-Pack, etc), here it’s the right time to use it. If you don’t have it, then you can click on “Skip this” button and reach a page that would allow you to pay to open a so-called “Pay Per Incident”.

    6- Eventually you’ll reach a page containing fields you can fill in order to describe the issue, to set the severity and to submit the request.

     

    Hope this will help ISVs out there on correctly opening Service Requests!

     

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Basics on Windows CE\Mobile: an alternative way to approach the Memory Monster

    • 4 Comments
    • Introduction on Windows CE\Mobile 
    • Virtual Memory on Windows CE 5.0\Mobile (a visual approach) 
    • Troubleshooting Memory Leaks for NETCF applications

    Lately I’ve been involved on starting up my team blog: if you read Italian and want to listen about stories from Support Engineers working on various technologies, you can’t avoid signing up to the feed:

    itasupport

    Now guess what’s been the topic of the posts I’ve been writing there… Open-mouthed yes! Memory Management on Windows CE\Mobile and NETCF!! (so far the intro and then part 0, 1, 2 – don’t ask me why I made it 0-based, even after an intro… I really don’t remember) And since I’ve been adopting an approach that is not usually the one used to explain how things work, I honestly think I can reuse the same one and translate it here… probably I’m going to repeat some concepts I’ve already blogged about, however it may be worth in order to have one single document, this post, as hopefully a possible quite-ultimate way to describe how memory is handled… is the bar too high??

     

    INTRODUCTION ABOUT WINDOWS CE\MOBILE

    So, let’s start: Windows CE and Windows Mobile are not the same thing. After working on this for a while it can be obviuos, but if you’re at your first experience with these so-called “Smart Devices” then it may not be so. We must be clear about the terminology, specifically about terms like “platform”, “operating system”, “Platform Builder”, “Adaptation Kit”, “OEM”, “ODM”, etc.. In reality the true name of “Windows CE” would nowadays be “Windows Embedded CE”, however I don’t want to mess with a product that was once known as “Windows XP Embedded” and which nowadays is differentiated in the following products:

    • “Windows Embedded Stardard”
    • “Windows Embedded Enterprise”
    • “Windows Embedded POSReady”
    • “Windows Embedded NavReady”
    • “Windows Embedded Server”

    So, when I’ll write “Windows CE” here I’ll mean the historical name of “Windows Embedded CE”: let’s forget the other “Windows Embedded X” products (out of my scope) and let’s concentrate on Windows CE\Mobile.

    Windows CE is a platform for OEMs (Original Equipment Manufacturer). This means that we provide to the manufacturer an Integrated Development Environment very similar to Visual Studio (indeed, Windows CE 6.0 is integrated with Visual Studio), but with the aim of developing Operating Systems (instead of applications), based on the platform provided by Microsoft. The tool is called "Platform Builder for Windows CE”, and up to version 5.0 was a separate tool from Visual Studio.

    Windows CE is a modular platform. This means that the OEM is totally free to include only the modules, drivers and applications of his interest. Microsoft provides about 90% of the source code of the Windows CE platform, as well as code examples for drivers and various recommendations (which the OEM might or might not follow). For example, if the device is not equipped with an audio output, then the OEM won’t add a sound driver. If it doesn’t have a display, then the OEM will not develop nor insert a video driver. And so on for the network connectivity, or a barcode-scanner, a camera, and so on. On a Windows CE-based device, the OEM can include whatever he wants. That's why, from the point of view of technical support to application-developers, sometimes we can’t help a programmer who is targeting a specific device whose operating system is based on Windows CE: furthermore, the OEM can decide whether to offer application-developers the opportunity to programmatically interact with special functions of the device through a so-called "Private SDK” (which may also contain a emulator image, for example).

    An important detail: differently from Windows Embedded OSs (Standard \ Enterprise \ POSReady \ NavReady \ Server), for Operating Systems based on Windows CE the OEMs actually *COMPILE* the source code of the platform (apart from about 10% provided by Microsoft and corresponding to the core kernel and other features).

    Now: Windows Mobile is a particular customization of Windows CE, but in this case the OEM needs to create an Operating System that meets a set of requirements, called "Windows Mobile Logo Test Kit". The tool used by Windows Mobile-OEM is called "Adaptation Kit for Windows Mobile", a special edition of "Platform Builder" and allows to adapt the "Windows Mobile”-Platform to the exact hardware that the OEM has built or that he requested to an ODM (“Original Device Manufacturer”). In the Windows Mobile’s scenario we can’t forget Mobile Operators also, which often "brand" a device requiring the OEM to include specific applications and usually configure the connectivity of the mobile network (GPRS, UMTS, WAP, etc.).. WARNING: nothing prohibits a WinMo-OEM to include special features such as a barcode-scanner or an RFID chip or anything else... the important thing is that the minimal set is the same. Moreover, also WinMo-OEM can provide a “Private SDK” to programmatically expose specific functionality related to their operating system (see for example Samsung SDK containing private APIs for the accelerometer and other features, which are documented and supported by Samsung itself).

    Finally, one last thing before starting talking about memory: Windows Mobile 5.0, 6, 6.1 and 6.5 are all platforms based on Windows CE 5.0. So, they all share the same Virtual Memory management mechanisms, except in some details for the latter (mainly with some benefits for application-developers).

     

    VIRTUAL MEMORY ON WINDOWS CE\MOBILE

    So now we can start talking about how memory is managed on Operating Systems based on Windows CE 5.0. And I’m being specific on Windows Embedded CE *5.0* because on 6.0 memory management is (finally!) totally changed and there are no more the limitations we’re going to discuss in the remainder. Incidentally, this is part of the same limitations described for Windows CE 4.2 by Doug Boling’s Windows CE .NET Advanced Memory Management, although the article is dated 2002! Fortunately, some improvements have been introduced from Windows Mobile 6 and especially in 6.1 (and therefore also in 6.5), whereby not only the applications have more virtual memory available, but also the entire operating system is more stable as a whole.

    I don’t want to repeat here what can be found in documentation and on various blogs: in contrast, I’d like to actually show the theory, because only by looking at data like the following you can realize what a good programmer a Developer for Windows Mobile has to be! Hot The following is the output of a tool developed by Symbol (later acquired by Motorola), which allowed the manufacturer to understand how the device.exe process was responsible (or not) for various problems related to memory. Why? Because

    • device.exe is the process that loads the DLL for the driver in its own address space;
    • if it hasn’t yet been loaded by other processes, a DLL will occupy a portion of the virtual memory below the "DLL Load Point";
    • each DLL may have dependencies, also to be loaded;
    • each DLL can create its own heap to store data.

    The result was something like the following (I obfuscated possibly sensitive data of the software-house I worked with during the Service Request):

    memory

    So, above all, what did Symbol\Motorola mean by “Code” (blue) and “Data” (green)?

    • "Code": these are the *RAM* DLLs loaded or mapped into a process-slot. They start from the top of the 32MB slot and goes down. If several processes use the same DLL, the second one maps it to the same address where the first one had loaded it.
    • "Data" is the executable’s compiled code + Heap(s) + Stack. It starts from the bottom and grows.

    Finally, the red vertical line represents the "DLL Load Point", i.e. the address where a DLL is loaded in case it hadn’t yet by any other process.

    That is the situation of only the process slots, not the whole virtual memory - in particular the contents of the Large Memory Area is not shown:

    clip_image003

    Why did I specify *RAM* DLLs? Because those placed by the OEM in the ROM (= firmware) are executed directly there, without the need of the process loading their "compiled" code into its Address Space (they’re XIP DLL, i.e. "Executed in Place" – in Slot 1).

    That picture also shows that the green part (code + heap + stack) may exceed the DLL Load Point. Indeed, the problems related to lack of available virtual memory is usually of 2 types:

    1. a process tries to load a DLL that hadn’t been loaded by any other before, trying to move the DLL Load Point in an area already occupied by code + heap + stack
    2. a process tries to allocate memory (code + heap + stack) but the process slot has got saturated even on sections left free among loaded DLLs

    That's also because in general one of the advices to avoid memory problems had always been to load all DLLs used by the application itself at application startup, through an explicit call to LoadLibrary(). Another visual example is the following:

    memory2

    We’ll later discuss in detail the particularities of NETCF, but it's worth at this point noting a detail: apart from the actual CLR DLLs (mscoree*.dll, netcfagl*.dll), every other assembly doesn’t waste address space in the Process slot, but is loaded into the Large Memory Area. Even more, if you are using the version of the runtime included in the ROM by the OEM, also the runtime DLLs do not affect the process’ virtual memory space. Obviously it is different when the application P/Invoke native DLLs: these will be loaded in the process slot.

    Moreover, if you look at the picture showing all the alive processes, you’ll notice that in the upper bound of all the slots there’s a portion of "blue" virtual memory, which is the same for all the processes. This is the memory blocked by the Operating System whose size is equal to the sum of the binaries (the simple .EXE files) active at any given moment. So large monolithic EXEs (large for example because containing “many” resources) are not recommended at all on Windows CE 5.0! And in general supporting NETCF developers I can say I’ve seen many “big” applications... that is not a good practice for this reason!

    Through those pictures it is also easy to understand why the whole system stability is a function of all active processes, and in particular it is easy to see that very often DEVICE.EXE can be a source of headaches! Think of those Windows Mobile-based devices that have the radio stack (i.e. the phone), Bluetooth, WiFi, Camera, barcode-scanner, etc. .. each of these drivers is a DLL that device.exe has to load (blue line), and each can also create its own stack and heap (green line). Some OEMs allowed developers to programmatically disable some drivers (to reduce pressure done by device.exe), but obviously we can not take for granted for example that a user manually restarts that feature (or this is done by another application...).

    So, what has been done to fight device.exe’s power? In many cases, the driver-DLLs were loaded by services.exe, which is the host process for Service-DLLs on Windows CE. But very often it was not enough... What Windows Mobile 6.1 introduced is that native DLLs with size > 64KB are typically loaded into the so-called slots 60 and 61, which are part of the Large Memory Area. Another improvement in Windows Mobile 6.1 was to dedicate another slot (slot 59) to the driver stack (part of the Green Line to device.exe). Of course, this means that the memory-mapped files have now less space available (and I have recently handled a request for exactly this purpose, coming by a software company that was developing a GPS navigation software that could not load some map files in WinMo6.1), but in general the whole operating system has gained a stability that hadn’t before...

    To conclude, the tool I mentioned was developed by Symbol and I don’t think it’s publicly available. But a similar tool has recently been published on CodePlex (source code included!) through the article Visualizing the Windows Mobile Virtual Memory Monster. The term "Virtual Memory Monster" was invented years ago by Reed Robison… (part 1 e part 2). I've already been using it in a couple of requests and highly recommend it!

    TROUBLESHOOTING MEMORY LEAKS FOR NETCF APPLICATIONS

    Instead of explaining how things work in theory, which is a task I leave to more authoritative sources like the documentation itself and various blogs – one for all that of Abhinaba Basu, who is precisely the GC Guru inside the NETCF Dev Team (Back to basic: Series on dynamic memory management), I’d like to follow the troubleshooting flow I run through when a new Service Request arrives, about for example the following issues:

    • OutOfMemoryException
    • SqlCeException: “Not enough memory to complete this operation”
    • The application can’t load a DLL
    • Other misbehaviors that may lead thinking about a problem with the memory

    Firstly, we must determine whether the problem is specific to an OEM. The best approach, when possible, is to verify if the error occurs even on emulators contained in the various Windows Mobile SDK. If not, the help that Microsoft Technical Support can provide is limited, as it is possible that the error is due to a customization of the Windows Mobile platform by the OEM. In this case, it may be helpful to know about what I wrote about device.exe above.

    Another initial step, in the case of applications NETCF v2 SP2, is to check if just running the application on NETCF v3.5 gives any improvement. There is no need to recompile the application with the Visual Studio 2008 - just like for .NET Desktop applications, add a configuration XML file in the same folder that contains the file TheApplication.exe, named TheApplication.exe.config and whose content is simply (I mentioned here):

    <configuration>
      <startup>
        <supportedRuntime version="v3.5.*"/>
      </startup>
    </configuration>

    So, after having considered possible “trivial” causes, you can proceed to the analysis... Historically NETCF developers haven’t had an easy time in troubleshooting due to lack of appropriate tools – unlike Desktop cousins! – but over the years Microsoft has released tools that have gradually evolved over the current Power Toys for .NET Compact Framework 3.5. Apart from these you must know the(freeware!) EQATEC’s ones (Tracer and Profiler) and recently a tool on CodeProject that I mentioned earlier, that displays the status of virtual memory (VirtualMemory, with source code).

    Regarding power-toys, when you are dealing with a problem around memory, you have 2 of them that are of great help: the "CLR Profiler" and "Remote Performance Monitor (RPM)”. The first one is useful in visually making problems with objects’ allocation almost immediate and allows you to notice the problem in a visual way. Info on how using it are available through The CLR Profiler for the .Net Compact Framework Series Index. The second one provides, both in real time and through an analysis a posteriori, counters about the usage of MANAGED memory; also, through the "GC Heap Viewer” it allows not only to study the exact content of the managed heap, but also allows you to compare contents of the heap in different moments, in order to bring out a possible unexpected growth of a certain type of objects. Some images are available on Finding Managed Memory leaks using the .Net CF Remote Performance Monitor, which is useful also to get an idea about which counters are available, while a list and related explanations are provided on Monitoring Application Performance on the .NET Compact Framework - Table of Contents and Index. What I'd like to do here is not to repeat the same explanations, already detailed in the links above, but share some practical experience...

    For example, in the vast majority of cases I have handled about memory leaks, the problem was due to Forms (or Controls) that unexpectedly were NOT removed by the Garbage Collector. The instances of the Form class of the application are therefore the first thing to check through the Remote Performance Monitor and GC Heap Viewer. For this reason, where appropriate (e.g. if the total form are "not so many"), to avoid memory problems with NETCF applications it may be useful to adopt the so-called "Singleton Pattern": this way a single managed instance of a given form will exist throughout the application life cycle.

    So, supposing to be in the following situation: I used the Remote Performance Monitor and saved different .GCLOG files during normal use of the application, and thanks to the GC Heap Viewer I noticed that an unexpected number of forms stays in memory, and also that this increases during the life of the application, although there have been a certain number of Garbage Collections. Why the memory of a Form is not cleaned up by the garbage collector? Thanks to the GC Heap Viewer you can know exactly who maintains a reference to what, in the "Root View" on the right pane. Obviously knowing application’s architecture will help in identifying unexpected roots.

    A special consideration must be done for MODAL Forms in .NET (the dialogs, those that on Windows Mobile have the close button "Ok" instead of "X", and which permits a developer to prevent the user to return to the previous form). In many cases I have handled, the problem was simply due to the fact that the code was not invoking Close() (or. Dispose ()) after .ShowDialog():

    Form2 f2 = new Form2();
    f2.ShowDialog();
    f2.Close();

    Why should it matter? Because often (not always, for example, not when you expect a DialogResult) on Windows Mobile the user clicks on 'Ok' in the top right "close" the dialog. Also on Desktop, when a dialog is "closed" in this way the window is not closed, but "hidden"! And it could happen that the code creates a new instance of the form, without removing the old one in memory. It’s documented in “Form..::.ShowDialog Method” (the doc talks about “X” but of course for Windows Mobile refers to the 'Ok' referred to above):

    […] When a form is displayed as a modal dialog box, clicking the Close button (the button with an X at the upper-right corner of the form) causes the form to be hidden and the DialogResult property to be set to DialogResult.Cancel. Unlike modeless forms, the Close method is not called by the .NET Framework when the user clicks the close form button of a dialog box or sets the value of the DialogResult property. Instead the form is hidden and can be shown again without creating a new instance of the dialog box. Because a form displayed as a dialog box is not closed, you must call the Dispose method of the form when the form is no longer needed by your application.

    Anyway, we assumed so far that the memory leak is MANAGED, but in reality it may be that leak is with the NATIVE resources that are used by a .NET instance, which have not been successfully released by implementing the so-called "IDisposable Pattern. And around this there are some peculiarities in NETCF, that Desktop-developers don’t need to worry about, particularly with respect to SQL Compact objects and "graphical" objects, i.e. classes of the System.Drawing namespace. In NETCF the Font, Image, Bitmap, Pen, Brush objects are simple wrappers around their native resources, which in the Windows CE-based operating systems are handled by the GWES (Graphics, Windowing and Event Subsystem). What does this mean? It means that in their own .Dispose() they effectively release their native resources, and therefore one *must invoke .Dispose() for Drawing objects* (or invoke methods that indirectly call it, for example .Clear() in ImageList.ImageCollection – which has not the .Dispose() itself). Note that among the counters provided by the Remote Performance Monitor, the category "Windows.Forms" contains indeed:

    • Controls Created
    • Brushes Created
    • Pens Created
    • Bitmaps Created
    • Regions Created
    • Fonts Created
    • Graphics Created (FromImage)
    • Graphics Created (CreateGraphics)

    Note that I’m not talking about only objects directly “born” as Brush, Pen, etc.. I’m talking also about those objects whose properties contain graphic objects, such as a PictureBox or ImageList (or indirectly, an ImageList of a ToolBar). So, when you close a form, remember to:

    this.ImageList1.Images.Clear();
    this.ToolBar1.ImageList.Images.Clear();
    this.PictureBox1.Image.Dispose();
    //etc...

    Finally, still about Forms, a simple technique I often used to identify possible problems with items not properly released at the closing of a form has been to emulate user interaction by “automatic” opening and closing of the form. I’m purely talking about a test code like this:

    int N = 1000;
    for (int i = 1; i <= N; i++)
    {
       if (i % 200 == 0)  MessageBox.Show(i.ToString()); //just to know it's running
       // and to block execution in case you want to save counters (.STAT)
       // and GC Heap View (.GCLOG)    
       frmTest frm = new frmTest(/*...*/);    
       frm.Show();    
       frm.Close();    
       frm.Dispose(); 
    }
    MessageBox.Show("Over. Done. Finito.");

    After running the loop N times, the Remote Performance Monitor will be of considerable help to see what is going wrong... Nerd

    A final note before concluding this paragraph. It may be that an application is so complex to require "a lot of" virtual memory. This would not be a problem, as long as there is room for the lines "green" in my previous post. But requiring "a lot of" memory means that the Garbage Collector will get kicked more frequently, thus impacting general application performance (because the GC must first “lock” the threads in a safe state). The point is that if the application is so complex to require too frequent use of garbage collection (and therefore performance may not be acceptable by end-users), then it might be worthwhile to split the application into 2 parts, such as one for memory dog-guard and another for the user interface. This process at the cost of an additional process slot, but often it is something that can be paid. Or, since the managed DLLs are loaded in the Large Memory Area without wasting precious process’ address space, an idea would be to place all classes, even those of the form, not in the EXE but in the DLLs! A simple yet very effective idea, which Rob Tiffany has discussed about in his post MemMaker for the .NET Compact Framework

    Enjoy!
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Microsoft released a HotFix for NETCF v3.5 on Windows Mobile 6.1.4 onwards, to address basic functionalities of WebBrowser control

    • 5 Comments
    • Yet another demonstration of the added value provided by the Technical Support
    • A very impressive example of collaboration between Technical Support and Dev Team
    • FIX is public now (link to KB Article)
    • Update: the fixed CAB is automatically deployed by Marketplace

    A few weeks ago I’ve started handling a Service Request coming from a ISV whose NETCF v3.5 Application relied on the WebBrowser control to display a HTML FORM with some links on it… Imagine simply the following form’s code:

    private string m_Html = 
    "<html><body>" +
    "<a href=\"http://www.msn.com\">MSN</a><BR>" +
    "<a href=\"http://www.msn.com\">MSN</a><BR>" +
    "<a href=\"http://www.msn.com\">MSN    </a><a href=\"http://www.msn.com\">MSN    </a><BR>" +
    "<a href=\"http://www.msn.com\">MSN    </a><a href=\"http://www.msn.com\">MSN    </a><a href=\"http://www.msn.com\">MSN</a><BR>" +
    "<a href=\"http://www.msn.com\">MSN</a><BR>" +
    "</body></html>";
    
    private void menuItem2_Click(object sender, EventArgs e)
    {
           this.webBrowser1.DocumentText = m_Html;
    }

    Problem is that, after the HTML is loaded, when using navigation “buttons” (up\down\left\right):

    • On WM6 I can navigate through the links: this is the expected behavior… and when pressing the action button the control navigates to the href link.
    • On WM6.1.4 the whole page is moved instead; and when tapping on a link, nothing happens (the expected behavior was that the control navigates) apart from “giving focus” to the link: at this point even if you press the action button the control doesn’t navigate
    • On WM6.5 the behavior is similar to 6.1.4’s, and here even horizontal and vertical scrollbars don’t appear

    The problem is easily reproduced on the Emulators as well, thus demonstrating that the issue is not OEM-ish…

    I also found at least 2 other developers reporting the problem through Microsoft Connect:

    Troubleshooting steps included verifying which native control is wrapped by the NETCF v3.5’s WebBrowser: I noticed that when using the NATIVE code Mike Francis provided in his blog post (code sample available here), then I get the desired behavior on all the 3 Emulators. Mike’s code is based on the WM6’s SDK Sample named “Browse” (\Samples\CPP\Win32\Browse\browse) and explains how to add gesture- and selection-support to a native application hosting the NATIVE web-browser control.

    So, the problem was just with the NETCF v3.5’s WebBrowser here! And interestingly it’ NOT reproduced with NETCF v2 SP2 that ships in-ROM on WM6.x. So… what are the loaded DLLs (i.e. what is the native control wrapped by the NETCF v3.5’s WebBrowser)? And which Windows? [I was intereseted on Windows’ ClassNames due to a possible technique to customize the WebBrowser’s context-menu, which I discussed here]

    using
    NETCF v3.5 WebBrowser

    WM6

    WM6.1.4

    WM6.5

    WND ClassName PIEHTML Internet Explorer_Server Internet Explorer_server
    Loaded DLL webview shdocvw shdocvw

     

    using
    Native Control (Mike’s code)

    WM6

    WM6.1.4

    WM6.5

    WND ClassName PIEHTML PIEHTML PIEHTML
    Loaded DLL webview + htmlview webview + htmlview webview + htmlview

     

    To recap: the issues are due to the NETCF v3.5’s WebBrowser control loading the new native IE6on6 (shdocvw.dll) instead of relying on the native webview.dll + htmlview.dll, and this makes impossible for the user to (a) click a href link and (b) to navigate through them by using the up\down\left\right pad. Also, (c) on 6.5 the scrollbars are not created. When loading the same HTML by the same NETCF application on a WM6, the behavior is as expected. There’s NO problem on WM6.1.4 and 6.5 when using the native control directly (htmlview.dll and webview.dll), for example through the WM6 SDK Sample \Samples\CPP\Win32\Browse\browse.

    What happened at this point? As I mentioned other times (for example here), one of the added values provided by Technical Support is that we have a channel to the Dev Teams which we can use to advocate final users’ needs. You can imagine how many customers the Technical Support reaches every single day: we’re basically one of the ears of the Product Groups. And also this time I received an impressive collaboration from  the NETCF Dev Team, which firstly accepted to package the FIX, and then did it in a very few days: this is not the usual way it works, so if you’ll ever request a fix then don’t expect this in general. As you can imagine this depends on the code that needs to be modified, in terms of both the complexity and the possible regression risks that a modification introduces, compared to the risks of application usability and other factors if the fix is not provided. For example, many times developers and users can accept a “workaround” while waiting for next version of the product or the possible next Service Pack – I’ve discussed about an example of this here.

    To conclude: a FIX is ready to be asked to the Technical Support. KB Article is:

    FIX: You cannot scroll through a Web page or browse to a link by using a .NET Compact Framework 3.5-based application that hosts a WebBrowser control in Windows Mobile 6.1.4 and 6.5

    For the time being it’s a separate hotfix, maybe the Dev team will consider packaging it within a Service Pack – this is not yet decided. This is another demonstration of a kind of historical change, as this is just the second separate hotfix provided for NETCF on Windows Mobile-based OSs. First one was release few months ago and I blogged about it here.

    A question may arise: can ISVs distribute the FIX? I mean, imagine I’m an ISV and developed a NETCF v3.5 app based on the WebBrowser control: if I want to make my app usable on WM6.1.4 and 6.5 devices of my clients, do I need to ask each of them to open a Service Request to Microsoft asking for the hotfix? The answer is: ISVs can distribute also the “patched” NETCF’s CAB.

     

    <UPDATE date=”February 4, 2010” topic=”redist of the fix”>At the time I wrote this post, Marketplace was not yet opened so I didn’t mention an interesting detail regarding the distribution of the fixed NETCF v3.5 CAB: well, if you use Marketplace to distribute your application, then you don’t need to worry about the fixed CAB of the NETCF, because Marketplace will distribute the most updated Build of the NETCF runtime your application targets, accordingly also to the Application Submission Requirements for Windows® Marketplace for Mobile whose point “7.4. Restriction on .NET Compact Framework Redistribution” states:

    Application providers should not include the .NET Compact Framework (.NET CF) redistributable package with their applications. The Windows Marketplace installation process will ensure that either the .NET CF 2.0 or .NET CF 3.5 runtime is installed on the device as required.

    I did a quick test with a freeware application I knew was built for NETCF v3.5, on a WM6.5 Emulator (so just hard-reset with only NETCF v2 “in-ROM”) – note the user-friendly process (I grayed the portions naming the application becasue I’m sure there are many others out there based on NETCF v3.5 Open-mouthed):

    before 

    When the download\install finished, just to verify which exact Build of the NETCF v3.5 was installed, I’ve run \Windows\cgacutil.exe and could see that, at today, the Build is 3.5.9198, which is precisely the one mentioned in the KB Article I’ve mentioned above:

    after

    </UPDATE>

    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. In this precise case, you don’t have to install this hotfix if your NETCF v3.5 applications doesn’t use the WebBrowser control.

     

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    GPS.NET 3.0: thanks Jon!!

    • 0 Comments

    Unfortunately I had to put GPS-Programming in stand-by mode… Sad but finally some days ago I was reading one of the posts I wrote about GPS roughly one year ago, and tried to navigate to a company’s webpage I remembered containing a lot of interesting info and controls about GPS, i.e. GeoFrameworks: well, I realized that GeoFrameworks is now closed! Luckily their Mastering GPS Programming is still available, but, much better… the founder of GeoFrameworks, Jon Person, “[…] decided to release the full source code of GPS.NET to the public domain for the benefit of the open source development community”. If you’re interested on GPS-Programming, you must visit http://gps3.codeplex.com!

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    New opening: Italian Dev Support blog

    • 0 Comments

    Yesterday my Italian colleagues in Developer Support and I officially inaugurated our new team blog: if you can read Italian then join us at http://blogs.msdn.com/itasupport. Hot

    CIAO!
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Remote Desktop Mobile (RDP Client) disconnects after 10 minutes of inactivity

    • 25 Comments

    [UPDATE 10/9/2009: Source-Code and CAB are now available at http://mobilerdpnotimeout.codeplex.com/]
    [UPDATE 9/2/2010: v2 of the sample app is available at http://mobilerdpnotimeout.codeplex.com/: it takes care of the battery-life impacted by application continuing to emulate mouse-move even after user closed RDP session – see details below]

    • Error Message: “The remote session was ended because the idle timeout limit was reached. This limit is set by the server administrator or by network policy.”
    • RDP\TSC Clients and Windows Mobile OEMs
    • What’s new in Windows Mobile 6.5 about Remote Desktop Mobile

    This issue should be somehow well known by the Community, but I couldn’t easily find a solution so I had to think one by myself… :-) After 10 minutes of inactivity of a Terminal Server session on Windows Mobile devices, the Remote Desktop Mobile application needs you to logon again stating that a timeout “set by the server administrator or by network policy” occurred. Unfortunately it’s not really a good error message, since many times the server is correctly set and the problem is just client-side... Anyway, probably it was not very frustrating since in the most of the cases one is logged via RDP only to actively work on the server. But this is not always true, so I’d guess either I wasn’t able to find someone with a possible solution, either simply people accepted to live with it…

    Remote Desktop Mobile on WM6.x (and, before this, the TSC client on WM5) is a component that OEMs are not required to include to have their OSs pass the Windows Mobile Logo Test Kit, and this is basically why you may find devices with it in-ROM and others where you need to find alternative solutions. In any case, regarding the inactivity timeout issue, luckily Windows Mobile 6.5 _should_ now have an updated version of the Remote Desktop Mobile application that OEMs can include in their images, easier to use on the field as it allows, for example, to finally customize an idle timeout!! Unfortunately it’s not included in the emulators coming with the Windows Mobile 6.5 Developer Tool Kit.

    Back to the problem… after some researches I understood that the timer is reset only when the Remote Desktop Mobile’s application window receives mouse or keyboard input. Refreshing the window, or sending exotic WM_xx messages wasn’t enough. So, I came to thinking what was a possible keypress or mouseevent that can be programmatically simulated (through keybd_event() or mouse_event()) that wouldn’t impact application at all?? After some tests I’ve realized that MOUSEEVENTF_MOVE, which is the event of user “moving the mouse”, has no effect on current Windows Mobile-based devices: that event is maintained as codebase with Windows CE, where an OEM may also include a non-touch screen where you really have a mouse to move... but its only effect on Windows Mobile device is for example to modify display’s brightness, in case the display entered a sort of power-safe state (well, obviously the mouse-pointer is moved on the remote server)

    So, the idea was to develop a Win32 Console application that simply launches the Remote Desktop Mobile application and every X minutes (5? 9?) simulates a mouse-movement of some pixels and after a short time another one to place the mouse on the previous position.

    This is NOT elegant code at all, absolutely, but it does what was meant to run a specific task… Nerd

    Obviously this watch-dog application would “waste” one of the 30 process slots… is it acceptable? As you can see the application doesn’t do anything special or unsupported: it’s simply a monitor-console application with no particularly elegant code. As usual, I would strongly encourage on adding some error-checking!!

    
    int _tmain(int argc, _TCHAR* argv[])
    {
        int const FIVEMINUTES = 1000*60*5;
        HWND hWndRDM = NULL;
        
        //Firstly launch RDP Client
        SHELLEXECUTEINFO sei = {0};
        sei.cbSize = sizeof(sei);
        sei.nShow = SW_SHOWNORMAL; 
        sei.lpFile = TEXT("\\Windows\\wpctsc.exe");
        sei.lpParameters = TEXT(" ");
        if (!ShellExecuteEx(&sei))
        {
            MessageBox(NULL, TEXT("Couldn't launch RDP Client"), TEXT("Remote Desktop Launcher"), MB_OK);
            goto Exit;
        }
    
    
        //Now every X minutes check if it's still running and if so "refresh" its window
        //if it's no longer running, exit
        do 
        {
            //check if RDP Client is running, otherwise exit
            hWndRDM = FindWindow(_T("TSSHELLWND"), NULL);
            if (NULL != hWndRDM)
            {
                ////Get foreground window -- this is not needed if RDM is launched Full-Screen as it was in this case
                //hWndForeground = GetForegroundWindow();
                //Sleep(500);
    
                //Give focus to the RDP Client window (even if the logon screen, in case user logged out in the meantime)
                SetForegroundWindow(hWndRDM);
    
                //The timer is reset when the rdp window receives mouse or keyboard input
                //with no MOUSEEVENTF_ABSOLUTE the move is relative
    
                mouse_event(MOUSEEVENTF_MOVE, 100, 0, 0, 0);
                Sleep(250);
                mouse_event(MOUSEEVENTF_MOVE, -100, 0, 0, 0);
    
                ////Give focus back to previous foreground window
                //SetForegroundWindow(hWndForeground);
    
                //Sleep 
                Sleep(FIVEMINUTES);
            }
            else 
            {
                //RDP Client is not running - let's exit
                goto Exit;
            }
        }
        while (TRUE); //The check (NULL != hWndRDM) is already done inside the loop
    
    
    Exit:
        if (NULL != hWndRDM)
            CloseHandle(hWndRDM);
        
        return 0;
    }
     

    I can imagine that this problem affects more the WinMo Users rather than the WinMo “Devs”, therefore I had in mind to distribute directly the CAB of the application, if anyone else doesn’t want to do it for the Community. I’m wondering if CodePlex would be a good place, I’ll see and update this post. In any case, remember that it’s up to the OEMs to decide if including RDM (\Windows\wpctsc.exe) in their OSs based on Windows Mobile platform!

    Cheers!
    ~raffaele

     

    EDIT 9/2/2010:

    After interacting with some users of the sample application and tested a v2 build on some scenarios, I’d like to share it now – note that the CodePlex project page will continue containing both versions. The issue was that MyRDM continued to emulate mouse-move even after user closed session, and this impacted on battery-life: this was not a bug, but simply a well known limitation of the tiny sample application… so we wanted a code that acted on the following possible conditions after every sleep-interval:

    • Wpstsc is no longer running (user “minimized” and OS closed it) –> in this case, MyRDM.exe exits
    • Wpstsc is running AND:
      • The “remote session” is foreground –> then MyRDM.exe emulates mouse-move and goes back to sleep
      • The “remote session” is not foreground for one of the possible causes:
        • User “minimized” wpstsc –> then MyRDM.exe kills wpstsc and exits
        • User logged off, and the foreground window is the RDM Logon window –> then MyRDM.exe does nothing and simply goes back to sleep

    So the main portion of the code is the following (the same disclaimers apply as before):

            do 
            {
                    //Sleep 
                    Sleep(FIVEMINUTES);
    
                    //check if RDP Client is still running, otherwise exit
                    hWndRDM = FindWindow(_T("TSSHELLWND"), NULL);
                    if (NULL != hWndRDM)
                    {
                            hWndForeground = GetForegroundWindow();
                            if (hWndRDM == hWndForeground)
                            {
                                    //The timer is reset when the rdp window receives mouse or keyboard input
                                    //with no MOUSEEVENTF_ABSOLUTE the move is relative
                                    mouse_event(MOUSEEVENTF_MOVE, 100, 0, 0, 0);
                                    Sleep(250);
                                    mouse_event(MOUSEEVENTF_MOVE, -100, 0, 0, 0);
                            }
                            else //this means that RDM main windows is no longer foreground, however it may be that the RDP logon page is active
                            {
                                    pid = NULL;
                                    threadId = GetWindowThreadProcessId(hWndForeground, &pid);
                                    //only if the foreground window is not the RDP logon page, then kill wpctsc.exe and exit
                                    if (pid != GetProcessIdByName(TEXT("wpctsc.exe")))
                                    {
                                            KillProcessByName(TEXT("wpstsc.exe"));
                                            goto Exit;
                                    }
                            }
                    }
                    else //hWndRDM == NULL: RDP Client is not running - let's exit
                    {                       
                            goto Exit;
                    }
            }
            while (TRUE);

     

    HTH, ciao!
    ~raffaele

  • Mobile Development - 'Support Side Story'

    SIP Button Icon disappears after closing dialogs in NETCF v2 applications

    • 0 Comments

    I thought this was something well known by the Mobile Dev Community, but maybe this is not true, since I’ve recently worked on a similar case.. so it may be worth sharing it here!

    Under some special circumstances, which weren’t possible to reproduce for Technical Support and NETCF Dev Team at that time (about 2-3 years ago) – also because it was resolved in NETCF v3.5 – it happened that on NETCF v2 applications when a form gains back the focus after it was given to a dialog (MessageBox.Show() or anotherForm.ShowDialog()), then _sometimes_ that little squared SIP icon at the middle of the menubar at the bottom suddenly disappeared.

    That nasty problem was due to some faulty interaction between the OS and the .NET runtime, in any case no longer reproduced with v3.5. In some cases it had sufficed to reset this.Menu (or this.Parent.Menu) to the main menu. But that wasn’t the case in our service request… and also the simple refresh of the form at form’s OnActivated was NOT enough:

    protected override void OnActivated(EventArgs e)
    {
         this.Menu = mainMnu;
         this.Refresh();
    
         base.OnActivated(e);
    }

    So we had to find a satisfactory temporary solution for NETCF v2 applications (migration to v3.5 was already in progress..). As usual I proposed to play with WINDOWS… :-) and we came up with the following propositions – the first one did the trick! (remember to add error-handling to whatever code I may suggest!!)

    1. FindWindow + ShowWindow
        protected override void OnActivated(EventArgs e)
        {
            IntPtr hSip = FindWindow("MS_SIPBUTTON", "MS_SIPBUTTON");
            if ((hSip != IntPtr.Zero) /*&& (!IsWindowVisible(hSip))) */
            {
                bool retShowWin = ShowWindow(hSip, EShowWindow.SW_SHOW);
            }
            base.OnActivated(e);
        }
        
        
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr FindWindow(string _ClassName, string _WindowName);
        
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool IsWindowVisible(IntPtr hWnd);
        
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool ShowWindow(IntPtr hwnd, EShowWindow nCmdShow);
        
        private enum EShowWindow : uint
        { 
            SW_HIDE = 0,
            SW_SHOWNORMAL = 1,
            //SW_SHOWNOACTIVATE = 4,
            SW_SHOW           = 5 ,
            //SW_MINIMIZE       = 6,
            SW_SHOWNA         = 8 //,
            //SW_SHOWMAXIMIZED  = 11,
            //SW_MAXIMIZE           = 12,
            //SW_RESTORE            = 13
        }
         
    2. FindWindow + BringWindowToTop
        protected override void OnActivated(EventArgs e)
        {
            IntPtr hSip = FindWindow("MS_SIPBUTTON", "MS_SIPBUTTON");
            if ((hSip != IntPtr.Zero) /*&& (!IsWindowVisible(hSip))) */
            {
                bool retBringWin = BringWindowToTop(hSip);
            }
            base.OnActivated(e);
        }
        
        
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr FindWindow(string _ClassName, string _WindowName);
        
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool IsWindowVisible(IntPtr hWnd);
        
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool BringWindowToTop(IntPtr hWnd);
         
    3. FindWindow + GetWindowLong
        protected override void OnActivated(EventArgs e)
        {
            IntPtr hSip = FindWindow("MS_SIPBUTTON", "MS_SIPBUTTON");
            if ((hSip != IntPtr.Zero) /*&& (!IsWindowVisible(hSip))) */
            { 
                int style = GetWindowLong(hSip, GWL_STYLE);
                style |= WS_POPUP | WS_VISIBLE;
                int retSetWin = SetWindowLong(hSip, GWL_STYLE, style);
        
                int exstyle = GetWindowLong(hSip, GWL_EXSTYLE);
                exstyle |= WS_EX_ABOVESTARTUP | WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
                retSetWin = SetWindowLong(hSip, GWL_EXSTYLE, exstyle);
            }
            base.OnActivated(e);
        }
        
        
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern IntPtr FindWindow(string _ClassName, string _WindowName);
        
        [DllImport("coredll.dll", SetLastError = true)]
        private static extern bool IsWindowVisible(IntPtr hWnd);
        
        private const int GWL_EXSTYLE = (-20);
        private const int GWL_STYLE = (-16);
        
        [DllImport("coredll", SetLastError = true)]
        private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
        
        [DllImport("coredll", SetLastError = true)]
        private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
        
        private const int
            WS_POPUP = unchecked((int)0x80000000),
            WS_VISIBLE = 0x10000000,
            WS_EX_ABOVESTARTUP = 0x20000000,
            WS_EX_NOACTIVATE = 0x08000000,
            WS_EX_TOOLWINDOW = 0x00000080,
            WS_EX_TOPMOST = 0x00000008;

      

    Cheers,
    ~raffaele

  • Mobile Development - 'Support Side Story'

    Resolution-Aware CAB on Windows Mobile

    • 0 Comments
    • GetSystemMetrics or GetDeviceCaps inside a CAB’s Setup.dll
    • wceload.exe is not resolution- and DPI-aware
    • RESDLL SDK Sample 

    Sincerely I haven’t easily found entries about this over the web, so I think it’s worth mentioning it. Maybe many know, but possible many others don’t… smile_regular Imagine you want to perform different actions during install depending on the device’s resolution. A possible code to retrieve resolution can use GetSystemMetrics or GetDeviceCaps. My troubleshooting, as initial step, was as usual to verify if I could reproduce ISV’s problem, even on Device Emulator (thus avoiding possible problems related to OEMs’ customizations). So I included the following in a CAB’s Setup.dll’s Install_Init (Install_Exit is the same):

            HRESULT hr = S_OK;
    
            LPTSTR pszBuf = new TCHAR[256];
            ZeroMemory(pszBuf, 255);
    
            int X = GetSystemMetrics(SM_CXSCREEN);
            int Y = GetSystemMetrics(SM_CYSCREEN);
    
            hr = StringCchPrintf(pszBuf, 
                            LocalSize(pszBuf) / sizeof(TCHAR),
                            TEXT("X: %d\r\nY: %d"), 
                            X, Y);
            CHR(hr);
    
            MessageBox(hwndParent, pszBuf, TEXT("Install_Exit"), MB_OK);
    
        Exit:
            return codeINSTALL_INIT_CONTINUE; //or codeINSTALL_EXIT_DONE for Install_Exit

    Specifically:

    • on a VGA WM6 Emulator (640x480) it returned 320x240
    • on a square VGA WM6 Emulator (480x480) it returned 240x240
    • on a square QVGA WM6 Emulator (320x320) it returned 240x240
    • on a WM6.1.4 EMULATOR (480x800) it returned 240x400

    When using precisely the same code in a Win32 Smart Device Console application, the values returned were the expected ones…

    After working with a colleague (thanks Manfred! smile_regular) I got the confirmation about an idea… the problem here is that a CAB’s Setup.dll gets loaded by wceload.exe when installing the CAB, and wceload.exe is not resolution- and DPI-aware . Therefore when setup.dll’s code runs inside the wceload’s context it can’t successfully call GetSystemMetrics or also GetDeviceCaps.

    It turned out that to address this limitation, a sample is provided in the SDKs explaining how to do this: "C:\Program Files (x86)\Windows Mobile 6 SDK\Samples\PocketPC\CPP\win32\resdll" (MSDN Doc here). Notice that setup.dll’s Install_Exit\_Init containing code to run an external DPI-aware application, whose sample code is also provided.

    Cheers,
    ~raffaele

  • 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

  • Mobile Development - 'Support Side Story'

    FindWindowEx on Windows Mobile: not supported…

    • 0 Comments

    … so how can I grab the handle of a particular child window, considering that FindWindow retrieves all top-level windows? I worked on this when writing a previous post of mine, and got a wonderful comment from Lionel (thanks again smile_regular). Today I had to re-use that code, and found that something was missing and also the code required some enhancements… so here it is!

     

            private static IntPtr FindChildWindow(string strChildClassName, string strChildWindowCaption, IntPtr hWndTopLevel)
            {
                IntPtr hwndCur = IntPtr.Zero;
                hwndCur = GetWindow(hWndTopLevel, (uint)GetWindowFlags.GW_CHILD);
                return RecurseFindWindow(strChildClassName, strChildWindowCaption, hwndCur);
            }
    
            private static bool m_bFound = false;
    
            private static IntPtr RecurseFindWindow(string strChildClassName, string strChildWindowCaption, IntPtr hWndParent)
            {
                //bool bFound = false;
                IntPtr hwndCur = IntPtr.Zero;
                char[] chArWindowClass = new char[32];
                if (hWndParent == IntPtr.Zero)
                    return IntPtr.Zero;
                else
                {
                    //check if we got the searched class name
                    GetClassName(hWndParent, chArWindowClass, 256);
                    string strWndClass = new string(chArWindowClass);
                    strWndClass = strWndClass.Substring(0, strWndClass.IndexOf('\0'));
                    if (strWndClass.ToLower() == strChildClassName.ToLower())
                    {
                        //check if we got the searched window name
                        int length = GetWindowTextLength(hWndParent);
                        StringBuilder sb = new StringBuilder(length + 1);
                        GetWindowText(hWndParent, sb, sb.Capacity);
                        m_bFound = (sb.ToString().ToLower() == strChildWindowCaption.ToLower());
                        if (m_bFound)
                            return hWndParent;
                    }
                    else
                    {
                        //recurse into first child
                        IntPtr hwndChild = GetWindow(hWndParent, (uint)GetWindowFlags.GW_CHILD);
                        if (hwndChild != IntPtr.Zero)
                            hwndCur = RecurseFindWindow(strChildClassName, strChildWindowCaption, hwndChild);
                        if (!m_bFound)
                        {
                            IntPtr hwndBrother = IntPtr.Zero;
                            //enumerate each brother windows and recurse into
                            do
                            {
                                hwndBrother = GetWindow(hWndParent, (uint)GetWindowFlags.GW_HWNDNEXT);
                                hWndParent = hwndBrother;
                                if (hwndBrother != IntPtr.Zero)
                                {
                                    hwndCur = RecurseFindWindow(strChildClassName, strChildWindowCaption, hwndBrother);
                                    if (m_bFound)
                                        break;
                                }
                            }
                            while (hwndBrother != IntPtr.Zero);
                        }
                    }
                    return hwndCur;
                }
            }
    
    
            [DllImport("coredll.dll", SetLastError = true)]
            private static extern IntPtr FindWindow(string _ClassName, string _WindowName);
    
            [DllImport("coredll.dll", SetLastError = true)]
            private static extern IntPtr GetWindow(IntPtr hwnd, uint relationship);
    
            [DllImport("coredll.dll", SetLastError = true)]
            private static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
    
            [DllImport("coredll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            private static extern int GetClassName(IntPtr hwnd, char[] windowClass, int maxText);
    
            [DllImport("coredll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            private static extern int GetWindowTextLength(IntPtr hWnd);
    
            [DllImport("coredll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    
            [Flags]
            private enum GetWindowFlags
            {
                GW_HWNDFIRST = 0,
                GW_HWNDLAST = 1,
                GW_HWNDNEXT = 2,
                GW_HWNDPREV = 3,
                GW_OWNER = 4,
                GW_CHILD = 5,
            }

     

    Cheers,

    ~raffaele

  • Mobile Development - 'Support Side Story'

    Programmatically Discriminate between Upgrade or Uninstall of a CAB on Windows Mobile

    • 14 Comments

    Recently I've worked with a developer on an interesting issue I’ve not found any clue on the web about, and the solution is based on one of those details that you can empirically retrieve but that there are not documented anywhere, therefore on future releases may change without any warning. This was for example what happened to the ClassName of NETCF applications... see Daniel Moth's post about this: "#NETCF_AGL_". I’ve also discussed about this in a MSDN Forum post I found interesting, where the topic was something like “how to prevent the CLR to not allow a second instance of the same NETCF application to run on Windows Mobile”. As I probably wrote elsewhere, “undocumented” doesn’t mean “technically not achievable”: it means that Product Group may change it as it doesn’t have to be backward-compatible.

    In this case we had an application that may have been updated at a later time: the ISV was wondering if there’s any way in the setup.dll of application’s CAB to specify, during uninstallation, if the uninstall is taking place during a version-upgrade or if it's a pure uninstallation. This is because, for example, the application's installation copies also some large files that user no longer needs after the uninstall and therefore are deleted: but it still needs them if the user is uninstalling a former version of the app in order to install a newer one. I hope I've been clear... smile_confused Things can get more complicated by the fact that the when you do an “upgrade” of the same ap

    Well... we found out that there's no documented and standard way to achieve the goal, so we had to be creative - as usual... Nerd To understand how to operate, we needed to understand the actual flow when installing\uninstalling\upgrading (=installing the CAB of a newer version of the app while a older one is installed); moreover, we had to take care a particular condition, i.e. when upgrading the user is prompted with the message “The previous version of… Select Ok to continue or cancel to quit” -- and here it comes handy the “undocumented but empirically retrievable” info, that I'm going to show in a minute.

    The regular flow when installing and uninstalling is:

    • Install:
      1. DLL_PROCESS_ATTACH – Setup.dll is loaded
      2. Install_Init
      3. Install_Exit
      4. DLL_PROCESS_DETACH – Setup.dll is unloaded
    • Uninstall:
      1. DLL_PROCESS_ATTACH – Setup.dll is loaded
      2. Uninstall_Init
      3. Uninstall_Exit
      4. DLL_PROCESS_DETACH – Setup.dll is unloaded

    When upgrading, the flow is as follows:

    1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
    2. Install_Init
    3. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
    4. Message prompt to the user to confirm uninstall of previous version
      • Select Ok:
        1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded, *BUT* the installer doesn’t know if we’re uninstalling because of a real uninstall or an upgrade
        2. Uninstall_Init
        3. Uninstall_Exit
        4. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
        5. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
        6. Install_Init
        7. Install_Exit
        8. running the exec
        9. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
      • Select Cancel:
        1. Nothing happens (setup.dll was already unloaded)

    So the problem is how to let the installer know that it’s uninstalling or upgrading… the idea I had was to modify the flow this way, based on the fact that when “upgrading”, the flow involves firstly a Install_Init and secondly a Uninstall_Init; in contrast when “uninstalling” the flow doesn’t involve a first step through Install_Init:

    a. Install

    1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
    2. Install_Init (query if the app is already installed (through the Uninstall CSP) and set a registry key or whatever, e.g.[HKLM\UpgradeKey]Upgrade=0 if it was not installed and 1 viceversa) –> now: Upgrade=0
    3. Install_Exit
    4. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded

    b. Upgrade:

    1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
    2. Install_Init (query if the app is already installed (through the Uninstall CSP) and set a registry key or whatever, e.g.[HKLM\UpgradeKey]Upgrade=0 if it was not installed and 1 viceversa) –> now: Upgrade=1
    3. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
    4. Message prompt to the user to confirm uninstall of previous version
      • Select Ok:
        1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
        2. Uninstall_Init (QUERY [HKLM\UpgradeKey]Upgrade and act accordingly) –> now: Upgrade=1 (was just set by Install_Init at point 2. of the Upgrade flow, and then it can be set back to 0)
        3. Uninstall_Exit
        4. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
        5. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
        6. Install_Init
        7. Install_Exit
        8. running the exec
        9. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded
      • Select Cancel:
        1. Nothing happens (setup.dll was already unloaded)

    c. Uninstall

    1. DLL_PROCESS_ATTACH – SetupDLL.dll is loaded
    2. Uninstall_Init (query if we’re upgrading by looking at the registry key) –> now: Upgrade=0 (it wasn’t changed by anyone)
    3. Uninstall_Exit
    4. DLL_PROCESS_DETACH – SetupDLL.dll is unloaded

    To conclude, the idea was to:

    • Install_Init creates the “Upgrade” registry key (or other info) and sets 0 if the application is NOT already installed and 1 viceversa. To check if an application is already installed I think I’ve already discussed once on the Uninstall Configuration Service Provider… yes, see this post.
    • Uninstall_Init checks the value of the key and act accordingly (just as an example, if that’s an “uninstall” then remove some files that are no longer used)

     

    HOWEVER… smile_confused this approach had a problem… what happens if user answers “Cancel” to the prompt “The previous version of… Select Ok to continue or cancel to quit”? Nobody can restore [HKLM\UpgradeKey]Upgrade to 0 after that Install_Init set it to 1, and future possible “Uninstalls” are considered as “Upgrades”! So basically the problem is when user firstly doesn't accept to uninstall the previous version during upgrade and then secondly she uninstalls the previous version on her own: when doing this second action, the uninstall procedure would find that the Upgrade registry key is set to 1 and therefore would consider an upgrade even if in reality it's an uninstall.

    So, next question was: is there any programmatic way to know if user selects “Cancel” when prompted about uninstalling previous version? The only way I could think at was to get ahold of the WCELOAD.EXE process and invoke GetExitCodeProcess() API to retrieve its return value: the assumption was that it was different when user hits “Cancel”… it turned out that this is true, but this approach involved an external application to be launched for example in setup.dll’s DLL_PROCESS_ATTACH, that can monitor WCELOAD.EXE and check its return value during Uninstall phase… Why an external process? Because the prompt comes up EVEN BEFORE the setup.dll can handle Install_Init.

    The “undocumented but empirically retrievable” info I was mentioning at the beginning is precisely the return value of WCELOAD.EXE when user hits Cancel. As I said, not being documented it may change on future releases without any notice..

    And now some code please!!

    I’m talking about the following in setup.dll:

    #define DELETE_STR(s) \
    if (NULL != s) \
    delete [] s;
     
     
    HINSTANCE g_hinstModule;
     
    BOOL APIENTRY DllMain(
        HANDLE hModule, 
        DWORD  ul_reason_for_call, 
        LPVOID lpReserved
        )
    {
        //MessageBox(NULL, TEXT("Now attach the debugger"), TEXT("Test"), MB_OK);
     
        switch (ul_reason_for_call)
        {
            case DLL_THREAD_ATTACH:
            case DLL_THREAD_DETACH:
            case DLL_PROCESS_DETACH:
                g_hinstModule = (HINSTANCE)hModule;
                break;
     
            case DLL_PROCESS_ATTACH:
                  g_hinstModule = (HINSTANCE)hModule;
     
                  //check if UpgCheck.exe is already available on device (1st time it won't, but in any case we don't need it)
                  LPCWSTR pszFileNameWithPath = new TCHAR[MAX_PATH];
                  pszFileNameWithPath = TEXT("\\Windows\\UpgCheck.exe");
                  WIN32_FIND_DATA wfdFindFileData;
                  HANDLE hFile = FindFirstFile(pszFileNameWithPath, &wfdFindFileData);
                  if(hFile == INVALID_HANDLE_VALUE)
                  {
                                DELETE_STR(pszFileNameWithPath);
                                break;
                  }
                  FindClose(hFile);
     
                  //Launch external process that will monitor wceload.exe
                  BOOL bRet;
                  SHELLEXECUTEINFO sei = {0};
     
                  sei.cbSize = sizeof(sei);
                  sei.nShow = SW_SHOWNORMAL; 
                  sei.lpFile = pszFileNameWithPath;
                  sei.lpParameters = TEXT(" ");
                  bRet = ShellExecuteEx(&sei);
     
                  //if (!bRet)
                  //     MessageBox(NULL, TEXT("Could not launch UpgCheck"), TEXT("Test"), MB_OK);
     
                  DELETE_STR(pszFileNameWithPath);
                  break;
        }
     
    return TRUE;
    }
     

    And I’m talking about something similar in the wceload-monitor:

    int _tmain(int argc, _TCHAR* argv[])
    {
           int const MAXBUF = 32;
           HRESULT hr = E_FAIL;
           HANDLE hProcess = NULL;
           BOOL bRes = FALSE;
           DWORD dwRes = 0;
     
           LPTSTR lpBuf = new TCHAR[MAXBUF];
           ZeroMemory(lpBuf, MAXBUF - 1);
     
           //retrieve process handle of wceload.exe, until it's found
           do{
                  hr = GetProcessHandleByName(TEXT("wceload.exe"), &hProcess);
                  CHR(hr);
                  Sleep(1000);
           } while (INVALID_HANDLE_VALUE == hProcess);
     
           //hr = LogToFile(TEXT("\r\nwceload found!\r\n"), g_pszFilename);
           //CHR(hr);
     
           //retrieve wceload.exe exit code, until it exits
           do {
                  Sleep(1000);
                  bRes = GetExitCodeProcess(hProcess, &dwRes);
     
                  if ( !bRes )
                  {
                         goto Exit; //GetLastError
                  }
           } while (STILL_ACTIVE == dwRes); 
           
           hr = StringCchPrintf(lpBuf, 
                  LocalSize(lpBuf) / sizeof(TCHAR),
                  TEXT("ExitCode %d"),
                  dwRes); //2147754005 when user select Cancel (0x80042015)
           CHR(hr);
     
           hr = LogToFile(lpBuf, g_pszFilename);
           CHR(hr);      
     
           //success
           hr = S_OK;
     
    Exit:
           DELETE_STR(lpBuf);
     
           return 0;
    }

     

    Where the helper functions are:

    // **************************************************************************
    // Function Name: GetProcessHandleByName
    HRESULT GetProcessHandleByName (LPCTSTR pszProcessName, LPHANDLE phProcessHandle)
    {
           HRESULT hr = E_FAIL;
     
           if (pszProcessName == NULL)
                  goto Exit;
     
           HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
           if (hSnapshot == INVALID_HANDLE_VALUE)
                  goto Exit;
     
           *phProcessHandle = NULL;
           PROCESSENTRY32 pe;
           pe.dwSize = sizeof(pe);
     
           if (Process32First(hSnapshot, &pe))
           {
                  do {
                         //log Exe name
                         hr = LogToFile(pe.szExeFile, g_pszFilename);
                         CHR(hr);
                         hr = LogToFile(TEXT("\r\n"), g_pszFilename);
                         CHR(hr);
                         
                         //compare current Exe name with passed Process Name
                         if (lstrcmpi(pszProcessName, pe.szExeFile) == 0)
                         {
                               //get the handle of the Exe name in case we reached the Exe we were looking for
                               *phProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
     
                               CloseHandle(hSnapshot);
                               return TRUE;
                         }
                  } while (Process32Next(hSnapshot, &pe));
           }
     
           //Success
           hr = S_OK;
     
    Exit:
           if (NULL != hSnapshot)
                  //UPDATE: thanks Vino!
                  //Contrarily to desktop Win32, don't invoke CloseHandle() to close the snapshot call.
                  //Desktop (http://msdn.microsoft.com/en-us/library/ms682489(VS.85).aspx): 
                  //       "[...] To destroy the snapshot, use the CloseHandle function.".
                  //Windows CE\Mobile (http://msdn.microsoft.com/en-us/library/aa911386.aspx): 
                  //       "[...] To close a snapshot, call the CloseToolhelp32Snapshot function."
                  CloseToolhelp32Snapshot(hSnapshot);
     
           return hr;
    }
     
     
    // **************************************************************************
    // Function Name: LogToFile 
    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;
    }

     

    Hope this can help someone that absolutely has to distinguish if the application needs to be uninstalled or upgraded… but maybe the code above can find other meaningful usage! smile_nerd

     

    Cheers,

    ~raffaele

  • Mobile Development - 'Support Side Story'

    Yet another post about Memory on Windows Mobile

    • 0 Comments

    • Abhinaba’s blog
    • What’s new in Windows Mobile 6.1 and 6.5 around memory
    • Dynamic assembly unloading

    Again again again… I've discussed about Memory Management in my very first post, which I suggest reading as it may help you on avoiding memory leaks and also improving memory management of your NETCF application. I’ve discussed about this other times, and to demonstrate how sensible this topic is, recently Abhinaba Basu (Developer working on the .NET Compact Framework team) started blogging about how the NETCF runtime handles memory: thanks Abhinaba for all those details! smile_regular

    So, why am I still talking about memory?? Because that’s the scarcest resource whose lack mobile developers have to face sometimes… btw let me digress: remember that Windows Mobile 6.1 and 6.5 help a lot ISVs on preventing OOM by modifying some details of the memory management, in particular to alleviate memory pressure on the process slot 0 basically (considering all the "drivers" that OEMs have come up with, which need to be loaded by device.exe process -- thus lowering the DLL Load point (in previous versions of the OS)). Doug Boling discussed about this here.

    Obviously a downside for this is that there’s less room for memory-mapped files in the Large Memory Area (LMA), but, apart from some special circumstances, this shouldn’t affect ISVs’ job, considering also that the LMA may be used by the OS and by other applications, therefore ISVs shouldn’t expect it to be totally available for their usage.

    Also, I’m still talking about memory because from time to time I got questions like “… ok, so why don’t we dynamically load and unload assemblies?”. When this question arises, I usually answer suggesting to start by reading some quite old references:

    As documented here, “[…] To unload managed code running in a process from memory so that memory can be used for other purposes, the host must unload the application domain in which the code is running. Individual assemblies or types cannot be unloaded.”. A quite complex “trick” (I wouldn’t really recommend for NETCF applications) is to create a new AppDomain (AppDomain.CreateDomain is supported by the NETCF) and load an assembly into it. The point is that as soon as you try to create objects from that assembly which is loaded into the new AppDomain, the assembly will be loaded into the current AppDomain. In order to solve this problem, you have to use a middle assembly, which is loaded into both the current AppDomain and the new AppDomain. Once the new AppDomain is unloaded, the assembly will be unloaded from the memory as well. However, the middle assembly, will stay in the memory. This approach might be known among DESKTOP .NET developers: I don’t know if anyone has ever tried to implement this within a NETCF application.

    IN ANY CASE, assemblies are loaded into the system’s 1GB area, therefore the benefit would only be for PHYSICAL memory in this case (and for the JIT-ed code). And we’re usually talking about roughly 25% of the size of the assembly. If you only need to have certain assemblies loaded at certain times, but never all at once then AppDomains can help; if you plan to have all your assemblies loaded simultaneously then AppDomains don’t offer much (for memory pressure relief).

    Note: what if an assembly depends on a native DLL (so loaded into the 32MB process slot) for example via P/Invoke? When the assembly is unloaded (by destroying the AppDomain), starting on NETCF v3.5, if the native DLL is used only by that assembly then its virtual memory is released from the process slot.

     

    Cheers,

    ~raffaele

Page 1 of 3 (69 items) 123