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
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:
[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:
This then can be used as:
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 ( ).
Parameter
Description
BestFitMapping
CallingConvention
CharSet
EntryPoint
ExactSpelling
PreserveSig
SetLastError
ThrowOnUnmappableChar
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.
[DllImport("coredll.dll", EntryPoint = "#286", SetLastError = true)]
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:
[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:
Public Declare Function FindWindow Lib "Coredll" _
Alias "FindWindowVB" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
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
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:
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
Summary
1000 .data 1000 .reloc 2000 .text
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
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++
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.
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
Click here to provide feedback and input