July 31, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Mixing Managed and Unmanaged Code in Your Windows Mobile Applications

  • June 20, 2007
  • By Alex Gusev
  • Send Email »
  • More Articles »

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.





Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel