Download PDF and XPS versions of the book here.
Chapter 1: Introducing Marshaling
Chapter 2: Marshaling Simple Types
Chapter 3: Marshaling Compound Types
Chapter Contents
Contents of this chapter:
- Chapter Contents
- Overview
- Introduction
- Marshaling Unmanaged Structures
- How to Marshal a Structure
- Handling Memory Layout Problem
- Try It Out!
- Marshaling Unions
- A Short Speech About Unions
- How to Marshal a Union
- Unions with Arrays
- Try It Out!
- Value-Types and Reference-Types
- Passing Mechanism
- Real-World Examples
- The DEVMODE Structure
- Working with Display Settings
- Summary
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.
Compound types come in two kinds:
- Unmanaged Structures
- Unmanaged Unions
An example of a structure is OSVERSIONINFOEX structure that encapsulates operating system version information together. For those who are somewhat familiar with DirectX, they may find that DirectX API relies heavily on structures.
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.
In the next section, we will focus of the first kind, structures.
Marshaling Unmanaged Structures
How to Marshal a Structure
Unmanaged structures can be marshaled as managed structures or even classes. Choosing between a managed structure and a class is up to you, there are no rules to follow. However, when marshaling as managed classes, there are some limitations with the passing mechanism as we will see later in this chapter.
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.
You can marshal an unmanaged structure in few steps:
- Create the marshaling type either a managed structure or a class.
- Add the type fields (variables) only. Again, layout and size of the type are very crucial. Therefore, fields must be ordered as they are defined, so that the Windows can access them correctly.
- Decorate your type with StructLayoutAttribute attribute specifying the memory layout kind.
Handling Memory Layout Problem
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.
Consider the following structures:
Listing 3.1 SMALL_RECT and COORD Unmanaged Signature
typedef struct SMALL_RECT {
SHORT Left;
SHORT Top;
SHORT Right;
SHORT Bottom;
};
typedef struct COORD {
SHORT X;
SHORT Y;
};
When we declare those structures in our code they are laid-out into memory and got addresses like that:
Figure 3.1 How Memory is Laid-Out
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.
This property can take one of three values:
- 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.
Listing 3.2 SMALL_RECT and COORD Managed Signature
[StructLayout(LayoutKind.Sequential)]
public struct SMALL_RECT
{
public UInt16 Left;
public UInt16 Top;
public UInt16 Right;
public UInt16 Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public UInt16 X;
public UInt16 Y;
}
Marshaling Unions
A Short Speech About Unions
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:
Listing 3.3 SOME_CHARACTER Unmanaged Signature
typedef union SOME_CHARACTER {
int i;
char c;
};
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.
Figure 3.2 SOME_CHARACTER 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:
Listing 3.4 Unions Example 1
int main()
{
union CHARACTER ch;
ch.i = 65;
printf("c = %c", ch.c);
printf("\n");
ch.c += 32;
printf("i = %d", ch.i);
printf("\n");
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:
Listing 3.5 Unions Example 2
int main()
{
union CHARACTER ch;
ch.i = 330;
printf("c = %c", ch.c);
printf("\n");
ch.c += 32;
printf("i = %d", ch.i);
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.
Listing 3.6 Unions Example 3
int main()
{
union {
int i;
char c;
short n;
} ch;
ch.i = 2774186;
printf("i = %d", ch.i);
printf("\n");
printf("c = %i", (unsigned char)ch.c);
printf("\n");
printf("n = %d", ch.n);
printf("\n");
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:
Listing 3.7 Unions Example 4
typedef union myval{
int i;
char str[4];
};
In addition, unions offer you more performance than casts. Moreover, your code will be more readable and efficient when you use unions.
More on how unions are laid-out into memory in chapter 6.
How to Marshal a Union
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:
- Create your marshaling type, no matter whether your marshaling type is a managed structure or class. Again, classes require special handling when passed as function arguments. Passing mechanism is covered soon.
- Decorate the type with the StructLayoutAttribute attribute specifying LayoutKind.Explicit for the explicit layout kind.
- Add the type fields. Do not add fields other than those defined in the unmanaged signature. Because we are controlling the type layout explicitly, order of fields is not important.
- Decorate every field with the FieldOffsetAttribute attribute specifying the absolute position in bytes of the member from the start of the type.
The following example demonstrates how to marshal our SOME_CHARACTER union.
Listing 3.8 SOME_CHARACTER Managed Signature
[StructLayout(LayoutKind.Explicit)]
public struct SOME_CHARACTER
{
[FieldOffset(0)]
[MarshalAs(UnmanagedType.U4)]
public int i;
[FieldOffset(0)]
public char c;
}
public static void Main()
{
SOME_CHARACTER character = new SOME_CHARACTER();
character.i = 65;
Console.WriteLine("c = {0}", character.c);
character.c = 'B';
Console.WriteLine("i = {0}", character.i);
}
From the last code, we learn that…
- 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.
Unions with Arrays
Another example of a union is as following:
Listing 3.9 UNION_WITH_ARRAY Unmanaged Signature
typedef union UNION_WITH_ARRAY
{
INT number;
CHAR charArray[128];
};
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.)
That is, all value-types inherit -directly or indirectly- from System.ValueType.
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.
Listing 3.10 UNION_WITH_ARRAY Union Managed Signature
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct UNION_WITH_ARRAY_1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string charArray;
}
[StructLayout(LayoutKind.Sequential, Size = 128)]
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 and Reference-Types
In the realm of .NET, types are broken into two categories:
- 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.
Stack and heap! Confused? Check chapter 6 for more details.
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.
Moreover, an argument passed by reference can be passed either as Input/Output (In/Out) or Output (Out). In/Out arguments used by the function for receiving the input from the caller and posting the changes back to him. Therefore, In/Out arguments must be initialized before handing them to the function. On the other hand, output (Out) arguments are only used for returning output to the caller. Therefore, they do not require pre-initialization because the function will initialize them.
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.
Next is the definition of the function along with the structure:
Listing 3.11 GetVersionEx() Unmanaged Signature
BOOL GetVersionEx(
OSVERSIONINFO lpVersionInfo
);
typedef struct OSVERSIONINFO{
DWORD dwOSVersionInfoSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformId;
TCHAR szCSDVersion[128];
};
In addition, this is the managed version with the text code:
Listing 3.12 Retrieving System Version Information Sample
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetVersionEx
([param: In, Out]
ref OSVERSIONINFO lpVersionInfo);
[StructLayout(LayoutKind.Sequential)]
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;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szCSDVersion;
}
static void Main()
{
OSVERSIONINFO info = new OSVERSIONINFO();
info.dwOSVersionInfoSize = (uint)Marshal.SizeOf(info);
GetVersionEx(ref info);
Console.WriteLine("System Version: {0}.{1}",
info.dwMajorVersion, info.dwMinorVersion);
}
More about the passing mechanism in chapter 6.
Compound Types and Character Encoding
As you know, the size and layout of the marshaling type is the most important. If the compound type contains a textual data, sure special handling should be taken to ensure correct marshaling of the data.
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:
- 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.
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.
Listing 3.13 DEVMODE and POINTL Unmanaged Signature
typedef struct DEVMODE {
BCHAR dmDeviceName[CCHDEVICENAME];
WORD dmSpecVersion;
WORD dmDriverVersion;
WORD dmSize;
WORD dmDriverExtra;
DWORD dmFields;
union {
struct {
short dmOrientation;
short dmPaperSize;
short dmPaperLength;
short dmPaperWidth;
short dmScale;
short dmCopies;
short dmDefaultSource;
short dmPrintQuality;
};
POINTL dmPosition;
DWORD dmDisplayOrientation;
DWORD dmDisplayFixedOutput;
};
short dmColor;
short dmDuplex;
short dmYResolution;
short dmTTOption;
short dmCollate;
BYTE dmFormName[CCHFORMNAME];
WORD dmLogPixels;
DWORD dmBitsPerPel;
DWORD dmPelsWidth;
DWORD dmPelsHeight;
union {
DWORD dmDisplayFlags;
DWORD dmNup;
}
DWORD dmDisplayFrequency;
#if(WINVER >;= 0x0400)
DWORD dmICMMethod;
DWORD dmICMIntent;
DWORD dmMediaType;
DWORD dmDitherType;
DWORD dmReserved1;
DWORD dmReserved2;
#if (WINVER >;= 0x0500) || (_WIN32_WINNT >;= 0x0400)
DWORD dmPanningWidth;
DWORD dmPanningHeight;
#endif
#endif /* WINVER >;= 0x0400 */
};
typedef struct POINTL {
LONG x;
LONG y;
};
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:
Listing 3.14 DEVMODE and POINTL Managed Signature
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public struct DEVMODE
{
[FieldOffset(0)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public Char[] dmDeviceName;
[FieldOffset(32)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmSpecVersion;
[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(44)]
public DEVMODE_PRINT_SETTINGS dmSettings;
[FieldOffset(44)]
public POINTL dmPosition;
[FieldOffset(44)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayOrientation;
[FieldOffset(44)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmDisplayFixedOutput;
[FieldOffset(60)]
[MarshalAs(UnmanagedType.I2)]
public Int16 dmColor;
[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;
[FieldOffset(72)]
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 32,
ArraySubType = UnmanagedType.U1)]
public Byte[] dmFormName;
[FieldOffset(102)]
[MarshalAs(UnmanagedType.U2)]
public UInt16 dmLogPixels;
[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 dmDisplayFlags;
[FieldOffset(116)]
[MarshalAs(UnmanagedType.U4)]
public UInt32 dmNup;
[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;
}
[StructLayout(LayoutKind.Sequential)]
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;
}
[StructLayout(LayoutKind.Sequential)]
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.”
From the last code we learn that…
- 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.
Working with Display Settings
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:
Listing 3.15 EnumDisplaySettings() and ChangeDisplaySettings() Unmanaged Signature
BOOL EnumDisplaySettings(
LPCTSTR lpszDeviceName,
DWORD iModeNum,
[In, Out] LPDEVMODE lpDevMode
);
LONG ChangeDisplaySettings(
LPDEVMODE lpDevMode,
DWORD dwflags
);
For more information about these functions, refer to the MSDN documentation.
The next is the managed version of the functions:
Listing 3.16 EnumDisplaySettings() and ChangeDisplaySettings() Managed Signature
[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:
Listing 3.17 Accessing/Modifying Display Settings Sample
public static void GetCurrentSettings()
{
DEVMODE mode = new DEVMODE();
mode.dmSize = (ushort)Marshal.SizeOf(mode);
if (EnumDisplaySettings(null,
ENUM_CURRENT_SETTINGS, ref mode) == true)
{
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);
}
}
public static void EnumerateSupportedModes()
{
DEVMODE mode = new DEVMODE();
mode.dmSize = (ushort)Marshal.SizeOf(mode);
int modeIndex = 0;
Console.WriteLine("Supported Modes:");
while (EnumDisplaySettings(null,
modeIndex, ref mode) == true)
{
Console.WriteLine("\t{0} by {1}, {2} bit, " +
"{3} degrees, " +
"{4} hertz",
mode.dmPelsWidth, mode.dmPelsHeight,
mode.dmBitsPerPel, mode.dmDisplayOrientation * 90,
mode.dmDisplayFrequency);
modeIndex++;
}
}
public static void ChangeDisplaySettings
(int width, int height, int bitCount)
{
DEVMODE originalMode = new DEVMODE();
originalMode.dmSize = (ushort)Marshal.SizeOf(originalMode);
EnumDisplaySettings(null, ENUM_CURRENT_SETTINGS, ref originalMode);
DEVMODE newMode = originalMode;
newMode.dmPelsWidth = (uint)width;
newMode.dmPelsHeight = (uint)height;
newMode.dmBitsPerPel = (uint)bitCount;
int result = ChangeDisplaySettings(ref newMode, 0);
if (result == DISP_CHANGE_SUCCESSFUL)
{
Console.WriteLine("Succeeded.\n");
GetCurrentSettings();
Console.WriteLine();
Console.ReadKey(true);
ChangeDisplaySettings(ref originalMode, 0);
}
else if (result == DISP_CHANGE_BADMODE)
Console.WriteLine("Mode not supported.");
else if (result == DISP_CHANGE_RESTART)
Console.WriteLine("Restart required.");
else
Console.WriteLine("Failed. Error code = {0}", result);
}
public static void RotateScreen(bool clockwise)
{
if (clockwise)
if (newMode.dmDisplayOrientation <; DMDO_270)
newMode.dmDisplayOrientation++;
else
newMode.dmDisplayOrientation = DMDO_DEFAULT;
else
if (newMode.dmDisplayOrientation >; DMDO_DEFAULT)
newMode.dmDisplayOrientation--;
else
newMode.dmDisplayOrientation = DMDO_270;
uint temp = newMode.dmPelsWidth;
newMode.dmPelsWidth = newMode.dmPelsHeight;
newMode.dmPelsHeight = temp;
}
The Console Library
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.
Listing 3.18 The Console Library Sample
SafeNativeMethods.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
internal static class SafeNativeMethods
{
public const int STD_INPUT_HANDLE = -10;
public const int STD_OUTPUT_HANDLE = -11;
public const int STD_ERROR_HANDLE = -12;
public const char WHITE_SPACE = ' ';
[DllImport("Kernel32.dll")]
public static extern IntPtr GetStdHandle([param: MarshalAs(UnmanagedType.I4)] int nStdHandle);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool WriteConsole
(IntPtr hConsoleOutput,
string lpBuffer,
[param: MarshalAs(UnmanagedType.U4)] uint nNumberOfCharsToWrite,
[param: MarshalAs(UnmanagedType.U4)] [Out] out uint lpNumberOfCharsWritten,
[param: MarshalAs(UnmanagedType.U4)]
uint lpReserved);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ReadConsole(
IntPtr hConsoleInput,
StringBuilder lpBuffer,
[param: MarshalAs(UnmanagedType.U4)] uint nNumberOfCharsToRead,
[param: MarshalAs(UnmanagedType.U4)] [Out] out uint lpNumberOfCharsRead,
[param: MarshalAs(UnmanagedType.U4)] uint lpReserved);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetConsoleCursorInfo(
IntPtr hConsoleOutput,
[Out] out CONSOLE_CURSOR_INFO lpConsoleCursorInfo);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetConsoleCursorInfo(
IntPtr hConsoleOutput,
ref CONSOLE_CURSOR_INFO lpConsoleCursorInfo);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ScrollConsoleScreenBuffer(
IntPtr hConsoleOutput,
ref SMALL_RECT lpScrollRectangle,
IntPtr lpClipRectangle,
COORD dwDestinationOrigin,
ref CHAR_INFO lpFill);
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetConsoleScreenBufferInfo
(IntPtr hConsoleOutput,
[Out] out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FillConsoleOutputCharacter
(IntPtr hConsoleOutput,
char cCharacter,
[param: MarshalAs(UnmanagedType.U4)] uint nLength,
COORD dwWriteCoord,
[param: MarshalAs(UnmanagedType.U4)][Out] out uint lpNumberOfCharsWritten);
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetConsoleCursorPosition
(IntPtr hConsoleOutput, COORD dwCursorPosition);
}
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_SCREEN_BUFFER_INFO
{
public COORD dwSize;
public COORD dwCursorPosition;
[MarshalAs(UnmanagedType.U2)]
public ushort wAttributes;
public SMALL_RECT srWindow;
public COORD dwMaximumWindowSize;
}
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
[MarshalAs(UnmanagedType.I2)]
public short X;
[MarshalAs(UnmanagedType.I2)]
public short Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct SMALL_RECT
{
[MarshalAs(UnmanagedType.I2)]
public short Left;
[MarshalAs(UnmanagedType.I2)]
public short Top;
[MarshalAs(UnmanagedType.I2)]
public short Right;
[MarshalAs(UnmanagedType.I2)]
public short Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_CURSOR_INFO
{
[MarshalAs(UnmanagedType.U4)]
public uint dwSize;
[MarshalAs(UnmanagedType.Bool)]
public bool bVisible;
}
[StructLayout(LayoutKind.Sequential)]
public struct CHAR_INFO
{
public char Char;
[MarshalAs(UnmanagedType.U2)]
public ushort Attributes;
}
ConsoleLib.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
public enum ConsoleTextAlignment
{
Left,
Right,
Center
}
public enum ConsoleStandardDevice
{
Input = SafeNativeMethods.STD_INPUT_HANDLE,
Output = SafeNativeMethods.STD_OUTPUT_HANDLE,
Error = SafeNativeMethods.STD_ERROR_HANDLE
}
public static class ConsoleExtensions
{
public static void ClearScreen()
{
COORD location = new COORD();
location.X = 0;
location.Y = 0;
ClearScreen(location);
}
public static void ClearScreen(COORD location)
{
FillConsoleBuffer(location, 0, SafeNativeMethods.WHITE_SPACE);
}
public static void FillConsoleBuffer(COORD location, uint count, char character)
{
IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
uint length;
if (count == 0)
{
CONSOLE_SCREEN_BUFFER_INFO info = GetBufferInfo(ConsoleStandardDevice.Output);
length = (uint)(info.dwSize.X * info.dwSize.Y);
}
else
length = count;
uint numChars;
SafeNativeMethods.FillConsoleOutputCharacter(handle, character,
length, location, out numChars);
SetCursorPosition(location);
}
public static IntPtr GetStandardDevice(ConsoleStandardDevice device)
{
return SafeNativeMethods.GetStdHandle((int)device);
}
public static void WriteLine()
{
WriteLine(string.Empty);
}
public static void WriteLine(string txt)
{
WriteLine(txt, ConsoleTextAlignment.Left);
}
public static void WriteLine(string txt, ConsoleTextAlignment alignment)
{
Write(txt + Environment.NewLine, alignment);
}
public static void Write(string txt)
{
Write(txt, ConsoleTextAlignment.Left);
}
public static void Write(string txt, ConsoleTextAlignment alignment)
{
if (alignment == ConsoleTextAlignment.Left)
InternalWrite(txt);
else
{
CONSOLE_SCREEN_BUFFER_INFO info = GetBufferInfo(ConsoleStandardDevice.Output);
COORD pos = new COORD();
if (alignment == ConsoleTextAlignment.Right)
pos.X = (short)(info.dwSize.X - txt.Length);
else
pos.X = (short)((info.dwSize.X - txt.Length) / 2);
pos.Y = info.dwCursorPosition.Y;
SetCursorPosition(pos);
InternalWrite(txt);
}
}
private static void InternalWrite(string txt)
{
uint count;
IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
SafeNativeMethods.WriteConsole(handle, txt, (uint)txt.Length, out count, 0);
}
public static void ShowCursor(bool show)
{
CONSOLE_CURSOR_INFO info;
IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
SafeNativeMethods.GetConsoleCursorInfo(handle, out info);
info.bVisible = show;
SafeNativeMethods.SetConsoleCursorInfo(handle, ref info);
}
public static string ReadText()
{
StringBuilder buffer = new StringBuilder(256);
uint count;
SafeNativeMethods.ReadConsole(GetStandardDevice(ConsoleStandardDevice.Input), buffer,
(uint)buffer.Capacity, out count, 0);
return buffer.ToString().Substring(0, (int)(count - Environment.NewLine.Length));
}
public static CONSOLE_SCREEN_BUFFER_INFO GetBufferInfo(ConsoleStandardDevice device)
{
IntPtr handle = GetStandardDevice(device);
CONSOLE_SCREEN_BUFFER_INFO info;
SafeNativeMethods.GetConsoleScreenBufferInfo(handle, out info);
return info;
}
public static void SetCursorPosition(COORD pos)
{
IntPtr handle = SafeNativeMethods.GetStdHandle(SafeNativeMethods.STD_OUTPUT_HANDLE);
SafeNativeMethods.SetConsoleCursorPosition(handle, pos);
}
public static void WriteBufferInfo(CONSOLE_SCREEN_BUFFER_INFO info)
{
WriteLine("Console Buffer Info:");
WriteLine("--------------------");
WriteLine("Cursor Position:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "\t{0}, {1}",
info.dwCursorPosition.X, info.dwCursorPosition.Y));
WriteLine("Maximum Window Size:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "\t{0}, {1}",
info.dwMaximumWindowSize.X,
info.dwMaximumWindowSize.Y));
WriteLine("Screen Buffer Size:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "\t{0}, {1}",
info.dwSize.X, info.dwSize.Y));
WriteLine("Screen Buffer Bounds:");
WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture,
"\t{0}, {1}, {2}, {3}",
info.srWindow.Left, info.srWindow.Top,
info.srWindow.Right, info.srWindow.Bottom));
WriteLine("--------------------");
}
public static void MoveText(string txt)
{
WriteLine(txt);
IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
CONSOLE_SCREEN_BUFFER_INFO screenInfo = GetBufferInfo(ConsoleStandardDevice.Output);
SMALL_RECT rect = new SMALL_RECT();
rect.Left = 0;
rect.Top = (short)(screenInfo.dwCursorPosition.Y - 1);
rect.Bottom = (short)(rect.Top);
while (true)
{
rect.Right = (short)(rect.Left + (txt.Length - 1));
if (rect.Right == (screenInfo.dwSize.X - 1))
break;
CHAR_INFO charInfo = new CHAR_INFO();
charInfo.Char = SafeNativeMethods.WHITE_SPACE;
SafeNativeMethods.ScrollConsoleScreenBuffer(handle, ref rect, IntPtr.Zero,
new COORD() { X = (short)(rect.Left + 1), Y = rect.Top }, ref charInfo);
System.Threading.Thread.Sleep(100);
rect.Left++;
}
}
}
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.
Now it is the time for arrays. The next chapter discusses what arrays are and how to marshal them.
Download PDF and XPS versions of the book here.
Chapter 1: Introducing Marshaling
Chapter 2: Marshaling Simple Types
Chapter 3: Marshaling Compound Types