Examples of COM Automation in Silverlight 4

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Read: Introducing COM Automation in Silverlight 4

Sample 1: TalkingApps Preview Download
Sample 2: FeaturedBlogs Preview Download
Sample 3: FileSurfer PreviewDownload

 

Contents

Contents of this article:

  • Contents
  • Introduction
  • Concepts
    • Finding COM Components
    • Accessing the COM Component
    • COM and Late Binding
  • Preparing Your Project
  • OOB Characteristics
  • Sample 1: Talking Apps
  • Sample 2: Office Automation
    • Word Automation
    • Outlook Automation
    • Excel Automation
  • Sample 3: File Surfer
  • What’s next

 

Introduction

In the previous lesson we talked about COM automation support introduced in Silverlight 4 and we said that COM automation is available only for Silverlight OOB (Out-of-Browser) applications that have Elevated Trust, and that’s one of the security restrictions imposed by Silverlight.

Today, we’re going to talk about COM automation in more details and give few Silverlight examples that make use of this great feature.

 

Concepts

Finding COM Components

COM components expose their interfaces and classes via Windows Registry. COM classes exposed to public available in this machine are registered in the Registry at HKCRCLSID (which is a repository for all COM classes registered in this machine.) Because the machine may have thousands of COM components installed, every COM class is identified with a GUID (Globally Unique Identifier.) This GUID is very long and difficult to remember (it’s something like {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}) so COM classes are using another way to identify themselves; that is the ProgID (Programmatic ID.)

ProgID for a COM class is a simple and clear string that identifies that class and represents it. For example, Microsoft Word exposes its functionalities through a COM class that you can reach it using the ProgID Word.Application. So, instead of the GUID of a class, you simply refer to it using its simple and easy-to-remember ProgID.

Then, how to find the right COM component? Or how to know if a COM component exposes a certain functionality that I need? Google it, that’s the only way! If you are looking for a COM component that does something you need just google it. And always have the COM component in your right hand, and have its documentation in the other. And don’t miss a chance to ask MSDN for help.

Accessing the COM Component

Now you have the COM component and know what to do with it. The next step is to create a new instance from the COM class using its ProgID. This is can be done using the AutomationFactory class found in the namespace System.Runtime.InteropServices.Automation (exist in System.Windows.dll.) This class exposes a few functions and only one property. The only property is IsAvailable that returns whether or not COM is supported by the operating system (COM is supported only in Windows.) Of the few functions AutomationFactory supports, we care about only CreateObject() that takes the ProgID as an input and returns the created object.

COM and Late Binding

When you assign an object to a variable that assignment can be made as early-binding or late-binding.

Early-binding is that what you make every day and all the time. By default, object assignment is made as early-binding, means that you create a variable of specific type and bind that object to that variable. Then, the compiler always knows the type of object assigned and the members (methods, properties, etc.) it supports and that allows him to do some checks, optimizations, and perform some memory allocations before application start.

In addition, early-bound variables can be thought as strongly-typed objects, means that you can check for their exposed members, use some IDE features like Intellisense and Object Explorer, and also receive compiler errors when you try to make calls to members that don’t exist (for instance.)

Late-binding on the other hand is made at runtime, means that the compiler doesn’t have enough information about the type at compile time. That means that no type checks, no method lookups or Intellisense, no verifications, no optimizations, and also no compilation errors from the late-bound object.

Is there any benefit from late-binding? Does it have anything to do with COM? Actually, it’s not as ugly as you think, it has lots of benefits and it also the core of COM automation in Silverlight (and .NET 4.0 too.)

Late-binding allows you not to embed COM types and interfaces in your .NET assembly, and that would help reduce your application size dramatically, and also protects you from versioning fails and falling in the DLL Hell.

Worth mentioning that late-binding was introduced in .NET 4.0 via the dynamic keyword and DLR (Dynamic Language Runtime) libraries. Before .NET 4.0, late-binding was supported only via reflection. Regarding Silverlight, late-binding was introduced in version 4 supporting easier COM automation and HTML DOM.

After all, to create a COM object in Silverlight you first ensure that Microsoft.CSharp.dll and Microsoft.Core.dll are referenced to your project. After that, you can call AutomationFactory.CreateObject() and assign the returned object to a dynamic variable and start coding with it.

 

Preparing Your Project

Now, let’s get to work. We’ll now prepare a Silverlight application for communication with COM components. Remember that this process requires a Silverlight 4 application running as OOB and having the elevated trust.

Start with a Silverlight project and ensure that you have selected version 4 from the New Application dialog. (The application will run as OOB, so you can safely uncheck the web application hosting option.)

Figure 1 - Creating New Silverlight Application
Figure 1 - Creating New Silverlight Application

After creating the project, open project properties and from the bottom of the Silverlight tab check the option “Enabling running application out of browser” (check figure 2.)

Figure 2 - Configuring Silverlight to run as OOB
Figure 2 - Configuring Silverlight to run as OOB

Then click Out-of-Browser Settings button and from the bottom of this dialog too check the “Require elevated trust when running” option (check figure 3.)

Figure 3 - Enabling Elevated Trust
Figure 3 - Enabling Elevated Trust

Now click OK and close project properties and save the project.

Next, add support for dynamic variables and late-binding feature to the project by referencing Microsoft.CSharp.dll (and System.Core.dll too if it’s not currently referenced) in the project.

Figure 4 - Adding Reference to Microsoft.CSharp.dll
Figure 4 - Adding Reference to Microsoft.CSharp.dll

 

OOB Characteristics

First of all, you can launch your OOB application by clicking Run or pressing F5 (figure 5.)

Figure 5 - Running OOB Application
Figure 5 - Running OOB Application

OOB applications can also run in a web page (by default, you’ll redirect the user to a web page where he can install the application to his PC.) Try browsing to TestPage.html (in Bin<debug|release>, check figure 6) or referencing the project in web application Keep in mind that COM support is not available for OOB applications running in the web.

Figure 6 - OOB Applications from the Web
Figure 6 - OOB Applications from the Web

When running OOB application from a web page the user can right click the application and chooses to install it on his machine (figure 7.) This behavior is the default, but you can disable it from the Out-of-Browser Settings dialog (check figure 3.)

Figure 7 - Installing Silverlight OOB App
Figure 7 - Installing Silverlight OOB App

When trying to install an OOB application that requires elevated trust the user may accept a security warning before the installation goes on (figure 8.)

Figure 8 - Installing Elevated Trust App
Figure 8 - Installing Elevated Trust App

You can also order the application to launch the installation process via Application.Install(), but this requires to be called in response to a user-initiated action (e.g. clicking a button.)

Another great feature of ApplicationClass is IsRunnignOutOfBrowser that returns whether the application is running OOB.

 

Sample 1: Talking Apps

Our first example is an application that makes use of the speech API and speech COM library, sapi.dll, to read a textual input from the user. The code uses the SAPI.SpVoice class that has the Speak() function that we’ll make use of.

First, design a window that has an input text box and a button to read the text. You might get help from the following XAML:


    
        
        
        
    

    

    

    

The application might look like this:

Figure 9 - The Talking App
Figure 9 - The Talking App

Now, start coding the Click event of the button:

Remember to include a using statement for System.Runtime.InteropServices.Automation.

private void Button_Click(object sender, RoutedEventArgs e)
{
    if (!Application.Current.IsRunningOutOfBrowser)
    {
        MessageBox.Show("This application cannot be run from the browser.");
        return;
    }
    if (!AutomationFactory.IsAvailable)
    {
        MessageBox.Show("Your operating system does not support COM.");
        return;
    }

    try
    {
        using (dynamic obj = AutomationFactory.CreateObject("SAPI.SpVoice"))
        {
            obj.Speak(inputTextBox.Text);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show("An error has occurred.n" + ex.Message);
    }
}

In the previous code we have checked first for whether the application is running as OOB or not (using Application.IsRunningOutOfBrowser property.) After that, we made a second check for ensuring that COM automation is supported by the operating system (i.e. the user must be working on Windows.)

Then, we have created our first COM object using AutomationFactory.CreateObject() function specifying the class ProgID which is SAPI.SpVoice. As you see, we have assigned the created object to a dynamic variable and we have also encapsulated the object in a using statement to ensure that system resources get released quickly as soon as we finish working with the object.

You can try an updated version and download it from here:

PreviewDownload

 

Sample 2: Microsoft Office Automation

We won’t dig into Microsoft Office SDK or even one of its products, we don’t have much space here, and it also requires a punch of articles in its own. Instead, we’ll have just a few examples and two samples that clear up the ambiguities of COM automation. More help can be found in the documentation of the Office programming model available in MSDN.

Word Automation

Microsoft Word exposes its programming model via COM components that you can reach its main Application object via the ProgID Word.Application. This model allows you to almost do everything programmatically from creating and opening documents to saving and printing them, even running macros and recording them is available through this model.

The following code creates a Word document and writes some text to it:

using (dynamic app = AutomationFactory.CreateObject("Word.Application"))
{
    dynamic doc = word.Documents.Add();

    dynamic par = document.Content.Paragraphs.Add();
    par.Range.Text = "Hello, World!";
    par.Range.Font.Bold = true;

    word.Visible = true;
}

More information about Word object model can be found here: http://msdn.microsoft.com/en-us/library/kw65a0we.aspx.

Outlook Automation

Like all other Microsoft Office products, Microsoft Outlook can be managed completely by the code using its programming model exposed through the ProgID Outlook.Application. The following code sends an email using Outlook (thanks to Jeff Prosise for providing the code):

using (dynamic app = AutomationFactory.CreateObject("Outlook.Application"))
{
    dynamic mail = app.CreateItem(0);
    mail.Recipients.Add("");
    mail.Subject = "Hello, World!";
    mail.Body = "Silverlight COM Automation is so cool ;)";
    mail.Save();
    mail.Send();
}

A full documentation of Outlook programming model can be found here: http://msdn.microsoft.com/en-us/library/ms268893.aspx.

Excel Automation

The following sample uses Excel automation to export some data of a DataGrid to an Excel file:

Figure 10 - The FeaturedBlogs App
Figure 10 - The FeaturedBlogs App

PreviewDownload

More about Excel programming model can be found here: http://msdn.microsoft.com/en-us/library/wss56bz7.aspx.

 

Sample 3: File Surfer

The last sample here makes use of the most powerful file managing COM component exposed through the ProgID Scripting.FileSystemObject. This class gives your application the power to do almost everything on file system even if your Silverlight application lonely (without COM support) that runs with elevated trust don’t have such those privileges (remember what we have said about COM automation? It’s the ultimate power for Silverlight.)

The next is an example of a code that creates a text file on drive C: and writes some text to it:

using (dynamic fso = AutomationFactory.CreateObject("Scripting.FileSystemObject"))
{
    string path = "C:\file.txt";

    dynamic file = fso.CreateTextFile(filePath, true);
    file.WriteLine("Hello, World!");
    file.Close();

    file = fso.OpenTextFile(filePath, 1, true);
    file.Close();
}

And the following is an application that helps you surfing the file system with no restrictions as if you were using File Explorer:

Figure 11 - The FileSurfer App
Figure 11 - The FileSurfer App

PreviewDownload

 

What’s next

You might like to check Justin Angel’s hot article about COM automation in Silverlight, lots of examples are available there.

Introducing COM Automation in Silverlight 4

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Read: Examples of COM Automation in Silverlight 4

SilverlightIn April 2010 Silverlight 4 was released to the world with one of its great features ever, COM-automation support, that allows you to create cutting-edge applications that do everything you can imagine.

What COM is

COM (Common Object Model) is a Microsoft technology introduced in 1993 and currently used by many programming languages (including C++ and VB6) for developing and building software components. Simply, you can think about COM as the counterpart for .NET assemblies in languages like C++ and VB6, .NET assemblies hold program objects and so do COM components, there’re many differences of course and one of them is that COM components are much more capable than .NET assemblies and that what makes accessing them is a great improvement to Silverlight.

Today there’s much COM components available, Microsoft Office is an example of a COM-based SDK, lots of Windows features are introduced through COM interfaces (Text-to-Speech is a good example,) and lots of 3rd party software (e.g. Adobe Reader) expose their functionalities through COM-based interfaces. Thus, you can’t leave COM automation behind if you’re willing to create cutting-edge Silverlight applications that do almost everything.

Silverlight OOB Applications

Another great improvement to Silverlight over Adobe Flash is its support for installing and running offline as a desktop application with no network presence. Those Silverlight desktop clients are described as OOB (Out-of-Browser) applications and they have been introduced in Silverlight 3 and extended their functionalities in version 4.

OOB applications are sophisticated desktop clients that can run on any platform with no such code changes required. WPF applications are much capable of course but they support only the Windows platform.

Notice that COM is a Microsoft technology that won’t run in a non-Windows machine.

Silverlight 4 and COM

Starting from version 4, you have the ability to call COM-components through your Silverlight application with a small amount of simple and clear code. But don’t expect much, you still constrained by two security restrictions:

  1. Your application needs to be running OOB.
  2. Your application must have the Elevated Trust.

Both requirements are easy as two mouse clicks; you simply set application requirements in project properties.

Programmatically, to start communicating with a COM component you make use of the AutomationFactory class found in namespace System.Runtime.InteropServices.Automation (exist in the main System.Windows.dll.) Combined the AutomationFactory class with dynamic coding (and the dynamic keyword) you can create your COM objects in Silverlight and play with it.

In the next few lessons we’ll discuss Silverlight COM automation in details and see it in action though many useful examples that will move your application to infinity. Enjoy!

 

.NET Interoperability at a Glance 3 – Unmanaged Code Interoperation

هذه المقالة متوفرة باللغة العربية أيضا، اقرأها هنا.

See more Interoperability examples here.

Contents

Contents of this article:

  • Contents
  • Read also
  • Overview
  • Unmanaged Code Interop
  • Interop with Native Libraries
  • Interop with COM Components
  • Interop with ActiveX Controls
  • Summary
  • Where to go next

Read also

More from this series:

Overview

This is the last article in this series, it talks about unmanaged code interoperation; that’s, interop between .NET code and other code from other technologies (like Windows API, native libraries, COM, ActiveX, etc.)

Be prepared!

Introduction

Managed code interop wasn’t so interesting, so it’s the time for some fun. You might want to call some Win32 API functions, or it might be interesting if you make use of old, but useful, COM components. Let’s start!

Unmanaged Code Interop

Managed code interoperation isn’t so interesting, but this is. Unmanaged interoperation is not easy as the managed interop, and it’s also much difficult and much harder to implement. In unmanaged code interoperation, the first system is the .NET code; the other system might be any other technology including Win32 API, COM, ActiveX, etc. Simply, unmanaged interop can be seen in three major forms:

  1. Interoperation with Native Libraries.
  2. Interoperation with COM components.
  3. Interoperation with ActiveX.

Interop with Native Libraries

This is the most famous form of .NET interop with unmanaged code. We usually call this technique, Platform Invocation, or simply PInvoke. Platform Invocation or PInvoke refers to the technique used to call functions of native unmanaged libraries such as the Windows API.

To PInvoke a function, you must declare it in your .NET code. That declaration is called the Managed Signature. To complete the managed signature, you need to know the following information about the function:

  1. The library file which the function resides in.
  2. Function name.
  3. Return type of the function.
  4. Input parameters.
  5. Other relevant information such as encoding.

Here comes a question, how could we handle types in unmanaged code that aren’t available in .NET (e.g. BOOL, LPCTSTR, etc.)?

The solution is in Marshaling. Marshaling is the process of converting unmanaged types into managed and vice versa (see figure 1.) That conversion can be done in many ways based on the type to be converted. For example, BOOL can simply be converted to System.Boolean, and LPCTSTR can be converted to System.String, System.Text.StringBuilder, or even System.Char[]. Compound types (like structures and unions) are usually don’t have counterparts in .NET code and thus you need to create them manually. Read our book about marshaling here.

Figure 1 - The Marshaling Process
Figure 1 – The Marshaling Process

To understand P/Invoke very well, we’ll take an example. The following code switches between mouse button functions, making the right button acts as the primary key, while making the left button acts as the secondary key.

In this code, we’ll use the SwapMouseButtons() function of the Win32 API which resides in user32.dll library and has the following declaration:

BOOL SwapMouseButton(
    BOOL fSwap
    );

Of course, the first thing is to create the managed signature (the PInvoke method) of the function in .NET:

// C#
[System.Runtime.InteropServices.DllImport("user32.dll")]
static extern bool SwapMouseButton(bool fSwap);
' VB.NET
Declare Auto Function SwapMouseButton Lib "user32.dll" _
    (ByVal fSwap As Boolean) As Boolean

Then we can call it:

// C#

public void MakeRightButtonPrimary()
{
    SwapMouseButton(true);
}

public void MakeLeftButtonPrimary()
{
    SwapMouseButton(false);
}
' VB.NET

Public Sub MakeRightButtonPrimary()
    SwapMouseButton(True)
End Sub

Public Sub MakeLeftButtonPrimary()
    SwapMouseButton(False)
End Sub

Interop with COM Components

The other form of unmanaged interoperation is the COM Interop. COM Interop is very large and much harder than P/Invoke and it has many ways to implement. For the sake of our discussion (this is just a sneak look at the technique,) we’ll take a very simple example.

COM Interop includes all COM-related technologies such as OLE, COM+, ActiveX, etc.

Of course, you can’t talk directly to unmanaged code. As you’ve seen in Platform Invocation, you have to declare your functions and types in your .NET code. How can you do this? Actually, Visual Studio helps you almost with everything so that you simply to include a COM-component in your .NET application, you go to the COM tab of the Add Reference dialog (figure 2) and select the COM component that you wish to add to your project, and you’re ready to use it!

Figure 2 - Adding Reference to SpeechLib Library
Figure 2 – Adding Reference to SpeechLib Library

When you add a COM-component to your .NET application, Visual Studio automatically declares all functions and types in that library for you. How? It creates a Proxy library (i.e. assembly) that contains the managed signatures of the unmanaged types and functions of the COM component and adds it to your .NET application.

The proxy acts as an intermediary layer between your .NET assembly and the COM-component. Therefore, your code actually calls the managed signatures in the proxy library that forwards your calls to the COM-component and returns back the results.

Keep in mind that proxy libraries also called Primary Interop Assemblies (PIAs) and Runtime Callable Wrappers (RCWs.)

Best mentioning that Visual Studio 2010 (or technically, .NET 4.0) has lots of improved features for interop. For example, now you don’t have to ship a proxy/PIA/RCW assembly along with your executable since the information in this assembly can now be embedded into your executable; this is what called, Interop Type Embedding.

Of course, you can create your managed signatures manually, however, it’s not recommended especially if you don’t have enough knowledge of the underlying technology and the marshaling of functions and types (you know what’s being said about COM!)

As an example, we’ll create a simple application that reads user inputs and speaks it. Follow these steps:

  1. Create a new Console application.
  2. Add a reference to the Microsoft Speech Object Library (see figure 2.)
  3. Write the following code and run your application:
// C#

using SpeechLib;

static void Main()
{
    Console.WriteLine("Enter the text to read:");
    string txt = Console.ReadLine();
    Speak(txt);
}

static void Speak(string text)
{
    SpVoice voice = new SpVoiceClass();
    voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault);
}
' VB.NET

