MobileMixing Managed and Unmanaged Code in Your Windows Mobile Applications

Mixing Managed and Unmanaged Code in Your Windows Mobile Applications

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

What Is This Article All About?

The Compact Framework has become rich and powerful enough to be used in a wide range of programming tasks on the Windows Mobile platform. You can achieve most things you normally implemented in C++ before. Besides, there is a lot of legacy code you might have to interface with. In either case, you need to use unmanaged code at some point. This article illustrates some common recipes that help you make this process easier. You can find lots of samples on the web.

A few common tasks you usually want to perform are:

  • Declare an unmanaged function or data type (you can add here COM interfaces too) in managed code
  • Pass parameters from managed code to a declared function
  • Retrieve data back from unmanaged code

Most of your day-to-day programming needs should be covered by those topics. The following sections will guide you through. My goal is to give you minimal, ready-to-use recipes rather than to duplicate SDK documentation, so I will not describe all possible options. You can always consult your favorite help source for more details.

Simple Data Types and Method Declarations

The very first thing you will write is a declaration of a function from the unmanaged DLL you want to call in your own program. CF.NET has PInvoke mechanism and provides a System.Runtime.InteropServices namespace that contains of a number of classes to help you make such declarations easier. Depending on the programming language you work with, you might write something like this:

' VB.NET
Private Declare Function GetModuleFileName Lib "coredll" _
   Alias "GetModuleFileNameW" _
   (ByVal hModule As IntPtr, _
   <MarshalAs(UnmanagedType.LPWStr)> _
   ByVal lpFileName As String, ByVal nSize As UInteger) As UInteger

or

// C#
[DllImport("coredll.dll", EntryPoint = "GetModuleFileNameW", _
   SetLastError = true)]
private static extern uint GetModuleFileName(IntPtr hModule,
   [MarshalAs(UnmanagedType.LPWStr)]
   string lpFileName,
   uint nSize);

Each declaration is used to call a Win32 API:

DWORD GetModuleFileName(HANDLE hModule, LPTSTR lpszFileName, _
   DWORD dwSize);

So, in this simple case you call it, for example in C#, as:

private void button1_Click(object sender, EventArgs e)
{
   string path = new string((char)0x20,255);
   GetModuleFileName((IntPtr)null, path, (uint)path.Length);
   int slash = path.LastIndexOf('');
   if (slash > -1)
      path = path.Remove(slash, path.Length - slash);
   textBox1.Text = path;
}

The VB sample is a carbon copy of the snippet above:

Private Sub button1_Click_1(ByVal sender As System.Object,
   ByVal e As System.EventArgs) Handles button1.Click

   Dim path As String = New String(Chr(&H20), 255)
   GetModuleFileName(0, path, path.Length)

   Dim slash As Integer = path.LastIndexOf("")
   If slash > -1 Then
      path = path.Remove(slash, path.Length - slash)
   End If
   textBox1.Text = path
End Sub

This is pretty simple, isn’t it? All you need to keep in mind is to match unmanaged function signature. In case you were mistaken, you will get a ‘NotSupported’ exception. The same is true when you declare function parameters or return code that is not supported by the CF.NET framework; for example, trying to pass a string by reference (which might be something like LPWSTR* in unmanaged code). Usually MSDN provides much more information about data types and how to bounce parameters back and forth, so I don’t dwell on this too long.

Manual Parameters Marshalling

Although CF.NET takes care about suitable marshalling automatically when you pass simple types or even structures without reference types, there are cases where it is just not enough. For more complex cases, you will be required to do it manually. As you may guess, the Marshall class provides all the means for effective interactions between managed and unmanaged memory. Prior to CF.NET 2.0, with the lack of COM Interop support and many other things, Marshall had about a dozen methods for reading from and writing to unmanaged memory space as well as few informational functions. CF.NET 2.0 and 3.X extend it towards its desktop counterpart. Now, you have a powerful mechanism to utilize.

The obvious practical task you might face is when an unmanaged component has to allocate memory and return it to a managed party. Consider a slightly out-of-date example; nevertheless, it is quite a good illustration for the technique I’m going to discuss here. Until version 2.0, the Compact Framework had no support for the Pocket Outlook Object Model (POOM). Thus, if you needed, for instance, to enumerate an email account on a PDA with VS2003, C# or VB.NET code had to call to native C++ wrappers to implement a functionality that takes only a dozen lines of code in the recent CF.NET 2.0. Anyway, take a look at one possible solution. I will base it on the code from one of my previous articles; it deals with CEMAPI. What you are trying to achieve is really simple: Query all defined email accounts on a Pocket PC device and show them in a ListBox control. Your C++ wrapper will do the job for the C# component and return requested data in the buffer it allocates in unmanaged space. You will use IntPtr as a parameter type; iy is suitable for many other cases as well. Your steps might be as follows:

  • Pass the IntPtr parameter by reference; in other words, it will be pointer to pointer in C++.
  • Do all required steps in unmanaged C++ DLL, allocate the appropriate memory, and fill it in.
  • In managed C# code, iterate through returned data.
  • Release previously allocated memory.

