Welcome to MSDN Blogs Sign in | Join | Help
  • 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"WEBfactory SL for Embedded";
    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

  • 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

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

  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

  • 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

  • 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

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

[UPDATE 10/9/2009: Source-Code and CAB are now available at http://mobilerdpnotimeout.codeplex.com/

  • 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

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

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

  • 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

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

  • 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

  • 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

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

More Posts Next page »
 
Page view tracker