Building Applications that Can Talk

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

Overview

Stephen Hawking is one of the most famous people using speech synthesis to communicate
Stephen Hawking is one of the most famous people using speech synthesis to communicate

In this article we are going to explore the Speech API library that’s part of the TTS SDK that helps you reading text and speaking it. We’re going to see how to do it programmatically using C# and VB.NET and how to make use of LINQ to make it more interesting. The last part of this article talks about…… won’t tell you more, let’s see!

Introduction

The Speech API library that we are going to use today is represented by the file sapi.dll which’s located in %windir%System32SpeechCommon. This library is not part of the .NET BCL and it’s not even a .NET library, so we’ll use Interoperability to communicate with it (don’t worry, using Visual Studio it’s just a matter of adding a reference to the application.)

Implementation

In this example, we are going to create a Console application that reads text from the user and speaks it. To complete this example, follow these steps:

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

    Figure 1 - Adding Reference to SpeechLib Library
    Figure 1 - Adding Reference to SpeechLib Library
  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.

Building Talking Strings

Next, we’ll make small modifications to the code above to provide an easy way to speak a given System.String. We’ll make use of the Extension Methods feature of LINQ to add the Speak() method created earlier to the System.String. Try the following code:

// C#

using SpeechLib;

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

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

Imports SpeechLib
Imports System.Runtime.CompilerServices

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

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

I Love YOU ♥

Let’s make it more interesting. We are going to code a VBScript file that says “I Love YOU” when you call it. To complete this example, these steps:

  1. Open Notepad.
  2. Write the following code:
    CreateObject("SAPI.SpVoice").Speak "I love YOU!"

    Of course, CreateObject() is used to create a new instance of an object resides in a given library. SAPI is the name of the Speech API library stored in Windows Registry. SpVoice is the class name.

  3. Save the file as ‘love.vbs’ (you can use any name you like, just preserve the vbs extension.)
  4. Now open the file and listen, who is telling that he loves you!

Microsoft Speech API has many voices; two of them are Microsoft Sam (male), the default for Windows XP and Windows 2000, and Microsoft Ann (female), the default for Windows Vista and Windows 7. Read more about Microsoft TTS voices here.

Thanks to our friend, Mohamed Gafar, for providing the VBScript.

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

Microsoft Agent; Providing a Custom Popup Menu

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

A second honeymoon with Microsoft Agent. Do you remember our article Programming Microsoft Agent in WinForms and our sample application PartIt? After you have included your desired agent in your application, are you feeling bad with the default popup menu? If so, then you are in the right place (welcome :).)

Enough talking, let’s get to work! First, prepare your code that loads the Microsoft Agent and brings it to the screen.

After that, create your System.Windows.Forms.ContextMenuStrip and add your required items (well, including ‘Hide’ maybe) and finish the item event handlers.

Now, let’s complete it. Get to the code that loads the agent character (e.g. calls the Characters.Load() method of the agent control object, AxAgentObjects.AxAgent) and just disable the AutoPopupMenu flag/property of the character object, AgentObjects.IAgentCtlCharacterEx. This flag/property determines whether to allow the default popup menu or not.

For example, the following code disables this property:

    AxAgentObjects.AxAgent agentCtl;
    AgentObjects.IAgentCtlCharacterEx agentChar;

    // initializing 'agentCtl'
    // . . .

    agentCtl.Characters.Load("Merlin", "merlin.acs");
    agentChar = agentCtl.Characters.Character("Merlin");
    agentChar.AutoPopupMenu = false;

Next comes the interesting point. When the character is clicked, the ClickEvent event of the agent control (AxAgent) fires. So the next step is to handle this event and to provide your code that brings up your custom context menu. Consider the following code:

// agentCtl.ClickEvent += agent_ClickEvent;

public void agentCtl_ClickEvent(object sender, AxAgentObjects._AgentEvents_ClickEvent e)
{
    if (e.characterID == "Merlin")  // check for this if you have many characters
    {
        if (e.button == 2) // 1 = left, 2 = right
        {
            myContextMenu.Show(e.x, e.y);
        }
    }
}

Well done!

Have a nice Sunday!

Hard Links vs. Soft Links

Contents

Contents of this writing:

  • Contents
  • Overview
  • Introducing Hard Links
  • Introducing Soft Links
  • Creating a Hard Link
  • Creating a Soft Link

Overview

This writing talks about hard links and soft links; two of the nice features of NTFS file system.

You can divide file links into two categories:

  • Normal Links (shortcuts)
  • Symbolic Links:
    This divided into two subcategories:

    • Hard Links (for files)
    • Soft Links (for directories)

Introducing Hard Links

A hard link is a way to represent a single file (i.e. data volume) by more than one path. In other words, it is a way to create multiple directory entries for a single file.

Consider the following example, we could have two files, e.g. D:DocumentsBills.doc and D:FilesCompanyInvoices.doc, both files represent the same data, so when you change one file the other changes too!

A hard link is almost like a normal link (i.e. shortcut) but there is a big difference. If you delete the source file of a shortcut, the shortcut would be broken (points to a non-existing file.) A hard link on the other hand, would be still working fine if you delete the source file as if it was just a copy.

Notice that, hard disk space is not multiplied for each hard link. Because they are share the same data volume, they share the same size.

