Вы находитесь на странице: 1из 14

Chapter 4

Calling Native Functions


In this chapter, we discuss Platform Invoke – a .NET feature for calling unmanaged libraries
from managed code.[Comment 4.1]

Author's Note: In this review chapter C#-Specific Text is highlighted in YELLOW.


Platform Invoke................................................................................................................................ 2
Terminology ........................................................................................................................ 2
When to Use P/Invoke........................................................................................................ 3
Why We Prefer .NET Functions over Win32 Functions ..................................................... 5
Porting Unmanaged Code to Managed Code .................................................................... 5
Creating P/Invoke Declarations ....................................................................................................... 6
A Simple Example: MessageBox ....................................................................................... 7
Unmanaged Function Details ............................................................................................. 8
Function Return Values .................................................................................................... 11
A Tool – The P/Invoke Wizard.......................................................................................... 12
Supported P/Invoke Function Parameters .................................................................................... 13
The Limits of Parameter Passing ..................................................................................... 13
Simple Data Types ........................................................................................................... 14
Boolean................................................................................................................ 15
Signed versus Unsigned Integers ....................................................................... 15
The C# long type ................................................................ Error! Bookmark not defined.
By-Value versus By-Reference ........................................................................................ 16
String Parameters Pass By-Value.................................................................................... 17
Structures ......................................................................................................................... 17
Sample: MemoryStatus ....................................................................................... 19
Value Types and Reference Types as Parameters ......................................................... 22
Value Types as Parameters ................................................................................ 23
Reference Types as Parameters......................................................................... 23
Arrays ............................................................................................................................... 24
Example: Passing an Array of Integers ............................................................... 25
Sample: CallWin32 ........................................................................................................................ 26
Writing Win32 Dynamic Link Libraries........................................................................................... 33
Declaring C++ Functions in a DLL ................................................................................... 33
Manual P/Invoke Parameter Passing ............................................................................................ 40
The Marshal Class............................................................................................................ 40
Allocating Unmanaged Memory ....................................................................................... 41
Copying To Unmanaged Memory .................................................................................... 43
Creating Objects from Unmanaged Memory.................................................................... 45
Sample: FindMemoryCard................................................................................... 45
Communicating Between Unmanaged and Managed Code ......................................................... 48
The MessageWindow Class ............................................................................................. 48
Other Ways To Communicate Between Unmanaged and Managed Code ..................... 50
Conclusion ..................................................................................................................................... 55

Chapter 4 – Calling Native Functions Page 1 Draft – 2002Nov17


Copyright © 2003 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

Platform Invoke
This chapter covers Platform Invoke – sometimes known as P/Invoke – a .NET feature for calling
functions in unmanaged dynamic link libraries. Most of the time the .NET Compact Framework
provides all the support you need to build programs. There are times, however, when you want to
do something not supported by the Compact Framework, but which can be done by a function in
a Win32 dynamic link library.[Comment 4.2]
There are limitations on the parameters you can pass using P/Invoke, and we explore these
limits in this chapter. In brief, acceptable parameters include value types, strings, structures
created exclusively with value types, and arrays of value types. Unsupported parameters include
arrays of strings, structures that contain any reference type (including strings, structures, arrays,
unions, or any other kind of objects that is a reference type). There are workarounds for
unsupported parameters, some of which are addressed in this chapter.[Comment 4.3]
Programmers who have worked with the Desktop Framework might wonder how Compact
Framework support for P/Invoke compares with the support found on the desktop. A sidebar in
this chapter, Comparing Desktop Framework and Compact Framework Support for P/Invoke,
provides insights into the differences.[Comment 4.4]

Terminology
P/Invoke connects two different types of code together1: managed code and unmanaged code.
Before we dig into P/Invoke, we need to discuss some of the differences between these two.
Table 4.1 summarizes the important differences.[Comment 4.5]
Unmanaged code is often referred to by various different names. It is sometimes called
Native Code because the executable file format is not the portable IL used by .NET, but is instead
the native machine instructions of, say, an SH3 or StrongARM processor. It is sometimes called
Unsafe Code because it lacks the protection from memory leaks, bad pointers, and array overruns
that are supported in .NET. We sometimes call it Win32 Code because such DLLs are often (but
not always) built to directly use the Win32 API. The Win32 API provides the foundation for every
version of Windows.[Comment 4.6]
Each term reflects a .NET-centric way of looking at unmanaged code, and serves to highlight
some of the benefits of the .NET architecture. To put unmanaged code into perspective, we
present the related or implied managed code concept next to each of the terms used for
unmanaged code. Managed code is Portable Code, because .NET executables can run on any
platform that has the appropriate .NET runtime regardless of the CPU that is present. Managed
code is also Safe Code, because a broad set of features help avoid problems that plague
unmanaged code: implicit pointers and automatic memory management eliminates memory
leaks, and array boundary protection prevents memory overwrites. We sometimes use the term
.NET code because managed code relies directly on the programming interfaces provided within
the .NET Compact Framework.[Comment 4.7]
Table 4.1 Comparison between managed and unmanaged code
Managed Code Unmanaged Code
Alias Portable Code Native Code
Safe Code Unsafe Code
.NET Code Win32 Code
C# / VB .NET code C Code / C++ Code

1
Experienced Windows programmers will see that P/Invoke is like two older Windows
technologies: (1) the "thunking" that date back to Windows 1.x, and (2) the proxy/stub elements
used in COM to marshal data between operating system processes and across a network in
DCOM. Each represents a stateless layer to support a transition – a bridge – between two
otherwise disconnected bodies of code. P/Invoke adopts the COM term "marshalling" to refer to
the process of passing data into and receiving return parameters back from unmanaged libraries.

Chapter 4 – Calling Native Functions Page 2 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

Supported Languages C#, VB .NET (in CF) C, C++


EXE /DLL File Format IL – portable intermediate language Native machine language of the
that is converted to native code when target processor, for example SH3,
a function is loaded into memory SH4, MIPS, StrongARM, XScale,
x86
Memory Management Provided by the Common Language Automatic clean up of Win32 objects
Runtime as needed occurs when a process terminates,
otherwise application (or driver) code
is responsible for cleaning up
memory and Win32 objects..
Safety Checking Yes No
Array Boundary Protection Yes No
Support for Pointers Implicit pointer support is built-in, Explicit pointers widely used
explicit pointers require the unsafe
keyword. The Marshal2 class
supports the creation of pointers for
passing to unmanaged code.

When to Use P/Invoke


You use P/Invoke to access features unavailable in managed code but which are available in
unmanaged code. In some cases, you will find the desired features in your own unmanaged
DLLs. In other cases, you can get the job done by calling the unmanaged Windows CE system
libraries. Generally speaking, when there is a managed code way to do something and an
unmanaged way, we prefer the managed way. We regard P/Invoke as something of a last resort.
And yet, there are quite a few situations when it is needed. As discussed in Chapter 1, an
important design goal for the Compact Framework team was to run well – which means it had to
have a small memory footprint. To achieve that, the development team cut back on available
features compared to what you find in the desktop .NET Framework.[Comment 4.8]
Table 4.2 summarizes some of the situations where you are likely to need P/Invoke, at least
as implemented in version 1.0 of the Compact Framework. We expect that access to custom
unmanaged DLLs to be the most common one. After all, application developers have been
building, testing, and deploying countless unmanaged Win32 DLLs on Windows CE for many
years now. P/Invoke provides the means to tap into the value that has been created in the large
number of such libraries.[Comment 4.9]
The rest of the items in this list are Win32 features that are not supported in version 1.0 of the
Compact Framework. As of this writing, Microsoft is shipping first version of Compact Framework.
It is safe to say that plans are already underway for new features in future versions. As features
are added to the Compact Framework, we plan to call the managed Compact Framework
functions instead of the equivalents in the unmanaged system libraries.[Comment 4.10]
Table 4.2 Examples of when to use P/Invoke for Win32 features not supported in .NET
Feature Comments
ActiveSync ActiveSync is a set of services for connecting a desktop PC with a
Windows CE device (see chapter 21). Most ActiveSync services
are provided on the desktop itself, so that getting .NET support for
ActiveSync involves creating a set of P/Invoke wrappers for the
desktop .NET Framework. We provide just such a set on the CD
which accompanies this book.
Clipboard Cut, copy, and paste data between applications
COM Components Compact Framework cannot directly call COM and ActiveX
components – a feature that in the desktop .NET Framework is
known as "COM Interop." To use your COM components, you

2
System.Runtime.InteropServices.Marshal

Chapter 4 – Calling Native Functions Page 3 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

must create a set of unmanaged functions that wrap the COM


components. You can find one example of a sample wrapper for a
COM interface on the web site www.gotdotnet.com, which shows
how to wrap the Pocket Outlook Object Model (POOM) in a
Win32 DLL.
Common Dialog Boxes Common dialog boxes provide pre-built dialog boxes for opening
and closing files, allowing a user to select a font or a color, and
printing. The Compact Framework supports the file open & save
dialogs, but not the font and color pickers. In such cases, direct
calls to the underlying Win32 functions help fill this need.
Cryptography API The Crypto API provides encryption and decryption support to
supplement the support provided in the System.Security
namespace.
Custom Controls Custom controls implemented in unmanaged DLLs can be
supported in Compact Framework Windows Forms applications.
Custom, Third-Party Unmanaged Can access exported C-callable functions in a DLL.
Dynamic Link Libraries (DLLs)
Microsoft Message Queue (MSMQ) MSMQ allows for asynchronous communication between
different computers.
Point-To-Point Message Queues This inter-process communication feature was introduced in
Windows CE .NET version 4.0. Provides anonymous pipe support
for inter-process or inter-thread communication.
Real-Time Threads Windows CE provides great real-time support, with very
consistent response times for the highest priority thread in the
system and for the highest priority interrupt. When you have hard
real-time requirements, the safest approach to take is to delegate
that work to Win32 code. Unexpected delays can occur in
managed code due to any of the following: (1) IL JIT – time taken
to convert IL to native machine instructions, (2) Garbage
Collection – all managed threads are blocked during memory
cleanup (unmanaged P/Invoke threads run freely without blocking,
and block only on return to managed code if garbage collection is
running).
Registry Read and write to the system registry. In the Desktop Framework,
this is supported by the Microsoft.Win32.Registry class
– but not supported in the Compact Framework.
Running Programs Call the Win32 CreateProcess function to run a program.
Shell functions Some very useful Win32 functions have a prefix of SH – which
stands for "Shell." Some of the interesting ones include
SHGetSpecialFolderLocation,
SHGetSpecialFolderPath, SHBrowseForFolder (CE
.NET 4.0 and later)
Sounds Compact Framework does not support sounds, which are available
with the Win32 sndPlaySound function.
Stream interface driver control codes Windows CE uses stream interface drivers for system services
such as a web server (HTTPD), Microsoft Message Queue
(MSMQ), Universal Plug and Play (UPNP), and the Audio
Compression Manager (ACM). This type of driver is also used for
device drivers for various types of hardware including the serial
port, the infrared port, and various kinds of disk drives. Full access
to these drivers requires the Win32 file I/O functions, including
the DeviceIoControl function to send driver-specific control
codes.

Chapter 4 – Calling Native Functions Page 4 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

Why We Prefer .NET Functions over Win32 Functions


