Compact 2013 Ebook

17.4 Platform Invoke
Created by djones on 6/28/2013 3:43:12 AM

Calling Native Code from Managed Code

Platform Invoke (P/Invoke) enables Managed (.NET) Code to call unmanaged code. There is a marshalling of the calling parameters from .NET context to the unmanaged context, calling of the unmanaged code, then marshalling of the returned parameters and return type back into managed types. Note that when in the unmanaged phase, the code is unprotected. There can be memory leaks and system level errors through such things as memory access violations. "P/Invoking" is implemented in a static internal class which contains P/Invoke signatures for DLL API. These signatures mirror the DLL's header file declarations. The functions in

P/Invoke Signatures

Given a native code DLL function and its return and parameter types, it can be called directly from managed code via a signature and by using the correct marshalled data types with that signature. For Win32 API calls there is a site that lists P/Invoke signatures.
http://www.pinvoke.net/

For example, the Win32 FindWindowW in .NET CF:

C# FindWindowsW( ) Signature
[DllImport("coredll.dll", EntryPoint = "FindWindowW", SetLastError = true)]
private static extern IntPtr FindWindowCE(string lpClassName, string lpWindowName);

The function is declared as static because function is not instance dependent, and extern because C# compiler should be notified not to expect implementation (it uses coredll).

This means:

  • DLL to load: coredll.dll, the main Compact 2013 Win32 DLL
  • Function name in that DLL (EntryPoint): FindWindowW
  • Function name in C# code: FindWindowCE
    Note it can be but doesn't have to be the same as the native code name.

This then can be used as:

P/Invoke FindWindowW()
  1. IntrPr hndl = FindWindowW("","MyApplication");

The hndl then is the managed code version of a window handle and is a handle to the current application window. This can then be passed to other Win32 functions using P/Invoke, such as DestroyWindow ( ).

DllImport Parameters

Parameter

Description

BestFitMapping

Marshaler will find a best match for chars that can't be mapped between ANSI & Unicode when enabled. Defaults to true.

CallingConvention

The calling convention of a DLL entry point. Defaults to stdcall.

CharSet

Indicates how to marshal string data and which entry point to choose when both ANSI and Unicode versions are available. Defaults to Charset.Auto.

EntryPoint

This specifies the name or ordinal value of the entry point to be used in the DLL. If not given, function name is used as entry point.

ExactSpelling

It controls whether the interop marshaler will perform name mapping.

PreserveSig

Specifies whether to preserve function signature while conversion.

SetLastError

Whether the method will call Win32 SetLastError API or not. To retrieve the error, use Marshal.GetLastWin32Error

ThrowOnUnmappableChar

If false, unmappable characters are replaced by a question mark (?). If true, an exception is thrown when an unmappable character is encountered.

EntryPoint

The EntryPoint in the signature is the reference to the function in the DLL. Normally it is the name of the function but can be its Ordinal. Each function exposed in the external interface for the DLL, as listed in its .def file, is given and unique index in a sequence when the DLL is compiled. This index is called its Ordinal. The ordinal can be used instead of the function name in the signature. The Compact 2013 coredlll Ordinal for FindWindowW is 286.

C# FindWindowW using Ordinal
[DllImport("coredll.dll", EntryPoint = "#286", SetLastError = true)]
private static extern IntPtr FindWindowCE(string lpClassName, string lpWindowName);

The EntryPoint is optional. If not given, then the C# function name is assumed. An error occurs if it doesn't exist in the DLL. eg:

No EntryPoint Signature
[DllImport("coredll.dll")]
public static extern void DoUnmanaged();

This would cause an error when called because DoUnmanaged( ) does not exist in coredll.dll

Visual Basic Signature

The VB equivalent FindWindow( ) signature is:

VB FindWindowW() Signature
 Public Declare Function FindWindow Lib "Coredll" _
       Alias "FindWindowVB" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr

Note the line extension character (underscore) in the first line.

Name Mangling

The interface to a native DLL is defined in its .def file. This lists functions, C++ classes and storage that applications can refer to when they load the DLL. When a C++ compiler compiles a DLL, the names of these entities get "mangled" so that they are unique. C++ supports the use of the same symbol name in different contexts hence the need for removing the ambiguity by mangling the symbol names. Examining the native code DLL from chapter 15, the GetTime( ) function is mangled as:

        1    0 00001AF4 ?GetTime@@YAXPAG@Z = ?GetTime@@YAXPAG@Z (void __cdecl GetTime(unsigned short *))

This means its is ordinal 1,  and its name is now GetTime@@YAXPAG@Z. Its signature for P/Invoke would then use the ordinal #1, or the name GetTime@@YAXPAG@Z. But if the DLL is recoded and rebuilt, the ordinal and mangled name both could change. Whilst the native code calls to the DLL don't have a problem with the mangled names, the managed code does from this perspective. It would be better if the original symbol name GetTime could be used in the signature.

This is implemented by inserting: extern "C" in front of TimeDLL_API in the header and .cpp files for the GetTime function. When that is done the function is compiled as:

   1    0 00001AF8 GetTime = _GetTime

… i.e. "unmangled".

Dumpbin