Imports SpeechLib

Sub Main()
    Console.WriteLine("Enter the text to read:")
    Dim txt As String = Console.ReadLine()
    Speak(txt)
End Sub

Sub Speak(ByVal text As String)
    Dim voice As New SpVoiceClass()
    voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault)
End Sub

If you are using Visual Studio 2010 and .NET 4.0 and the application failed to run because of Interop problems, try disabling Interop Type Embedding feature from the properties on the reference SpeechLib.dll.

Interop with ActiveX Controls

ActiveX is no more than a COM component that has an interface. Therefore, nearly all what we have said about COM components in the last section can be applied here except the way we add ActiveX components to our .NET applications.

To add an ActiveX control to your .NET application, you can right-click the Toolbox, select Choose Toolbox Items, switch to the COM Components tab and select the controls that you wish to use in your application (see figure 3.)

Figure 3 - Adding WMP Control to the Toolbox
Figure 3 – Adding WMP Control to the Toolbox

Another way is to use the aximp.exe tool provided by the .NET Framework (located in Program FilesMicrosoft SDKsWindowsv7.0Abin) to create the proxy assembly for the ActiveX component:

aximp.exe "C:WindowsSystem32wmp.dll"

Not surprisingly, you can create the proxy using the way for COM components discussed in the previous section, however, you won’t see any control that can be added to your form! That way creates control class wrappers for unmanaged ActiveX controls in that component.

Summary

So, unmanaged code interoperation comes in two forms: 1) PInvoke: interop with native libraries including the Windows API 2) COM-interop which includes all COM-related technologies like COM+, OLE, and ActiveX.

To PInvoke a method, you must declare it in your .NET code. The declaration must include 1) the library which the function resides in 2) the return type of the function 3) function arguments.

COM-interop also need function and type declaration and that’s usually done for you by the Visual Studio which creates a proxy (also called RCW and PIA) assembly that contains managed definitions of the unmanaged functions and types and adds it to your project.

Where to go next

Read more about Interoperability here.

More from this series:

.NET Interoperability at a Glance 2 – Managed Code Interoperation

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

See more Interoperability examples here.

Contents

Contents of this article:

  • Contents
  • Read also
  • Overview
  • Introduction
  • Forms of Interop
  • Managed Code Interop
  • Summary
  • Where to go next

Read also

More from this series:

Overview

In the previous article, you learnt what interoperability is and how it relates to the .NET Framework. In this article, we’re going to talk about the first form of interoperability, the Managed Code Interop. In the next article, we’ll talk about the other forms.

Introduction

So, to understand Interoperation well in the .NET Framework, you must see it in action. In this article, we’ll talk about the first form of .NET interoperability and see how to implement it using the available tools.

Just a reminder, Interoperability is the process of communication between two separate systems. In .NET Interop, the first system is always the .NET Framework; the other system might be any other technology.

Forms of Interop

Interoperability in .NET Framework has two forms:

  • Managed Code Interoperability
  • Unmanaged Code Interoperability

Next, we have a short discussion of each of the forms.

Managed Code Interop

This was one of the main goals of .NET Framework. Managed Code Interoperability means that you can easily communicate with any other .NET assembly no matter what language used to build that assembly.

Not surprisingly, because of the nature of .NET Framework and its runtime engine (the CLR,) .NET code supposed to be called Managed Code, while any other code is  unmanaged.

To see this in action, let’s try this:

  1. Create a new Console application in the language you like (C#/VB.NET.)
  2. Add a new Class Library project to the solution and choose another language other than the one used in the first project.
  3. In the Class Library project, add the following code (choose the suitable project):
    // C#
    
    public static class Hello
    {
        public static string SayHello(string name)
        {
            return "Hello, " + name;
        }
    }
    ' VB.NET
    
    Public Module Hello
        Public Function SayHello(ByVal name As String) As String
            Return "Hello, " & name
        End Function
    End Module
  4. Now, go back to the Console application. Our goal is to call the function we have added to the other project. To do this, you must first add a reference to the library in the first project. Right-click the Console project in Solution Explorer and choose Add Reference to open the Add Reference dialog (figure 1.) Go to the Projects tab and select the class library project to add it.

    Figure 1 - Add Reference to a friend project
    Figure 1 - Add Reference to a friend project
  5. Now you can add the following code to the Console application to call the SayHello() function of the class library.
    // C#
    
    static void Main()
    {
        Console.WriteLine(ClassLibrary1.Hello.SayHello("Mohammad Elsheimy"));
    }
    ' VB.NET
    
    Sub Main()
        Console.WriteLine(ClassLibrary1.Hello.SayHello("Mohammad Elsheimy"))
    End Sub

How this happened? How could we use the VB.NET module in C# which is not available there (or the C#’s static class in VB which is not available there too)?

Not just that, but you can inherit C# classes from VB.NET (and vice versa) and do all you like as if both were created using the same language. The secret behind this is the Common Intermediate Language (CIL.)

When you compile your project, the compiler actually doesn’t convert your C#/VB.NET code to instructions in the Machine language. Rather, it converts your code to another language of the .NET Framework, which is the Common Intermediate Language. The Common Intermediate Language, or simply CIL, is the main language of .NET Framework which inherits all the functionalities of the framework, and which all other .NET languages when compiled are converted to it.

So, the CIL fits as a middle layer between your .NET language and the machine language. When you compile your project, the compiler converts your code to CIL statements and emits them in assembly file. In runtime, the compiler reads the CIL from the assembly and converts them to machine-readable statements.

How CIL helps in interoperation? The communication between .NET assemblies is now done through the CIL of the assemblies. Therefore, you don’t need to be aware of structures and statements that are not available in your language since every statement for any .NET language has a counterpart in IL. For example, both the C# static class and VB.NET module convert to CIL static abstract class (see figure 2.)

Figure 2 - CIL and other .NET languages
Figure 2 - CIL and other .NET languages

Managed Interop is not restricted to C# and VB.NET only; it’s about all languages run inside the CLR (i.e. based on .NET Framework.)

If we have a sneak look at the CIL generated from the Hello class which is nearly the same from both VB.NET and C#, we would see the following code:

.class public abstract auto ansi sealed beforefieldinit ClassLibrary1.Hello
       extends [mscorlib]System.Object
{

    .method public hidebysig static string  SayHello(string name) cil managed
    {
      // Code size       12 (0xc)
      .maxstack  8
      IL_0000:  ldstr      "Hello, "
      IL_0005:  ldarg.0
      IL_0006:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
      IL_000b:  ret
    } // end of method Hello::SayHello

} // end of class ClassLibrary1.Hello

On the other hand, this is the code generated from the Main function (which is also the same from VB.NET/C#):

.class public abstract auto ansi sealed beforefieldinit ConsoleApplication1.Program
       extends [mscorlib]System.Object
{

    .method private hidebysig static void  Main() cil managed
    {
      .entrypoint
      .maxstack  8
      IL_0000:  ldstr      "Mohammad Elsheimy"
      IL_0005:  call       string [ClassLibrary1]ClassLibrary1.Hello::SayHello(string)
      IL_000a:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_000f:  ret
    } // end of method Program::Main

} // end of class ConsoleApplication1.Program

You can use the ILDasm.exe tool to get the CIL code of an assembly. This tool is located in Program FilesMicrosoft SDKsWindows<version>bin.

Here comes a question, is there CIL developers? Could we write the CIL directly and build it into .NET assembly? Why we can’t find much (if not any) CIL developers? You can extract the answer from the CIL code itself. As you see, CIL is not so friendly and its statements are not so clear. Plus, if we could use common languages to generate the CIL, we we’d like to program in CIL directly? So it’s better to leave the CIL for the compiler.

Now, let’s see the other form of .NET interoperation, Unmanaged Code Interoperability.

Summary

So, the secret of Managed Code Interoperation falls in the Common Intermediate Language or CIL. When you compile your code, the compiler converts your C#/VB.NET (or any other .NET language) to CIL instructions and saves them in the assembly, and that’s the secret. The linking between .NET assemblies of different languages relies on the fact that the linking is actually done between CILs of the assemblies. The assembly code doesn’t (usually) have any clue about the language used to develop it. In the runtime, the compiler reads those instructions and converts them to machine instructions and execute them.

Next, we’ll talk about the other form of .NET Interoperation, it’s the interoperation with unmanaged code.

Where to go next

Read more about Interoperability here.

More from this series:

.NET Interoperability at a Glance 1 – Introduction

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

See more Interoperability examples here.

Contents

Contents of this article:

  • Contents
  • Read also
  • Overview
  • Introduction
  • Summary
  • Where to go next

Read also

More from this series:

Overview

.Net Framework Logo
Image via Wikipedia

In this article and the few following it, we’ll try to take a tour in Interoperability in .NET Framework.

In this lesson, we’ll start by an introduction to the concept of Interoperability. In the next few lessons, we’ll have a look at Interoperability and how it fits into the .NET Framework and other technologies.

Since Interoperability is a very huge topic and cannot be covered in just a few articles, we’ll concentrate on Interoperability in .NET Framework (not any other technologies) and summarize its uses.

Here we go!

Introduction

Let’s get hands on the concept of Interoperability and it’s relation to the .NET Framework.

Concept

Interoperability (reduced to Interop) is the ability of two diverse systems or different systems to communicate (i.e. inter-operate) with each other. When I say ‘two systems’ I assume that the first one is always the .NET Framework, since we are interested in .NET and also the interoperability is a very huge topic and cannot be summarized in just a few articles. The other system might be any other software, component, or service based on any technology other than the .NET Framework. For example, we could interoperate with Win32 API, MFC applications, COM/ActiveX components, and so on.

So we have two different systems, the first is the .NET Framework, while the other is any other technology. Our goal is to communicate with that stranger; that’s the main goal of Interoperability in .NET Framework.

Goals and Benefits

Here comes a question (or a few questions!), why do I need interoperation? Why I do need to communicate with other systems at all? If I need specific features, couldn’t I just use existing functionalities of .NET Framework to accomplish my tasks? I can even redevelop them!

We can summarize the answer of those questions in a few points:

  • First, in many cases, you can’t redevelop those components because the functionalities they offer is either very difficult (sometimes impossible) or maybe you don’t sufficient knowledge to redevelop them! Unless if you are very brilliant and have enough knowledge of the Assembly language, you can develop your API that would replace current system API, and then you’ll have also to interoperate with your API to be able to call it from your .NET Framework application.
  • If you’re not convinced yet, this is should be for you. You might be not having enough time to redevelop the component that may take a very long time and effort to complete. Imagine how much time would take to code, debug, and test your component. Plus, you can rely on existing components and trust them, many bugs can appear in your code from time to time and you’ll have to fix them all!
  • Other 3rd party component might not exist, or maybe the company you work for require you to use such those components.
  • You don’t need to reinvent the wheel, do you?

So, including Interop code in your .NET projects is sometimes inevitable (especially when working with Windows API) that you definitely can’t keep yourself away from them.

Summary

So you have now basic understanding of what Interoperability means. As a reminder, Interoperability is the process of two diverse systems communicate with each other. For us, the first system is the .NET Framework. The other system is any other technology (Windows API, MFC, COM/ActiveX, etc.)

You can’t live without Interop, actually you did some interoperation in your work (you may be actually do that every day.)

Now you are ready to take a look at how Interop fits in .NET Framework.

Where to go next

Read more about Interoperability here.

More from this series:

Setting Device Information in MCI

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Interested in MCI multimedia processing? First check this article out if you didn’t:

Creating a Sound Recorder in C and C#

After we received your feedbacks and comments about th.e article, we decided to add a small appendix to the end of the article about setting information (volume, channel, sampling rate, etc.) to a MCI device (a Waveform device of course.)

Like anything else in MCI, you can set device information using a MCI command (string/numeric), and this time it’s the MCI_SET command.

This command is used to set information about a specific device. This command requires an input parameter of the MCI_SET_PARMS structure. However, that input parameter might have extended members for specific devices. Because we are concentrating of Waveform devices, so we are going to use the MCI_WAVE_SET_PARMS structure that contains the extended members for our device and is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwTimeFormat;
    DWORD     dwAudio;
    UINT      wInput;
    UINT      wOutput;
    WORD      wFormatTag;
    WORD      wReserved2;
    WORD      nChannels;
    WORD      wReserved3;
    DWORD     nSamplesPerSec;
    DWORD     nAvgBytesPerSec;
    WORD      nBlockAlign;
    WORD      wReserved4;
    WORD      wBitsPerSample;
    WORD      wReserved5;
} MCI_WAVE_SET_PARMS;

This structure contains all and every little piece of information that can be set to a device. I expect that you read the main article and you are already familiar with members like dwCallback (other members are self-explanatory) that we have talked about many times, and you are fine too with function calls and setting up input parameters, so I won’t get into the discussion of the structure or how you are going to use that command. However, if you need more help setting up the input parameters for the structure, you should take a brief look at the MCI_WAVE_SET_PARMS Structure documentation in the MSDN.

As you know, the MCI_WAVE_SET_PARMS unmanaged structure can be marshaled in C# as following:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_WAVE_SET_PARMS
{
    public IntPtr dwCallback;
    public uint     dwTimeFormat;
    public uint     dwAudio;
    public uint      wInput;
    public uint      wOutput;
    public ushort      wFormatTag;
    public ushort      wReserved2;
    public ushort      nChannels;
    public ushort      wReserved3;
    public uint     nSamplesPerSec;
    public uint     nAvgBytesPerSec;
    public ushort      nBlockAlign;
    public ushort      wReserved4;
    public ushort      wBitsPerSample;
    public ushort      wReserved5;
}

Congratulations! You did set the device information! So how to get them back?

This can be done through the MCI_STATUS (discussed earlier) by setting up the MCI_STATUS_ITEM flag and setting the query item to the required information you need to query about (like MCI_DGV_STATUS_VOLUME to query about volume.)

More about the MCI_STATUS command can be found in the MSDN documentation.

Serialization vs. Marshaling

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Overview

Are you somewhat confused between Serialization and Marshaling? This writing would break this confusion up, it would give you a basic understanding of the process of Serialization and the process of Marshaling, and how you can get the most out of each.

Serialization

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file, a memory buffer, or transmitted across a network connection to be “resurrected” later in the same or another computer environment. And this sequence of bits can be of any format the user chooses; however, they are usually formed as XML or binary.

Serialization comes in many forms in .NET Framework, it can be observed in ADO.NET, Web services, WCF services, Remoting, and others.

For example, calling the WriteXml() function of a DataSet serializes this DataSet into a XML file.

    ds.WriteXml("data.xml");

And if we have such this structure:

    public struct User
    {
        public int id;
        public string name;
    }

we can get the following results if we serialize a collection of that structure into XML:



    
        12
        Mark
    
    
        13
        Charles
    
    
        14
        John
    

Serialization can be observed in Web and WCF services too. The request and parameter information for a function are serialized into XML, and when the function returns the response and the returned data too are serialized into XML. Actually, you don’t have to think about these XML data, CLR handles this for you.

In the same vein, when it comes to Remoting, the sender and recipient must agree to the same form of XML data. That’s, when you send some data CLR serializes this data for you before it sends it to the target process. When the target process receives this XML data, it turns it back (deserializes it) to its original form to be able to handle it.

Thus, the process of converting data structures and objects into series of bits is called Serialization. The reverse of this process, converting these bits back to the original data structures and objects, is called Deserialization.

Therefore, the following ADO.NET line does deserializes the XML file:

    DataSet ds;
    ds.ReadXml("data.xml");

And when your application receives response from the server or from another process, the CLR deserializes that XML data for you.

So why XML is preferred over binary serialization? That’s because XML is text-based. Thus, it’s free to be transmitted from a process to another or via a network connection, and firewalls always allow it.

Marshaling

Marshaling is the process of converting managed data types to unmanaged data types. There’re big differences between the managed and unmanaged environments. One of those differences is that data types of one environment is not available (and not acceptable) in the other.

For example, you can’t call a function like SetWindowText() -that sets the text of a given window- with a System.String because this function accepts LPCTSTR and not System.String. In addition, you can’t interpret (handle) the return type, BOOl, of the same function, that’s because your managed environment (or C# because of the context of this writing) doesn’t have a BOOL, however, it has a System.Boolean.

To be able to interact with the other environment, you will need to not to change the type format, but to change its name.

For example, a System.String is a series of characters, and a LPCTSTR is a series of characters too! Why not just changing the name of the data type and pass it to the other environment?

Consider the following situation. You have a System.String that contains the value €œHello€:

System.String str = "Hello";

The same data can be represented in an array of System.Char too, like the following line:

System.Char[] ch = str.ToCharArray();

So, what is the difference between that System.String variable and that System.Char array? Nothing. Both contain the same data, and that data is laid-out the same way in both variables. That’s what Marshaling means.

So what is the difference between Serialization and Marshaling?

C# has a System.Int32, and Windows API has an INT, and both refer to a 32-bit signed integer (on 32-bit machines.) When you marshal the System.Int32 to INT, you just change its type name, you don’t change its contents, or lay it in another way (usually.) When you serialize a System.Int32, you convert it to another form (XML for instance,) so it’s completely changed.

Summary

Look, after I get back to Wikipedia documentation for Marshaling, I realized that my answer was so specific to C#!

I mean that, Marshaling is a very general term used to describe transformations of memory. Theoretically, it’s more general than Serialization. In Python for instance, the terms Marshaling and Serialization are used interchangeably. There (in Python,) Marshaling = Serialization, and Serialization = Marshaling, there’s no difference. In computer methodology, there’s a silent difference between Marshaling and Serialization (check the Wikipedia definition.)

So what is that System.MarshalByRefObject class? Why that name -specifically- was used? First, System.MarshalByRefObject class allows objects to be passed by reference rather than by value in applications that use Remoting.

Personally, I like to say that Microsoft .NET Framework team’s name was very scientific when they have called that object “MarshalByRefObject” with respect to that silent difference between serialization and marshaling or maybe that name was derived from Python, dunno!

After all, we should keep in mind that in .NET methodology, there’s a big difference between Serialization and Marshaling, Marshaling usually refers to the Interop Marshaling. In .NET Remoting, it refers to that serialization process.

By the way, Marshalling is so named because it was first studied in 1962 by Edward Waite Marshall, then with the General Electric corporation.

That’s all.

Have a nice day!

Creating a Sound Recorder in C and C#

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Code: SMPREC – C.zip
Code: SimpleRec – C#.zip

Contents

Contents of this article:

  • Contents
  • Overview
  • Introduction
  • Sending MCI Commands
  • The Wait, Notify, and Test Flags
  • Handling MCI Errors
  • Recording from an Input Device
    • Opening the Device
    • Starting Recording
    • Pausing Recording
    • Resuming Recording
    • Stopping Recording
    • Retrieving Buffer Length
    • Saving the Recorded Buffer
    • Closing the Device
  • Playing a Sound File
    • Loading the File
    • Playing the File
    • Pausing
    • Resuming
    • Retrieving Current Position
    • Retrieving File Length
    • Seeking a Specific Position
    • Closing the Device
    • In a Nutshell
  • MCI and .NET
    • Creating the Managed Signature
    • Receiving MCI_MMNOTIFY
  • Appendix A: Setting Device Information [NEW]
  • Sample Code

Overview

This writing will focus on how you can record sound from an input device and how you can play sound files using MCI (Media Control Interface) in C and C#.

This writing does not involve a discussion or even an introduction to MCI. Instead, it provides technical discussion of what we will need to use to record and to play sound files. If you need an introduction to MCI refer to the MSDN documentation.

We will begin by a discussion to types and functions required to accomplish our tasks. Then we will look on how you can utilize those types and functions in your C or C# application.

Our demonstration examples in this writing will be in C. In the last section we will have a look at .NET and C#. Besides this, there are sample applications written by C and C# attached with the article.

Introduction

Icon to represent multimedia
Image via Wikipedia

Actually, there are many ways to record and play sound files. However, in this writing we will focus on MCI (Media Control Interface) as it is one of the high-level easy-to-use interfaces for controlling all media types.

You can control a multimedia hardware using MCI by either sending commands like Windows messages or by sending string messages to the MCI. The second approach is preferable in scripting languages. As we are concentrating on languages C and C#, we will consider the first approach only.

Because .NET Framework does not include classes for handling MCI (or multimedia at all,) we will need to dig into Windows SDK, specifically WinMM.dll (Windows Multimedia Library) that resides in the system directory.

In order to access this library from your C# application, you will need to create your own marshaling types and PInvoke (Platform Invocation) methods to allow your managed code to communicate with the unmanaged server. That is what the last section of this article is devoted for.

To access this library from your C application, just reference the library object file WinMM.lib in your project. Notice that all of the functions and structures related to the MCI are prefixed with mci.

In the next sections we will talk about the functions and structures that we need to be aware of before digging into coding.

Sending MCI Commands

The key function to Windows multimedia programming using MCI is mciSendCommand(). This function sends a command to the MCI. As we have said, you can program MCI using one of two approaches, you can send Windows-messages-like commands, and you can also send string messages to the MCI. The key function that we will use to send MCI commands is mciSendCommand(). To send string messages, you can use the function mciSendMessage() which is not covered here.

The definition of the function mciSendCommand() is as follows:

MCIERROR mciSendCommand(
  MCIDEVICEID IDDevice,
  UINT        uMsg,
  DWORD       fdwCommand,
  DWORD_PTR   dwParam
);

This function receives four input arguments:

  • IDDevice:
    The ID of the device to which to receive the command. For example, the ID of the input device when recording or the output device if playing. As you know, many devices could be connected to the PC. You can forget about this argument and just pass the function 0 to direct the message to the default device (selected in the Sound applet in the Control Panel.)
  • uMsg:
    The message (command) to be sent. Common messages are covered in the next few sections.
  • fdwCommand:
    Flags (options) of the message. Every message has its own options. However, all messages share the flags MCI_WAIT, MCI_NOTIFY, and MCI_TEST (covered soon.)
  • dwParam:
    A structure contains the specific parameter for the command message.

As a result, every command (message) has its name, flags, and structure parameter.

This function returns 0 if succeeded or an error code otherwise.

The Wait, Notify, and Test Flags

Common flags for all MCI messages are, MCI_WAIT, MCI_NOTIFY, and MCI_TEST.

The Wait flag, MCI_WAIT, indicates that the message should be processed synchronously; means that the function would not return until message processing finishes. For example, if you send a play message with the MCI_WAIT, your application would be suspended until the entire file finishes. Therefore, you should not use MCI_WAIT for play or record messages.

The Notify flag, MCI_NOTIFY, is the reverse of the Wait flag. It indicates that the message should be processed asynchronously; means that the function would return immediately and does not wait for the completion of the message. When the processing of the message finishes, it sends a MM_MCINOTIFY message to the windows specified in the dwCallback member of the message parameter. This flag should be used for play and record messages.

The wParam value of the MM_MCINOTIFY message is usually set to either MCI_NOTIFY_SUCCESSFUL to indicate command success or MCI_NOTIFY_FAILURE otherwise.

The Test flag, MCI_TEST, checks if the device can process this message, it does not process the message. The function would return 0 if the device can process the message or non-zero otherwise. You will use this flag in rare cases only.

Keep in mind that you should choose only one of the three flags, you cannot combine them.

If you didn’t specify any of these three flags, the call would be treated asynchronously and you will not be notified when it completes.

Handling MCI Errors

Every MCI command could succeed or fail. If the command succeeded, mciSendCommand() returns 0 (FALSE/false.) Otherwise, it returns the error code.

MCI defines error codes as constants that are prefixed with MCIERR_ like MCIERR_DEVICE_NOT_INSTALLED and MCIERR_FILE_NOT_FOUND (names are self-explanatory.) You can get the friendly error message of the code using the function mciGetErrorString(). The definition of this function is as follows:

BOOL mciGetErrorString(
  DWORD fdwError,
  LPTSTR lpszErrorText,
  UINT cchErrorText
);

This function accepts three arguments:

  • fdwError:
    Error code returned by mciSendCommand() function.
  • lpszErrorText:
    A string buffer that receives the description of the error.
  • cchErrorText:
    Length of the buffer in characters.

The following C example shows how you can display a friendly error message to the user if an error occurred:

	TCHAR szBuffer[256];
	DWORD dwErr;

	dwErr = mciSendCommand(/* arguments */);

	mciGetErrorStringW(dwErr, szBuffer, 256);
	// cchErrorText =
	//     sizeof(szBuffer) / sizeof(szBuffer[0])

Recording from an Input Device

To record from an input device using MCI, follow these steps:

  1. Open the input device to receive the data from.
  2. Order the MCI to start the recording process.
  3. When you finish, stop the recording process.
  4. Save the record buffer if applicable.
  5. Close the opened device.

Keep in mind that you should check whether an error occurred or not after sending each command. The previous approach to retrieve error messages would be very useful.

Opening the Device

To open a device just pass the open command MCI_OPEN to the mciSendCommand() function along with its flags and parameter.

The parameter of MCI_OPEN is the structure MCI_OPEN_PARMS. This structure contains information about the open command. The definition of this structure is as following:

typedef struct {
    DWORD_PTR    dwCallback;
    MCIDEVICEID  wDeviceID;
    LPCSTR       lpstrDeviceType;
    LPCSTR       lpstrElementName;
    LPCSTR       lpstrAlias;
} MCI_OPEN_PARMS;

Actually, you will make use of only the third and fourth members, lpstrDeviceType and lpstrElementName, of the structure when you open a device. lpstrDeviceType determines the type of the device (digital-audio, digital-video, etc.) that will be used. In our example that records and plays sound files, we will set this member to €œwaveaudio€ to indicate that we are going to work with waveform (WAV) data.

lpstrElementName on the other hand, should be set to an empty string (that is €œ€) if you are opening an input device for recording. If you want to play a file, set this member to the full path of that file.

Common flags of the command MCI_OPEN are:

  • The Wait, Notify, and Test flags:
    The Wait command is usually used for MCI_OPEN.
  • MCI_OPEN_ELEMENT:
    Mandatory. The lpstrDeviceType of the MCI_OPEN_PARMS is set. It is set to €œwaveaudio€ for WAV data.
  • MCI_OPEN_TYPE:
    Mandatory. The lpstrElementType of the MCI_OPEN_PARMS is set. It is set to an empty string if recording or a path to a file if you want to play it.

You will always combine the flags MCI_WAIT, MCI_OPEN_ELEMENT, and MCI_OPEN_TYPE for the MCI_OPEN command.

When the function returns, the wDeviceID member of the structure is set to the ID of the device opened. You should keep this ID for future calls on that device until you close it using the close command.

The following C code shows how you can open an input device for recording:

	MCI_OPEN_PARMS parmOpen;
	WORD wDeviceID;

	parmOpen.dwCallback       = 0;
	parmOpen.wDeviceID        = 0;	// the default device
	parmOpen.lpstrDeviceType  = TEXT("waveaudio");
	parmOpen.lpstrElementName = TEXT("");
	parmOpen.lpstrAlias       = 0;

	mciSendCommand(0, // the default device
		MCI_OPEN,
		MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
		(DWORD)&parmOpen);

	// Keep the device ID for future calls
	wDeviceID = parmOpen.wDeviceID;

Starting Recording

After you have opened the input device, you can order the MCI to start the recording process using the command MCI_RECORD. This command requires an opened input device and a parameter of type MCI_RECORD_PARMS. The definition of this structure is as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwFrom;
    DWORD     dwTo;
} MCI_RECORD_PARMS;

Members this structure defines are as following:

  • dwCallback:
    The window handle that should be called after the processing of the command finishes if MCI_NOTIFY is specified in command flags.
  • dwFrom:
    Indicates the position of the buffer (in thousands of a second) to start recording from. In most cases this would be set to zero.
  • dwTo:
    Indicates the position of the buffer to stop recording when it is reached. Unless you want to record for a given period, this member should be zero.

Common flags for MCI_RECORD are:

  • MCI_WAIT, MCI_NOTIFY, and MCI_TEST:
    Usually you will use the MCI_NOTIFY flag. If so, you should handle the MM_MCINOTIFY message.
  • MCI_FROM:
    Set if you used the dwFrom member of the parameter.
  • MCI_TO:
    Set if you used the dwTo member of the parameter.

If you want to record for a specific period, set dwTo member of the structure to that specific period (in thousands of seconds) and combine your flags with MCI_TO. When this period ends, MCI automatically stops the recording process and it sends a MM_MCINOTIFY message if you have set MCI_NOTIFY in the flags.

The following C example shows how you can start recording:

	MCI_RECORD_PARMS parmRec;

	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 0;

	// We do not need a notification message
	// we will send a Stop command, when
	// we finish.
	mciSendCommand(wDeviceID, MCI_RECORD, 0, (DWORD)&parmRec);

The following code shows how you can record for a specific period:

	MCI_RECORD_PARMS parmRec;

	parmRec.dwCallback = hWnd;	// window handle
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 30000; // 30 seconds

	// Notify me when you finish the 30 seconds
	mciSendCommand(wDeviceID, MCI_RECORD,
		MCI_NOTIFY | MCI_TO, (DWORD)&parmRec);

Pausing Recording

To pause the recording process, just pass the MCI_PAUSE command to the MCI. This command accepts a parameter of the structure MCI_GENERIC_PARMS which is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
} MCI_GENERIC_PARMS;

This structure contains only one member, dwCallback. As you know, if you specify MCI_NOTIFY in command flags, MCI will send a MM_MCINOTIFY message to the window specified in this member.

MCI_PAUSE does not have specific flags, just the Wait, Notify, and Test flags.

The following C example shows how you can pause the recording process. Notice that you should already be recording or an error would be returned by the mciSendCommand().

	MCI_GENERIC_PARMS parmGen;

	parmGen.dwCallback = 0;

	mciSendCommand(wDeviceID, MCI_PAUSE, MCI_WAIT, (DWORD)&parmGen);

Resuming Recording

To resume after pausing, you can send a MCI_RESUME command to MCI. This command is very similar to the MCI_PAUSE command. It accepts the same parameter and the same flags. The example is the same, just change command name.

Stopping Recording

After you finish recording you will need to stop the recording process. To accomplish this, pass the MCI_STOP command along with the device ID and its parameter to the MCI.

This command is the same as MCI_PAUSE and MCI_RESUME. It accepts the same flags and the same parameters, and the example is identical too, just change the command name.

Retrieving Buffer Length

How long have you been recording? This could be easily answered with the MCI_STATUS command. This command queries MCI and retrieves information about the current session.

MCI_STATUS accepts a structure parameter of type MCI_STATUS_PARMS which is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwReturn;
    DWORD     dwItem;
    DWORD     dwTrack;
} MCI_STATUS_PARMS;

This structure defines the following members:

  • dwCallback:
    Discussed previously.
  • dwReturn:
    After returning from the call, it should contains the return value of the query.
  • dwItem:
    The item to query about.
  • dwTracks:
    Length or number of tracks (specific to some query items.)

Common flags that MCI_STATUS accepts:

  • The Wait, Notify, and Test flags:
    You will usually use the Wait flag.
  • MCI_STATUS_ITEM:
    Mandatory in most cases. Indicates that the dwItem of the structure is set.

If you want to query MCI about specific information, pass the MCI_STATUS command to the MCI along with MCI_STATUS_ITEM and MCI_WAIT flags, and set the dwItem field of the parameter to one of the following values (some are for output devices):

  • MCI_STATUS_LENGTH:
    Retrieves the total length of the buffer (in thousands of a second.)
  • MCI_STATUS_MODE:
    Retrieves current mode of the device which can be one of the following values (names are self-explanatory):

    • MCI_MODE_NOT_READY
    • MCI_MODE_PAUSE
    • MCI_MODE_PLAY
    • MCI_MODE_STOP
    • MCI_MODE_RECORD
    • MCI_MODE_SEEK
  • MCI_STATUS_NUMBER_OF_TRACKS:
    Retrieves the total number of tracks.
  • MCI_STATUS_POSITION:
    Retrieves current position (in thousands of a second.)
  • MCI_STATUS_READY:
    Returns TRUE (true in C#) if the device is ready or FALSE (false in C#) otherwise.

When the function returns, the dwReturn field of the structure should contain the result of the query item selected.

The following C example retrieves the length of the buffer recorded:

	MCI_STATUS_PARMS parmStatus;

	parmStatus.dwCallback = 0;
	parmStatus.dwReturn   = 0;
	parmStatus.dwItem     = MCI_STATUS_LENGTH;
	parmStatus.dwTrack    = 0;

	mciSendCommand(0, MCI_STATUS,
		MCI_WAIT | MCI_STATUS_ITEM, (DWORD)&parmStatus);

	// Display the number of seconds
	// parmStatus.dwReturn / 1000

Saving the Recorded Buffer

Before you close the device, you can save the current recorded buffer in an audio (waveform) file. The command MCI_SAVE orders MCI to save the buffer to the file specified in its structure parameter.

The parameter of MCI_SAVE is a MCI_SAVE_PARMS structure and it is defined as following:

typedef struct {
    DWORD_PTR  dwCallback;
    LPCTSTR    lpfilename;
} MCI_SAVE_PARMS;

This structure contains only two members, dwCallback (discussed before) and lpfilename. lpfilename points to a string buffer contains the name and full path of the file to save.

MCI_SAVE accepts few flags:

  • The Wait, Notify, and Test flags:
    You will always use the Wait (MCI_WAIT) flag.
  • MCI_SAVE_FILE:
    Mandatory. You will always set this flag. It indicates that lpfilename contains the path of the target file.

The following example saves the recorded buffer to the file €œrecording.wav€:

	MCI_SAVE_PARMS parmSave;

	parmSave.dwCallback = 0;
	parmSave.lpfilename = TEXT("recording.wav");
	// save to the current directory

	mciSendCommand(wDeviceID, MCI_SAVE,
		MCI_WAIT | MCI_SAVE_FILE, (DWORD)&parmSave);

Closing the Device

You strictly should always close the device when you finish working with it, and this is done through the MCI_CLOSE command which accepts a parameter of type MCI_GENERIC_PARMS (discussed before.)

MCI_CLOSE accepts only the Wait, Notify, and Test flags.

The following C example closes the opened device:

	MCI_GENERIC_PARMS parmsGen;

	parmsGen.dwCallback = 0;

	mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)&parmsGen);

Playing a Sound File

Keeping in mind what you have learnt from the previous section would be very helpful in our discussion on how to play sound files using MCI.

Actually, the same rules and commands applied in the previous section, will be applied here.

To play a sound file on MCI, you should follow these steps:

  1. Load the audio file. This automatically opens the output device.
  2. Order the MCI to start the playing process.
  3. Close the opened device when you finish.

Again, you should check whether an error occurred or not after each call to mciSendCommand().

Loading the File

As you know, to open a multimedia device, you pass MCI the MCI_OPEN command. To specify a file to be loaded, specify the full path of the file in the lpstrElementName field of the MCI_OPEN_PARMS structure.

The following C example shows how you can open an output device and load a file for playing:

	MCI_OPEN_PARMS parmOpen;
	WORD wDeviceID;

	parmOpen.dwCallback       = 0;
	parmOpen.wDeviceID        = 0;	// the default device
	parmOpen.lpstrDeviceType  = TEXT("waveaudio");
	parmOpen.lpstrElementName = TEXT("recording.wav");
	parmOpen.lpstrAlias       = 0;

	mciSendCommand(0, // the default device
		MCI_OPEN,
		MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
		(DWORD)&parmOpen);

	// Keep the device ID for future calls
	wDeviceID = parmOpen.wDeviceID;

Playing the File

To start playing the currently loaded file, you can use the MCI_PLAY command. This command accepts a structure parameter of type MCI_PLAY_PARMS which is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwFrom;
    DWORD     dwTo;
} MCI_PLAY_PARMS;

At first sight, you notice that it is identical to the MCI_RECORD_PARMS. Actually, you are right. This structure defines the same members as the MCI_RECORD_PARMS structure. In addition, the description of the members is the same.

dwFrom specifies the position (in thousands of a second) where to start playing. dwTo on the other hand specifies the position (in thousands of a second too) where to end playing. If you set dwFrom, you will need to set the flag MCI_FROM. Conversely, if you set dwTo, you will need to set the flag MCI_TO. If you need to play the file from the start to the end, leave both members and do not specify either MCI_FROM or MCI_TO.

You will most likely combine MCI_PLAY flags with the Notify flag, MCI_NOTIFY, to allow the code to continue execution while the file plays. If you did so, your application would receive MM_MCINOTIFY message to the window specified in the dwCallback member of the parameter structure.

The following C example shows how to start playing a file:

	MCI_PLAY_PARMS parmPlay;

	// Play the file
	// from the start to the end

	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 0;

	// notify me when you finish
	mciSendCommand(wDeviceID, MCI_PLAY,
		MCI_NOTIFY, (DWORD)&parmRec);

And the following code plays only three minutes from the file:

	MCI_PLAY_PARMS parmPlay;

	// play only 3 minutes
	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	// 3 * 60 * 1000
	parmRec.dwTo	   = 180000;

	// notify me when you finish
	mciSendCommand(wDeviceID, MCI_PLAY,
		MCI_NOTIFY | MCI_TO, (DWORD)&parmRec);

Pausing

As you know, you can pause the playback using the MCI_PAUSE command which accepts the MCI_GENERIC_PARMS parameter.

Resuming

To resume the playback after pausing, you can use the MCI_RESUME command discussed before.

Retrieving Current Position

To retrieve the current position of the file, pass the MCI_STATUS command along with its flags (MCI_WAIT and MCI_STATUS_ITEM) and its parameter (MCI_STATUS_PARMS) to the MCI. Do not forget to set dwItem member of the structure to MCI_STATUS_POSITION to retrieve the current position (in thousands of a second) in the dwReturn member of the structure. The following C example demonstrates this:

	MCI_STATUS_PARMS parmStatus;

	parmStatus.dwCallback = 0;
	parmStatus.dwReturn   = 0;
	parmStatus.dwItem     = MCI_STATUS_POSITION;
	parmStatus.dwTrack    = 0;

	mciSendCommand(wDeviceID, MCI_STATUS,
		MCI_WAIT | MCI_STATUS_ITEM, (DWORD)&parmStatus);

	// Display the current position
	// parmStatus.dwReturn / 1000

Retrieving File Length

Like retrieving current position, you can retrieve full file length the same way. However, you will need to specify the item MCI_STATUS_LENGTH instead.

Seeking a Specific Position

To change the current position, pass MCI the command MCI_SEEK. This command accepts the structure parameter MCI_SEEK_PARMS which is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwTo;
} MCI_SEEK_PARMS;

This parameter defines the following members:

  • dwCallback:
    The window to be notified with the MCI_MMNOTIFY message if the call was carried out asynchronously using the MCI_NOTIFY flag.
  • dwTo:
    The position (in thousands of a second) to jump to.

The command MCI_SEEK accepts the following flags:

  • The Wait, Notify, and Test flags:
    You will always use the Wait flag.
  • MCI_SEEK_TO_END:
    If specified, it would jump to the end of the file.
  • MCI_SEEK_TO_START:
    If specified, it would jump to the start of the file.
  • MCI_TO:
    If specified, MCI would use the value in the dwTo member of the parameter structure.

The following C example jumps to the start of the file:

	MCI_SEEK_PARMS parmSeek;

	parmStatus.dwCallback = 0;
	parmStatus.dwTo	      = 0;

	mciSendCommand(wDeviceID, MCI_SEEK,
		MCI_WAIT | MCI_SEEK_TO_START, (DWORD)&parmSeek);

And the following example jumps to the third minute of the file:

	MCI_SEEK_PARMS parmSeek;

	parmStatus.dwCallback = 0;
	parmStatus.dwTo	      = 180000;

	mciSendCommand(wDeviceID, MCI_SEEK,
		MCI_WAIT | MCI_TO, (DWORD)&parmSeek);

Closing the Device

You should close the device as soon as you finish working with it, and this is done (as you know) through the MCI_CLOSE command.

In a Nutshell

The following table summarizes the commands that you will usually use with audio files along with their parameter structures and flags.

Command Input/Output Parameter Structure Commonly Used Flags
MCI_OPEN In/Out MCI_OPEN_PARMS MCI_WAIT, MCI_OPEN_ELEMENT, and MCI_OPEN_TYPE
MCI_RECORD In MCI_RECORD_PARMS (none) or MCI_NOTIFY
MCI_PLAY Out MCI_PLAY_PARMS MCI_NOTIFY
MCI_PAUSE In/Out MCI_GENERIC_PARMS MCI_WAIT
MCI_RESUME In/Out MCI_GENERIC_PARMS MCI_WAIT
MCI_STOP In/Out MCI_GENERIC_PARMS MCI_WAIT
MCI_SEEK Out MCI_SEEK_PARMS MCI_WAIT and MCI_TO / MCI_SEEK_TO_START / MCI_SEEK_TO_END
MCI_SAVE In MCI_SAVE_PARMS MCI_WAIT and MCI_SAVE_FILE
MCI_STATUS In/Out MCI_STATUS_PARMS MCI_WAIT and MCI_STATUS_ITEM
MCI_CLOSE In/Out MCI_GENERIC_PARMS MCI_WAIT

MCI and .NET

Creating the Managed Signature

Because .NET does not support MCI and it does not allow you to call unmanaged code directly, you will need to create your own marshaling types and PInvoke methods.

Keep in mind that you can get the handle of a window by using Control.Handle property.

The following class is the managed signature of our unmanaged structures and functions along with the required constants:

internal static class SafeNativeMethods
{
    // Constants

    public const string WaveAudio = "waveaudio";

    public const uint MM_MCINOTIFY = 0x3B9;

    public const uint MCI_NOTIFY_SUCCESSFUL = 0x0001;
    public const uint MCI_NOTIFY_SUPERSEDED = 0x0002;
    public const uint MCI_NOTIFY_ABORTED = 0x0004;
    public const uint MCI_NOTIFY_FAILURE = 0x0008;

    public const uint MCI_OPEN = 0x0803;
    public const uint MCI_CLOSE = 0x0804;
    public const uint MCI_PLAY = 0x0806;
    public const uint MCI_SEEK = 0x0807;
    public const uint MCI_STOP = 0x0808;
    public const uint MCI_PAUSE = 0x0809;
    public const uint MCI_RECORD = 0x080F;
    public const uint MCI_RESUME = 0x0855;
    public const uint MCI_SAVE = 0x0813;
    public const uint MCI_LOAD = 0x0850;
    public const uint MCI_STATUS = 0x0814;

    public const uint MCI_SAVE_FILE = 0x00000100;
    public const uint MCI_OPEN_ELEMENT = 0x00000200;
    public const uint MCI_OPEN_TYPE = 0x00002000;
    public const uint MCI_LOAD_FILE = 0x00000100;
    public const uint MCI_STATUS_POSITION = 0x00000002;
    public const uint MCI_STATUS_LENGTH = 0x00000001;
    public const uint MCI_STATUS_ITEM = 0x00000100;

    public const uint MCI_NOTIFY = 0x00000001;
    public const uint MCI_WAIT = 0x00000002;
    public const uint MCI_FROM = 0x00000004;
    public const uint MCI_TO = 0x00000008;