As a general rule, we prefer .NET functions over Win32 functions. We have several reasons
– first is that .NET functions get automatic memory management. By contrast when you call a
Win32 function that creates a system object, you must eventually call the associated Win32
cleanup function. For example, if you create a bitmap by calling the Win32 CreateDIBSection
function, you must later call DeleteObject to cleanup the memory allocated for the bitmap.
Win32 functions do not have automatic garbage collection like .NET functions do, and so you
must remember to take out the trash yourself or you cause memory leaks. A memory leak on a
desktop or server system with a gigabyte of RAM is a bad thing because the system eventually
slows down and crashes. A memory leak on a mobile or embedded system with 64 MB of
memory causes the same thing as on a desktop system, except that a crash is likely to occur
sooner.[Comment 4.11]
A second reason we prefer .NET functions is that their use enhances an application’s
portability. An application written today – for either the Desktop Framework or the Compact
Framework – is more portable when it has fewer dependencies on Win32 libraries. By this we
mean that if the .NET Framework becomes available on non-Windows platforms, programs
without P/Invoke are more likely to be able to run without modification3.[Comment 4.12]
Related to portability is the issue of trust and security. Code that uses P/Invoke is less trusted
than code that does not – the part of the Common Language Runtime that is responsible for
security can look at CIL code and determine whether it is safe to run or not. When it cannot be
verified to be safe, it is called unsafe code. Version 1.0 of the Compact Framework does nothing
to prevent unsafe code from running, although in certain scenarios - like using code downloaded
from a website – the Desktop Framework will prevent such code from running.[Comment 4.13]
A fourth reason we prefer managed functions is because more work is required to call Win32
functions with P/Invoke. You have to create a function declaration, and also constants and data
structures to support that function. In this chapter, we introduce a tool to help you do that – the
P/Invoke Wizard – which considerably cuts down on the effort. Managed functions, by contrast,
are immediately available by adding a reference to the Solution Explorer and the appropriate
namespace reference (using in C# and Imports in VB).[Comment 4.14]

Porting Unmanaged Code to Managed Code


We are big fans of managed code; but our enthusiasm has a limit. Programmers in our
training classes often ask when to port unmanaged code to run as managed DLLs instead of
using P/Invoke to access the unmanaged code. There is only one answer: "it
depends."[Comment 4.15]
If you have built and deployed an unmanaged DLL, you should use it. You have, after all,
invested time and money in building and testing that DLL. Before porting it to a managed DLL,
you have to make a business case that it makes sense to do so. When you have an unmanaged
library that works, we suggest you use P/Invoke to access its services. By porting an unmanaged
library to a .NET library, you have doubled your code maintenance without adding any real
features.[Comment 4.16]
There are, of course, reasons to port. Libraries with large amounts of user-interface code, for
example, can benefit from being ported because running as a managed code library can simplify
interoperating with .NET user-interface features. (You may, however, also lose some features
since the Compact Framework controls generally support fewer features than their Win32
counterparts.) [Comment 4.17]
A second reason to port Win32 code to .NET is that debugging P/Invoke code can be difficult.
Visual Studio .NET only supports debugging between managed and unmanaged code when
dealing with desktop .NET Framework code; this feature is not supported with Compact
Framework code. When a P/Invoke function call fails under the Compact Framework, you get an

3
We do not know of any plans for this to actually happen, but the high-tech industry changes
quickly – who knows what tomorrow will bring?

Chapter 4 – Calling Native Functions Page 5 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

error message – in the form of a .NET exception – with few details to help you identify the cause
of the failure.[Comment 4.18]
P/Invoke Debugging Tip
If you are creating P/Invoke wrappers for a large number of DLL
functions, it might be worth your while to port your library (or a portion of
your library) to the desktop. When you have your P/Invoke code working
on the desktop, you can then port the debugged code back to Windows
CE and the Compact Framework.[Comment 4.19]
We suggest this approach because the debugger in Visual Studio .NET
2003 does not support stepping from managed code into unmanaged
code for the Compact Framework; this feature is only supported for the
desktop .NET Framework. So if you find that you need debugger help
while creating the P/Invoke declarations for a particular function, build a
tiny Win32 DLL for the desktop containing that difficult function. You can
then use the debugger to help you figure out the correct P/Invoke
declarations to make your function work.[Comment 4.20]
There are differences between the P/Invoke support on the desktop and
in the Compact Framework. For this to be effective, you have to make
sure that you do not use a P/Invoke feature that does not exist in the
Compact Framework. In particular, avoid the MarshalAs attribute in your
P/Invoke declarations. This declaration helps fine-tune the passing of
parameters; among other things, it allows function parameters to that
consist of structure containing reference types (like a string), which the
Compact Framework P/Invoke support does not allow. [Comment 4.21]
So port your trickier P/Invoke functions to a desktop DLL, do your
debugging, then port it back. The desktop debugging support can save
you time resolving managed-to-unmanaged coding problems. But be
careful not to use desktop-only P/Invoke features.[Comment 4.22]
A third reason to port an unmanaged DLL to a managed DLL is to simplify the packaging and
setup of your program, particularly when the software you deploy must run on systems with
different CPUs with different instruction sets. A single .NET program, for example, can run on all
CPUs; a comparable unmanaged DLL needs a different executable file for each supported
CPU.[Comment 4.23]

Creating P/Invoke Declarations


To call a function in an unmanaged DLL, you start by creating a .NET-compatible declaration
(sometimes called a "P/Invoke wrapper"). A P/Invoke wrapper follows the same rules for regular
function declarations: they must be made within a class, have a return type, and have zero or
more parameters. In addition, a P/Invoke wrapper needs these additional elements:[Comment
4.24]
• the DllImport attribute
• the static keyword
• the extern keyword
[Comment 4.25]

Chapter 4 – Calling Native Functions Page 6 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

A Simple Example: MessageBox


The most common P/Invoke example is for the MessageBox function, featured in the online
MSDN documentation. We review it, and then dig more deeply into P/Invoke. MessageBox is
declared in a Win32 include files, winuser.h, as follows:[Comment 4.26]

// C++ Win32 declaration


int WINAPI MessageBoxW(HWND hWnd, LPCWSTR lpText,
LPCWSTR lpCaption, UINT uType);

The 'W' at the end of this function marks this as a Unicode function. With few exceptions4, all
Win32 functions in Windows CE are implemented as Unicode functions – that is, which means all
string parameters must be 16-bit (a.k.a. 'UTF-16') Unicode strings. You can either include or omit
the 'W' in a P/Invoke declaration, because P/Invoke adds a 'W' for you. Here are two valid
declarations for MessageBox:[Comment 4.27]

// Two C# MessageBox declarations -- one with 'W'...


[DllImport("coredll.dll"]
public static extern int
MessageBoxW (int hWnd, String lpText, String lpCaption,
UInt32 uType);

// ...and one without 'W'


[DllImport("coredll.dll"]
public static extern int
MessageBox (int hWnd, String lpText, String lpCaption,
UInt32 uType);

This declaration contains a reference to 'DllImport', which is worth discussing on several


counts. From the perspective of .NET and C# programming, this is an example of an attribute. In
C#, attributes always appear between square brackets ('[' and ']'), and allow for the fine-tuning of
various .NET declarations. For example, the WebMethod attribute defines functions that are
available through an XML Web service. Like all other .NET objects, the DllImport attribute is
defined in a class that resides in a namespace. To use this class, we reference the namespace
with the following declaration:

using System.Runtime.InteropServices;

With the namespace declaration, we call MessageBox as if it were a regular .NET function:

MessageBoxW(IntPtr.Zero, "Call to MessageBoxW", "Message", 0);

MessageBox(IntPtr.Zero, "Call to MessageBox", "Message", 0);

4
The exceptions are for some network functions, for which both Unicode and Multibyte functions
are supported – for example, there is both an InternetOpenW and an InternetOpenA
function in wininet.dll.

Chapter 4 – Calling Native Functions Page 7 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

From the perspective of P/Invoke, the DllImport attribute identifies functions which reside
in unmanaged libraries. An attribute declarations is a constructor for an object, and like other
kinds of object constructors, an attribute constructor can accept parameters. A DllImport
attribute is created from the DllImportAttribute class (by convention, all attribute classes
have a suffix of "Attribute"). The DllImport attribute constructor takes a single parameter, a
string which is the name of an unmanaged DLL.[Comment 4.32]
As you begin to create P/Invoke declarations, you need to proceed slowly because the
debugging support and error reporting is somewhat limited. You cannot, for example, use the
Visual Studio .NET debugger to trace from Compact Framework code into unmanaged code. An
error causes an exception to be raised, which results in the display of a message like that shown
in figure 4-1. More often than not, this is the only error reporting that you get when a problem is
encountered in P/Invoke. If you look closely at the error message displayed, the only information
you get is the name of the exception: NotSupportedException.[Comment 4.33]
Figure 4-1 P/Invoke errors cause an exception like the one shown here

[Comment 4.34]

Unmanaged Function Details


In C#, a function with the DllImport attribute must also have the static and extern
keywords. The static keyword means that you can call the function anytime without having to
create an instance of the class – this is the closest thing that C# has to a global function that C
programmers are so used to. C++ programmers may recognize that the static keyword in a C#
class function is similar to the same keyboard in a C++ class. The extern keyword indicates that
the actual implementation of a function is in another language besides C#. [Comment 4.36]

Chapter 4 – Calling Native Functions Page 8 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

The function we are calling, MessageBox, takes four parameters: (1) a window handle, (2)
message text, (3) caption text, and (4) some flags. A close look at each of these will tell us more
about how to pass parameters to this Win32 function.[Comment 4.37]

// C++ Win32 declaration


int WINAPI MessageBoxW(HWND hWnd, LPCWSTR lpText,
LPCWSTR lpCaption, UINT uType);

The first parameter – HWND hWnd – is a window handle that identifies a message box's
parent window. When the message box is displayed, the parent window is disabled; when the
user dismisses the message box, the parent is enabled and activated. While the Win32 API uses
handles in many places, handles are not used in .NET and so we need an alternative type for a
window handle; we define handles using IntPtr, a type provided for Win32 handles5. When
there is no parent window, we pass a special value, IntPtr.Zero, which is the same as a
Win32 null handle. Most of the time, null is an invalid value for a handle. But in a few cases, null
handle can be used. In a call to MessageBox – for example, a null handle means that the
message box has no parent window.[Comment 4.38]
Some P/Invoke examples use regular integer types for handles. While this works, such a
usage makes code less portable. While all implementations of .NET today are 32-bit
implementations, there may someday be a 64-bit (or 128-bit) implementation. IntPtr is always
the right size to hold handles (and pointers) for the platform on which a program is running, so its
use helps make your code more portable. A second use of the IntPtr type is for unmanaged
memory pointers; a feature which helps unravel some trickier problems encountered in calls to
unmanaged code.[Comment 4.39]
The second and third parameters – LPCWSTR lpText and LPCWSTR lpCaption – are
strings. To an unmanaged function, these appear as string pointers. P/Invoke correctly passes
simple string pointers like that shown in the call to MessageBox. But P/Invoke has trouble
handling strings which are embedded in structures, nested structures, and arrays. For each of
these situations, P/Invoke needs help from the IntPtr type and members of the Marshal6
class. We dig into just such an example later in this chapter.[Comment 4.40]
The last parameter – UINT uType – is a bit-field flag parameter. This unsigned integer holds
flags which indicate the buttons and icons to display in the message box. To fill in the proper bit
values, you create .NET definitions for the Win32 flag values. (In a pinch, you can also hard-code
constant values; but this approach makes code harder to maintain, so we suggest you avoid that
practice.) Here are the C++ definitions for the legal values to pass in the uType parameter, as
defined in WINUSER.H:[Comment 4.41]

#define MB_OK 0x00000000L


#define MB_OKCANCEL 0x00000001L
#define MB_ABORTRETRYIGNORE 0x00000002L
#define MB_YESNOCANCEL 0x00000003L
#define MB_YESNO 0x00000004L
#define MB_RETRYCANCEL 0x00000005L
#define MB_ICONHAND 0x00000010L
#define MB_ICONQUESTION 0x00000020L
#define MB_ICONEXCLAMATION 0x00000030L
#define MB_ICONASTERISK 0x00000040L

When you need to define flags or constant values to call unmanaged functions, there are at
least two approaches you can take. One approach is to define a set of constant values. A second

5
An IntPtr is also used for pointers, as demonstrated in the FindFlashCard example later in
this chapter.
6
Or, more completely, the System.Runtime.InteropServices.Marshal class.

Chapter 4 – Calling Native Functions Page 9 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

approach is to define an enumeration. We show you both, in listing 4.1, so you can decide which
you prefer, along with the associated declaration and call to MessageBox.[Comment 4.42]
Which should you use? One benefit of the enumeration is that once defined in your code, it
can be used by Visual Studio .NET to show the available options in a drop-down window as you
type your code. This is very handy, and for this reason, we prefer enumerations.[Comment 4.43]
Listing 4.1 – MessageBox.cs

// MessageBox.cs - Two ways to call Win32 MessageBox function.


//
// Platform Invoke Sample.
// Code from _Programming the .NET Compact Framework with C#_
// (c) Copyright 2002-2003 Paul Yao and David Durant.
// All rights reserved.

using System;
using System.Runtime.InteropServices;

namespace PlatformInvoke
{
class PI_MessageBox
{
public const string strApp = "MessageBox";

[DllImport("coredll.dll")]
public static extern Int32
MessageBox (IntPtr hWnd, string lpText, string lpCaption,
int uType);

public const int MB_OK = 0x00000000;


public const int MB_OKCANCEL = 0x00000001;
public const int MB_ABORTRETRYIGNORE = 0x00000002;
public const int MB_YESNOCANCEL = 0x00000003;
public const int MB_YESNO = 0x00000004;
public const int MB_RETRYCANCEL = 0x00000005;
public const int MB_ICONHAND = 0x00000010;
public const int MB_ICONQUESTION = 0x00000020;
public const int MB_ICONEXCLAMATION = 0x00000030;
public const int MB_ICONASTERISK = 0x00000040;
public const int MB_ICONWARNING = MB_ICONEXCLAMATION;
public const int MB_ICONERROR = MB_ICONHAND;
public const int MB_ICONINFORMATION = MB_ICONASTERISK;
public const int MB_ICONSTOP = MB_ICONHAND;
public const int MB_DEFBUTTON1 = 0x00000000;
public const int MB_DEFBUTTON2 = 0x00000100;
public const int MB_DEFBUTTON3 = 0x00000200;
public const int MB_DEFBUTTON4 = 0x00000300;
public const int MB_APPLMODAL = 0x00000000;
public const int MB_SETFOREGROUND = 0x00010000;
public const int MB_TOPMOST = 0x00040000;

[DllImport("coredll.dll")]
public static extern Int32
MessageBoxW (IntPtr hWnd, string lpText, string lpCaption,
MB uType);

public enum MB

Chapter 4 – Calling Native Functions Page 10 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

{
MB_OK = 0x00000000,
MB_OKCANCEL = 0x00000001,
MB_ABORTRETRYIGNORE = 0x00000002,
MB_YESNOCANCEL = 0x00000003,
MB_YESNO = 0x00000004,
MB_RETRYCANCEL = 0x00000005,
MB_ICONHAND = 0x00000010,
MB_ICONQUESTION = 0x00000020,
MB_ICONEXCLAMATION = 0x00000030,
MB_ICONASTERISK = 0x00000040,
MB_ICONWARNING = MB_ICONEXCLAMATION,
MB_ICONERROR = MB_ICONHAND,
MB_ICONINFORMATION = MB_ICONASTERISK,
MB_ICONSTOP = MB_ICONHAND,
MB_DEFBUTTON1 = 0x00000000,
MB_DEFBUTTON2 = 0x00000100,
MB_DEFBUTTON3 = 0x00000200,
MB_DEFBUTTON4 = 0x00000300,
MB_APPLMODAL = 0x00000000,
MB_SETFOREGROUND = 0x00010000,
MB_TOPMOST = 0x00040000
}

static void Main()


{
// Flags defined as set of const values.
MessageBox(IntPtr.Zero, "One way to define flags",
strApp, MB_OK | MB_TOPMOST);

// Flags defined as members of enum


MessageBox(IntPtr.Zero, "Another way to define flags",
strApp, MB.MB_OK | MB.MB_TOPMOST);
}
}
}

Function Return Values


As in managed code, unmanaged functions are not required to return a value. C#
programmers specify a void return type, just as in C and C++. [Comment 4.46]
An unmanaged function can return integers which are 32-bit and narrower. This means that
you can return one-byte integers, two-byte integers, and four byte integers; 64-bit integers,
however, cannot be used as return types. As far as floating point goes, neither single nor double
floating point values can be used as return values (although you can accomplish the same thing
by passing a by-reference parameter). Valid return types are summarized in the last column of
table 4.3.[Comment 4.47]
When calling unmanaged code which returns a data pointer, you declare the function to
return an IntPtr (which is a 32-bit integer). Once you get the IntPtr back from a called
function, you next call various members of the Marshal class to copy the values to managed
objects. You can find examples of using IntPtr and Marshal later in this chapter.[Comment
4.48]

Chapter 4 – Calling Native Functions Page 11 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

Setting up a P/Invoke call like this requires a lot of work – you need to declare the function
and define supporting constants. For some function calls, you also need to define one or more
data structures needed for the function call. To simplify this task, we present a tool created by
The Paul Yao Company: the P/Invoke Wizard.[Comment 4.49]

A Tool – The P/Invoke Wizard


The P/Invoke Wizard accepts a set of C declarations for functions, enumerations, #define
constant values, and data structures. It returns a set of .NET-compatible declarations, in your
choice of either C# or VB. Figure 4.2 shows the Platform Invoke Wizard with.NET-compatible
P/Invoke declarations for the Win32 MessageBox function and the required flag values. The
P/Invoke Wizard is available in two flavors: a Basic Edition and an Advanced Edition. The Basic
Edition supports the use of P/Invoke as supported by the Compact Framework. The Advanced
Edition lets a developer select Compact Framework compatibility, or desktop .NET Framework
compatibility, in the P/Invoke declarations. The Basic Edition is available on the CD
accompanying this book. The Advanced Edition is available for sale through The Paul Yao
Company's web site (http://www.paulyao.com).[Comment 4.50]
To create a .NET-compatible P/Invoke declaration for an unmanaged function, you need a
set of Win32 declarations: a function prototype, flag or constant values, and data structure
definitions. Where do you get these declarations? There are two choices: the include files and the
online documentation. [Comment 4.51]
The Advanced Edition of the P/Invoke Wizard can open and convert a complete include file.
You could, for example, open include files like kernel32.h, gdi32.h, and user32.h. This is a quick
and easy way to get a large number of functions converted. We tested the P/Invoke Wizard on all
the Win32 include files. In addition, we have gotten feedback from users who have used the
P/Invoke Wizard with include files from third party DLLs.[Comment 4.52]
In a few cases, such as the Remote API (RAPI), include file function declarations do not have
parameter names, only parameter types. When the P/Invoke Wizard encounters this situation, it
creates placeholder names (param1, param2, etc.). When we encounter that, we rely on function
declarations taken from the online documentation. The P/Invoke Wizard can then create more
usable function prototypes.[Comment 4.53]
Figure 4.2 The P/Invoke Wizard converting MessageBox to C#

Chapter 4 – Calling Native Functions Page 12 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Programming the .NET Compact Framework in C# By Paul Yao & David Durant

The goal of the P/Invoke Wizard is to save time. We wanted to make it easy for programmers
to handle the mind-numbing simple cases, which make up 99% of the work involved with creating
P/Invoke declarations. This gives you more time to study and fix the other 1% that require
refinement of the output generated by our wizard. You may need to handle some of the cases by
hand because the P/Invoke Wizard is not a full-blown compiler, meaning it does not parse the full,
rich syntax of the C and C++ programming languages. It can, however, handle declarations found
within include files. We know that it works well with the Win32 include files, because we used
those files in our testing. It can read and create great declarations for both desktop and Windows
CE versions of kernel32.h, gdi32.h, user32.h, toolhelp.h, rapi.h, advapi32.h, and the other include
files that make up the Win32 API.[Comment 4.56]

Supported P/Invoke Function Parameters


After you understand the basic P/Invoke syntax, the next step to using P/Invoke is to
understand what parameter types can be passed. In his online series, Working with C#, Eric
Gunnerson of Microsoft writes that, "If the functions have simple interfaces, it's fairly easy to call
them from a .NET language. As things get more complex, however, it's harder to use runtime
interop."7 This summarizes nicely our experience with P/Invoke.[Comment 4.57]
Simple data types – meaning value types – can be passed, as can strings, structures
containing only value types, or arrays of value types. Strings are a reference type, not a value
type, and so cannot be contained within a structure or array that is to be passed as a parameter.
But strings can be used as parameters to Win32 functions called with P/Invoke (as you saw in the
MessageBox example).[Comment 4.58]

The Limits of Parameter Passing


There are limits to the built-in support that Compact Framework provides for parameters to an
unmanaged function. Things that cannot be passed include the following:[Comment 4.59]
• Passing a variable number of parameters is not supported
• Passing structures that contain strings, objects, arrays, other structures, or unions is not
supported
• Arrays of complex types are not supported
• Arrays of strings are not supported
• Function pointers cannot be passed as .NET parameters, because calls to managed code
from unmanaged code is not supported
We should mention that our focus is on the Compact Framework. Generally speaking, the
desktop .NET Framework has richer support for P/Invoke than the Compact Framework. Given a
bit of diligence, however, we have found that we can pass just about all data types to unmanaged
code – it just takes more work in the Compact Framework. The major limit in Compact
Framework P/Invoke support is with code pointers – meaning callback functions, interface
functions, and class functions – which cannot be passed from managed code to unmanaged code
in any meaningful way. Most other data pointers can be passed into (and, as appropriate, back
out of) managed code using a combination of the IntPtr data types and members of the
Marshal class. The FindMemoryCard sample shows how this is done.[Comment 4.60]
7
Using Existing Code in C#, by Eric Gunnerson, July 14, 2002.
http://msdn.microsoft.com/library/en-us/dncscol/html/csharp07152002.asp. This article focus on
P/Invoke as implemented in the desktop .NET Framework. So it can provide good background,
but some features that Eric discusses – like COM Interop and marshalling attributes – are not
supported in the Compact Framework.

Chapter 4 – Calling Native Functions Page 13 Draft – 2002Nov17


Copyright © 2002 Paul Yao & David Durant
Thank You
Thank you for taking the time to read this preview chapter. We hope it has provided you
insights and tips to help with your Compact Framework programming project. You can help us
create a better book by clicking the comment link at the end of each paragraph, and sending your
comments and suggestions on our review web site.

Preview Chapter Text


Our public review site provides the complete table of contents for the Compact Framework
book at this link: http://www.paulyao.com/cfbook.htm. That table of contents contains links to all
the preview chapters. The preview chapter provides the complete outline of topics covered in a
chapter, and also the first section or two from each chapter.

Complete Chapter Text


You can get the complete text for each chapter, available to readers who register at our web
site. Registration is simply and easy – we only ask for an email address. To register, click on this
link: http://www.paulyao.com/ReaderFeedback/Logon.aspx.
When you register, you can download the available chapters from the full-text Table of
Contents, available at this link: http://www.paulyao.com/ReaderFeedback/default.aspx. We notify
registered readers of new chapters – and chapter updates – as they become available.

Chapter 1, 1

Вам также может понравиться