Start with the C++ part. You define your method as the following:

EXTERN_C int __stdcall GetEMailAccounts(int* nCount,
   TCHAR** ppAccounts)
{
   CMAPISession session;
   HRESULT hr = 0;

   // Log on to PocketOutlook
   if ( !session.Logon() )
   {
      *nCount = 0;
      return 0;
   }

   CStringArray saAccounts;
   if ( SUCCEEDED(hr = session.EnumStores(saAccounts)) )
   {
      *nCount = saAccounts.GetSize();
      if ( *nCount )
      {
         *ppAccounts = (TCHAR*)CoTaskMemAlloc((*nCount)*sizeof_
         (TCHAR)*MAX_PATH);
         memset(*ppAccounts,0,(*nCount)*sizeof(TCHAR)*MAX_PATH);
         TCHAR *pStr = *ppAccounts;
            int nStrLen = 0;
            ULONG ulMemSize = 0;
         for (int i = 0; i < *nCount; i++)
         {
            _tcscpy(pStr,saAccounts[i]);
            nStrLen = saAccounts[i].GetLength() + 1;
            pStr += nStrLen;
            ulMemSize += nStrLen * sizeof(TCHAR);
         }
         *ppAccounts = (TCHAR*)CoTaskMemRealloc(*ppAccounts,ulMemSize);
         return *nCount;
      }
      else
      {
         return 0;
      }
   }
   else
   {
      return 0;
   }
}

In this code snippet, you enumerate existing accounts, allocate memory for maximum capacity, and fill in the information. At the end, the code reallocates the buffer to its real size. Now, see how this function is called from C#:

[DllImport("Sample.dll", EntryPoint = "GetEMailAccounts", _
   SetLastError = true)]
private static extern int GetEMailAccounts(ref int cCount, _
   ref IntPtr accounts);
...
IntPtr ptr = IntPtr.Zero;
int nCount = 0;
int rc = GetEMailAccounts(ref nCount, ref ptr);
IntPtr ptrAccounts = ptr;
int i = 0;
do
{
    string str = Marshal.PtrToStringUni(ptrAccounts);
    ptrAccounts = (IntPtr)((int)ptrAccounts + 2 * (str.Length + 1));
    listBoxAccounts.Items.Add(str);
}
while (++i < nCount);
Marshal.FreeCoTaskMem(ptr);

Call the function, get the next string, use pointer arithmetic to loop through returned buffer—bingo! Just as simple as that. The same technique is applicable for more complex data such as arrays of structures. Well, in the worst case you always can launch the Read/Write functions of Marshall class to manipulate the memory.

CF.NET and COM Interop

As I said before, CF.NET 2.0 and later supports COM Interop. You can call to COM objects from managed code as well as create components visible to COM; for example, in C#:

#region Using directives
using System;
using System.Text;
using System.Runtime.InteropServices;

#endregion

namespace classCS
{
   [ComVisible(true)]
   public class MyException : ApplicationException
   {
      const int SecondLevelHResult = unchecked((int)0x81234567);

      public MyException(string message)
         : base(message)
      {
         //this.`HResult = -10;
      }
   }

   [Guid("694C1820-04B6-4988-928F-FD858B95C880")]
   public interface csCOM_Interface
   {
      [DispId(1)]
      void Method1();
      [DispId(2)]
      void Method2();
      [DispId(3)]
      int Method3();
   }

   [Guid("9E5E5FB2-219D-4ee7-AB27-E4DBED8E123E")]
   public class csCOM_Class : csCOM_Interface
   {
      public csCOM_Class()
      {
      }

      public void Method1()
      {
         if (true)
            throw new ArithmeticException("Arithmetic");
      }
      public void Method2()
      {
         if (true)
            throw new DivideByZeroException("DivideByZero");
      }
      public int Method3()
      {
         if (true)
            throw new MyException("Our Custom Exception");
      }
   }
}

and then use them in C++:

#import "path_to_tlbclassCS.tlb"
...
   classCS::csCOM_InterfacePtr csObj;
   HRESULT hr = csObj.CreateInstance(__uuidof(classCS::_csCOM_Class));
   hr = csObj->Method1();

This is maybe a slightly weird way of tackling CF.NET, but in case you need COM-like behavior—here you go. More info about COM Interop under Windows Mobile can me found here.

Conclusion

In this article, you briefly explored a few methods of mixing managed and unmanaged code in a mobile environment. Although the Compact Framework is not yet as great as on the desktop, it does allow you to implement many things to reach pretty efficient code, and your ‘old buddy’ unmanaged C++ helps you there a lot.

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. After working almost a decade for an international retail software company as a team leader of the Windows Mobile R department, he has decided to dive into Symbian OS ™ Core development.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories