About

Lets completely work through a Platform Invoke Example.

For simplicity I’ll use a Windows Mobile Emulator, but it would simply apply to an actual Windows Mobile Device/Windows CE device.

Context:

  • Visual Studio 2008 with a suitable Windows Mobile or Windows CE Emulator/SDK installed.
  • Using C# for the application and C++ for the native DLL.
  • I have also tried this on  my Windows Mobile 6.53 Phone

First

  1. Create a new C# Smart Device Project .. Ignore the .NET Version in the first step.  Call it PInvoke. 
  2. Choose a suitable Target Platform, Set the .NET CF Version (I selected 2.0), Select Device Application (This creates a Windows Form App)
    • A Windows CE Target might be a suitable choice.

image

  1. Complete the project creation, the Target Form will show.
  2. Add a Button and Text Box
  3. Double Click on the button add the code:
    • textBox1.Text = DateTime.Now.ToString();
  4. Build and run the application, this might take a while first time.

OK So we have connectivity and can create, build and run an application.

 

 

Next

Next we add a class which will encapsulate the native code in the C# application.

  1. Add a class to the application, call it Native
  2. Code it as follows:

using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
    internal static class Native
    {

    }
}

  1. Create a new C++ Smart Device Project-Win32SmartDeviceProject, called Native
  2. [Next]
  3. Make sure the correct SDK (only) is the right hand pane.
  4. [Next]
  5. Choose DLL  -  Export Symbols
  6. [Finish]
  7. We just want some functions so delete the exported variable and the class definition in the .cpp and header (.h) files.
    • We’ll just get the BigBlue answer (The meaning of everything .  Hitch Hikers ..)
    • Add a second function prototype to the header file so we have:

    NATIVE_API int fnNative(void);

    NATIVE_API int fnNative2(int num);

  8. Add it to the the source file (.cpp):

NATIVE_API int fnNative(void)
{
    return 42;
}

NATIVE_API int fnNative2(int num)
{
    return num*2;
}

 

  1. Build this subproject and resolve any typos.
  2. We now specvify the native functions in the Native class.
    Note: I like to copy the function prototypes from teh C++ header file into the class file as comments.
  3. Place this code in the Native class:

const string DLLFile = "Native.dll";

//NATIVE_API int fnNative(void);

//NATIVE_API int fnNative2(int num);

[DllImport(DLLFile)]
public static extern int fnNative();

[DllImport(DLLFile)]
public static extern int fnNative2(int num);

  1. Run the Configuration Manager and make sure that both projects get built and deployed..
    (Right click on the solution)
  2. Make sure both projects target the same target.
    This can be a little difficult at times.  You run the application but the DLL isn’t found because the DLL has be built for a slightly different target.
  3. Set the application as the project to run.
  4. Build the whole project and resolve any errors.
  5. Add two more buttons to the application, Native42 and NativeDouble.  Code theere click events as (respectively):

private void button1_Click(object sender, EventArgs e)
{
    textBox1.Text = DateTime.Now.ToString();
}

private void Native42_Click(object sender, EventArgs e)
{
    //Should get 42 in textbox
    int num = PInvoke.Native.fnNative();
    textBox1.Text = num.ToString();
}

private void NativeDouble_Click(object sender, EventArgs e)
{
    //Convert the number in the textbox into a number and then double it
    try
    {
        int num = int.Parse(textBox1.Text);
        num = PInvoke.Native.fnNative2(num);
        textBox1.Text = num.ToString();
    }
    catch { textBox1.Text = "Enter a number (only) here"; }
}

  1. Look at the C# application Project Properties (Menu) .. Go to Devices.  Examine the Output folder.  It is:
    %CSIDL_PROGRAM_FILES%\PInvoke
  2. Similarly look at the Project Properties for the C++ project –Deployment. It is
    %CSIDL_PROGRAM_FILES%\Native
  3. The native DLL needs to be in the application directory or in Windows.
    Change its deployment folder to %CSIDL_PROGRAM_FILES%\PInvoke
  4. Build, deploy and test the application

IT WON’T WORK .. We have one more thing to do:

C++ compiler mangles names (the function names).  We have just specified the functions in the C# code by the same names as in the C++ source.  We could use ordinals to get to them instead.  Its easier to tell the compiler not to mangle the names with an extern “C” directive.  (There are heaps of articles on the web on C++ name mangling).

  1. In the C++ header add extern “C” in front of  NATIVE_API  on the function prototypes (only):
    extern "C" NATIVE_API int fnNative(void);

    extern "C" NATIVE_API int fnNative2(int num);

     

  2. Do same in the source code. (.cpp)
  3. Build, deploy and test the application

    WALA!

Try

  1. Change the data type returned, say to a string, or a structure/class.
  2. Have a variable in the DLL that gets set by a void function  (ie   void Set( int num) ).
    Then have a function that gets that value back  (ie  int Get(void)  )
    PInvoke these.

Conclusion

We can create a native DLL that can be called from managed code.  We could potentially use this to call hardware in Windows CE.  But hardware calls often require callbacks.  That’s next.