Export (0) Print
Expand All

Using COM Interop in .NET Compact Framework 2.0

.NET Compact Framework 1.0
 

Maarten Struys
PTS Software bv

November 2005

Applies to:
   Microsoft .NET Compact Framework version 2.0
   Microsoft Windows CE
   Microsoft Visual Studio 2005
   Windows Mobile version 5.0

Summary: With the arrival of the .NET Compact Framework 2.0, interoperability between managed code and native code has tremendously improved. This article explores the new COM Interop capabilities of the .NET Compact Framework 2.0. You will learn about using existing COM objects from inside managed code. You will also learn how the new Windows Mobile 5.0 managed APIs make it even easier to use a number of existing COM objects—instance, to access data in Pocket Outlook. This article is filled with code examples that are taken from the download sample code, which you may want to have available when reading this article. All sample code runs in the new Windows Mobile 5.0 emulators which are available as part of the Windows Mobile 5.0 SDK that you should install after installing Visual Studio 2005. Reading this article will help you to make maximum use of existing COM components. (31 printed pages)


Download the COM_Interop.msi code sample.

Contents

Introduction
Simple COM Object
Using COM Objects with the .NET Compact Framework 1.0
Using COM Objects with the .NET Compact Framework 2.0
Using COM Interop to Access Pocket Outlook
Using the New Windows Mobile 5.0 Managed APIs to Access Pocket Outlook
Conclusion

Introduction

Many people who are using the .NET Compact Framework version 1.0 have asked for ways to incorporate existing Component Object Model (COM) components in new managed applications. Unfortunately, the .NET Compact Framework 1.0 does not support COM Interop. To be able to use existing COM objects inside a managed application, the approach is to write a native C++ wrapper DLL around the COM object. The wrapper exposes simple C functions that internally call into the COM object and pass the result to the caller of the DLL. In that way, a managed application can use platform invoke to call functions inside the wrapper DLL.

This article shows you how you can use a simple COM object in the .NET Compact Framework 1.0 by using platform invoke in combination with a wrapper DLL. After that, the article describes COM Interop, available in the Microsoft .NET Compact Framework 2.0. You will experience how COM Interop makes using existing COM objects much easier. The next part of this article explains how you can use COM Interop to interact with Microsoft Pocket Outlook. Finally, you will learn how it is even simpler to use managed wrappers around Pocket Outlook that are available with the new Windows Mobile version 5.0 managed APIs.

Simple COM Object

To see COM Interop in action, you will use a very simple COM object, as shown in Figure 1.

Figure 1. A simple COM object

As you can see, the COM object implements a simple calculator, called SimpleCalc, that exposes two different interfaces apart from IUnknown. Right now, you can concentrate on the Add, Subtract, Multiply, and Divide methods of the ISimpleCalc interface because the user of a COM object doesn't have to worry about the implementation details of the component. Implementing the Add, Subtract, Multiply and Divide methods is very simple. Each method takes two numeric input parameters and passes the result in an output parameter. A method inside a COM object is expected to return an HRESULT value to indicate the success or failure of the method. In the following code example, you can see the implementation of the Add method inside the SimpleCalc COM component.

STDMETHODIMP CSimpleCalc::Add(int firstValue, int secondValue, int* pResult)
{
    *pResult = firstValue + secondValue;
    return S_OK;
}

The entire COM object, written in C++ by means of the Active Template Library (ATL), is available as part of this article's download code sample. The SimpleCalc COM object and its type library containing the description of the SimpleCalc COM object including its interfaces are compiled into Calculator.dll. If you are not interested in how to call COM objects inside .NET Compact Framework 1.0 applications, you can skip the next section of this article and continue to the section called Using COM Objects with the .NET Compact Framework 2.0.

Using COM Objects with the .NET Compact Framework 1.0

Because the .NET Compact Framework 1.0 does not support COM Interop, work is involved whenever you want to use existing COM objects inside a managed .NET Compact Framework 1.0 application. Because there is (limited) support for platform invoke, you can call unmanaged or native functions that reside in DLLs from within a managed application. For more information about platform invoke in the .NET Compact Framework 1.0, see the article An Introduction to P/Invoke and Marshaling on the Microsoft .NET Compact Framework.

If you want to use a COM object in a managed .NET Compact Framework 1.0 application, the only possibility you have is to make use of platform invoke. By using platform invoke, you can only call native functions inside DLLs. If you write a DLL in C++ with functions that wrap around all of the interfaces of the COM object you want to use, you have created a way to call into your COM object. However, there is one problem to be solved: because there is no managed method available to instantiate the COM object you want to use, you will have to do so in the DLL as well. Typically, when you are using Microsoft Visual Studio .NET 2003 with the .NET Compact Framework 1.0, you need a separate development environment, eMbedded Visual C++ version 4.0, to create the native wrapper DLL. However, with Visual Studio 2005, you can create both managed and native applications for smart devices, as long as your device runs Windows Mobile 2003-based software or later. You can even use Visual Studio 2005 to create .NET Compact Framework 1.0 applications for these devices.

Because Visual Studio 2005 also supports writing native C++ applications for smart devices, this article uses Visual Studio 2005 to show you how to create a native wrapper DLL around a COM object for those cases where you want to interoperate with a COM object inside a .NET Compact Framework 1.0 application. Using Visual Studio 2005, the first thing you need to do if you want to use an existing COM object inside a .NET Compact Framework 1.0 application is to create a new Visual C++ smart device project, as shown in Figure 2.

Click here for larger image

Figure 2. Visual Studio 2005 New Project wizard. Click the thumbnail for a larger image.

Stepping through the New Project wizard, you need to specify a number of settings. The project you will create must target the correct platform because, unlike managed code, native code is platform dependent. Right now, you should select the Windows Mobile 5.0 Pocket PC SDK as target platform SDK. You also need to specify the application type for the project. Because you will create a wrapper DLL for a COM object, the application type must be DLL. After you step through the wizard and set the correct options, Visual Studio 2005 creates all files necessary for your wrapper DLL, which is called CalculatorFlatInterface in this sample.

To use the SimpleCalc object, you must first initialize the COM library by using the API CoInitializeEx. After you finish using the SimpleCalc object, you must close the COM library by using the API CoUninitialize. Because you are using a wrapper DLL to continuously communicate with the SimpleCalc object, the DLL entry function is a good location to call CoInitializeEx and CoUninitialize. If you are using multiple COM wrappers inside your application, you should call these APIs during application initialization and application termination respectively. The following code shows how to initialize and close the COM library inside the DllMain function.

Note   If Windows Mobile 5.0 Pocket PC SDK does not appear as an option, this is an indication that the Windows Mobile 5.0 Pocket PC SDK has not yet been installed. The SDK can be downloaded from the Microsoft Download Center.
HRESULT hr;
BOOL APIENTRY DllMain (HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
            hr = CoInitializeEx(0, COINIT_MULTITHREADED);
            break;
        case DLL_THREAD_ATTACH:
            break;
        case DLL_THREAD_DETACH:
            break;
        case DLL_PROCESS_DETACH:
            CoUninitialize();
            break;
    }
        return TRUE;
}

After initializing the COM library, you can call into the SimpleCalc object. In the CalculatorFlatInterface wrapper DLL, you can now create a number of functions that call into the different methods that are exposed by the interfaces of the SimpleCalc object and that return the results of these calls to the calling application. The entire CalculatorFlatInterface wrapper DLL is available as part of this article's download code sample.

An important thing to think about is that C++ uses name mangling which significantly modifies the publicly visible function name to create unique names for each function—something that is necessary to support function overloading. To suppress name mangling, you need to precede function declarations in the wrapper DLL that you want to be available for platform invoke by managed code with extern "C". In the download code sample, you suppress name mangling by including extern "C" in the CALCULATORFLATINTERFACE_API macro definition.

#ifdef CALCULATORFLATINTERFACE_EXPORTS
#define CALCULATORFLATINTERFACE_API extern "C" __declspec(dllexport)
#else
#define CALCULATORFLATINTERFACE_API __declspec(dllimport)
#endif

Typically, you write a function in your wrapper DLL for each method of the SimpleCalc object you want to expose to a managed application, as shown in the following code.

CALCULATORFLATINTERFACE_API int Add(int iFirstValue, int iSecondValue, int* piResult)
{
    HRESULT         hrLocal;
    ISimpleCalc     *ISimpleCalc = NULL;
    int             iRetVal = -1;

    // Check if the call to CoInitializeEx in DllMain was succesful
    if(SUCCEEDED(hr))
    {
        // Instantiate the SimpleCalc object and retrieve its         // ISimpleCalc interface
        hrLocal = CoCreateInstance(CLSID_SimpleCalc, NULL, CLSCTX_INPROC_SERVER, IID_ISimpleCalc, (void**) &ISimpleCalc);
        if(SUCCEEDED(hrLocal))
        {
            ISimpleCalc->Add(iFirstValue, iSecondValue, piResult);
            ISimpleCalc->Release();
            iRetVal = 0;
        }
    }
    return iRetVal;
} 

At first sight, you might feel that this code is not too difficult to write, but you can see that a considerable amount of extra code is necessary to call into a COM object from managed code if you are creating a .NET Compact Framework 1.0 application. You have to know how to call a COM object from native code, and you have to keep in mind that the API you expose in the wrapper DLL can be called from managed code. Additional work might be necessary—especially if you have to pass complex structures as parameters—because of limitations in data marshalling between managed and native code.

In the preceding code, you can see that the SimpleCalc object is created each time a method in the wrapper DLL is called. This is not the most efficient way to use COM objects, and this way will only work properly if the COM object is stateless. However, it shows you all that is needed to create, call, and destroy the object in one single function. A more efficient way to use a COM object would be to provide additional wrapper functions to create the object and to destroy the object. The create function should pass an IntPtr back to the managed application and should be called before the first method in the COM object is used. The IntPtr can then be used as a reference to the live COM object and should be passed to each individual method call. The destroy function should be called when the object is no longer needed.

Things become even more complex if your COM object also has a connection point with which it can call back into an application. In the .NET Compact Framework 1.0, you cannot pass delegates to a native function by using platform invoke. So whenever a COM object contains entry points for callback functions, that functionality should be implemented entirely in the wrapper DLL. You should use another mechanism—for instance, a MessageWindow class—to pass information from the COM object back to the managed application through the wrapper DLL. In this article's download code sample, you will find an example of this mechanism as well. For more information about using the MessageWindow class, see the article Using the Microsoft .NET Compact Framework MessageWindow Class.

The Divide method that is part of the ISimpleCalc interface and that is implemented in the SimpleCalc object optionally makes use of a callback function. When the Divide method detects a division-by-zero error, it not only sets an error return value, but it also calls a callback function (when present) to pass a friendly error message back to the caller.

As shown earlier in Figure 1, the ISimpleCalcCallBack interface expects a ShowMsg function. This function acts as a callback function. It should have the following signature.

void ShowMsg (LPTSTR pMsg)

When the SimpleCalc object calls ShowMsg, it passes a Unicode string to it. The implementation of ShowMsg determines what to do with the passed string. Because a delegate to a ShowMsg implementation cannot be passed from managed code to native code, the wrapper DLL is responsible for providing the callback function, and it should find a way to pass the Unicode string back to the managed application. The following code example shows a wrapper function to call into the SimpleCalc object and to set a callback function.