    // Structures

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_OPEN_PARMS
    {
        public IntPtr dwCallback;
        public uint wDeviceID;
        public IntPtr lpstrDeviceType;
        public IntPtr lpstrElementName;
        public IntPtr lpstrAlias;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_RECORD_PARMS
    {
        public IntPtr dwCallback;
        public uint dwFrom;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_PLAY_PARMS
    {
        public IntPtr dwCallback;
        public uint dwFrom;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_GENERIC_PARMS
    {
        public IntPtr dwCallback;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_SEEK_PARMS
    {
        public IntPtr dwCallback;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_SAVE_PARMS
    {
        public IntPtr dwCallback;
        public IntPtr lpfilename;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_STATUS_PARMS
    {
        public IntPtr dwCallback;
        public uint dwReturn;
        public uint dwItem;
        public uint dwTrack;
    } ;

    // Functions

    [DllImport("winmm.dll", CharSet = CharSet.Ansi,
        BestFitMapping = true, ThrowOnUnmappableChar = true)]
    [return: MarshalAs(UnmanagedType.U4)]
    public static extern uint mciSendCommand(
        uint mciId,
        uint uMsg,
        uint dwParam1,
        IntPtr dwParam2);

    [DllImport("winmm.dll", CharSet = CharSet.Ansi,
        BestFitMapping = true, ThrowOnUnmappableChar = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool mciGetErrorString(
        uint mcierr,
        [MarshalAs(UnmanagedType.LPStr)]
        System.Text.StringBuilder pszText,
        uint cchText);
}

Receiving MCI_MMNOTIFY

In C, you can handle the MCI_MMNOTIFY message in the way you handle any other message. Just add the handler to the window procedure (WndProc) function.

In .NET, you do not have a WndProc function. However, .NET emulates this function using the protected Control.WndProc() function. You can override this function in your form and do the required processing. The following example demonstrates this:

public partial class MainForm : Form
{
    . . .

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == SafeNativeMethods.MM_MCINOTIFY)
        {
            // Handle the message
        }

        // DO NOT REMOVE the following line
        base.WndProc(ref m);
    }
}

Appendix A: Setting Device Information

After we received your feedbacks and comments about the article, we decided to add a small appendix to the end of the article about setting information (volume, channel, sampling rate, etc.) to a MCI device (a Waveform device of course.)

Like anything else in MCI, you can set device information using a MCI command (string/numeric), and this time it’s the MCI_SET command.

This command is used to set information about a specific device. This command requires an input parameter of the MCI_SET_PARMS structure. However, that input parameter might have extended members for specific devices. Because we are concentrating of Waveform devices, so we are going to use the MCI_WAVE_SET_PARMS structure that contains the extended members for our device and is defined as following:

typedef struct {
  DWORD_PTR dwCallback;
  DWORD     dwTimeFormat;
  DWORD     dwAudio;
  UINT      wInput;
  UINT      wOutput;
  WORD      wFormatTag;
  WORD      wReserved2;
  WORD      nChannels;
  WORD      wReserved3;
  DWORD     nSamplesPerSec;
  DWORD     nAvgBytesPerSec;
  WORD      nBlockAlign;
  WORD      wReserved4;
  WORD      wBitsPerSample;
  WORD      wReserved5;
} MCI_WAVE_SET_PARMS;

This structure contains all and every little piece of information that can be set to a device. I expect that you read the main article and you are already familiar with members like dwCallback (other members are self-explanatory) that we have talked about many times, and you are fine too with function calls and setting up input parameters, so I won’t get into the discussion of the structure or how you are going to use that command. However, if you need more help setting up the input parameters for the structure, you should take a brief look at the MCI_WAVE_SET_PARMS Structure documentation in the MSDN.

As you know, the MCI_WAVE_SET_PARMS unmanaged structure can be marshaled in C# as following:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_WAVE_SET_PARMS
{
  public IntPtr dwCallback;
  public uint     dwTimeFormat;
  public uint     dwAudio;
  public uint      wInput;
  public uint      wOutput;
  public ushort      wFormatTag;
  public ushort      wReserved2;
  public ushort      nChannels;
  public ushort      wReserved3;
  public uint     nSamplesPerSec;
  public uint     nAvgBytesPerSec;
  public ushort      nBlockAlign;
  public ushort      wReserved4;
  public ushort      wBitsPerSample;
  public ushort      wReserved5;
}

Congratulations! You did set the device information! So how to get them back?

This can be done through the MCI_STATUS (discussed earlier) by setting up the MCI_STATUS_ITEM flag and setting the query item to the required information you need to query about (like MCI_DGV_STATUS_VOLUME to query about volume.)

More about the MCI_STATUS command can be found in the MSDN documentation.

Sample Code

Attached with this writing the sample applications, SampleRec (C#) and SMPREC (C).

Download “SMPREC – C.zip”

Download “SampleRec – C#.zip”

Marshaling with C# – Chapter 3: Marshaling Compound Types

Read the full book here.

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:

  1. Create the marshaling type either a managed structure or a class.
  2. 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.
  3. 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
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

    // Laying-out the structure sequentially
    [StructLayout(LayoutKind.Sequential)]
    //public class SMALL_RECT
    public struct SMALL_RECT
    {
        // Because we are laying the structure sequentially,
        // we preserve field order as they are defined.

        public UInt16 Left;
        public UInt16 Top;
        public UInt16 Right;
        public UInt16 Bottom;
    }

    // The same as SMALL_RECT applies to COORD
    [StructLayout(LayoutKind.Sequential)]
    //public struct COORD
    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
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;			// 65 for A
	printf("c = %c", ch.c);	// prints 'A'
	printf("n");

	ch.c += 32;			// 97 for a
	printf("i = %d", ch.i);	// prints '97'
	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);	// 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.

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:

  1. 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.
  2. Decorate the type with the StructLayoutAttribute attribute specifying LayoutKind.Explicit for the explicit layout kind.
  3. 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.
  4. 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

    // Unions require explicit memory layout
    [StructLayout(LayoutKind.Explicit)]
    //public class SOME_CHARACTER
    public struct SOME_CHARACTER
    {
        // Both members located on the same
        // position in the beginning of the union

        // This is the continer it is 4 bytes
        [FieldOffset(0)]
        [MarshalAs(UnmanagedType.U4)]
        public int i;
        // This is only 1 byte. Therefore, it is contained
        [FieldOffset(0)]
        public char c;
    }

    public static void Main()
    {
        SOME_CHARACTER character = new SOME_CHARACTER();

        // The code for letter 'A'
        character.i = 65;
        // Should prints 'A'
        Console.WriteLine("c = {0}", character.c);

        character.c = 'B';
        // Should prints 66
        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

    // 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

        // Setting MarshalAsAttribute is required
        // for the array and the string

        //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
        //public char[] charArray;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string charArray;
    }

    // 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 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]
        // 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;

        // Can be marshaled as an array too
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string szCSDVersion;
    }

    static void Main()
    {
        OSVERSIONINFO info = new OSVERSIONINFO();
        info.dwOSVersionInfoSize = (uint)Marshal.SizeOf(info);

        //GetVersionEx(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

    // Setting StructLayout.LayoutKind to LeyoutKind.Explicit to allow
    // precisely choosing of member position. It is required for unions
    // This structure is 156-bytes
    [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
    //public class DEVMODE
    public struct DEVMODE
    {
        // You can define the following constant
        // BUT OUTSIDE THE STRUCTURE
        // because you know that size and layout of the structure
        // is very important
        // CCHDEVICENAME = 32 = 0x50
        [FieldOffset(0)]
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public Char[] dmDeviceName;
        // In addition you can define the last character array
        // as following:
        //MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        //public string dmDeviceName;

        // After the 32-bytes array
        [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;

        // ************ Union Start ************
        // Because DEVMODE_PRINT_SETTINGS is the hugest member and it is
        // 16-bytes, it is the container for other members
        // Remeber, you cannot emit the container
        [FieldOffset(44)]
        public DEVMODE_PRINT_SETTINGS dmSettings;

        // Positioned within DEVMODE_PRINT_SETTINGS
        // It is 8-bytes only
        [FieldOffset(44)]
        public POINTL dmPosition;

        // Positioned within DEVMODE_PRINT_SETTINGS
        [FieldOffset(44)]
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 dmDisplayOrientation;

        // Positioned within DEVMODE_PRINT_SETTINGS
        [FieldOffset(44)]
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 dmDisplayFixedOutput;
        // ************* Union End *************

        // Because DEVMODE_PRINT_SETTINGS structure
        // is 16-bytes, dmColor is positioned on byte 60
        [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;

        // CCHDEVICENAME = 32 = 0x50
        [FieldOffset(72)]
        [MarshalAs(UnmanagedType.ByValArray,
            SizeConst = 32,
            ArraySubType = UnmanagedType.U1)]
        public Byte[] dmFormName;

        // After the 32-bytes array
        [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;

        // ************ Union Start ************
        // Because both members are 4-bytes, the union is 4-bytes
        // and its members are overlapped
        // Again, you cannot emit the container
        // Except if both are equal, you can emit anyone of them
        [FieldOffset(116)]
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 dmDisplayFlags;

        [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;
    }

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,            // 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.

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) // Succeeded
        {
            Console.WriteLine("Current Mode:nt" +
                "{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; // 0 = The first mode

        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);

            modeIndex++; // The next mode
        }
    }

    public static void ChangeDisplaySettings
        (int width, int height, int bitCount)
    {
        DEVMODE originalMode = new DEVMODE();
        originalMode.dmSize = (ushort)Marshal.SizeOf(originalMode);

        // Retrieving current settings to edit them
        EnumDisplaySettings(null, ENUM_CURRENT_SETTINGS, ref originalMode);

        // Making a copy of the current settings
        // to allow reseting to the original mode
        DEVMODE newMode = originalMode;

        // Changing the settings
        newMode.dmPelsWidth = (uint)width;
        newMode.dmPelsHeight = (uint)height;
        newMode.dmBitsPerPel = (uint)bitCount;

        // Capturing the operation result
        int result = ChangeDisplaySettings(ref newMode, 0);

        if (result == DISP_CHANGE_SUCCESSFUL)
        {
            Console.WriteLine("Succeeded.n");

            // Inspecting the new mode
            GetCurrentSettings();

            Console.WriteLine();

            // Waiting for seeing the results
            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)
    {
        // Retrieving current settings
        // ...

        // Rotating the screen
        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;

        // Swapping width and height;
        uint temp = newMode.dmPelsWidth;
        newMode.dmPelsWidth = newMode.dmPelsHeight;
        newMode.dmPelsHeight = temp;

        // Capturing the operation result
        // ...
    }

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;

/// <summary>
/// Safe native functions
/// </summary>
internal static class SafeNativeMethods
{
    /// <summary>
    /// Standard input device.
    /// </summary>
    public const int STD_INPUT_HANDLE = -10;
    /// <summary>
    /// Standard output device.
    /// </summary>
    public const int STD_OUTPUT_HANDLE = -11;
    /// <summary>
    /// Standard error device (usually the output device.)
    /// </summary>
    public const int STD_ERROR_HANDLE = -12;

    /// <summary>
    /// White space character for clearing the screen.
    /// </summary>
    public const char WHITE_SPACE = ' ';

    /// <summary>
    /// Retrieves a handle for the console standard input, output, or error device.
    /// </summary>
    /// <param name="nStdHandle">The standard device of which to retrieve handle for.</param>
    /// <returns>The handle for the standard device selected.
    /// Or an invalid handle if the function failed.</returns>
    [DllImport("Kernel32.dll")]
    public static extern IntPtr GetStdHandle([param: MarshalAs(UnmanagedType.I4)] int nStdHandle);

    /// <summary>
    /// Writes a character string to the console buffer starting from the current cursor position.
    /// </summary>
    /// <param name="hConsoleOutput">A handle for the opened output device.</param>
    /// <param name="lpBuffer">The string of which to write.</param>
    /// <param name="nNumberOfCharsToWrite">Number of characters to write.</param>
    /// <param name="lpNumberOfCharsWritten">Outputs the number of characters written.</param>
    /// <param name="lpReserved">Reserved.</param>
    /// <returns>True if succeeded, otherwise False.</returns>
    [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);

    /// <summary>
    /// Read a character string from the console buffer starting from the current cursor position.
    /// </summary>
    /// <param name="hConsoleInput">A handle for the opened input device.</param>
    /// <param name="lpBuffer">The string read from the buffer.</param>
    /// <param name="nNumberOfCharsToRead">The number of characters to read.</param>
    /// <param name="lpNumberOfCharsRead">Outputs the number of characters read.</param>
    /// <param name="lpReserved">Reserved.</param>
    /// <returns>True if succeeded, otherwise False.</returns>
    [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);

    /// <summary>
    /// Retrieves information about the console cursor such as the size and visibility.
    /// </summary>
    /// <param name="hConsoleOutput">A handle for the opened output device.</param>
    /// <param name="lpConsoleCursorInfo">The cursor info.</param>
    /// <returns>True if succeeded, otherwise False.</returns>
    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetConsoleCursorInfo(
        IntPtr hConsoleOutput,
        [Out] out CONSOLE_CURSOR_INFO lpConsoleCursorInfo);

    /// <summary>
    /// Sets the console cursor properties as the size and visibility.
    /// </summary>
    /// <param name="hConsoleOutput">A handle for the opened output device.</param>
    /// <param name="lpConsoleCursorInfo">The cursor info.</param>
    /// <returns>True if succeeded, otherwise False.</returns>
    [DllImport("kernel32.dll")]

    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool SetConsoleCursorInfo(
        IntPtr hConsoleOutput,
        ref CONSOLE_CURSOR_INFO lpConsoleCursorInfo);

    /// <summary>
    /// Moves a block of data in a screen buffer.
    /// </summary>
    /// <param name="hConsoleOutput">A handle for the opened output device.</param>
    /// <param name="lpScrollRectangle">The coordinates of the block to move.</param>
    /// <param name="lpClipRectangle">The coordinates affected by the scrolling.</param>
    /// <param name="dwDestinationOrigin">The coordinates represents
    /// the new location of the block.</param>
    /// <param name="lpFill">Specifies the character and color info for the cells
    /// left empty after the move.</param>
    /// <returns>True if succeeded, otherwise False.</returns>
    /// <remarks>
    /// Because we are going to set the <paramref name="lpClipRectangle"/> to NULL,
    /// we marshaled it as IntPtr so we can set it to null using IntPtr.Zero.
    /// If you do need to set its value, you can marshal it as SMALL_RECT.
    /// </remarks>
    [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);

    /// <summary>
    /// Retrieves information about the specified console screen buffer.
    /// </summary>
    /// <param name="hConsoleOutput">A handle for the device of which to get its
    /// information.</param>
    /// <param name="lpConsoleScreenBufferInfo">Outputs the information of the
    /// specified screen buffer.</param>
    /// <returns>True if succeeded, otherwise False.</returns>
    [DllImport("Kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetConsoleScreenBufferInfo
        (IntPtr hConsoleOutput,
        [Out] out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);

    /// <summary>
    /// Fills the console buffer with a specific character.
    /// </summary>
    /// <param name="hConsoleOutput">A handle for the opened output device.</param>
    /// <param name="cCharacter">The character of which to fill the buffer width.
    /// Setting this character to a white space means clearing the cells.</param>
    /// <param name="nLength">The number of cells to fill.</param>
    /// <param name="dwWriteCoord">The location of which to start filling.</param>
    /// <param name="lpNumberOfCharsWritten">Outputs the number of characters written.</param>
    /// <returns>True if succeeded, otherwise False.</returns>
    [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);

    /// <summary>
    /// Sets the console cursor to a specific position.
    /// </summary>
    /// <param name="hConsoleOutput">A handle for the opened output device.</param>
    /// <param name="dwCursorPosition">The new cursor position inside the console buffer.</param>
    /// <returns>True if succeeded, otherwise False.</returns>
    [DllImport("Kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool SetConsoleCursorPosition
        (IntPtr hConsoleOutput, COORD dwCursorPosition);
}

/// <summary>
/// Information about the screen buffer.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_SCREEN_BUFFER_INFO
{
    /// <summary>
    /// The size of the buffer.
    /// </summary>
    public COORD dwSize;
    /// <summary>
    /// The location of the cursor inside the buffer.
    /// </summary>
    public COORD dwCursorPosition;
    /// <summary>
    /// Additional attributes about the buffer write the fore color and back color.
    /// </summary>
    [MarshalAs(UnmanagedType.U2)]
    public ushort wAttributes;
    /// <summary>
    /// The location and bounds of the window.
    /// </summary>
    public SMALL_RECT srWindow;
    /// <summary>
    /// The maximum size of the window.
    /// </summary>
    public COORD dwMaximumWindowSize;
}

/// <summary>
/// Coordinates (X, Y).
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
    /// <summary>
    /// The location from the left (X).
    /// </summary>
    [MarshalAs(UnmanagedType.I2)]
    public short X;
    /// <summary>
    /// The location from the top (Y).
    /// </summary>
    [MarshalAs(UnmanagedType.I2)]
    public short Y;
}

/// <summary>
/// Defines the coordinates of the upper left and right bottom coordinates of a rectangle.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct SMALL_RECT
{
    /// <summary>
    /// The X-coordinate of the upper left corner of the rectangle.
    /// </summary>
    [MarshalAs(UnmanagedType.I2)]
    public short Left;
    /// <summary>
    /// The Y-coordinate of the upper left corner of the rectangle.
    /// </summary>
    [MarshalAs(UnmanagedType.I2)]
    public short Top;
    /// <summary>
    /// The X-coordinate of the lower right corner of the rectangle.
    /// </summary>
    [MarshalAs(UnmanagedType.I2)]
    public short Right;
    /// <summary>
    /// The Y-coordinate of the lower right corner of the rectangle.
    /// </summary>
    [MarshalAs(UnmanagedType.I2)]
    public short Bottom;
}

/// <summary>
/// Defines the console cursor info.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct CONSOLE_CURSOR_INFO
{
    /// <summary>
    /// The size of the cursor. Usually 0.25 of the cell.
    /// </summary>
    [MarshalAs(UnmanagedType.U4)]
    public uint dwSize;
    /// <summary>
    /// If cursor is visible or not.
    /// </summary>
    [MarshalAs(UnmanagedType.Bool)]
    public bool bVisible;
}

/// <summary>
/// Defines a character information.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct CHAR_INFO
{
    /// <summary>
    /// The character.
    /// </summary>
    public char Char;
    /// <summary>
    /// Additional attributes of the character like fore color and back color.
    /// </summary>
    [MarshalAs(UnmanagedType.U2)]
    public ushort Attributes;
}

ConsoleLib.cs

using System;
using System.Runtime.InteropServices;
using System.Text;

// Console horizontal text alignment.
public enum ConsoleTextAlignment
{
    /// <summary>
    /// Text is left aligned.
    /// </summary>
    Left,
    /// <summary>
    /// Text is right aligned.
    /// </summary>
    Right,
    /// <summary>
    /// Text is centered.
    /// </summary>
    Center
}

/// <summary>
/// Console standard devices.
/// </summary>
public enum ConsoleStandardDevice
{
    /// <summary>
    /// The input device.
    /// </summary>
    Input = SafeNativeMethods.STD_INPUT_HANDLE,
    /// <summary>
    /// The output device.
    /// </summary>
    Output = SafeNativeMethods.STD_OUTPUT_HANDLE,
    /// <summary>
    /// The error device (usually the output device.)
    /// </summary>
    Error = SafeNativeMethods.STD_ERROR_HANDLE
}

/// <summary>
/// Extension methods for the console.
/// </summary>
public static class ConsoleExtensions
{
    /// <summary>
    /// Clears the screen buffer.
    /// </summary>
    public static void ClearScreen()
    {
        // Clearing the screen starting from the first cell
        COORD location = new COORD();
        location.X = 0;
        location.Y = 0;

        ClearScreen(location);
    }
    /// <summary>
    /// Clears the screen buffer starting from a specific location.
    /// </summary>
    /// <param name="location">The location of which to start clearing
    /// the screen buffer.</param>
    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);
    }

    /// <summary>
    /// Fills a specific cells with a specific character starting from a specific location.
    /// </summary>
    /// <param name="location">The location of which to start filling.</param>
    /// <param name="count">The number of cells starting
    /// from the location to fill.</param>
    /// <param name="character">The character to fill with.</param>
    public static void FillConsoleBuffer(COORD location, uint count, char character)
    {
        // Getting the console output device handle
        IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);

        uint length;

        // If count equals 0 then user require clearing all the screen
        if (count == 0)
        {
            // Getting console screen buffer info
            CONSOLE_SCREEN_BUFFER_INFO info = GetBufferInfo(ConsoleStandardDevice.Output);
            // All the screen
            length = (uint)(info.dwSize.X * info.dwSize.Y);
        }
        else
            length = count;

        // The number of written characters
        uint numChars;

        // Calling the Win32 API function
        SafeNativeMethods.FillConsoleOutputCharacter(handle, character,
            length, location, out numChars);

        // Setting the console cursor position
        SetCursorPosition(location);
    }

    /// <summary>
    /// Rettrieves a handle for a specific device.
    /// </summary>
    /// <param name="device">The device of which to retrieve the handle for.</param>
    /// <returns>The handle for the specified device.</returns>
    public static IntPtr GetStandardDevice(ConsoleStandardDevice device)
    {
        // Calling the Win32 API function
        return SafeNativeMethods.GetStdHandle((int)device);
    }

    /// <summary>
    /// Writes an empty line to the console buffer on the current position of the cursor.
    /// </summary>
    public static void WriteLine()
    {
        WriteLine(string.Empty);
    }
    /// <summary>
    /// Writes specific text followed by a line terminator to the console buffer on
    /// the current position of the cursor.
    /// </summary>
    /// <param name="txt">The text to write.</param>
    public static void WriteLine(string txt)
    {
        WriteLine(txt, ConsoleTextAlignment.Left);
    }
    /// <summary>
    /// Writes specific text followed by a line terminator to the console buffer on the
    /// current position of the cursor with the specified line alignemnt.
    /// </summary>
    /// <param name="txt">The text to write.</param>
    /// <param name="alignment">The horizontal alignment of the text.</param>
    public static void WriteLine(string txt, ConsoleTextAlignment alignment)
    {
        Write(txt + Environment.NewLine, alignment);
    }
    /// <summary>
    /// Writes specific text to the console buffer on the current position of the cursor.
    /// </summary>
    /// <param name="txt">The text to write.</param>
    public static void Write(string txt)
    {
        Write(txt, ConsoleTextAlignment.Left);
    }
    /// <summary>
    /// Writes specific text to the console buffer on the current position of the cursor
    /// with the specified line alignment.
    /// </summary>
    /// <param name="txt">The text to write.</param>
    /// <param name="alignment">The horizontal alignment of the text.</param>
    public static void Write(string txt, ConsoleTextAlignment alignment)
    {
        if (alignment == ConsoleTextAlignment.Left)
            InternalWrite(txt);
        else
        {
            // Determining the location of which to begin writing
            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 // Center
                pos.X = (short)((info.dwSize.X - txt.Length) / 2);

            pos.Y = info.dwCursorPosition.Y;

            // Changing the cursor position
            SetCursorPosition(pos);

            // Now writing on the current position
            InternalWrite(txt);
        }
    }
    /// <summary>
    /// Writing a specific text to the console output buffer starting from the
    /// current cursor position.
    /// </summary>
    /// <param name="txt">The text to write.</param>
    private static void InternalWrite(string txt)
    {
        // Required for the WriteConsole() function
        // It is the number of characters written
        uint count;
        // Getting the output handle
        IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);
        // Calling the Win32 API function
        SafeNativeMethods.WriteConsole(handle, txt, (uint)txt.Length, out count, 0);
    }

    /// <summary>
    /// Shows or hides the cursor.
    /// </summary>
    /// <param name="show">Specifies whether to show the cursor or not.</param>
    public static void ShowCursor(bool show)
    {
        CONSOLE_CURSOR_INFO info;
        // Getting the output device
        IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);

        // Getting the cursor info
        SafeNativeMethods.GetConsoleCursorInfo(handle, out info);

        // Determining the visibility of the cursor
        info.bVisible = show;

        // Setting the cursor info
        SafeNativeMethods.SetConsoleCursorInfo(handle, ref info);
    }

    /// <summary>
    /// Read the next line from the input device.
    /// </summary>
    /// <returns></returns>
    public static string ReadText()
    {
        // The buffer
        // Maximum number of characters is 256
        StringBuilder buffer = new StringBuilder(256);
        // Required for the function call
        uint count;
        // Getting the input device that's used for receiving user input
        SafeNativeMethods.ReadConsole(GetStandardDevice(ConsoleStandardDevice.Input), buffer,
            (uint)buffer.Capacity, out count, 0);
        // Returning the user input cutting up the line terminator
        return buffer.ToString().Substring(0, (int)(count - Environment.NewLine.Length));
    }

    /// <summary>
    /// Retrieves the buffer info of the specified device.
    /// </summary>
    /// <param name="device">The device of which to retrieve its information.</param>
    /// <returns>The buffer info of the specified device.</returns>
    public static CONSOLE_SCREEN_BUFFER_INFO GetBufferInfo(ConsoleStandardDevice device)
    {
        // Returning the handle for the selected device
        IntPtr handle = GetStandardDevice(device);

        // Getting console screen buffer information
        CONSOLE_SCREEN_BUFFER_INFO info;
        SafeNativeMethods.GetConsoleScreenBufferInfo(handle, out info);

        return info;
    }

    /// <summary>
    /// Sets the cursor position in the buffer.
    /// </summary>
    /// <param name="pos">The coordinates of which to move the cursor to.</param>
    public static void SetCursorPosition(COORD pos)
    {
        // Getting the console output device handle
        IntPtr handle = SafeNativeMethods.GetStdHandle(SafeNativeMethods.STD_OUTPUT_HANDLE);

        // Moving the cursor to the new location
        SafeNativeMethods.SetConsoleCursorPosition(handle, pos);
    }

    /// <summary>
    /// Writes the buffer information to the screen.
    /// </summary>
    /// <param name="info">The information of which to write.</param>
    public static void WriteBufferInfo(CONSOLE_SCREEN_BUFFER_INFO info)
    {
        // Discovering console screen buffer information
        WriteLine("Console Buffer Info:");
        WriteLine("--------------------");

        WriteLine("Cursor Position:");
        WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "t{0}, {1}",
            info.dwCursorPosition.X, info.dwCursorPosition.Y));

        // Is this information right?
        WriteLine("Maximum Window Size:");
        WriteLine(string.Format(System.Globalization.CultureInfo.InvariantCulture, "t{0}, {1}",
            info.dwMaximumWindowSize.X,
            info.dwMaximumWindowSize.Y));

        // Is this information right?
        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("--------------------");
    }

    /// <summary>
    /// Writes the specific text followed by a line terminator to the left and moves
    /// it to the far right.
    /// </summary>
    /// <param name="txt">The text of which to write.</param>
    public static void MoveText(string txt)
    {
        // First, writing the text
        WriteLine(txt);

        // Getting the handle for the output device
        IntPtr handle = GetStandardDevice(ConsoleStandardDevice.Output);

        // Getting the screen buffer info for the output device
        CONSOLE_SCREEN_BUFFER_INFO screenInfo = GetBufferInfo(ConsoleStandardDevice.Output);

        // Selecting the text to be moved
        SMALL_RECT rect = new SMALL_RECT();
        rect.Left = 0; // The 1st cell
        rect.Top = (short)(screenInfo.dwCursorPosition.Y - 1); // The row of the text
        rect.Bottom = (short)(rect.Top); // Only a single line

        while (true)
        {
            // Moving to the right
            rect.Right = (short)(rect.Left + (txt.Length - 1));

            // Do not move it nore if we are in the far right of the buffer
            if (rect.Right == (screenInfo.dwSize.X - 1))
                break;

            // 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

            // Calling the API function
            SafeNativeMethods.ScrollConsoleScreenBuffer(handle, ref rect, IntPtr.Zero,
                new COORD() { X = (short)(rect.Left + 1), Y = rect.Top }, ref charInfo);

            // Blocking the thread for the user to see the effect
            System.Threading.Thread.Sleep(100);

            // Moving the rectangle
            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.

Marshaling with C# – Chapter 2: Marshaling Simple Types

Read the full book here.

Chapter Contents

Contents of this chapter:

  • Chapter Contents
  • Overview
  • Simple and Compound data Types
  • Blittable and Non-Blittable Data Types
  • Marshaling Blittable Data Types
    • Numeric Data Types
    • Textual Data Types
    • Examining Type Definition
    • Variants
    • Try It Out!
  • A Rule of Thumb
  • Marshaling Booleans
    • The Two Types
    • Try It Out!
  • Marshaling Textual Data types
    • How to Marshal Strings and Buffers
    • Handling Character Encoding
    • Try It Out!
  • Marshaling Handles
    • Generic Handles
    • Safe Handles
    • Critical Handles
  • Passing Mechanism
  • Additional Techniques
    • Encapsulation
    • Creating Wrappers
    • Working with Nullable Arguments
    • Working out the CLS Problem
  • Real-World Examples
    • Programmatically Swapping Mouse Buttons
    • Programmatically Turning On the Screen Saver
    • Dragging a Form without a Title Bar
  • Summary

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 and Compound Data Types

There are two kinds of data types:

  • 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.

Some considers that strings are not primitives.

Blittable and Non-Blittable Data Types

Most data types have common representations in both managed and unmanaged memory and do not require special handling. These types are called blittable types because they do not require special handling when passed between managed and unmanaged code. Other types that require special handling are called non-blittable types. You can think that most of simple types are blittable and all of compound types are non-blittable.

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

Description Managed Type
8-bit signed integer. System.SByte
8-bit unsigned integer

System.Byte

16-bit signed integer.

System.Int16

16-bit unsigned integer

System.UInt16

32-bit signed integer

System.Int32

32-bit unsigned integer

System.UInt32

64-bit signed integer

System.Int64

64-bit unsigned integer

System.UInt64

Signed pointer

System.IntPtr

Unsigned pointer

System.UIntPtr

More information about pointers later in this chapter.

Marshaling Blittable Data Types

You can marshal an unmanaged simple data type by tracking its definition then finding its counterpart (marshaling type) in the managed environment based on its definition (we will see how soon.)

Numeric Data Types

The following table lists some of the unmanaged data types in Windows, their C/C++ keywords, and their counterparts (marshaling types) in .NET. As you might guess, by tracking each of these unmanaged types, we were able to find its managed counterpart. Notice that so

Table 2.2 Numeric Data Types

Description Windows Type C/C++ Keyword Managed Type C# Keyword
8-bit unsigned integer BYTE unsigned char System.Byte Byte
16-bit signed integer SHORT Short System.UInt16 ushort
16-bit unsigned integer WORD and USHORT unsigned short System.Int16 short
32-bit signed integer INT, INT32, LONG, and LONG32 int, long System.UInt32 Uint
32-bit unsigned integer DWORD, DWORD32, UINT, and UINT32 unsigned int, unsigned long System.Int32 int
64-bit signed integer INT64, LONGLONG, and LONG64 __int64, long long System.UInt64 ulong
64-bit unsigned integer DWORDLONG, DWORD64, ULONGLONG, and UINT64 unsigned __int64, unsigned long long System.Int64 long
Floating-point integer FLOAT float System.Double double

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.

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.

To keep things simple, we will focus on Windows API in our examples.

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.

Textual Data Types

In addition to the numeric data types, you will need to know how to marshal unmanaged textual data types (a single character or a string.) However, these types are non-blittable, so they require special handling.

The following table lists briefly unmanaged textual data types.

Table 2.3 Textual Data Types

Description Unmanaged Type(s) Managed Type
8-bit ANSI character CHAR System.Char
16-bit Unicode character WCHAR System.Char
8-bit ANSI string of characters LPSTR, LPCSTR, PCSTR, and PSTR System.String
16-bit Unicode string of characters LPCWSTR, LPWSTR, PCWSTR, and PWSTR System.String

Soon we will cover textual data types in details.

Examining Type Definition

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
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.

More on pointers later in this chapter. Memory management is discussed in details in chapter 6.

In addition, for textual data types, there are types variable based on Unicode definition (strings and buffers are covered soon.) Examples are:

  • TBYTE and TCHAR:
    Defined as WCHAR if UNICODE defined, otherwise CHAR.
  • LPCTSTR, LPTSTR, and PCTSTR:
    All defined as LPCWSTR if UNICODE defined, otherwise LPCSTR.
  • PTSTR:
    Defined as PWSTR if UNICODE defined, otherwise PSTR.

More on textual data types and Unicode later in this chapter.

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.

The definition of the MessageBoxEx() function is as following:

Listing 2.1 MessageBoxEx() Unmanaged Signature

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.

Listing 2.2 MessageBoxEx() Managed Signature

     // CharSet.Unicode defines the UNICODE.
     // Use either this way to control
     // the whole function, or you can control
     // the parameters individually using the
     // MarshalAsAttribute attribute
     [DllImport("User32.dll", CharSet = CharSet.Unicode)]
     [return: MarshalAs(UnmanagedType.I4)]
     static extern Int32 MessageBoxEx
         (IntPtr hWnd,
         // Marshaling as Unicode characters
         [param: MarshalAs(UnmanagedType.LPTStr)]
         String lpText,
         // Marshaling as Unicode characters
         [param: MarshalAs(UnmanagedType.LPTStr)]
         String lpCaption,
         // Marshaling as 4-bytes (32-bit) unsigned integer
         [param: MarshalAs(UnmanagedType.U4)]
         UInt32 uType,
         // Marshaling as 2-bytes (16-bit) unsigned integer
         [param: MarshalAs(UnmanagedType.U2)]
         UInt16 wLanguageId);

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.

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.)

The following is the definition of the famous CloseHandle() function:

Listing 2.3 CloseHandle() Unmanaged Signature

BOOL CloseHandle(HANDLE hObject);

The managed version of CloseHandle() is as following:

Listing 2.4 CloseHandle() Managed Signature

     [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.

Marshaling Textual Data Types

How to Marshal Strings and Buffers

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.

Conversely, when using StringBuilder, it passes a reference to the internal buffer of StringBuilder if passed by value. Passing a StringBuilder by reference actually passes a pointer to the StringBuilder object into memory to the function not a pointer to the buffer itself.

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.

To summarize, System.String is preferable when working with immutable strings, especially for input (In) arguments. On the other hand, System.Text.StringBuilder is preferable with changeable strings especially output (Out) arguments.

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?)

Compound types discussed in the next chapter.

Handling Character Encoding

Encoding of a character is very important because it determines the value that the character can hold and the size it occupies into memory. For example, if the character is ANSI-encoded it can be one of only 256 characters. Likewise, if it is Unicode-encoded, it can hold one of 65536 characters, which is very good for most languages.

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.

Listing 2.6 Reading from the Console Screen Buffer Example

        // For retrieving a handle to a specific console device
        [DllImport("Kernel32.dll")]
        static extern IntPtr GetStdHandle(
            [param: MarshalAs(UnmanagedType.U4)]
            int nStdHandle);

        // Used with GetStdHandle() for retrieving console input buffer
        const int STD_INPUT_HANDLE = -10;

        // Specifying the DLL along with the character set
        [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool ReadConsole(
            // Handle to the input device
            IntPtr hConsoleInput,
            // The buffer of which to write input to
            [param: MarshalAs(UnmanagedType.LPTStr), Out()]
            // [param: MarshalAs(UnmanagedType.AsAny)]
            StringBuilder lpBuffer,
            // Number of characters to read
            [param: MarshalAs(UnmanagedType.U4)]
            uint nNumberOfCharsToRead,
            // Outputs the number of characters read
            [param: MarshalAs(UnmanagedType.U4), Out()]
            out uint lpNumberOfCharsRead,
            // Reserved = Always set to NULL
            [param: MarshalAs(UnmanagedType.AsAny)]
            uint lpReserved);

        // For getting the code for the last error occurred
        [DllImport("Kernel32.dll")]
        [return: MarshalAs(UnmanagedType.U4)]
        static extern uint GetLastError();

        // Retrieves error messages
        [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.U4)]
        static extern uint FormatMessage(
            // Options
            [param: MarshalAs(UnmanagedType.U4)]
            uint dwFlags,
            // Source to get the message from
            // [param: MarshalAs(UnmanagedType.AsAny)]
            [param: MarshalAs(UnmanagedType.U4)]
            uint lpSource,
            // Message code = error code
            [param: MarshalAs(UnmanagedType.U4)]
            uint dwMessageId,
            // Language ID (Reserved)
            [param: MarshalAs(UnmanagedType.U4)]
            uint dwLanguageId,
            // Outputs the error message
            [param: MarshalAs(UnmanagedType.LPTStr), Out()]
            out string lpBuffer,
            // Size of error message
            [param: MarshalAs(UnmanagedType.U4)]
            uint nSize,
            // Additional options
            [param: MarshalAs(UnmanagedType.U4)]
            uint Arguments);

        // 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;

        static void Main()
        {
            // Handle to input buffer
            IntPtr handle = GetStdHandle(STD_INPUT_HANDLE);

            const int maxCount = 256;

            uint noCharacters;
            StringBuilder builder = new StringBuilder(maxCount);

            if (ReadConsole(handle, builder, (uint)maxCount,
                out noCharacters, 0) == false) // false = non-zero = failed
            {
                string errMsg;
                FormatMessage(FORMAT_MESSAGE_FLAGS,
                    FORMAT_MESSAGE_FROM_HMODULE,
                    GetLastError(),
                    0,  // Means NULL
                    out errMsg,
                    0,  // Maximum length
                    0); // Means NULL

                Console.WriteLine("ERROR:n{0}", errMsg);
            }
            else // true = zero = succeeded
                // Writing user input withour the newline
                Console.WriteLine("User wroted: = " +
                    builder.ToString().Substring(0,
                    builder.Length - Environment.NewLine.Length));

            Console.WriteLine(new string('-', 25));

            builder = new StringBuilder(maxCount);

            // Invalid handle
            handle = GetStdHandle(12345);

            if (ReadConsole(handle, builder, (uint)maxCount,
                out noCharacters, 0) == false) // false = non-zero = failed
            {
                string errMsg;
                FormatMessage(FORMAT_MESSAGE_FLAGS,
                    FORMAT_MESSAGE_FROM_HMODULE,
                    GetLastError(),
                    0,  // Means NULL
                    out errMsg,
                    0,  // Maximum length
                    0); // Means NULL

                Console.WriteLine("ERROR: {0}", errMsg);
            }
            else // true = zero = succeeded
                // Exculding the newline characters
                Console.WriteLine("User wroted: = " +
                    builder.ToString().Substring(0,
                    builder.Length - Environment.NewLine.Length));
        }

The last code demonstrates other useful techniques:

  • Until now, handles should be marshaled as System.IntPtr. The following section talks in details about handles.
  • Because LPVOID and LPCVOID are both defined as a pointer to a variant (i.e. any type,) you can set them to any type you want. They are very similar to System.Object in the .NET methodology or Variant for people who are familiar with the traditional Visual Basic. In our example, we have marshaled LPVOID as System.UInt32 and set it to zero. Again, you are free to play with the marshaling types. LPVOID and LPCVOID are both 32-bit integer. Why not just marshaling them as any of the 32-bit managed types and forgetting about them? In addition, you can marshal it as System.IntPtr, and pass it System.IntPtr.Zero to indicate a NULL value. Moreover, you can marshal it as System.Object, and set it to any value, even null to indicate the NULL value. Variant has been discussed in details previously in the section €œMarshaling Blittable Data Types.€
  • va_list* is a pointer to an array of specific arguments. You can marshal it as an array, or System.IntPtr. System.IntPtr is preferred if you intend to pass it a NULL value.
  • If the function requires a parameter passed by value or by reference you can add the required modifiers like ref and out to the parameter, and decorate the parameter with either InAttribute or OutAttribute, or both. The section €œPassing an Argument by Value or by Reference€ later in this chapter discusses by-value and by-reference parameters.
  • While DWORD is defined as unsigned 32-bit integer and it should be marshaled as System.UInt32, we find that the GetStdHandle() can take one of three values: -10 for the input device, -11 for the output device, and -12 for the error device (usually is the output device.) Although System.UInt32 does not support negative values, Windows handles this for you. It converts the signed value to its equivalent unsigned value. Therefore, you should not worry about the value passed. However, keep in mind that the unsigned values are too different (from the perspective of most developers.) For example, the unsigned value of -11 is 0xFFFFFFF5! Does this seem strange for you? Start by consulting the documentation about binary notation.

Marshaling Handles

Generic Handles

What is a handle? A handle is a pointer to some resource loaded in memory, such as handles to the console standard input, output, and error devices, the handle for the window, and the handle to a device context (DC.)

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.

Managed handles allow you to pass, to unmanaged code, a handle to an unmanaged resource (such as DC) wrapped by managed class.

There are two kinds of managed handles safe and critical handles.

Safe handles

Safe handles represented by the abstract System.Runtime.InteropServices.SafeHandle. Safe handles provide protection from recycling security attacks by perform reference counting (and that makes safe handles slower.) In addition, it provides critical finalization for handle resources. As a refresher, finalization means releasing the object and its resources from the memory, and critical finalization ensures object finalization under any circumstances. Figure 2.2 shows the definition of SafeHandle and its descendants.

Figure 2.2 SafeFileHandle and Descendants Class Definitions
Figure 2.2 SafeFileHandle and Descendants Class Definitions

As the diagram illustrates, SafeHandle is the base class that represents any safe handle. It inherits from System.Runtime.ConstrainedExecution.CriticalFinalizerObject that ensures the finalization process. The following are the most common members of SafeHandle:

  • 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.

As the diagram illustrates, two concrete classes inherit from SafeHandleZeroOrMinusOneIsInvalid:

  • 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:

Listing 2.7 CreateFile() Unmanaged Signature

HANDLE CreateFile(
  LPCTSTR lpFileName,
  DWORD dwDesiredAccess,
  DWORD dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  DWORD dwCreationDisposition,
  DWORD dwFlagsAndAttributes,
  HANDLE hTemplateFile
);

In addition, here is the .NET code:

Listing 2.8 Create File Example

    [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern SafeFileHandle CreateFile(
        string lpFileName,
        uint dwDesiredAccess,
        uint dwShareMode,
        // Because we are going to set the argument
        // to NULL we marshaled it as IntPtr
        // so we can set it to IntPtr.Zero
        // to represent a NULL value
        IntPtr lpSecurityAttributes,
        uint dwCreationDisposition,
        uint dwFlagsAndAttributes,
        // A handle for a template file
        // we are going to set it to NULL
        // so e can marshal it as System.IntPtr
        // and pass IntPtr.Zero for the NULL value
        // But, this is another way
        SafeFileHandle hTemplateFile);

    // Accessing the file for writing
    const uint GENERIC_WRITE = 0x40000000;
    // Do now allow file sharing
    const uint FILE_SHARE_NONE = 0x0;
    // Create the file and overwrites it if exists
    const uint CREATE_ALWAYS = 0x2;
    // Normal file, no attribute set
    const uint FILE_ATTRIBUTE_NORMAL = 0x80;

    static void Main()
    {
        SafeFileHandle handle =
            CreateFile("C:\MyFile.txt",
            GENERIC_WRITE,
            FILE_SHARE_NONE,
            IntPtr.Zero, // NULL
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            new SafeFileHandle(IntPtr.Zero, true));

        // Because SafeFileHandle inherits
        // SafeHandleZeroOrMinusOneIsInvalid
        // IsInvalid returns true only if
        // the handle equals to 0 or -1
        if (handle.IsInvalid) // 0 or -1
        {
            Console.WriteLine("ERROR: {0}", Marshal.GetLastWin32Error());
            return;
            // Marshal.GetLastWin32Error() returns the last error only
            // if DllImportAttribute.SetLastError is set to true
        }

        FileStream stream = new FileStream(handle, FileAccess.Write);
        StreamWriter writer = new StreamWriter(stream);
        writer.WriteLine("Hello, World!");
        writer.Close();

        /*
         * 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:

Listing 2.9 Custom Safe Handle Example

    public sealed class SafeHandleZeroIsInvalid : SafeHandle
    {
        [DllImport("Kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);

        // If ownsHandle equals true handle will
        // be automatically released during the
        // finalization process, otherwise, you
        // will have the responsibility to
        // release it outside the class.
        // Automatic releasing means calling
        // the ReleaseHandle() method.
        public SafeHandleZeroIsInvalid
            (IntPtr preexistingHandle, bool ownsHandle)
            : base(IntPtr.Zero, ownsHandle)
        {
            this.SetHandle(preexistingHandle);
        }

        public override bool IsInvalid
        {
            get
            {
                // this.handle.ToInt32() == 0
                // this.handle == new IntPtr(0)
                return this.handle == IntPtr.Zero;
            }
        }

        protected override bool ReleaseHandle()
        {
            return CloseHandle(this.handle);
        }
}

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.

Critical handles represented by the abstract System.Runtime.InteropServices.CriticalHandle. Figure 2.3 shows the definition of CriticalHandle and its descendants.

Figure 2.3 CriticalHandle and Descendants Class Definitions
Figure 2.3 CriticalHandle and Descendants Class Definitions

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.

Arguments passed by reference can be either In/Out (Input/Output) or only Out (Output.) In/Out arguments are used for passing input to the function and returning output. On the other hand, Out arguments used for returning output only. Therefore, In/Out arguments must be initialized before they are passed to the function. Conversely, Out arguments do not require pre-initialization.

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.

Listing 2.10 WriteConsole() Unmanaged Signature

BOOL WriteConsole(
  HANDLE hConsoleOutput,
  VOID lpBuffer,
  DWORD nNumberOfCharsToWrite,
  LPDWORD lpNumberOfCharsWritten,
  LPVOID lpReserved
);

And this is the managed version along with the test code:

Listing 2.11 Writing to Console Screen Example

    [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool WriteConsole(
        IntPtr hConsoleOutput,
        String lpBuffer,
        [param: MarshalAs(UnmanagedType.U4)]
        UInt32 nNumberOfCharsToWrite,
        [param: MarshalAs(UnmanagedType.U4)]
        out UInt32 lpNumberOfCharsWritten,
        [param: MarshalAs(UnmanagedType.AsAny)]
        object lpReserved);

    [DllImport("Kernel32.dll")]
    static extern IntPtr GetStdHandle(
        [param: MarshalAs(UnmanagedType.U4)]
        Int32 nStdHandle);

    const int STD_OUTPUT_HANDLE = -11;

    static void Main()
    {
        IntPtr handle = GetStdHandle(STD_OUTPUT_HANDLE);

        String textToWrite = "Hello, World!" + Environment.NewLine;
        uint noCharactersWritten;

        WriteConsole(handle,
            textToWrite,
            (uint)textToWrite.Length,
            out noCharactersWritten,
            null);

        Console.WriteLine("No. Characters written = {0}",
            noCharactersWritten);
    }

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:

Listing 2.12 Message Box Example

    [DllImport("User32.dll", CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.I4)]
    static extern UInt32 MessageBoxEx
        (IntPtr hWnd,
        [param: MarshalAs(UnmanagedType.LPTStr)]
        String lpText,
        [param: MarshalAs(UnmanagedType.LPTStr)]
        String lpCaption,
        [param: MarshalAs(UnmanagedType.U4)]
        UInt32 uType,
        [param: MarshalAs(UnmanagedType.U2)]
        UInt16 wLanguageId);

    public enum MB_BUTTON : uint
    {
        MB_OK = 0x0,
        MB_OKCANCEL = 0x1,
        MB_ABORTRETRYIGNORE = 0x2,
        MB_YESNOCANCEL = 0x3,
        MB_YESNO = 0x4,
        MB_RETRYCANCEL = 0x5,
        MB_HELP = 0x4000,
    }
    public enum MB_ICON : uint
    {
        MB_ICONHAND = 0x10,
        MB_ICONQUESTION = 0x20,
        MB_ICONEXCLAMATION = 0x30,
        MB_ICONASTERISK = 0x40,
        MB_ICONERROR = MB_ICONHAND,
        MB_ICONSTOP = MB_ICONHAND,
        MB_ICONWARNING = MB_ICONEXCLAMATION,
        MB_ICONINFORMATION = MB_ICONASTERISK,
    }
    public enum MB_DEF_BUTTON : uint
    {
        MB_DEFBUTTON1 = 0x0,
        MB_DEFBUTTON2 = 0x100,
        MB_DEFBUTTON3 = 0x200,
        MB_DEFBUTTON4 = 0x300,
    }
    public enum MB_MODAL : uint
    {
        MB_APPLMODAL = 0x0,
        MB_SYSTEMMODAL = 0x1000,
        MB_TASKMODAL = 0x2000,
    }
    public enum MB_SPECIAL : uint
    {
        MB_SETFOREGROUND = 0x10000,
        MB_DEFAULT_DESKTOP_ONLY = 0x20000,
        MB_SERVICE_NOTIFICATION_NT3X = 0x40000,
        MB_TOPMOST = 0x40000,
        MB_RIGHT = 0x80000,
        MB_RTLREADING = 0x100000,
        MB_SERVICE_NOTIFICATION = 0x200000,
    }
    public enum MB_RETURN : uint
    {
        IDOK = 1,
        IDCANCEL = 2,
        IDABORT = 3,
        IDRETRY = 4,
        IDIGNORE = 5,
        IDYES = 6,
        IDNO = 7,
        IDCLOSE = 8,
        IDHELP = 9,
        IDTRYAGAIN = 10,
        IDCONTINUE = 11,
    }

    static void Main()
    {
        UInt32 result = MessageBoxEx(IntPtr.Zero, // NULL
            "Do you want to save changes before closing?",
            "MyApplication",
            (UInt32)MB_BUTTON.MB_YESNOCANCEL |
            (UInt32)MB_ICON.MB_ICONQUESTION |
            (UInt32)MB_DEF_BUTTON.MB_DEFBUTTON3 |
            (UInt32)MB_SPECIAL.MB_TOPMOST,
            0);// Reserved

        if (result == 0) // error occurred
            Console.WriteLine("ERROR");
        else
        {
            MB_RETURN ret = (MB_RETURN)result;

            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.

Figure 2.4 Message Box Example Result
Figure 2.4 Message Box Example Result

In addition, you can marshal an argument as an enumeration which of the argument type of course. The following example demonstrates this:

Listing 2.13 Console Standard Devices Example

    [DllImport("Kernel32.dll")]
    static extern IntPtr GetStdHandle(
        [param: MarshalAs(UnmanagedType.U4)]
        CONSOLE_STD_HANDLE nStdHandle);

    public enum CONSOLE_STD_HANDLE
    {
        STD_INPUT_HANDLE = -10,
        STD_OUTPUT_HANDLE = -11,
        STD_ERROR_HANDLE = -12
    }

    static void Main()
    {
        IntPtr handle;
        handle =
            GetStdHandle(CONSOLE_STD_HANDLE.STD_INPUT_HANDLE);
        if (handle == IntPtr.Zero)
            Console.WriteLine("Failed!");
        else
            Console.WriteLine("Succeeded!");
    }

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:

Listing 2.14 Message Box Example Revised

    public static MB_RETURN MessageBox
        (IntPtr handle, string text, string title,
        MB_BUTTON buttons, MB_ICON icon, MB_DEF_BUTTON defaultButton,
        MB_MODAL modality, MB_SPECIAL options)
    {
        UInt32 result = MessageBoxEx(handle,
            "Do you want to save changes before closing?",
            "MyApplication",
            (UInt32)buttons |
            (UInt32)icon |
            (UInt32)defaultButton |
            (UInt32)modality |
            (UInt32)options,
            0);

        if (result == 0)
            // Not recommended throwing System.Exception
            // throw a derived exception instead
            throw new Exception("FAILED");
        return (MB_RETURN)result;
    }

In addition, it is recommended changing the type of enumerations to any CLS-compliant type like System.Int32. Check the last technique in this section.

Working with Nullable Arguments

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:

Code abbreviated for clarity.

Listing 2.15 ScrollConsoleScreenBuffer() Managed Signature

    [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool ScrollConsoleScreenBuffer(
        IntPtr hConsoleOutput,
        SMALL_RECT lpScrollRectangle,
        SMALL_RECT lpClipRectangle,
        COORD dwDestinationOrigin,
        CHAR_INFO lpFill);

    [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool ScrollConsoleScreenBuffer(
        IntPtr hConsoleOutput,
        SMALL_RECT lpScrollRectangle,
        IntPtr lpClipRectangle,
        COORD dwDestinationOrigin,
        CHAR_INFO lpFill);
    ...

Working Out the CLS Problem

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.

It is very helpful consulting the documentation about System.CLSCompliantAttribute attribute.

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.

Programmatically Swapping Mouse Buttons

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.

Listing 2.16 Swapping Mouse Buttons Sample

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SwapMouseButton
    ([param: MarshalAs(UnmanagedType.Bool)] bool fSwap);

public void MakeRightButtonPrimary()
{
    SwapMouseButton(true);
}

public void MakeLeftButtonPrimary()
{
    SwapMouseButton(false);
}

Programmatically Turning On the Screen Saver

The following code shows how to turn on the screen saver programmatically.

Listing 2.19 Dragging a Form without a Title Bar Sample

[DllImport("User32.dll")]
public static extern int SendMessage
    (IntPtr hWnd,
    uint Msg,
    uint wParam,
    uint lParam);

public const uint WM_SYSCOMMAND = 0x112;
public const uint SC_SCREENSAVE = 0xF140;

public enum SpecialHandles
{
    HWND_DESKTOP = 0x0,
    HWND_BROADCAST = 0xFFFF
}

public static void TurnOnScreenSaver()
{
    SendMessage(
        new IntPtr((int)SpecialHandles.HWND_BROADCAST),
        WM_SYSCOMMAND,
        SC_SCREENSAVE,
        0);
}

Dragging a Form without a Title Bar

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.

Listing 2.18 Dragging a Form without a Title Bar Sample

SafeNativeMethods.cs

internal static class SafeNativeMethods
{
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.I4)]
    public static extern int SendMessage(
        IntPtr hWnd,
        [param: MarshalAs(UnmanagedType.U4)]
        uint Msg,
        [param: MarshalAs(UnmanagedType.U4)]
        uint wParam,
        [param: MarshalAs(UnmanagedType.I4)]
        int lParam);

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool ReleaseCapture();

    public const uint WM_NCLBUTTONDOWN = 0xA1; // 161
    public const uint HTCAPTION = 2;
}

HelperMethods.cs

internal static class HelperMethods
{
    public static void MoveObject(IntPtr hWnd)
    {
        SafeNativeMethods.ReleaseCapture();
        SafeNativeMethods.SendMessage
            (hWnd, SafeNativeMethods.WM_NCLBUTTONDOWN,
            SafeNativeMethods.HTCAPTION, 0);
    }
}

MainForm.cs

// In the form, write the following code
// in the handler of the MouseDown event

private void MainForm_MouseDown(object sender, MouseEventArgs e)
{
    HelperMethods.MoveObject(this.Handle);
}

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.

Next, you will learn how to work with compound types and marshal them in your managed environment.