Marshalling
Marshalling
Marshalling
Marshaling is the process of creating a bridge between managed code and unmanaged
code; it is the homer that carries messages from the managed to the unmanaged
environment and reverse. It is one of the core services offered by the CLR (Common
Language Runtime.)
Why Marshaling?
You already know that there is no such compatibility between managed and unmanaged
environments. In other words, .NET does not contain such the types HRESULT, DWORD,
and HANDLE that exist in the realm of unmanaged code. Therefore, you need to find a
.NET substitute or create your own if needed. That is what called marshaling.
Overview
This chapter discusses the nitty-gritty part of marshaling process. It is the base for the
rest of discussion about marshaling. It is about marshaling simple data types.
The first section of this chapter breaks data types into two categories, simple and
compound. Simple types (integers, booleans, etc.) are those that are not made of other
types. On the contrary, compound types (structures and classes) are those types that
require special handling and made of other types.
After that, we will dig into the discussion of simple types and we will break them into two
categories, blittable and non-blittable.
Before we end this chapter, we will discuss the passing mechanism and handles in .NET
Framework.
Simple (primitive/basic)
Compound (complex)
Primitive data types are those that are not defined in terms of other data types. They are
the basis for all other types. Examples of managed primitives are numbers like
System.Byte, System.Int32, System.UInt32, and System.Double, strings like System.Char
and System.String, and handles like System.IntPtr.
Compound data types are those that built up of other data types. For example a class or
a structure that encapsulates simple types and other compound types.
We will use terms simple, primitive, and basic types to refer to base types like integers,
strings, etc. Terms compound, and complex types also will be used interchangeably to
refer to classes and structures.
The following table lists the blittable data types exist in .NET (their counterparts in
unmanaged code will be covered soon):
Table 2.1 Blittable Types
C/C++ Managed C#
Description Windows Type
Keyword Type Keyword
8-bit signed
CHAR char System.SByte sbyte
integer
8-bit unsigned
BYTE unsigned char System.Byte byte
integer
16-bit signed
SHORT short System.Int16 short
integer
16-bit
unsigned WORD and USHORT unsigned short System.UInt16 ushort
integer
32-bit
DWORD, DWORD32, unsigned int,
unsigned System.UInt32 uint
UINT, and UINT32 unsigned long
integer
DWORDLONG, unsigned
64-bit
DWORD64, __int64,
unsigned System.UInt64 ulong
ULONGLONG, and unsigned long
integer
UINT64 long
Floating-point
FLOAT float System.Double double
integer
This is not an exclusive list, many types with the same meaning exist.
Notice that long and int defer from a platform to another and from a compiler to another.
In 32-bit versions of Windows, most compilers refer to both long and int as 32-bit integers.
Some types are based on the version of Windows. DWORD, for instance, is 32 bits on 32-
bit versions and 64 bits on 64-bit versions of Windows. This writing assumes that it is a 32-
bit version of Windows.
Know that there is no difference between Windows data types and C/C++ data types.
Windows data types are just aliases for the actual C types.
Do not be confused with the many types that refer to one thing, they are all just names
(aliases.) INT, INT32, LONG, and LONG32 are all 32-bit integers for instance.
Although, some unmanaged types have names similar to names of some managed
types, they have different meanings. An example is LONG, it has similar name as
System.Long. However, LONG is 32-bit and System.Long is 64-bit!
If you need to learn more about these types, check out the article “Windows Data Types”
in MSDN library.
Managed
Description Unmanaged Type(s)
Type
8-bit ANSI string of characters LPSTR, LPCSTR, PCSTR, and PSTR System.String
characters PWSTR
As we have said, for the sake of simplicity, we will use Windows API as the base for our
discussion in this book. Therefore, you need to know that all Windows Data Types (INT,
DWORD, etc.) are just names (technically, typedefs) for the actual C types. Therefore,
many names may refer to one thing just as INT and LONG.
Thus, we can say that LONG is defined as C int and DWORD is defined as C unsigned
long.
INT and LONG are easy to marshal. However, there are primitive types that you will need
to track their definitions to know how to marshal it.
Remember that we will use MSDN documentation (specially the article “Windows Data
Types”) when tracking unmanaged data types (Windows data types specially.)
The next are some of the types defined as another types. You can think of these types as
aliases for the base types. Yet, some are platform-specific, and others not.
HRESULT:
As you will see, plenty of functions return a HRESULT to represent the status of the
operation. If HRESULT equals to zero, then the function succeeded, otherwise it
represents the error code or status information for the operation. HRESULT defined as
LONG, and LONG in turn defined as a 32-bit signed integer. Therefore, you can marshal
HRESULT as System.Int32.
BOOL and BOOLEAN:
Both are Boolean types, that means that they take either TRUE (non-zero) or FALSE
(zero.) The big difference between BOOL and BOOLEAN is that BOOL is defined as INT,
thus occupies 4 bytes. BOOLEAN on the other hand is defined as BYTE, thus occupies
only 1 byte. Booleans are covered soon.
HFILE:
A handle to a file opened using one of the Windows File IO functions
like OpenFile() function. This type is defined as INT, and INT in turn is defined as a 32-bit
signed integer. Therefore, you can marshal HFILE as System.Int32. Although, HFILE
defined as INT, handles should be marshaled as System.IntPtr, which is internally
encapsulates the raw handle. To be clear, you would better marshal an unmanaged
handle as a System.Runtime.InteropServices.SafeHandle or CriticalHandle, this is the ideal
marshaling type for any handle. Hence, file handles best marshaled as
Microsoft.Win32.SafeHandles.SafeFileHandle that is derived from
SafeHandleZeroOrMinusOneIsInvalid that is in turn derived from the abstract class
System.Runtime.InteropServices.SafeHandle. For more details about handles, refer to the
section “Marshaling Handles” later in this chapter.
In addition, there are types that are variable based on the operating system. Examples
are:
INT_PTR:
A pointer to a signed integer. Defined as INT64 if this is a 64-bit OS, or INT otherwise.
LONG_PTR:
A pointer to a signed long. Defined as INT64 if this is a 64-bit OS, or LONG otherwise.
UINT_PTR:
A pointer to an unsigned integer. Defined as DWORD64 if this is a 64-bit OS, or DWORD
otherwise.
ULONG_PTR:
A pointer to an unsigned long. Defined as DWORD64 if this is a 64-bit OS, or DWORD
otherwise.
Keep in mind that there is a big difference between a variable and a pointer to a variable.
A variable refers directly to its value into the memory. However, a pointer contains an
address of another value into the memory. Consider the following illustration, Figure 2.1:
Figure 2.1 - Pointers into Memory
In the illustration above, the variable i contains the value 320 and you can get the value
from the variable directly. The pointer ptr on the other hand contains the address of the
variable i. Thus, it indirectly contains the value of the variable i. That is why we cannot
get the value of the pointer directly. We need to dereference it first before retrieving its
value.
In addition, for textual data types, there are types variable based on Unicode definition
(strings and buffers are covered soon.) Examples are:
Notice that some types have special characters in their names. For example, A in textual
data types stands for ANSI, and W in stands for Wide, which means Unicode. In addition,
the letter T in textual information too means it varies based on OS. Another example is
the prefix P (lowercase,) it means a pointer, and LP means a long pointer. LPC stands
for long pointer to a constant.
Variants
In addition, Win32 API defines the types VOID, LPVOID, and LPCVOID. VOID indicates
that the function does accept no arguments. Consider the following function:
DWORD GetVersion(VOID);
It is required to tag the function with VOID if it does not accept any arguments (that is
one of the specifications of C89.) Notice that VOID is defined as void.
LPVOID and LPCVOID are defined as any type (variant). That means that they can accept
any value. They can be marshaled as integers, strings, handles, or even compound types,
anything you want. In addition, you can marshal them as System.IntPtr, so you can set
them to the address of any object in memory. In addition, you can marshal them as
pointers to object. For example, marshaling a LPCVOID as System.Int32* (a pointer to an
integer) in unsafe code. Moreover, you can use unsafe code and marshal them as void*.
Furthermore, you can marshal them as System.Object, so you can set them to any type
(refer to chapter 6 for more information about memory management and unsafe code.)
It is worth mentioning that when working with VOIDs it is recommended decorating your
variable with MarshalAsAttribute attribute specifying UnmanagedType.AsAny which tells
the compiler to work out the marshaling process and sets the type of the argument at
runtime. Refer to the last chapter: “Controlling the Marshaling Process” for more
information about this attribute.
If you have worked with traditional Visual Basic, thinking about LPVOID and LOCVOID as
a Variant could help too much.
If you are interoperating with the traditional Visual Basic code, you can use the same way
we did on marshaling LPVOID and LPCVOID in marshaling the type Variant.
Try It Out!
Now, we will try to create the PInvoke method for the MessageBoxEx() function. The
example demonstrates how to control precisely the marshaling process using the
MarshalAsAttribute attribute. We will cover this attribute and more in the last chapter of
this book: “Controlling the Marshaling Process.” Handles are covered in the section:
“Marshaling Handles” of this chapter.
The following example creates the PInvoke method for the MessageBoxEx() function and
calls it to display a friendly message to the user.
int MessageBoxEx(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType,
WORD wLanguageId);
And here is the managed signature (the PInvoke method) of this function:
In order for the example to run you must add a using statement to
System.Runtime.InteropServices namespace. Be sure to add it for all examples throughout
this book.
For more information about marshaling strings, see section “Marshaling Strings and
Buffers” later in this chapter.
A Rule of Thumb
Keep in mind that. .NET Framework allows you to take a granular level of control over
the marshaling process and that would be very complicated. However, things can be so
simple.
You can ignore attributes in most cases and just use the counterparts and CLR will do its
best. Likely, you are not required to use managed signed integers for unmanaged
equivalents. You can use managed signed integers for unmanaged unsigned integers
and vice versa. You can also marshal a SHORT as System.Char!
The key point is that as long as the managed marshal type occupies the same memory
size as the unmanaged type, you are in safe. However, keeping things in its right position
helps avoiding undesirable errors that maybe very difficult to know and handle.
Another thing that you should keep in mind that the information in this book can be
applied to any unmanaged environment. You can apply this information when
interoperating with Windows API, C/C++ libraries, Visual Basic, COM, OLE, ActiveX, etc.
However, for the sake of simplicity, we will talk about the Windows API as the source of
the unmanaged code.
Another thing, this writing assumes a 32-bit version of Windows. Thus, it considers that
DWORDs, HANDLEs, etc. are all 4 bytes. On 64-bit versions, they are 8 bytes.
Marshaling Booleans
The Two Types
In general, marshaling simple data types is very easy and booleans are no exception.
However, Booleans are non-blittable types. Therefore, they require some handling.
There are some notes about marshaling booleans in the managed environment. The first
thing to mention about is that Windows defines two types of Boolean variables:
1. BOOL:
Defined as INT, therefore, it is 4-bytes wide.
2. BOOLEAN:
Defined as BYTE, therefore it is only 1-byte.
Both can be set to non-zero to indicate a true (TRUE) value, and zero otherwise (FALSE.)
Again, the two types exist only in the Windows SDK. Other environments may define other
types with similar names.
While it is true that BOOL and BOOLEAN are best marshaled as System.Boolean, BOOL
can be marshaled as System.Int32 too, because it is defined as a 32-bit integer. On the
other hand, BOOLEAN can be marshaled as System.Byte or System.U1, because it is
defined as 8-bits integer. Do you remember the rule of thumb?
Take into consideration that whether you are marshaling your Boolean type to
System.Boolean, System.Int32, or System.Byte, it is recommended that you apply
MarshalAsAttribute attribute to the variable to specify the underlying unmanaged type.
For example, to specify that the underlying type is BOOL, specify UnmanagedType.Bool
(recommended) or UnmanagedType.I4 in the MarshalAsAttribute constructor. On the
other hand, BOOLEAN can be specified as UnmanagedType.U1. If you omit
MarshalAsAttribute, CLR assumes the default behavior for System.Boolean, which is 2
bytes wide. For more information about MarshalAsAttribute attribute, see the last
chapter: “Controlling the Marshaling Process.”
Try It Out!
Fortunately, plenty of functions return BOOL indicating whether the function succeeded
(TRUE) or failed (FALSE.)
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
// In addition, you can marshal it as:
// [return: MarshalAs(UnmanagedType.I4)]
// Moreover, You can change System.Boolean to System.Int32
static extern Boolean CloseHandle(IntPtr hObject)
Handles covered soon. For now, it is OK to know that all handles marshaled to
System.IntPtr.
This section discusses how to marshal strings and buffers. We will use the terms string
and buffer interchangeably to refer to a sequence of characters.
Two types exist in the managed environment for marshaling unmanaged string buffers.
They are System.String and System.Text.StringBuilder. Of course, they both hold
character sequences. However, StringBuilder is more advantageous because it is very
efficient working with mutable strings than System.String.
Every time you use one of the methods of System.String class or you pass a
System.String to a function, normally, you create a new string object in memory, which
requires a new allocation of memory space for the new object. In addition, if the function
changes the string you will not get the results back. That is why System.String is called
immutable. On the other hand, StringBuilder does not require re-allocating of space
unless you exceed its capacity. Besides the talk about marshaling, you should use
StringBuilder to accommodate performance issues if you often change the same string
many times.
To keep System.String immutable, the marshaler copies the contents of the string to
another buffer before calling the function, and then it passes that buffer to the function.
If you were passing the string by reference, the marshaler copies the contents of the
buffer into the original string when returning from the function.
Read more about passing a type by value or by reference in the section “Passing
Mechanism” later in this chapter.
Another feature of StringBuilder is its ability to specify buffer capacity. As we will see, this
can be very helpful in plenty of cases.
Noteworthy to say that StringBuilder cannot be used inside compound types. Therefore,
you will need to use String instead.
Another point to mention is that you can pass array of System.Char in place of a
System.String or System.Text.StringBuilder. In other words, you can marshal unmanaged
strings as managed arrays of System.Char (or System.Int16, do you remember?)
If you need more information about Unicode, you can check the official site of
Unicode, www.Unicode.org. In addition, Programming Windows 5th by Charles Petzold
includes a must-read introduction of Unicode and character sets.
For controlling character encoding when marshaling unmanaged types, you may take
one of two approaches or you can combine them as needed. You can control the
encoding of the overall function (i.e. at the function level,) or you can drill down and
control the encoding process at a granular level by controlling every argument
separately (the second approach is required in certain cases e.g. MultiByteToWideChar()
function.)
For changing the encoding of the overall function, DllImportAttribute offers the property
CharSet that indicates the encoding (character set) for the strings and arguments of the
function. This property can take one of several values:
CharSet.Auto (CLR Default):
Strings encoding varies based on operating system; it is Unicode-encoded on Windows
NT and ANSI-encoded on other versions of Windows.
CharSet.Ansi (C# Default):
Strings are always 8-bit ANSI-encoded.
CharSet.Unicode:
Strings are always 16-bit Unicode-encoded.
CharSet.None:
Obsolete. Has the same behavior as CharSet.Ansi.
Take into consideration that if you have not set the CharSet property, CLR automatically
sets it to CharSet.Auto. However, some languages override the default behavior. For
example, C# defaults to CharSet.Ansi.
It is worth mentioning that plenty of functions that accept strings and buffers are just
names (technically typedefs)! They are not real functions, they are entry-points (aliases)
for the real functions. For example, ReadConsole() function is nothing except an entry
point redirects the call to the right function, either ReadConsoleA() if ANSI is defined, or
ReadConsoleW() if Unicode is defined (A stands for ANSI, and W stands for Wide which
means Unicode.) Therefore, you can actually bypass this entry-point by changing the
PInvoke method name to match the right function or by changing
DllImportAttribute.EntryPoint to the name of the required function. In both cases, setting
DllImportAttribute.CharSet along with is no use.
If you want to control the encoding at a granular level, you can apply the
MarshalAsAttribute attribute to the argument specifying the underlying unmanaged
type.
Usually, you will need to unify the character encoding of all your native functions and
types. This is, all the functions should be either Unicode or ANSI. Under rare occasions,
some functions would be different in character encoding.
It is worth mentioning that, for fixed-length strings you will need to set the SizeConst
property of MarshalAsAttribute to the buffer length.
These techniques are not limited to arguments only! You can use them with variables of
compound types too. We will look at compound types in the following chapter.
Try It Out!
Now we will look on both ReadConsole() and FormatConsole() unmanaged functions and
how to call them from your managed environment. Next is the definition of both
functions and other functions required for the example:
Listing 2.5 GetStdHandle(), ReadConsole(), GetLastError(), and FormatMessage() Unmanaged
Signature
HANDLE GetStdHandle(
DWORD nStdHandle);
BOOL ReadConsole(
HANDLE hConsoleInput,
[out] LPVOID lpBuffer,
DWORD nNumberOfCharsToRead,
[out] LPDWORD lpNumberOfCharsRead,
LPVOID lpReserved);
DWORD GetLastError(void);
DWORD FormatMessage(
DWORD dwFlags,
LPCVOID lpSource,
DWORD dwMessageId,
DWORD dwLanguageId,
[out] LPTSTR lpBuffer,
DWORD nSize,
va_list* Arguments);
And this is the managed version along with the test code.
// Message Options
const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x0100;
const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x0200;
const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x1000;
const uint FORMAT_MESSAGE_FLAGS =
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_SYSTEM;
// Message Source
public const int FORMAT_MESSAGE_FROM_HMODULE = 0x0800;
uint noCharacters;
StringBuilder builder = new StringBuilder(maxCount);
// Invalid handle
handle = GetStdHandle(12345);
Marshaling Handles
Generic Handles
There are plenty of type handles in unmanaged code, here is some of them:
HANDLE:
This is the most widely used handle type in the unmanaged environment. It represents a
generic handle.
HWND:
Most widely used with Windows application. It is a handle to a window or a control.
HDC, HGDIOBJ, HBITMAP, HICON, HBRUSH, HPEN, and HFONT:
If you have worked with GDI, you will be familiar with these handles. HDC is a handle to a
device context (DC) object that will be used for drawing. HGDIOBJ is a handle for any
GDI object. HBITMAP is a handle to a bitmap, while HICON is a handle to an icon.
HBRUSH is a handle to a brush, HPEN is a handle to pen, and HFONT is a handle to a
font.
HFILE:
A handle to a file opened by any of Windows File IO functions like OpenFile() function.
HMENU:
A handle to a menu or menu item.
Again, from all you have seen, you may have noticed that most types identified by a prefix
or a suffix. For example, handles prefixed with the letter H, while some pointers have the
suffix _PTR, or the prefix P or LP. While strings with letter W are Unicode-encoded, and
strings with letter T are OS-based.
Handles can be marshaled as the managed type System.IntPtr that represents a pointer
to an object into memory. It is worth mentioning that because System.IntPtr represents a
pointer to an object no matter what the object is, you can use System.IntPtr for
marshaling any type not handles only, but that is not recommended because it is more
difficult to work with, and it is not very flexible, but it provides more control over the
object in memory. For more information about memory management, see chapter 6:
“Memory Management.”
In addition, starting from version 2.0, new managed types for working with unmanaged
handles added to the .NET Framework. A new namespace Microsoft.Win32.SafeHandles
that contains most of the new types has been added too. Other types exist in
System.Runtime.InteropServices. These types called managed handles.
There are two kinds of managed handles safe and critical handles.
Safe Handles
IsClosed:
Returns a value indicates whether the handle is closed.
IsInvalid:
Abstract. If overridden, returns a value indicates whether the handle is invalid or not.
Close() and Dispose():
Both close the handle and dispose its resources. Internally, they rely on the abstract
method ReleaseHandle() for releasing the handle. Therefore, classes inherit from
SafeHandle must implement this member. Be aware that Dispose() is inherited from
System.IDispose interface that is implemented by SafeHandle, and Close() does not do
anything except calling the Dispose() method. Therefore, you strictly should dispose
(close) the handle as soon as you finish your work with it.
ReleaseHandle():
Protected Abstract. Use to provide handle clean-up code. This function should returns
true if successfully released, or false otherwise. In the case of false, it generates a
ReleaseHandleFailed Managed Debugging Assistant (MDA) exception that will not
interrupt your code but provides you with a bad sign about it. Keep in mind that
ReleaseHandle() called internally by Dispose().
SetHandle():
Protected. Sets the handle to the specified pre-existing handle.
SetHandleAsInvalid():
Sets the handle as invalid so it is no longer used.
DangerousGetHandle():
Returns System.IntPtr that represents the handle. Beware that if you have called
SetHandleAsInvalid() before calling DangerousGetHandle(), it returns the original handle
not the invalid one.
DangerousRelease():
Manually releasing the handle in unsafe manner. It is recommended using Close() or
Dispose() methods instead.
DangerousAddRef():
Increments the reference count of the handle. It is not recommended using neither
DangerousRelease() nor DangerousAddRef(), use safe methods instead. However, when
working with COM, you will find yourself using these functions
Do not use unsafe methods unless you really need to use it because they pass the
protection level offered by safe handles.
Because SafeHandle is abstract, you must either implement it or use one of its
implementation classes. Only two classes from the new namespace
Microsoft.Win32.SafeHandles implement SafeHandle, both are abstract too:
SafeHandleMinusOneIsInvalid:
Represents a safe handle of which a value of -1 indicates that the handle is invalid.
Therefore, IsInvalid returns true only if the handle equals to -1.
SafeHandleZeroOrMinusOneIsInvalid:
Represents a safe handle of which a value of 0 or -1 indicates that the handle is invalid.
So, IsInvalid returns true only if the handle equals to 0 or -1.
Notice that, choosing between the two implementations is up to the type of the
underlying handle. If it considered invalid if set to -1, use SafeHandleMinusOneIsInvalid.
If it considered invalid if set to 0 or -1, use SafeHandleZeroOrMinusOneIsInvalid. Using
the right class for the handle ensures that methods like IsInvalid() returns correct results.
It also ensures that CLR will mark the handle as garbage only if it is invalid.
If you need to provide a safe handle for your object, you will need to inherit from
SafeHandleMinusOneIsInvalid, SafeHandleZeroOrMinusOneIsInvalid, or even from
SafeHandle. Be aware that, you will always need to override the ReleaseHandle() method
because neither SafeHandleMinusOneIsInvalid nor SafeHandleZeroOrMinusOneIsInvalid
does override it.
SafeFileHandle:
A wrapper class for an IO device handle (e.g. HFILE.) This class internally overrides the
ReleaseHandle() and calls the unmanaged CloseHandle() function to close the handle.
Use when working with HFILE handles in Windows File IO functions like OpenFile() and
CreateFile(). Internally, System.FileStream uses a HFILE as SafeFileHandle, and it exposes
a constructor that accepts SafeFileHandle.
SafeWaitHandle:
If you are working with unmanaged thread synchronization objects like a Mutex or an
Event, then this should be the desired marshaling type for synchronization objects’
handles.
Now, we are going to create a file using CreateFile() function with SafeFileHandle for the
marshaling process. The definition of CreateFile() is as following:
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
/*
* Order of methods called by
* StreamWriter by this example:
*
* StreamWriter.Close()
* - StreamWriter.BaseStream.Close()
* - - FileStream.SafeFileHandle.Close()
* - - - SafeHandleZeroOrMinusOneIsInvalid
* .Close()
* - - - - SafeHandle.Close()
* - - - - - SafeHandle.ReleaseHandle()
*/
}
Although, you can use IntPtr instead of SafeFileHandle, the FileStream constructor that
accepts the IntPtr is considered obsolete (.NET 2.0 and higher) and you should use the
constructor that accepts the SafeFileHandle.
The next example demonstrates how to create your custom safe handle. This custom
safe handle represents a handle invalid only if equals to zero. Although, you can extend
the functionality of either SafeHandleMinusOneIsInvalid or
SafeHandleZeroOrMinusOneIsInvalid, we have inherited SafeHandle directly. Code is very
simple:
Until now, I do not have an answer for why a handle could be invalid only if it is set to
zero! Maybe you will need this for your custom handles. However, this is just an
illustration.
Critical Handles
Critical handles are the same as safe handles, except that they do not perform reference
counting, so they do not provide protection from recycling security attacks.
Use critical handles instead of safe handles to address performance considerations, but
you will be required to provide necessary synchronization for reference counting
yourself.
As the diagram illustrates, CriticalHandle is the base class that represents any critical
handle. It inherits from System.Runtime.ConstrainedExecution.CriticalFinalizerObject that
ensures the finalization process. The members of CriticalHandle are the same as
SafeHandle, except that it does not include the Dangerous-prefixed methods because
critical handles themselves are dangerous because they do not provide the necessary
protection. For more information about CriticalHandle members, refer to members of
SafeHandle discussed previously.
Because CriticalHandle is abstract, you must either implement it or use one of its
implementation classes. Only two classes from the new namespace
Microsoft.Win32.SafeHandles implement CriticalHandle, both are abstract too:
CriticalHandleMinusOneIsInvalid:
Represents a critical handle of which a value of -1 indicates that the handle is invalid.
Therefore, IsInvalid returns true only if the handle equals to -1.
CriticalHandleZeroOrMinusOneIsInvalid:
Represents a critical handle of which a value of 0 or -1 indicates that the handle is
invalid. So, IsInvalid returns true only if the handle equals to 0 or -1.
Examples are the same as SafeHandle, only to change the type name.
Passing Mechanism
When passing an argument to a function, the function may require either passing the
argument by value or by reference. If the function intends to change argument value, it
requires it to be passed by reference, otherwise, by value. This is what called passing
mechanism.
Value arguments (i.e. input/In arguments,) when passed to a function, a copy of the
argument is sent to the function. Therefore, any changes to the argument do not affect
the original copy. On the other hand, reference arguments, when passed to a function,
the argument itself is passed to the function. Therefore, the caller sees any changes
happen inside the function.
When passing an argument by value, no changes to the PInvoke method are required.
Conversely, passing an argument by reference requires two additional changes. The first
is adding the ref modifier to the argument if it is In/Out argument, or the out modifier if
it is Out argument. The second is decorating your argument with both InAttribute and
OutAttribute attributes if it is In/Out argument or only OutAttribute if it is Out argument.
To be honest, applying those attributes is not required, the modifiers are adequate in
most cases. However, applying them gives the CLR a notation about the passing
mechanism.
As you have seen, when marshaling a string, you can marshal it as a System.String or as a
System.Text.StringBuilder. By default, StringBuilder is passed by reference (you do not
need to apply any changes.) System.String on the other hand is passed by value.
It is worth mentioning that Windows API does not support reference arguments. Instead,
if a function requires an argument to be passed by reference, it declares it as a pointer so
that caller can see the applied changes. Other code such as COM libraries can require
either a pointer or a reference argument. In either cases, you can safely apply the
changes required. You can also marshal a pointer argument as System.IntPtr or as the
unsafe void* for example.
Many of the previous examples demonstrated only functions those require arguments to
be passed by value. Some functions require one or more arguments to be passed by
reference. A good example of a function requires In/Out argument is GetVersionEx()
which returns version information of the current system. It requires a single reference
(In/Out) argument. The argument is of the structure OSVERSIONINFOEX. For our
discussion, we will leave this function to the next chapter in the discussion of compound
types.
A great deal of functions require Out arguments specially for returning results or status
information. Good examples are ReadConsole() and WriteConsole() that require by-
reference Out arguments for returning the characters read/written. The following is the
unmanaged signature for the WriteConsole() function.
BOOL WriteConsole(
HANDLE hConsoleOutput,
VOID lpBuffer,
DWORD nNumberOfCharsToWrite,
LPDWORD lpNumberOfCharsWritten,
LPVOID lpReserved
);
And this is the managed version along with the test code:
[DllImport("Kernel32.dll")]
static extern IntPtr GetStdHandle(
[param: MarshalAs(UnmanagedType.U4)]
Int32 nStdHandle);
WriteConsole(handle,
textToWrite,
(uint)textToWrite.Length,
out noCharactersWritten,
null);
Finally yet importantly, chapter 6 provides you with more granular and down-level
details about the memory management and the passing mechanism.
Additional Techniques
Here we will talk about techniques that should be taken into consideration when
working with unmanaged code, they are encapsulation, creating wrappers, working with
nullable arguments, and working out CLS problem.
Encapsulation
If the function requires an argument that can be set to a value or more, you can define
these values (constants or typedefs) in an enumeration so you can easily access every set
of values separately; that technique called encapsulation (grouping.) The following
example shows the MessageBoxEx() example, the most suitable function for the example:
if (ret == MB_RETURN.IDYES)
Console.WriteLine("User clicked Yes!");
else if (ret == MB_RETURN.IDNO)
Console.WriteLine("User clicked No!");
else if (ret == MB_RETURN.IDCANCEL)
Console.WriteLine("User clicked Cancel!");
}
}
You could also change the names of the constants to friendly names.
Figure 2.4 shows the message box resulted from running of the last code.
[DllImport("Kernel32.dll")]
static extern IntPtr GetStdHandle(
[param: MarshalAs(UnmanagedType.U4)]
CONSOLE_STD_HANDLE nStdHandle);
Creating Wrappers
Exposing PInvoke methods to the outside the assembly is not a good practice. It is
always recommended that you group your PInvoke methods into an internal class, and
that class should be named as NativeMethods, SafeNativeMethods or
UnsafeNativeMethods. For more information about this, check Code Analyzing Rules in
MSDN documentation. Read “Move PInvokes to Native Methods Class” article.
The following code segment illustrates the wrapper method for our MessageBoxEx()
function:
if (result == 0)
// Not recommended throwing System.Exception
// throw a derived exception instead
throw new Exception("FAILED");
return (MB_RETURN)result;
}
Some function arguments are nullable. Means that they can take a NULL (null in C#)
value. To pass a NULL value to an argument, you can marshal this argument as
System.IntPtr, so you can set it to System.IntPtr.Zero to represent a NULL value. Another
trick here is creating an overload for the function, in which the first is marshaled as the
argument type, and the other is marshaled as System.IntPtr. Thus, if you pass a
System.IntPtr.Zero, CLR directs the call to the function with System.IntPtr. Conversely,
passing a value to the argument, directs the call to the function with the correct type.
The following code segment demonstrates this technique:
You should know that some types are non-CLS-compliant and you should avoid
exposing them outside the assembly. For example, the famous System.UInt32 is non-
CLS-compliant, and you strictly should not expose it.
Being non-CLS-compliant means that the type violates with CLS (Common Language
Specifications) specifications. Following CLS specifications helps the interoperation of
.NET languages. It helps avoiding some actions like declaring specific types or following
uncommon naming conventions.
Why to avoid such these acts? This helps the big goal of .NET Framework, the
interoperation of .NET languages. Some languages for example does not support
variable names beginning with an underscore (_) others do. Therefore, following the CLS
specifications allows your assembly to be callable from any other assembly build with
any language easily.
To force the check of CLS specification, you can decorate the assembly with
System.CLSCompliantAttribute attribute -specifying true,- and that would result in
compiler warnings whenever you try to expose non-CLS-compliant type out.
To work out this CLS dilemma, for functions require UInt32 as an argument, you can
create a wrapper that behaves as an entry-point to the private non-CLS-compliant
method. That wrapper method accepts, for instance, System.Int32 and converts it
internally to System.UInt32.
For structures, you can declare the structure as internal and continue using it the normal
way.
Again, you could replace all non-CLS-compliant types like System.UInt32 with CLS-
compliant equivalents like System.Int32 and take advantage of easily distributing your
types and assembly. However, that would not be easy in all cases.
Real-World Examples
In this chapter, we have covered many aspects of marshaling in many examples.
However, most of all were just for illustration.
The following are some real-world examples that solve problems that you might face
while developing your application. Those problems can be solved only via
interoperability with unmanaged code.
The following code swaps mouse buttons programmatically. It makes the left button acts
like the right button (e.g. opens the context menu) and vice versa.
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SwapMouseButton
([param: MarshalAs(UnmanagedType.Bool)] bool fSwap);
The following code shows how to turn on the screen saver programmatically.
[DllImport("User32.dll")]
public static extern int SendMessage
(IntPtr hWnd,
uint Msg,
uint wParam,
uint lParam);
The following code allows the form to be dragged from its body. This code is a good
example for the wrapper creating technique discussed earlier.
SafeNativeMethods.cs
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReleaseCapture();
HelperMethods.cs
MainForm.cs
Summary
The last word to say is that MarshalAsAttribute is not required all the time. Sometimes it
is optional, and other times it is required.
For example, if you marshal blittable data types like DWORD, you can safely ignore
MarshalAsAttribute. Conversely, if you are marshaling non-blittable data types like
booleans and strings, you will need to use the MarshalAsAttribute to ensure correct
marshaling process. However, it is always better giving the CLR and other developers a
notation about the underlying data type by apply the MarshalAsAttribute attribute to
blittable data types too.
Finally yet importantly, this chapter was the key for the gate to the interoperation with
unmanaged environments. It discussed the most important part of the marshaling
process, marshaling the simple types, which you will always need to keep it into your
mind.
Overview
This chapter demonstrates how to marshal compound types. Compound types are those
build of other types, for example structures and classes.
Like the previous chapter. This chapter breaks unmanaged compound types into two
categories, structures and unions. We first discuss structures and then we will dive into
unions and how to marshal them.
You might ask, why you have divided compound types into just two categories,
structures and unions, I can create classes too? The answer is easy. For its simplicity, this
book will focus primarily on Windows API. Therefore, you will find much of our talking
about Win32 functions and structures. However, the same rules apply to classes and
other unmanaged types.
Introduction
A compound type encapsulates related data together; it provides an organized and
arranged container for transmitting a group of variables between the client application
and the unmanaged server. It consists (usually) of variables of simple types and
(optionally) other compound types. In addition, it could define other compound types
inside.
Unmanaged Structures
Unmanaged Unions
As you know, because there is no compatibility between .NET and unmanaged code,
data must undergo some conversion routines for transmitting from the managed code
to the unmanaged server and vice versa, and compound types are no exception.
When marshaling structures in the managed environment, you must take into
consideration that while you access a variable into your by its name, Windows accesses it
via its address (i.e. position) inside the memory, it does not care about field name, but it
cares about its location and size. Therefore, the memory layout and size of the type are
very crucial.
When marshaling an unmanaged structure, you must take care of how that type is laid-
out into memory.
Actually, application memory is divided into blocks (in a 4-bytes base,) and every block
has its own address. When you declare a variable or a type in your program it is stored
inside the memory and got its memory address. Consequently, all data members inside a
structure have their own addresses that are relative to the address of the beginning of
the structure.
When we declare those structures in our code they are laid-out into memory and got
addresses like that:
Thus, you should keep in mind that the size and location of each of type members is very
crucial and you strictly should take care of how this type is laid-out into the memory.
For now, you do not have to think about the last illustration. We will cover memory
management in details in chapter 6.
For handling the memory layout problem, you must apply the StructLayoutAttribute
attribute to your marshaling type specifying the layout kind using the LayoutKind
property.
LayoutKind.Auto (Default):
Lets the CLR chooses how the type is laid-out into memory. Setting this value prevents
interoperation with this type, that means that you will not be able to marshal the
unmanaged structure with this type, and if you tried, an exception will be thrown.
LayoutKind.Sequential:
Variables of the type are laid-out sequentially. When setting this value ensure that all
variables are on the right order as they are defined in the unmanaged structure.
LayoutKind.Explicit:
Lets you control precisely each variable’s location inside the type. When setting this
value, you must apply the FieldOffsetAttribute attribute to every variable in your type
specifying the relative position in bytes of the variable to the start of the type. Note that
when setting this value, order of variables becomes unimportant.
For the sake of simplicity, you should lay-out all of your types sequentially. However,
when working with unions, you are required to explicitly control every variable’s location.
Unions are covered in the next section.
We have said that you should add only the type members into the marshaling type,
however, this is not always true. In structures where there is a member that you can set to
determine the structure size (like the OPENFILENAME structure,) you can add your own
members to the end of the structure. However, you should set the size member to the size
of the entire structure minus the new members that you have added. This technique is
discussed in details in chapter 6.
Try It Out!
The following example demonstrates how to marshal the famous structures SMALL_RECT
and COORD. Both used earlier with the ScrollConsoleScreenBuffer() function in the last
chapter. You can check code listing 3.1 earlier in this chapter for the definition of the
structures.
Next is the managed signature for both the structures. Note that you can marshal them
as managed classes too.
A union is a memory location that is shared by two or more different types of variables.
A union provides a way for interpreting the same bit pattern in two or more different
ways (or forms.)
In fact, unions share structures lots of characteristics, like the way they defined and
marshaled. It might be helpful to know that, like structures, unions can be defined inside
a structure or even as a single entity. In addition, unions can define compound types
inside, like structures too.
To understand unions, we will take a simple example. Consider the following union:
This was a simple union defines a character. It declared two members, i and c, it defined
them in the same memory location. Thus, it provides two ways for accessing the
character, by its code (int) and by its value (char). For this to work it allocates enough
memory storage for holding the largest member of the union and that member is called
container. Other members will overlap with the container. In our case, the container
is i because it is 4 bytes (on Win32, 16 on Win16), while c is only 1 byte. Figure 3.2 shows
how the memory is allocated for the union.
Because the two members are sharing the same memory location, when you change one
member the other is changed too. Consider the following C example:
int main()
{
union CHARACTER ch;
return 0;
}
When you change any of the members of the union, other members change too because
they are all share the same memory address.
Now consider the same example but with values that won’t fit into the char member:
int main()
{
union CHARACTER ch;
ch.i = 330;
printf("c = %c", ch.c); // prints 'J'
printf("\n"); // Ops!
ch.c += 32;
printf("i = %d", ch.i); // prints '362'
printf("\n");
return 0;
}
What happened? Because char is 1 bye wide, it interprets only the first 8 bits of the
union that are equal to 32.
The same rule applies if you add another member to the union. See the following
example. Notice that order of member declarations doesn’t matter.
int main()
{
union {
int i;
char c;
short n;
} ch;
ch.i = 2774186;
return 0;
}
Now, member i, the container, interprets the 32 bits. Member c, interprets the first 8 bits
(notice that we converted it to unsigned char to not to show the negative value.)
Member n, interprets the first high word (16 bits.)
You might ask: Why I need unions at all? I could easily use the cast operator to convert
between data types!
The answer is very easy. Unions come very efficient when casting between types require
much overhead. Consider the following example: You are about to write an integer to a
file. Unfortunately, there are no functions in the C standard library that allow you to write
an int to a file, and using fwrite() function requires excessive overhead. The perfect
solution is to define a union that contains an integer and a character array to allow it to
be interpreted as an integer and as a character array when you need to pass it to fwrite()
for example. See the following code snippet:
In addition, unions offer you more performance than casts. Moreover, your code will be
more readable and efficient when you use unions.
You can marshal a union the same way as you marshal structures, except that because of
the way that unions laid-out into memory, you will need to explicitly set variable
positions inside the type.
Follow these steps to marshal a union:
character.c = 'B';
// Should prints 66
Console.WriteLine("i = {0}", character.i);
}
Unions are marshaled like structures, they can be marshaled as either managed
structures or classes.
Setting StructLayoutAttribute.LayoutKind to LayoutKind.Explicit allows us to
exactly control the memory location of our members.
We use the FieldOffsetAttribute to specify the starting location in bytes of the
field into the type in memory.
To create the union between the fields, we set both the fields to the same
memory location.
In the example, member i occupies byte 0 through byte 4, and
member c occupies byte 0 through byte 1.
If we do not need the benefits of unions, we can omit member c because it is
contained inside the range of member i. However, we cannot omit member c because it
is the container.
When we change either one of the union variables, the other variable changes
too because they share the same memory address.
This union must be marshaled in a special way because managed code does not permit
value types and reference types to overlap.
As a refresher, a value-type is the type stored in the memory stack; it inherits from
System.ValueType. All primitive data types, structures, and enumerations are considered
value-types. On the other hand, reference-types are those types stored in the memory
heap; they inherit from System.Object. Most types in .NET are reference-types (except
System.ValueType and its descendents of course.)
As a result, we cannot union both members of our example, because whether marshaling
the second variable charArray as an array, a System.String, or as a
System.Text.StringBuilder, it is still a reference-type. Therefore, we have to leave the
benefits of unions and marshal only a single member. For our example, we will create
two marshaling types for our union, one with the first member marshaled, and the other
with the other member.
As we know, the layout and size of the type inside the memory is the most crucial.
Therefore, we must preserve the layout and size of our union. This union has a 128 bytes
array as a container and only one member contained, and this member is only 2-bytes.
Therefore, we have two choices, to marshal the union with the container member, or to
marshal it with the contained member but to extend it enough to be as large as the
container. In this example, we will take the two approaches.
Try It Out!
The following are two code segments. The first demonstrates how to marshal only the
second member which is the container, while the second demonstrates how to marshal
the first member.
// Setting StructLayoutAttribute.CharSet
// ensures the correct encoding for all
// string members of the union in our example
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
//public struct UNION_WITH_ARRAY_1
public struct UNION_WITH_ARRAY_1
{
// As we know, character arrays can be marshaled
// as either an array or as a string
// StructLayoutAttribute.Size determines
// the size -in bytes- of the type.
// If the size specified is larger than
// members’ size, the last member will be extended
// Because this is only a single
// member, we laid it out sequentially.
[StructLayout(LayoutKind.Sequential, Size = 128)]
//public class UNION_WITH_ARRAY_2
public struct UNION_WITH_ARRAY_2
{
[MarshalAs(UnmanagedType.I2)]
public short number;
}
For more information about marshaling arrays, refer to the next chapter.
Value-Types:
These types are stored in the memory stack. They are destroyed when their scope ends,
therefore, they are short-lived. Types of this category are all types inherit from
System.ValueType (like all primitive data types, structures, and enumerations.)
Reference-Types:
These types are stored in the memory heap. They are controlled by the Garbage
Collector (GC,) therefore, they may retain in memory for a long while. Reference-types
are all types -directly or indirectly- inherit from System.Object (except System.ValueType
and descendants of course.) All .NET classes fall in this category.
Talking about value-types and reference-types leads us to talk about the passing
mechanism. And that is what the next section is devoted for.
Passing Mechanism
In the last chapter, we have talked about the passing mechanism with simple types and
how it affects the call. Actually, all we have learnt is applied to the compound types too.
As a refresher, when a type passed by value, a copy of type passed to the function, not
the value itself. Therefore, any changes to the type inside the function do not affect the
original copy. On the other hand, passing a type by reference passes a pointer to the
value to the function. In other words, the value itself is passed. Therefore, any changes to
the type inside the function are seen by the caller.
Functions require the type passed to be passed either by value or by reference. Plus, they
require the argument to be passed by reference only if the argument will be changed
inside.
All of the information learnt from the last chapter is applied to this chapter too.
Compound types also can be passed by value or by reference. When passing by value,
no changes need to be applied. On the other hand passing a type by reference requires
some changes to the PInvoke method and the call itself.
If you are marshaling as a structure, you may add the ref modifier to the parameter.
However, classes are -by default- reference-types. Thus, they are normally passed by
reference and they cannot be passed by value. Therefore, they do not need
the ref modifier.
On the other hand, if you are passing the type as output (Out,) you will need to add
the out modifier whether it is a structure or a class.
As you know, you can decorate In/Out arguments with both InAttribute and OutAttribute
attributes. For Out arguments, specify OutAttribute attribute only.
Notice that there is a big difference between managed and unmanaged classes.
Unmanaged classes are -by default- value-types. Manager classes are reference-types.
The following example demonstrates the PInvoke method for the function
GetVersionEx(). This function requires a single In/Out argument. That argument is of the
type OSVERSIONINFO.
The function uses OSVERSIONINFO’s dwOSVersionInfoSize field as input from the caller
for determining the type size, and it uses the remaining arguments as output for sending
the version information back. Therefore, the function requires the argument to be passed
by reference as In/Out.
BOOL GetVersionEx(
OSVERSIONINFO lpVersionInfo
);
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetVersionEx
([param: In, Out]
// If a class remove the "ref" keyword
ref OSVERSIONINFO lpVersionInfo);
[StructLayout(LayoutKind.Sequential)]
//public class OSVERSIONINFO
public struct OSVERSIONINFO
{
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwOSVersionInfoSize;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwMajorVersion;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwMinorVersion;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwBuildNumber;
[MarshalAs(UnmanagedType.U4)]
public UInt32 dwPlatformId;
//GetVersionEx(info);
GetVersionEx(ref info);
You already know that the character encoding can be either ANSI or Unicode.
When a string is ANSI-encoded, every character reserves only a single byte of application
memory. On the other hand, every character in a Unicode-encoded string reserves two
bytes of the memory. Therefore, a string like “C-Sharp” with 7 characters reserves 7 bytes
if ANSI-encoded and 14 bytes if Unicode-encoded.
You can determine the character encoding of the compound type by specifying the
CharSet property of the StructLayoutAttribute attribute. This property can take one of
several values:
Take into consideration that if you have not set the CharSet property, CLR automatically
sets it to CharSet.Auto. However, some languages override the default behavior. For
example, C# defaults to CharSet.Ansi.
In addition, you can determine the character encoding at a granular level by specifying
the CharSet property of the MarshalAsAttribute attribute applied to the member.
Real-World Examples
The DEVMODE Structure
Now, we are going to dig into real-world examples. In the first example, we are going to
marshal one of the most complex compound structures in the Windows API, it is the
DEVMODE structure.
If you have worked with GDI, you will be somewhat familiar with this structure. It
encapsulates information about initialization and environment of a printer or a display
device. It is required by many functions like EnumDisplaySettings(),
ChangeDisplaySettings() and OpenPrinter().
The complexity of this structure comes because of few factors. Firstly, there are unions
defined inside the structure. In addition, the definition of this structure defers from a
platform to another. As we will see, the structure defines some members based on the
operating system.
Here is the definition of DEVMODE structure along with the POINTL structure that is
referenced by DEVMODE.
You might have noticed that two unions are defined inside the structure. In addition, a
structure is defined inside the first union! Moreover, the last 8 members are not
supported in Windows NT. Plus, the very last two members, dmPanningWidth and
dmPanningHeight, are not supported in Windows 9x (95/98/ME.)
When working with Windows API, you should take care of operating system compatibility.
Some functions, for instance, are not supported on certain operating systems (e.g. most
Unicode versions are not supported on Win9x.) Other functions take arguments that vary
based on the OS (i.e. EnumPrinters() function.) If your application tried to call a function,
for instance, that is not supported by the current operating system, the call would fail.
If you need your application to be portable to every platform, you will need to create
three versions of the structure, one for Windows ME and its ascendants, one for
Windows NT, and the last for Windows 2000 and higher versions. In addition, you will
need to create three overloads of every function require DEVMODE structure; three
overloads for the three structures. For the sake of simplicity, we will assume that you are
working with Windows 2000 or a higher version. Thus, we will marshal all members of
the structure.
The following is the managed version of both DEVMODE and POINTL structures:
[FieldOffset(34)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmDriverVersion;
[FieldOffset(36)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmSize;
[FieldOffset(38)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmDriverExtra;
[FieldOffset(40)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmFields;
[FieldOffset(62)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmDuplex;
[FieldOffset(64)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmYResolution;
[FieldOffset(66)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmTTOption;
[FieldOffset(70)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmCollate;
// CCHDEVICENAME = 32 = 0x50
[FieldOffset(72)]
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 32,
ArraySubType = UnmanagedType.U1)]
public Byte[] dmFormName;
[FieldOffset(104)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmBitsPerPel;
[FieldOffset(108)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPelsWidth;
[FieldOffset(112)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPelsHeight;
[FieldOffset(116)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmNup;
// ************* Union End *************
[FieldOffset(120)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayFrequency;
[FieldOffset(124)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmICMMethod;
[FieldOffset(128)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmICMIntent;
[FieldOffset(132)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmMediaType;
[FieldOffset(136)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDitherType;
[FieldOffset(140)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmReserved1;
[FieldOffset(144)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmReserved2;
[FieldOffset(148)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPanningWidth;
[FieldOffset(152)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmPanningHeight;
}
// 16-bytes structure
[StructLayout(LayoutKind.Sequential)]
//public class DEVMODE_PRINT_SETTINGS
public struct DEVMODE_PRINT_SETTINGS
{
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
// 8-bytes structure
[StructLayout(LayoutKind.Sequential)]
//public class POINTL
public struct POINTL
{
public Int32 x;
public Int32 y;
}
As we have said earlier in the previous chapter, this writing assumes 32-bit versions of
Windows. For instance, in the DEVMODE example, we have assumed that DWORDs are 4
bytes. If you want to port your application to a 64-bit machine, DWORDs should be
considered as 8 bytes.
Lengthy, isn’t it? DEVMODE is one of the lengthy and compound GDI structures. If you
want to learn more about laying out structure into memory, refer to chapter 6 “Memory
Management.”
Whether the union defined as a single entity or inside a structure, you will need to
lay-out the type explicitly into memory to allow defining two or more variables at the
same memory location.
When setting the memory layout explicitly, we apply the FieldOffsetAttribute
attribute to the variable specifying the location -in bytes- of the variable from the start of
the type.
In the union that defines a structure inside, we marshaled the structure outside
the union and referred it to be the container of other members. Chapter 6 demonstrates
other techniques for laying-out structures into memory.
The follows example shows how you can access and modify display settings
programmatically using C# and Windows API. In this example we will create four
functions, one retrieves current display settings, another enumerates available display
modes, the third changes current display settings, and the last changes screen
orientation (i.e. rotates the screen.)
For our example, we will use the DEVMODE and POINTL structures that we have
marshaled previously. In addition, we will make use of two new Windows API functions,
EnumDisplaySettings and ChangeDisplaySettings. The following is the unmanaged
signature of both functions:
BOOL EnumDisplaySettings(
LPCTSTR lpszDeviceName, // display device
DWORD iModeNum, // graphics mode
[In, Out] LPDEVMODE lpDevMode // graphics mode settings
);
LONG ChangeDisplaySettings(
LPDEVMODE lpDevMode, // graphics mode
DWORD dwflags // graphics mode options
);
For more information about these functions, refer to the MSDN documentation.
[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern Boolean EnumDisplaySettings(
[param: MarshalAs(UnmanagedType.LPTStr)]
string lpszDeviceName,
[param: MarshalAs(UnmanagedType.U4)]
int iModeNum,
[In, Out]
ref DEVMODE lpDevMode);
[DllImport("User32.dll")]
[return: MarshalAs(UnmanagedType.I4)]
public static extern int ChangeDisplaySettings(
[In, Out]
ref DEVMODE lpDevMode,
[param: MarshalAs(UnmanagedType.U4)]
uint dwflags);
Finally, those are our four functions that utilize the native functions:
if (EnumDisplaySettings(null,
ENUM_CURRENT_SETTINGS, ref mode) == true) // Succeeded
{
Console.WriteLine("Current Mode:\n\t" +
"{0} by {1}, {2} bit, {3} degrees, {4} hertz",
mode.dmPelsWidth, mode.dmPelsHeight,
mode.dmBitsPerPel, mode.dmDisplayOrientation * 90,
mode.dmDisplayFrequency);
}
}
Console.WriteLine("Supported Modes:");
while (EnumDisplaySettings(null,
modeIndex, ref mode) == true) // Mode found
{
Console.WriteLine("\t{0} by {1}, {2} bit, " +
"{3} degrees, " +
"{4} hertz",
mode.dmPelsWidth, mode.dmPelsHeight,
mode.dmBitsPerPel, mode.dmDisplayOrientation * 90,
mode.dmDisplayFrequency);
if (result == DISP_CHANGE_SUCCESSFUL)
{
Console.WriteLine("Succeeded.\n");
Console.WriteLine();
There are functionalities of console applications that are not accessible from the .NET
Framework like clearing the console screen and moving a text around.
The following sample shows a tiny library for console applications. It contains some of
the common functionalities of the console (like writing and reading data) along with new
functionalities added.
SafeNativeMethods.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetConsoleCursorInfo(
IntPtr hConsoleOutput,
ref CONSOLE_CURSOR_INFO lpConsoleCursorInfo);
ConsoleLib.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
ClearScreen(location);
}
/// <span class="code-SummaryComment"><summary>
</span> /// Clears the screen buffer starting from a specific location.
/// <span class="code-SummaryComment"></summary>
</span> /// <span class="code-SummaryComment"><param name="location">The location of
which to start clearing
</span> /// the screen buffer.<span class="code-SummaryComment"></param>
</span> public static void ClearScreen(COORD location)
{
// Clearing the screen starting from the specified location
// Setting the character to a white space means clearing it
// Setting the count to 0 means clearing to the end, not a specific length
FillConsoleBuffer(location, 0, SafeNativeMethods.WHITE_SPACE);
}
uint length;
if (alignment == ConsoleTextAlignment.Right)
pos.X = (short)(info.dwSize.X - txt.Length);
else // Center
pos.X = (short)((info.dwSize.X - txt.Length) / 2);
pos.Y = info.dwCursorPosition.Y;
return info;
}
WriteLine("Cursor Position:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "\t{0},
{1}",
info.dwCursorPosition.X, info.dwCursorPosition.Y));
WriteLine("--------------------");
}
while (true)
{
// Moving to the right
rect.Right = (short)(rect.Left + (txt.Length - 1));
// The character to fill the empty cells created after the move with
CHAR_INFO charInfo = new CHAR_INFO();
charInfo.Char = SafeNativeMethods.WHITE_SPACE; // For clearing the cells
Summary
After all, you learned that compound types are unmanaged structures and unions, and
they called compound because they consisted of other types.
You learned that compound types can be marshaled as either a managed structure or a
class. In addition, you learned how to lay-out the type into memory.
Again and again, the memory layout and size of the type is very crucial.
After that, you have worked with unions and learned that unions are simply a group of
multiple variables share the same memory. In fact, it is the same memory location that is
shared by one or more variables. Therefore, bits are represents in several ways.