CALCULATORFLATINTERFACE_API int Divide(int iFirstValue, int iSecondValue, int* piResult)
{
    HRESULT             hrLocal;
    ISimpleCalc         *ISimpleCalc = NULL;
    // Instantiate a new C++ object that implements the
    // ISimpleCalcCallBack interface
    ISimpleCalcCallBack *callBack = new CallBack(hWndManaged);
    int                 iRetVal = -1;

    // Check if the call to CoInitializeEx in DllMain was succesful
    if(SUCCEEDED(hr))
    {
        // Instantiate the SimpleCalc object and retrieve
        // its ISimpleCalc interface
        hrLocal = CoCreateInstance(CLSID_SimpleCalc, NULL, CLSCTX_INPROC_SERVER, IID_ISimpleCalc, (void**) &ISimpleCalc);
        if(SUCCEEDED(hrLocal))
        {
            // Pass the callback object to the Calculator
            ISimpleCalc->SetCallback(callBack);
            ISimpleCalc->Divide(iFirstValue, iSecondValue, piResult);
            // Make sure the callback object is removed from the             // SimpleCalc object if you no longer need it
            ISimpleCalc->ResetCallback();
            ISimpleCalc->Release();
            iRetVal = 0;
        }
    }
    delete callBack;
    return iRetVal;
}

Instead of calling only one method in the SimpleCalc object, the preceding code calls additional methods to set a callback function prior to calling into the Divide method. When the Divide method returns, the callback function is reset again. In this particular implementation, the callback function is set and reset again each time the Divide method is called.

The callback function could have been set once during initialization of the SimpleCalc object, but this download code sample shows how you can combine several calls into the SimpleCalc object from within one single wrapper function. The actual callback function is a method inside a Callback object that is part of the wrapper DLL. The following code example shows the ShowMsg method that is called back from inside the SimpleCalc object.

HRESULT CallBack::ShowMsg(LPTSTR msg)
{
    SendMessage(hWndDestination, WM_COM_CALLBACKDATA, 0, (LPARAM)msg);
    return S_OK;
}

This implementation of ShowMsg is very simple. The Unicode string that is passed as parameter is simply sent to a window by using a user defined message WM_COM_CALLBACKDATA. However, to be able to show the string inside a managed application, the managed application has to use a MessageWindow class to be able to expose a window handle and to act on the Microsoft Windows message that it receives from the CalculatorFlatInterface DLL. The window handle of the mainform of the managed application is passed to the constructor of the CallBack object of the CalculatorFlatInterface DLL.

After this long introduction about how to create a wrapper DLL to be able to use COM objects from inside managed code, there is one more thing you need to know: how to use the wrapper DLL inside a .NET Compact Framework 1.0 application.

Calling COM Objects with the .NET Compact Framework 1.0 and a Wrapper DLL

To be able to use the SimpleCalc object in combination with the wrapper DLL, you need to create a managed application by using Visual Studio .NET 2003 or Visual Studio 2005. To do so, you can simply create a new C# or Visual Basic .NET application. All code examples in this article are shown in C#, but the download code samples are available in Visual Basic .NET as well.

Figure 3 illustrates calling a COM component from a managed .NET Compact Framework 1.0 application.

Figure 3. Calling a COM component from a managed .NET Compact Framework 1.0 application

To begin, you can create a very simple user interface with four different buttons that correspond to the methods exposed by the wrapper DLL that calls into the SimpleCalc object. Figure 4 shows such an interface.

Click here for larger image

Figure 4. Managed application that calls into the SimpleCalc object. Click the thumbnail for a larger image.

The code to call functions in the wrapper DLL is implemented in the different button_Click event handlers. To get an idea about how to call functionality in the SimpleCalc object by means of the wrapper DLL, look at the following code example. It shows how to make use of platform invoke to add two values and receive the result. Note that the function Add in the CalculatorFlatInterface DLL is responsible for calling the SimpleCalc COM object's Add function that in turn will perform the requested computation.

[DllImport("CalculatorFlatInterface.dll")]
private extern static int Add(int firstValue, int secondValue, ref int result);

private void btnAdd_Click(object sender, EventArgs e)
{
    int result = 0;
    int firstValue = Convert.ToInt32(tbValue1.Text);
    int secondValue = Convert.ToInt32(tbValue2.Text);
    if (Add(firstValue, secondValue, ref result) == 0)
    {
        tbResult.Text = result.ToString();
    }
    else
    {
        tbResult.Text = "???";
    }
}

The amount of work to create a wrapper DLL as previously described is not trivial. Therefore, third-party solutions, like CFCOM from Odyssey Software, are available to automate this work. Also, third-party wrapper DLLs, like PocketOutlook In The Hand, are available to wrap around a particular COM object.

The fact that you need to use a separate DLL to be able to use COM objects also has an impact on the memory consumption of a Windows Mobile–based device. This impact is especially relevant if you want to use several COM objects and thus need several wrapper DLLs as well, as is explained in the article Windows CE.NET Advanced Memory Management. Luckily, the .NET Compact Framework 2.0 has much better support for interoperability between managed code and native code, including COM Interop and the possibility to pass managed functions—by means of delegates—to native code, with which you can call back from native code into managed code.

Using COM Objects with the .NET Compact Framework 2.0

With the arrival of the .NET Compact Framework 2.0, which ships with Visual Studio 2005, interoperating with existing COM objects becomes much easier because the .NET Compact Framework 2.0 supports COM Interop. For a developer, this means that you no longer need to create your own wrapper DLL in native code to communicate with a COM object. Instead, you can directly communicate with the COM object from within managed code. Of course, this direct communication has a lot of advantages:

  • You have less code to write.
  • You don't have to know about low-level COM details.
  • You can call methods inside COM objects as though they were managed methods.
  • You have full IntelliSense technology available for COM object methods.

To use existing COM objects from managed code, you can add a reference to your COM object in your managed project, as shown in Figure 5. You can add references to files of type DLL, executable, and type library—a binary file that includes information about interfaces, types, and objects that a COM object exposes. Visual Studio 2005 then creates an interop assembly and automatically adds the assembly as a reference. The interop assembly contains managed types that allow you to program against unmanaged COM types. The interop assembly does not communicate directly with the COM object. Instead the Common Language Runtime COM Interop layer inserts a special proxy between the caller and the COM object, the so-called runtime callable wrapper. Thanks to the managed type definitions in the interop assembly, a COM object looks exactly like a managed object to a managed application.

Figure 5. COM Interop in version 2.0 of the .NET Compact Framework

To make use of the SimpleCalc object inside a managed application, you can simply create a new smart device project in Visual Studio 2005. Because all code examples in this article are in C#, you should choose a C# smart device project. Be sure to target the Windows Mobile 5.0 Pocket PC.

Note   To be able to use the SimpleCalc COM object, you must download and install the download code sample that comes with this article.

To use the SimpleCalc object, you should create a user interface according to Figure 6. Be sure to give the different UI controls names as shown.

Click here for larger image

Figure 6. Managed application that will use COM Interop. Click the thumbnail for a larger image.

You also need to add button_Click event handlers for each of the buttons by double-clicking them in the form's Design view.

Note   You have to switch back to Design view each time you add an event handler.

To be able to use the SimpleCalc object, you can simply add a reference to the DLL file containing the Calculator type library that provides a description of the COM object and its interfaces, as shown in Figure 7. To add a reference you need to perform the following actions:

  • Right-click the project (CalculatorAppV2) in Solution Explorer
  • Click Add Reference
  • Browse to the folder where the SimpleCalc object's DLL file and type library (.tlb) file are located
  • Select the type library file.
Note   The actual path of the SimpleCalc object depends on where you installed it.

Click here for larger image

Figure 7. Adding a reference to the Calculator type library. Click the thumbnail for a larger image.

When you click OK, Visual Studio 2005 creates an interop assembly that you can use to access methods of the SimpleCalc object. To find out what methods are available in the interop assembly, you can change to Class view, expand the Project References, and explore the CalculatorLib assembly, as shown in Figure 8. If you select the ISimpleCalc interface, you will see all methods that it exposes.

It is also possible to use a tool called tlbimp (type library importer) to generate an interop assembly from a type library. You can find Tlbimp in the .NET Framework Software Developer Kit. There are advantages in using tlbimp over adding a reference to a type library in Visual Studio 2005. By using tlbimp, you have the possibility to sign the resulting assembly and place it in the global assembly cache. This allows you to share the interop assembly among multiple applications. Because the SimpleCalc object is not meant to be shared over several different applications, adding a direct reference to its type library as shown in Figure 7 makes perfect sense.

Click here for larger image

Figure 8. SimpleCalc object's interop assembly in Class view. Click the thumbnail for a larger image.

Before being able to use the methods of the SimpleCalc object, you have to provide using directives for the following namespaces in your managed application.

using System.Runtime.InteropServices;
using CalculatorLib;

The "System.Runtime.InteropServices" namespace contains data types to support COM interop and platform invoke. The "CalculatorLib" namespace contains managed types to expose the SimpleCalc COM object class and interfaces. After providing the using directives for these namespaces, you can now use the methods from SimpleCalc as if it was a managed object, as you will see when you are adding functionality to the button_Click event handlers that you created for the CalculatorAppV2 application. Note that the following code example shows only one of the button_Click handlers. Implementing the other click handlers is similar.

namespace CalculatorAppV2
{
    public partial class Form1 : Form
    {
        private ISimpleCalc calculator = new SimpleCalc();

        public Form1()
        {
            InitializeComponent();
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            int firstValue = Convert.ToInt32(tbValue1.Text);
            int secondValue = Convert.ToInt32(tbValue2.Text);
            int result = calculator.Add(firstValue, secondValue);
            tbResult.Text = result.ToString();
        }
    }
}

In the preceding code, a private variable of type ISimpleCalc is assigned to a new instance of type SimpleCalc. This is, in fact, an instantiation of the SimpleCalc COM object. If you entered the preceding code yourself, you can also see that the Add method for the variable of type SimpleCalc just looks like a managed object and even has full IntelliSense support.

Figure 9 illustrates calling a COM component from a managed .NET Compact Framework 2.0 application.

Figure 9. Calling a COM component from a managed .NET Compact Framework 2.0 application

If you compare Figure 9 with Figure 3, you can see immediately that you have to write much less code yourself to use an existing COM object. As a matter of fact, you only need to concentrate on your own application's code. Visual Studio 2005 provides you with the interface to the COM object. If you compare the managed call to the Add method to the Add method as defined in the SimpleCalc COM object (see the download code sample), you will see a remarkable difference.

The signature of the managed Add method is as follows.

int Add (int firstValue, int secondValue);

The signature of the SimpleCalc object's Add method is as follows.

HRESULT Add ([in]int firstValue, [in]int secondValue, [out, retval]int* pResult);

The translation of the Add method in the SimpleCalc object to the managed method happened automatically when you added a reference to the type library file.

To recap, all it takes to use an existing COM object in a managed application is adding a reference to the COM object to generate an interop assembly. After that, you can use the COM object, just as you would use a managed object that exists in another assembly.