There is something that you should be aware of. File attributes are not updated for each of the hard links. This means that if you set a hard link (e.g. D:FilesCompanyInvoices.doc) to read-only, it is the only link that is set to this attribute. Other hard links including the source would be still read-write (unless you set them too.)

Hard links are only supported on NTFS file system; they are not supported by Win9x versions of Windows. Besides this, hard links creation is limited to the same local disk (or network drive) of the source file. For example, you cannot create a hard link on the C: drive for a file on the drive D: or on a network share.

Introducing Soft Links

Soft links (also called junctions,) are identical to hard links except that soft links are designated for directories not files.

For example, if you have one or more soft links to the directory D:Tools (e.g. D:Software and D:ProgramsTools,) all directories (including the source) would be updated when any one of them changes. That is, if you created a new file in one directory, it is created in all directory links, and so on.

Like hard links, soft links require NTFS file system. Plus, deleting the source directory does not affect other directories, all links share the same data volume, changing attributes of one directory does not be applied to others.

Unlike hard links, creating soft links is a special feature of Windows Vista and Windows Server 2008 (and future versions of course.) Therefore, it cannot be applied to earlier versions of Windows (like Windows XP or Windows Server 2003.)

Creating a Hard Link

Unfortunately, neither hard links nor soft links are supported by the .NET Framework. Therefore, you will need to dig into the Windows API to allow your application to consume this feature.

You can create a hard link using a single line of code using a simple call to the Win32 function, CreateHardLink(), that resides in the Kernel32.dll library. The definition of this function is as follows:

BOOL CreateHardLink(
    LPCTSTR lpFileName,
    LPCTSTR lpExistingFileName,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes
    );

This function accepts three arguments;

  • lpFileName:
    The path of the new hard link to be created. Notice that, it should be resides in the location (local disk or network share) as the source file.
  • lpExistingFileName:
    The path of the source file to create the hard link from.
  • lpSecurityAttributes:
    The security descriptor for the new file. Till now, it is reserved for future use and it should not be used.

This function returns TRUE if succeeded or FALSE otherwise.

Keeping the definition of the function in our minds, we can create its PInvoke method in C#. Simply, this is our C# PInvoke method:

[DllImport("Kernel32.dll", CharSet = CharSet.Unicode )]
static extern bool CreateHardLink(
    string lpFileName,
    string lpExistingFileName,
    IntPtr lpSecurityAttributes
    );

As you know, we have set the character encoding to Unicode because Windows NT allows Unicode file names. In addition, we have marshaled lpSecurityAttributes as System.IntPtr to allow passing it a null value using System.IntPtr.Zero.

Then, you simply call the function:

Change the source file to meet an existing file into your system. In addition, check that the directory of the new link exists or the call would fail.

Take into consideration that the function creates the new file for you, it should not be found or the function would fail (as the case with soft links.)

CreateHardLink(
    "D:\Files\Company\Invoices.doc",	// New Link
    "D:\Documents\Bills.doc",			// Source
    IntPtr.Zero);

And this is another link:

CreateHardLink(
    "D:\Important\Work Invoices.doc",			// New Link
    "D:\Files\Company\Invoices.doc",			// Source
    IntPtr.Zero);

As you see, it does not matter if you created the new link from the original file or from another link.

Now try to change any of the links, and check the others. They are all updated at the time you commit the change (save the file, for instance.)

Creating a Soft Link

Like hard links, you can create a soft link (a junction) using a single call to a Win32 API function; it is CreateSymbolicLink() function. Again, this function is only supported by Windows Vista and Windows Server 2008 (and future versions.)

Actually, the function CreateSymbolicLink() can be used to create both types of symbolic links, hard and soft links. The definition of this function is as follows:

BOOLEAN WINAPI CreateSymbolicLink(
    LPTSTR lpSymlinkFileName,
    LPTSTR lpTargetFileName,
    DWORD dwFlags
    );

This function accepts three arguments, the first and the second are identical to the CreateHardLink() function:

  • lpSymlinkFileName:
    The path of the new link (file or directory) that resides in the same drive (local disk or network share) of the target file.
  • lpTargetFileName:
    The path of the existing source (file or directory.)
  • dwFlags:
    The type of symbolic link. It can take the following values:

    • 0x0:
      The link is a hard link (source and destination are files.)
    • 0x1 = SYMBOLIC_LINK_FLAG_DIRECTORY:
      The link is a soft link (source and destination are directories.)

The following is the PInvoke method for our function:

[DllImport("Kernel32.dll", CharSet = CharSet.Unicode)]
static extern bool CreateSymbolicLink(
    string lpSymlinkFileName,
    string lpTargetFileName,
    uint dwFlags
    );

const uint SYMBLOC_LINK_FLAG_FILE		= 0x0;
const uint SYMBLOC_LINK_FLAG_DIRECTORY 	= 0x1;

The following code creates two soft links for one directory:

Again, the source should be the only directory that exists in your machine or the function would fail.

CreateSymbolicLink(
    "D:\Software",         // New Link
    "D:\Tools",            // Source
    SYMBOLIC_LINK_FLAG_DIRECTORY);

// Source
CreateSymbolicLink(
    "D:\Programs\Tools",  // New Link
    "D:\Software",         // Source
    SYMBOLIC_LINK_FLAG_DIRECTORY);

Now try changing one directory, other links change too. Try deleting the source, nothing would be happen to the other links.

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.