The identification of the mangled and unmangled names as above is determined using a tool called dumpbin.exe. This is available in the build environment and so can be accessed when in the release directory via Menu,, Build—>Open Release Directory in Build window in the OS project:

  1. Open the OS project with the TimeDLL subproject from chapter 17, i.e. article 17.4
  2. Menu,, Build—>Open Build window in Release Directory
  3. Rebuild the TimeDLL subproject
  4. dumpbin /exports timedll.dll
  5. dumpbin output for when the extern "C" directives are added

    C:\WINCE800\OSDesigns\OSDesign2\OSDesign2\RelDir\CEPC_x86_Release>dumpbin /exports timedll.dll
    Microsoft (R) COFF/PE Dumper Version 11.00.50727.114
    Copyright (C) Microsoft Corporation.  All rights reserved.


    Dump of file timedll.dll

    File Type: DLL

      Section contains the following exports for TimeDLL.dll

        00000000 characteristics
        51DBEF88 time date stamp Tue Jul 09 21:10:00 2013
            0.00 version
               1 ordinal base
               1 number of functions
               1 number of names

        ordinal hint RVA      name

              1    0 00001AF4 ?GetTime@@YAXPAG@Z = ?GetTime@@YAXPAG@Z (void __cdecl GetTime(unsigned short *))

      Summary

            1000 .data
            1000 .reloc
            2000 .text


  6. Add the the extern "C" directive to the header file function prototype and the fucntion implementation in the header file and .cpp file respectively (extern "C" prepended to TimeDLL_API in both):
    C++ extern "C" "unmangling"
    1. extern "C" TimeDLL_API void  GetTime(TCHAR * strTime)

  7. Rebuild the DLL and run dumpbin again as above.
  8. dumpbin output for when the extern "C" directives are added:

    C:\WINCE800\OSDesigns\OSDesign2\OSDesign2\RelDir\CEPC_x86_Release>dumpbin /exports timedll.dll
    Microsoft (R) COFF/PE Dumper Version 11.00.50727.114
    Copyright (C) Microsoft Corporation.  All rights reserved.


    Dump of file timedll.dll

    File Type: DLL

      Section contains the following exports for TimeDLL.dll

        00000000 characteristics
        51DBF664 time date stamp Tue Jul 09 21:39:16 2013
            0.00 version
               1 ordinal base
               1 number of functions
               1 number of names

        ordinal hint RVA      name

              1    0 00001AF8 GetTime = _GetTime

      Summary

            1000 .data
            1000 .reloc
            2000 .text


  9. You might like to get a list of the functions in coredll.dll into a text file using dumpbin:
    dumpbin /exports coredll.dll > coredll.txt
  10. This can be then examined in notepad or the function list could be extracted and loaded into Excel and then turned into a database of Win32 coredll functions as in the spreadsheet: coredll.xls

Marshaling

Note that this section is specific to .NET Compact Framework. There are some differences with the full Framework.

When the native code is called from the managed code as a Platform Innovation, P/Invoke, the differences between data types between the two platforms is called Marshaling. The data types have to be translated when passed from one to the other. Fortunately many of the basic data types map seamlessly onto native data types. These types are called blttable types. By using these types at one end, then types of the same name are used at the other end. But the structure may change; in particular the number of bytes. The Compact Framework only support UNICODE so native code strings (and chars) are always marshaled to the managed code as UNICODE strings, regardless of the native code string (char) type. Booleans are four bytes in .NET CF which map to a single byte in native code

Table 17.  Blittable numeric data types

Compact Framework

C#

VB

Wiin32

C./C++

System.Byte byte Byte BYTE unsigned char
System.Int16 short Short SHORT short
System.Int32 int Integer INT int
System.Int32 int Integer LONG long
System.Int64 long Long (only ByRef) LONGLONG long long
System.UInt64 ulong n/a DWORD unsigned long
System.Boolean bool Boolean BOOL long
System.Single float Single FLOAT float
System.Double Double Double DOUBLE double

The marshaler therefore can seamlessly map these blittable types directly when P/Invoking a native code function. It can also marshal arrays of blittable types as well as structures and classes of blittable types.

When passing a string to or from native code, it is simplest to use a StringBuilder type on the managed code side. This maps onto a TCHAR * string pointer (or LPTSTR) in native code. The StringBuilder should be instantiated with a specific size in the native code before it is passed to native code. The native code can then either read the contents of the TCHAR buffer and/or write to the buffer so as to return a string in the StringBuilder instance to the managed code.

So if creating native code functions for P/Invocation, (or wrapping Win32 functions into a custom DLL), use blittable types or StringBuilder instances at the managed code end to simplify marshaling.

Using Platform Invoke

P/Invoke is typically used call a Win32 function that is not available in the Compact Framework, or to call hardware. It is not efficient to implement a large sequence native code as P/Invokes from managed code to native code. This would be very inefficient as P/Invocation, especially marshaling is costly in terms of performance.

Whilst it would be possible to implement a WinForms application in managed code as hotch-potch of Win32 invocations, that would obviously be crazy! In managed code the occasional use of P/Invoke to call a specific Win32 function makes sense. If there is a requirement for a sequence of native code calls from managed code, it is best to implement that as a custom DLL with a single function call for each sequence. Where there are a number of Win32 calls but randomly placed, it may also be more efficient to wrapper each call as a native code function in a DLL for invocation.

For hardware, it is simplest to implement the complex aspects of the hardware functions in native code in a DLL and then expose some higher level functions for use in managed code.


NEXT:  17.5 Calling Native Code from C# Managed Code

print

Click here to provide feedback and input

  Comments

There is no comment.

Turkish porno izle video site in rokettubeporno izle