However, there are some restrictions about using COM objects in the .NET Compact Framework 2.0. You cannot refer to an existing ActiveX control from within a managed project because a managed application can not act as an ActiveX host. There are (unsupported) ways to host ActiveX controls inside managed .NET Compact Framework 2.0 applications, but they are beyond the scope of this article. Alex Feinman's article, "Hosting ActiveX Controls in the .NET Compact Framework 2.0" covers this topic, but it will publish on MSDN in November 2005. Unlike in the full .NET Framework, the .Net Compact Framework 2.0 does not support hosting the runtime from native code. As a result, you cannot have an existing native application call into managed code. However, you can call back into managed code from within a COM object by using COM callable wrappers (CCWs) if the COM object is being hosted by a managed application. A CCW is created by the Common Language Runtime and is invisible to other classes in the .NET Compact Framework. An important function of a CCW is to marshal calls between native and unmanaged code, thus allowing a COM object to call managed functions that are exposed through COM interfaces.

Calling a Managed Method from Inside a COM Object

Even though there is only limited CCW support, a COM object can expose managed interfaces that expect a callback function. Since it is possible with the .NET Compact Framework 2.0 to pass delegates to native code, you can also pass a delegate to an exposed interface, resulting in the possibility for a COM object to call back into managed code.

In the SimpleCalc COM object, you will find this scenario implemented. SimpleCalc has two different interfaces: ISimpleCalc and ISimpleCalcCallBack (as shown in Figure 10).

Figure 10. Calling .NET objects from COM

You use the ISimpleCalcCallBack interface to call a method outside the actual COM object. The way you will use this functionality in the sample code is to allow the COM object to pass a friendly error message in case a divide-by-zero error is trapped in the Divide method that is part of the ISimpleCalc interface. Before being able to use the callback interface in the SimpleCalc object, you have to pass a reference to an implementation of the callback interface to SimpleCalc. The implementation of the ISimpleCalcCallBack callback interface is provided by the managed MsgNotification class.

The following code shows the managed object that will be called from COM.

public class MsgNotification : ISimpleCalcCallBack
{
    public void ShowMsg(string message)
    {
        MessageBox.Show(message);
    }
}

To use the callback interface inside the SimpleCalc COM object, you have to pass a reference to the implementation of the callback interface from your managed application. The ISimpleCalc interface provides the SetCallback method for just this purpose, as shown in the following code. For example, you can call the SetCallback method in the main form's constructor of the CalculatorAppV2 managed application to pass the managed implementation of the ISimpleCalcCallBack interface, which is the MsgNotification class.

public partial class Form1 : Form
{
    private ISimpleCalc calculator = new SimpleCalc();
    private MsgNotification msgNotification = new MsgNotification();

    public Form1()
    {
        InitializeComponent();
        // Use a method of ISimpleCalc to pass a callback object to
        // the SimpleCalc COM object
        calculator.SetCallback((ISimpleCalcCallBack)msgNotification);
    }

    // Remaining functionality of class Form1
}

The preceding code is all you need to pass an implementation of the callback interface to the SimpleCalc COM object. Of course, the COM object determines when to actually call back into the method provided by the interface. In the sample SimpleCalc object, the callback interface's method will be invoked when SetCallback is executed to send a notification that the callback interface is ready to use.

The SimpleCalc object will also use the callback interface's method when a divide-by-zero error is trapped in the SimpleCalc object's Divide method. Of course, calling the Divide method inside managed code does not give any indication that the callback interface will be used; it is entirely up to the SimpleCalc object to determine when to use the callback interface. In fact, calling the Divide method of SimpleCalc from inside managed code looks exactly like calling any other method in SimpleCalc, as shown in the following code.

private void btnDivide_Click(object sender, EventArgs e)
{
    int firstValue = Convert.ToInt32(tbValue1.Text);
    int secondValue = Convert.ToInt32(tbValue2.Text);
    try
    {
        int result = calculator.Divide(firstValue, secondValue);
        tbResult.Text = result.ToString();
    }
    catch (ExternalException)
    {
        tbResult.Text = "Undefined";
    }
}

One thing you will note is that the btnDivide_Click handler of CalculatorAppV2 contains a try…catch block to handle exceptions. Particularly, an ExternalException exception is caught. The runtime automatically throws this exception when the method called inside the COM object returns anything other than S_OK. If the Divide method in the SimpleCalc object traps a divide-by-zero error, it will return an E_FAIL value, causing ExternalException to be thrown.

It is always a good idea to surround calls to COM objects by a try…catch block, especially because you are not always aware of the actual implementation of the COM object that you are using. The ExternalException exception contains an ErrorCode property that stores an integer value (HRESULT) that identifies the error.

Using COM Interop to Access Pocket Outlook

So far, you have learned how to use your own existing COM objects in a managed .NET Compact Framework 2.0 application. However, with COM Interop support, you also have easy access to Pocket Outlook by means of the Pocket Outlook Object Model (POOM). POOM is a COM component that exposes functionality of Pocket Outlook, so you can use its functionality in your own managed or native application.

In the .NET Compact Framework 1.0, using POOM was not straightforward. You either had to create a wrapper DLL in native code that flattened the POOM component or use a third-party library that flattened the POOM component for you. As you will see in this section, with the .NET Compact Framework 2.0, using POOM inside a managed application is now relatively easy because of COM Interop.

To see POOM in action, you will need to create a new managed application. With the code examples presented in this article, you will be able to browse and modify the contacts database from within your own application, and you will be able to make appointments for a particular contact. Of course, Pocket Outlook has much more functionality, but working through this section of the article should help you understand how to use Pocket Outlook inside your own application.

In the code examples presented in this article, you are not going to concentrate on a fancy user interface, but you will concentrate on using POOM inside a managed application. Figure 11 shows the user interface for a .NET Compact Framework 2.0 application that targets a Pocket PC 2003 Second Edition emulator.

Click here for larger image

Figure 11. Managed application making use of POOM. Click the thumbnail for a larger image.

