Short View of Data

Microsoft wrote many of the DLLs found in Windows using C, not C++ but straight C. Some of the DLLs use C++ and a very few use other languages (and we're talking a very few). This means that you'll have to work with C libraries to use the Win32 API in most cases. Unlike the unmanaged environment found in Visual Studio 6, Visual Studio .NET provides little in the way of wrappers that you can simply use to access the Win32 API without the frustration of working with C.

Working with C library files means converting data from the managed environment into a form that the library functions will understand. Of course, neither Visual Basic nor C# provides support for an HRESULT or a LPTSTR, which are the standard fare of C library routines. This means that you need to know the underlying data type for the C library types that you'll encounter. For example, you'll find that an HRESULT converts quite easily to a System.Int32 value. The problem is that you won't know this at first because none of the documentation provided with Visual Studio tells you about conversions between managed and unmanaged types—an issue we'll discuss throughout the book, but especially in this chapter.

In some cases, you can't directly convert an unmanaged type to a managed type. This is always true for structures, but you'll also run into the problem with some variable types. When this problem occurs, the .NET Framework generally provides some way to marshal the data using the [MarshalAs] attribute. You'll find this attribute in the System.Runtime.Interop-Services namespace. Listing 2.1 shows an example of how to use the [MarshalAs] attribute, along with a few new Win32 API techniques we haven't yet discussed. You'll find the source code for this example in the \Chapter 02\C#\ShowMessage and \Chapter 02\VB\ShowMessage folders of the CD.

Note The example code in Listing 2.1 shows all of the potential inputs for MessageBoxEx().

However, not all of the inputs are available in every version of Windows. In fact, many of the unique features are only available in Windows 2000 and Windows XP. Make sure you check the Platform SDK documentation for potential problems when using these features in other versions of Windows. The example was tested under both Windows 2000 and Windows XP—it doesn't work under most versions of Windows 9x.

Chapter 2: Working with Win32 API Data Listing 2.1: MessageBoxEx() Example using the [MarshalAs] Attribute

// MessageBoxEx() provides features, including a language identifier, // not found in the .NET Framework version. This function also enables // you to add special buttons and other features to the message box. [DllImport("user32.dll", CharSet=CharSet.Auto)] public static extern int MessageBoxEx( IntPtr hWnd,

[MarshalAs(UnmanagedType.LPTStr)]String Message, [MarshalAs(UnmanagedType.LPTStr)]String Header, UInt32 Type, UInt16 LanguagelD);

// Create a list of buttons.

public class MBButton {

public

const

UInt32

MB_

OK =

0x00000000

public

const

UInt32

MB_

OKCANCEL =

0x00000001

public

const

UInt32

MB_

ABORTRETRYIGNORE =

0x00000002

public

const

UInt32

MB_

YESNOCANCEL =

0x00000003

public

const

UInt32

MB_

YESNO =

0x00000004

public

const

UInt32

MB_

RETRYCANCEL =

0x00000005

public

const

UInt32

MB_

CANCELTRYCONTINUE =

0x00000006

public

const

UInt32

MB_

HELP =

0x00004000

publi publi publi publi publi publi publi publi publi

e a list of icon types, lass MBIcon c const UInt32 MB_ICONHAND =

c const UInt32 MB_ICONQUESTION =

c const UInt32 MB_ICONEXCLAMATION

c const UInt32 MB_ICONASTERISK =

c const UInt32 MB_USERICON =

c const UInt32 MB_ICONWARNING =

c const UInt32 MB_ICONERROR =

c const UInt32 MB_ICONINFORMATION

c const UInt32 MB ICONSTOP =

0x00000010 0x00000020 0x00000030 0x00000040 0x00000080; MB_ICONEXCLAMATION; MB_ICONHAND; MB_ICONASTERISK; MB ICONHAND;

// Create a list of default buttons.

public class MBDefButton {

public const UInt32 MB_DEFBUTTON1 public const UInt32 MB_DEFBUTTON2 public const UInt32 MB_DEFBUTTON3 public const UInt32 MB_DEFBUTTON4

0x00000000 0x00000100 0x00000200 0x00000300

// Create a list of message box modalities public class MBModal {

public const UInt32 MB_APPLMODAL = public const UInt32 MB_SYSTEMMODAL public const UInt32 MB_TASKMODAL =

0x00000000 0x00001000 0x00002000

// Create a list of special message box attributes.

public class MBSpecial {

public const UInt32 MB_SETFOREGROUND =

0x00010000;

public

const

UInt32

MB

DEFAULT DESKTOP ONLY =

0x00020000

public

const

UInt32

MB

SERVICE NOTIFICATION NT3X =

0x00040000

public

const

UInt32

MB

TOPMOST =

0x00040000

public

const

UInt32

MB

RIGHT =

0x00080000

public

const

UInt32

MB

RTLREADING =

0x00100000

public

const

UInt32

MB

SERVICE NOTIFICATION =

0x00200000

// Return values can use an enum in place of a class.

public enum MBReturn {

IDOK =

1,

IDCANCEL =

2,

IDABORT =

3,

IDRETRY =

4,

IDIGNORE =

5,

IDYES =

6,

IDNO =

7,

IDCLOSE =

8,

IDHELP =

9,

IDTRYAGAIN =

10,

IDCONTINUE =

11,

IDTIMEOUT =

32000

private void btnTest_Click(object sender, System.EventArgs e) {

MBReturn Result; // Result of user input. // Display a message box.

Result = (MBReturn)MessageBoxEx(this.Handle, "This is a message box.", "Test Message Box",

MBButton.MB_CANCELTRYCONTINUE | MBButton.MB_HELP | MBIcon.MB_ICONEXCLAMATION | MBModal.MB_SYSTEMMODAL | MBDefButton.MB_DEFBUTTON4 | MBSpecial.MB_TOPMOST, 0);

// Determine a result.

switch (Result) {

case MBReturn.IDCANCEL:

MessageBox.Show("Returned Cancel"); break;

case MBReturn.IDTRYAGAIN:

MessageBox.Show("Returned Try Again"); break;

case MBReturn.IDCONTINUE:

MessageBox.Show("Returned Continue"); break; default:

MessageBox.Show("Couldn't Determine Return Value"); break;

private void frmMain_HelpRequested(object sender, System.Windows.Forms.HelpEventArgs hlpevent)

// Display information about the help request.

MessageBox.Show("The user requested help:\r\n" + "\r\nSender: " + sender.ToString() + "\r\nMouse Position: " + hlpevent.MousePos, "Help Requested", MessageBoxButtons.OK, MessageBoxIcon.Information);

// Tell Windows that the help request was handled.

hlpevent.Handled = true;

Yes, this is a lot of code to display a simple message box, but the MessageBoxEx() function provides a lot of functionality that you won't find in the MessageBox.Show() function. Like MessageBox.Show(), you can associate a MessageBoxEx() message box with the current window. In fact, you have to provide the association to make the special features such as the Help button work correctly. If you want a working Help button, you also need to include a HelpRequested() event handler for the main form—see the frmMain_HelpRequested() method in Listing 2.1 for details.

Tip One of the problems you'll notice with the information provided to the frmMain_Help-Requested() method is that C# doesn't tell you who actually called the help routine. The best way to handle this problem is to set a property or field prior to the MessageBoxEx() call, and then check that value within the frmMain_HelpRequested() method. This technique helps you determine the true source of a help request, making context-sensitive help easier to provide.

The main focus of this section is the use of the [MarshalAs] attribute in the MessageBoxEx() declaration. Notice that we need to use this attribute for both string inputs. You might see some odd output without the attribute (or the call might simply fail). As previously mentioned, you need to use an IntPtr for handles. The Type variable can include a number of inputs as shown in the btnTest_Click() method. You use it for the buttons, icons, and special features. One special feature affects the modality of the resulting message box. We'll discuss the various enumerations in the "Working with Enumerations" section of the chapter. The LanguagelD variable doesn't appear to have any use within the current implementation of the MessageBoxEx() function—at least not according to the documentation. Given the amount of work Microsoft is doing with language specific features, you should expect to see this variable implemented sometime in the future.

The btnTest_Click() shows off a few of the unique features of the MessageBoxEx() function. Figure 2.1 shows the output of this code. Notice that the message box has four buttons and that we selected the Continue button as default. The first three buttons appear because of the MBButton.MB_CANCELTRYCONTINUE enumeration member, while the help button appears because of the MBButton.MB_HELP enumeration member.

Figure 2.1: The MessageBoxEx() function provides features you won't find in MessageBox.Show().

One of the special features of this message box is the result of the MBSpecial.MB_TOPMOST enumeration member. No matter what you do, this message box will remain on top—you can't hide it. The message box opens with the Help button selected due to the inclusion of the MBDefButton.MB_DEFBUTTON4 enumeration member. In addition, notice the System menu icon in the upper left corner of the message box. This icon is the result of the MBModal_.MB_SYSTEMMODAL enumeration member. As you can see in Figure 2.2, you have access to the normal System menu functions within this message box.

■ Test Message Box

X Close Alt+F4

ge box.

Cancel j Try Again Continue Help

Figure 2.2: The MessageBoxEx() function enables you to add a System menu to your message box.

The btnTest_Click() method checks the return value of the test message box. Notice that you can check for those special buttons. Replacing the Cancel, Try Again, and Help buttons with Abort, Retry, and Fail resulted in a "Couldn't Determine Return Value" return value. The return values are truly unique. Let's get back to the [MarshalAs] attribute. The [MarshalAs] attribute tells CLR how to interact with a variable. For example, you can tell CLR that you want to use a String variable as a substitute for a LPSTR, LPWSTR, LPTSTR, or BSTR variable by specifying the correct UnmanagedType enumeration value. You can also include arguments for variable type, array and safearray size, array and safearray subtype, cookies, and a custom marshaler.

Using a custom marshaler means that you can theoretically transform any managed type into an unmanaged equivalent—in practice this task is exceptionally difficult. Not only do you have the normal concerns in writing a marshaler, but you also have to consider the transition from the managed to unmanaged environment (and back in some cases). Fortunately, the need to write a custom marshaler is rare.

One final word of caution when working with the marshaler—don't count on all languages to implement it the same way. The marshaler tends to react differently based on language because each language has different native data types. For example, accessing the MessageBoxEx() function requires additional work in Visual Basic because of language differences. Here's the Visual Basic declaration of the same example.

<DllImport("user32.dll", _

EntryPoint:="MessageBoxExW", _ CharSet:=CharSet.Auto)> _ Public Shared Function MessageBoxEx( _ ByVal hWnd As IntPtr, _

<MarshalAs(UnmanagedType.LPTStr)> ByVal Message As String, _ <MarshalAs(UnmanagedType.LPTStr)> ByVal Header As String, _ <MarshalAs(UnmanagedType.U4)> ByVal Type As Integer, _ <MarshalAs(UnmanagedType.U4)> ByVal LanguageID As Integer) _ As Integer End Function

Notice that in the Visual Basic version of the declaration, you must include a specific entry point or the message text will fail to print properly (you'll see just the first letter). The <MarshalAs> attribute now appears for all input parameters except the window handle, because we have to define the input arguments as type Integer. Unlike the examples in Chapter 1, this function must be declared as Shared—simply declaring it public won't work. The call to the MessageBoxEx()function will fail with an ambiguous error. In short, Visual Basic tends to require more precise marshaling of variables than C# does.

Unmanaged Resources and the Garbage Collector

There are a number of problems that developers will face when working with Win32 API data in a managed environment—not the least of which is the Garbage Collector. It's essential to remember that the Garbage Collector is designed to work with managed data in a managed environment that the Garbage Collector can monitor. This statement points out two potential problems when working with the Win32 API.

The first problem occurs when a developer creates unmanaged data. For example, you might need to create a pointer to an interface in a COM object or create a handle to an icon that a Win32 API function can use. The Garbage Collector doesn't know about this resource, so it can't automatically release the resource when it goes out of scope. In short, you need to release the resource before the application terminates. Generally, you'll create the resource using a Win32 API function so you'll also free the resource using a Win32 API function.

The second problem occurs when you create a managed resource that you pass to a Win32 API function. The Garbage Collector will collect any resource without a reference, and it doesn't recognize the Win32 API function's use of the resource. Consequently, the Garbage Collector could release the resource before the Win32 API function finishes using it. To prevent this problem, you'll normally need to create a managed reference to the resource at the same scope level as the Win32 API function use of the resource. After you release the resource used by the Win32 API function, you can also release the managed reference to it.

The Garbage Collector can also cause other odd problems with your Win32 API calls. The big problem is that you can't view these problems in the debugger, in many cases, because the debugger creates a reference to the variables and modifies the behavior of the application. In short, troubleshooting an application can become difficult once the Garbage Collector is involved because the problems appear as "ghosts" that you'll have a hard time tracking.

Was this article helpful?

0 0

Post a comment