The reason to choose the Pocket PC 2003 SE emulator instead of the Windows Mobile 5.0–based Pocket PC emulator is that you typically access Pocket Outlook inside a Windows Mobile 5.0–based device by using the new Windows Mobile 5.0 managed APIs, as explained later in this article. However, those APIs are available only on Windows Mobile 5.0–based devices. If you want to use Pocket Outlook in applications that target other devices, you need to take the approach that is described here. Note that the device must be capable of running .NET Compact Framework 2.0 applications to be able to use COM Interop. Devices that can run .NET Compact Framework 2.0 applications are devices that run one of the following platforms:

  • Windows Mobile 5.0
  • Windows Mobile 2003 software for Pocket PCs
  • Windows Mobile 2003 Second Edition software for Pocket PCs
  • Generic Windows CE 5.0

If you don't want to enter all of the code necessary for this application yourself, the application is also part of this article's download code sample. The code sample is available in both C# and Visual Basic .NET.

If you want to enter all of the code for the application yourself, you should change the names of the four buttons according to Figure 11. The next thing you need to do is add a Form_Load event handler by clicking somewhere on the form outside the controls, and then clicking the Events button on the toolbar that is above Properties. If you now locate the Load event and double-click it, Visual Studio will generate a Form1_Load event handler for you. You should repeat the same action to add a Form_Closing event handler to the application. Finally, you need to add click event handlers for all four buttons by double-clicking them in the Form1.cs Design window. Note that you have to switch back to Design view each time you double-click a button. For the time being, you have finished creating the user interface part of the UsingPOOM application.

The easiest way to call a COM component from managed code (thus also POOM) is to start with a type library. Unfortunately, the type library for POOM is not included in either the Pocket PC 2003 or Smartphone 2003 SDKs, so you'll have to build your own. Fortunately, building the type library is easy. You can create a type library for POOM by using the Microsoft Interface Definition Language compiler (midl.exe). You typically need to complete this step for all COM components that you want to access from inside a managed application and that don't have a type library available.

The interface definition file for POOM is pimstore.idl. It does not ship with the Pocket PC 2003 SDK, so you have to download it separately from Windows Mobile Team blog.

Assuming you have downloaded pimstore.idl and stored it in the Include folder of the Pocket PC 2003 SDK that is installed automatically when you installed Visual Studio 2005, you can create a type library for pimstore.idl at a command prompt. To make sure that you correctly set all environment variables that enable you to use particular command-line tools that come with Visual Studio 2005, you need to open a Visual Studio 2005 command prompt. On the Windows XP Start menu, point to All Programs, point to Microsoft Visual Studio 2005, point to Visual Studio Tools, and then click Visual Studio 2005 Command Prompt.

In the command prompt window that opens, you need to change the working directory to the directory in which you stored the pimstore.idl file. To create a type library for POOM, you can simply type the following command.

midl pimstore.idl <Enter>

Carrying out the preceding command creates a type library file for you with the name pimstore.tlb. If midl shows warnings with numbers 2400 and 2401, you can simply ignore them. They are generated because some of the interface definitions in pimstore.idl are specified with an "optional" keyword that is already implied by another keyword in the interface definition (defaultvalue). The warnings have no impact on the generated type library.

The next step is to add a reference to pimstore.tlb in your Visual Studio 2005 managed project. If you have successfully added this reference to your project (as shown in Figure 12), you now should see a newly added reference to PocketOutlook in the Solution Explorer of your managed project. If you now open the class view, you can expand the PocketOutlook reference and examine the interfaces, objects, methods, and properties that are available for use inside your managed application.

Click here for larger image

Figure 12. Successfully imported the pimstore.tlb type library. Click the thumbnail for a larger image.

Now that you have created a type library and added a reference to it in your managed project, you are ready to use functionality from Pocket Outlook inside your application. The first thing to do in your application is add a number of namespaces, particularly the "System.Runtime.InteropServices" and "PocketOutlook" namespaces. The latter is available to you because you added a reference to the pimstore type library to your project. To be able to access Pocket Outlook methods inside your application, you also need to create a variable of type ApplicationClass, which is part of the PocketOutlook namespace. This type encapsulates Pocket Outlook functionality. You can think about it as your connection to Pocket Outlook.

To be able to use functionality from Pocket Outlook you need to log on to it. When you finish using Pocket Outlook, you need to log off from it again. In the sample application, you will use Pocket Outlook throughout the entire lifetime of the application, so a good place to log on to Pocket Outlook is in the Form.Load event handler. The Form.Closing event handler can be used to log off from Pocket Outlook.

The following code shows how to use the ApplicationClass type to log on to and log off from Pocket Outlook.

private ApplicationClass outlookApp;

private void Form1_Load(object sender, EventArgs e)
{
    outlookApp = new ApplicationClass();
    outlookApp.Logon(0);
}

private void Form1_Closing(object sender, CancelEventArgs e)
{
    outlookApp.Logoff();
}

You can now use functionality from Pocket Outlook. Keep in mind that you are using POOM in combination with COM Interop to be able to communicate with Pocket Outlook. For instance, showing all contacts that are currently available in the Pocket Outlook contacts database is simply a matter of retrieving the location where Pocket Outlook contacts are stored and walking through the Items collection to retrieve individual contacts. You can, for instance, create in your application a separate method that retrieves all contact information and displays that information in a list box. When you call that method inside the Form1_Load event handler, after logging on to Pocket Outlook, you will initially fill the list box of your application (see Figure 11) with contact information from Pocket Outlook.

The following code shows how to retrieve contacts from Pocket Outlook.

private void ShowContacts()
{
    // Fill the list box with contact information from Pocket Outlook
    Folder contactsFolder =
        outlookApp.GetDefaultFolder(OlDefaultFolders.olFolderContacts);

    PocketOutlook.Items contacts = (PocketOutlook.Items)contactsFolder.Items;

    listBox1.BeginUpdate();
    listBox1.Items.Clear();
    foreach (ContactItem contact in contacts)
    {
        string name = contact.FirstName + " " + contact.LastName;
        listBox1.Items.Add(name);
    }
    listBox1.EndUpdate();
}

As you can see in the preceding code, accessing contacts from Pocket Outlook is simple and straightforward. Accessing appointments and tasks is just as easy. Retrieving detailed information—for instance, from ContactItem data—is also very simple. In this article's download code sample, you will find separate forms to display contact details, to add a new contact, and to create appointments. To get a sense of how easy it is to add new contact information to the Pocket Outlook contacts database by using a managed application, look at the following code.

private void AddContact()
{
    // Locate the Contacts folder and add a new empty contact to the
    // collection
    Folder contactsFolder =
        outlookApp.GetDefaultFolder(OlDefaultFolders.olFolderContacts);
    PocketOutlook.Items contacts = (PocketOutlook.Items)contactsFolder.Items;
    ContactItem newContact = (ContactItem)contacts.Add();

    // Fill in contact details and save the newContact information
    newContact.FirstName = firstName;
    newContact.LastName = lastName;
    newContact.CompanyName = companyName;
    newContact.BusinessTelephoneNumber = phoneNumber;
    newContact.Email1Address = emailAddress;
    newContact.Save();
}

As you can see, adding new contact information is a straightforward operation. First, you have to access the Pocket Outlook Contacts folder to get access to the collection of contacts. Then, you can simply add a new item to the collection, fill in the contact details, and save the newly added contact in the collection.

In the download code sample, you will see a similar approach to store new contact information or modify existing contact information. The only difference is that the download code sample uses a separate form with a number of text boxes to enter or modify contact information.

Because Pocket Outlook data is stored in a consistent collection architecture, adding a new appointment is a similar operation to adding a new contact. The only difference is that appointment data is stored in a different folder, and of course the properties of appointments differ from contact properties. To add a new appointment to Pocket Outlook, look at the following code.

private void AddAppointment()
{
    // Locate the Appointments folder and add a new empty appointment
    // to the collection
    Folder apptsFolder =
        outlookApp.GetDefaultFolder(OlDefaultFolders.olFolderCalendar);
    PocketOutlook.Items appts = (PocketOutlook.Items)apptsFolder.Items;
    AppointmentItem newAppointment = (AppointmentItem)appts.Add();

    // Fill in appointment details and save the newAppointment     
    // information
    newAppointment.Subject = subject;
    newAppointment.Start = DateTime.Now();
    newAppointment.Save();
}

To delete an existing Pocket Outlook contact, you select the contact from the contact collection, and then you call the Delete method, as shown in the following code.

private void DeleteContact()
{
    Folder contactsFolder =
        outlookApp.GetDefaultFolder(OlDefaultFolders.olFolderContacts);
    PocketOutlook.Items contacts = (PocketOutlook.Items)contactsFolder.Items;

    // Assume that contact information is shown in a list box and the     // currently selected item should be deleted
    ContactItem ci = (ContactItem)contacts.Item(listBox1.SelectedIndex + 1);
    ci.Delete();

    // Refresh the list box's contact information (the deleted contact     // should no longer be visible)
    ShowContacts();
}

Even if you are not going to use your own application to maintain Pocket Outlook data, having access to Pocket Outlook data can add a lot of functionality to your managed application. Suppose you are creating a mobile line-of-business application and you want to store sales information that is stored in your contacts database. Instead of having to reenter the name of the client, you can simply access the contact information in Pocket Outlook to retrieve the name and any other information you need for that particular client. This ability reduces the amount of data entry for the user, which is very important in a mobile application, and it helps ensure that client information is consistent.

Using the New Windows Mobile 5.0 Managed APIs to Access Pocket Outlook

In the previous section of this article, you saw how the .NET Compact Framework 2.0 contains great functionality to make interoperability with native code easier and more complete. Because of COM Interop, using existing COM objects from inside a managed application is relatively easy. If you are developing applications that exclusively target Windows Mobile 5.0–based devices, it will even be easier to access a number of native objects by means of managed APIs that ship as part of the Windows Mobile 5.0 SDKs.

Particularly, using managed Windows Mobile 5.0 APIs will make using Telephony and Pocket Outlook very easy. For example, you can create a managed application, similar to the one you saw in the previous section of this article. Instead of using COM Interop, this time you will use the Windows Mobile 5.0 managed APIs. You do not need to create a type library to be able to use Pocket Outlook, and you do not need to use the COM–based Pocket Outlook Object Model. Instead, you can simply add a reference to the managed API you need and start using it. All managed APIs of Windows Mobile 5.0–based devices are available through the Microsoft.WindowsMobile class library.

To compare Windows Mobile 5.0 managed APIs with .NET Compact Framework COM Interop, you need to create a new managed project, this time targeting a Windows Mobile 5.0–based Pocket PC. You now should create a user interface similar to the one shown earlier in Figure 11. The next step is to implement exactly the same functionality as you had in the previous sample. Again, the complete source code is available in the download code sample in either C# or Visual Basic .NET.

To be able to create and access Pocket Outlook data items like contacts, appointments, and tasks, you need to add a reference to "Microsoft.WindowsMobile.PocketOutlook" in your managed project. The next thing to do in your application is add the "Microsoft.WindowsMobile.PocketOutlook" namespace.

To be able to access Pocket Outlook by using Windows Mobile 5.0 managed APIs inside your application, you need to create an instance of an OutlookSession object, as shown in the following code. Instantiating this object automatically establishes a connection between your application and Pocket Outlook.

OutlookSession olSession;

private void Form1_Load(object sender, EventArgs e)
{
    olSession = new OutlookSession();
}

private void Form1_Closing(object sender, CancelEventArgs e)
{
    olSession.Dispose();
}

If you compare the preceding code to the earlier code example about using the ApplicationClass type, you will see that they are almost identical. Using the Windows Mobile 5.0 managed APIs is a little easier because there is no need to separately log on to and log off from an ApplicationClass object. Instead, you are now dealing with an OutlookSession state object that connects to Pocket Outlook when it is instantiated.

To clean up resources after using the OutlookSession object, you should call the Dispose method when you no longer need it. When you have an OutlookSession object instantiated, you can use functionality from Pocket Outlook. For instance, showing all contacts that are currently available in the Pocket Outlook contacts database is simply a matter of retrieving the location where Pocket Outlook contacts are stored and walking through a contacts item collection to retrieve individual contacts. You can, for instance, create in your application a method that retrieves all contact information and displays that information in a list box. When you call that method inside the Form1_Load event handler, after logging on to Pocket Outlook, you will initially fill the list box of your application (see Figure 11) with contact information from Pocket Outlook.

The following code shows how to retrieve contacts from Pocket Outlook by using the Windows Mobile 5.0 managed APIs.

private void ShowContacts()
{
    ContactFolder contactFolder = olSession.Contacts;

    listBox1.BeginUpdate();
    listBox1.Items.Clear();

    foreach (Contact contact in contactFolder.Items)
    {
        string name = contact.FirstName + " " + contact.LastName;
        listBox1.Items.Add(name);
    }

    listBox1.EndUpdate();
}

If you compare the preceding code to the earlier POOM based–code about retrieving contacts from Pocket Outlook, you can see that accessing contacts from Pocket Outlook is even simpler when you use the new Windows Mobile 5.0 managed APIs. Accessing appointments and tasks is as easy. Retrieving detailed information—for instance, from contact data—is also very simple. In the download code sample, you will find separate forms to display contact details, to add a new contact, and to create appointments. To get a sense of how easy it is to add new contact information to the contacts database by using the Windows Mobile 5.0 managed APIs, look at the following code.

private void AddContact()
{
    // Locate the Contacts folder and add a new empty contact to the
    // collection
    ContactFolder cFolder = olSession.Contacts;
    Contact newContact = cFolder.Items.AddNew();

    // Fill in contact details and save the newContact information
    newContact.FirstName = firstName;
    newContact.LastName = lastName;
    newContact.CompanyName = companyName;
    newContact.BusinessTelephoneNumber = phoneNumber;
    newContact.Email1Address = emailAddress;
    newContact.Update();
}

As you can see, adding new contact information is a simple, straightforward operation. First, you have to access the Pocket Outlook Contacts folder to get access to the collection of contacts. Then, you can simply add a new Contact to the collection, fill in the contact details, and save the newly added item data in the collection by using the Update method.

In the download code sample, you will see a similar approach to store new contact information or modify existing contact information. The only difference is that the sample application uses a separate form with a number of text boxes to enter or modify contact information.

If you compare the preceding code—using the Windows Mobile 5.0 managed APIs—to the earlier code—using POOM—to add contact information, you will see that the majority of the code is identical. The biggest difference is that you now use a Contact object, instead of a ContactItem object, to refer to contact information. Another difference is that you use an Update method, instead of a Save method, to store changes to a contact.

Because Pocket Outlook data is stored in a consistent collection architecture, adding a new appointment is a similar operation to adding a new contact. The only difference is that appointment data is stored in a different folder, and of course the properties of appointments differ from contact properties. To add a new appointment to Pocket Outlook, look at the following code.

private void AddAppointment()
{
    // Locate the Appointments folder and add a new empty appointment
    // to the collection
    AppointmentFolder aFolder = olSession.Appointments;
    Appointment newAppointment = aFolder.Items.AddNew();

    // Fill in appointment details and save the newAppointment
    // information
    newAppointment.Subject = subject;
    newAppointment.Start = DateTime.Now();
    newAppointment.Update();
}

Because the Windows Mobile 5.0 managed APIs use strongly typed collections (like AppointmentCollection or ContactCollection), you do not need to cast to a specific type when accessing members of a collection as you did when accessing Pocket Outlook through COM Interop.

To delete an existing Pocket Outlook contact, you select the contact from the contact collection, and then you call the Delete method, as shown in the following code.

private void DeleteContact()
{
    ContactFolder cFolder = olSession.Contacts;
    cFolder.Items[listBox1.SelectedIndex].Delete();

    // Refresh the list box's contact information (the deleted contact
    // should no longer be visible)
    ShowContacts();
}

Using the Windows Mobile 5.0 Managed APIs makes the process of using Pocket Outlook functionality even easier than using POOM in combination with COM Interop.

Conclusion

With the .NET Compact Framework 2.0, you can use existing COM objects in managed applications. Even though there are a few limitations (no hosting of the common language runtime and no out-of-the-box support of ActiveX controls), COM Interop is a great extension to the .NET Compact Framework. Using existing COM objects inside managed applications enables you to reuse existing code, thus helping to make sure that previous investments can still be used with a minimal amount of work. The new Managed APIs that are exclusively available for Windows Mobile 5.0–based devices create wrappers around existing functionality. They make using that particular functionality extremely simple, for example giving you easy access to Pocket Outlook.

In combination with Visual Studio 2005, the .NET Compact Framework gives you a truly high-productivity development environment. You don't need to spend time creating wrapper DLLs to be able to use COM objects. Instead, you can concentrate on the functionality that is needed for your own application, with the possibility to extensively reuse existing COM objects and native DLLs.

Show:
© 2014 Microsoft