• No results found

Accessing the Win32 API

In document Sybex NET Framework Solutions pdf (Page 50-78)

Part I: An Overview of the Win32 API

Chapter 3: Accessing the Win32 API

So far we’ve discussed the perimeter of Win32 API development. You’ve learned about some of the functionality that the Win32 API can provide, and we’ve considered various aspects of data manipulation. However, we haven’t really discussed access techniques for the Win32 API. That’s what you’ll learn in this chapter.

There are four topics of interest for developers in this chapter. First, you need to know where to find the Win32 API calls because they don’t all reside in the same DLL and some don’t reside properly in DLLs at all—they appear as part of C LIB files. Second, you need to know what you’re giving up by using the Win32 API calls. We’ve already talked about a few of these issues in previous chapters. Third, you need to know which tools are available to help you locate and observe the effects of Win32 API calls. Finally, you need to know how to obtain error information when working with Win32 API calls and how to interpret the error codes.

Consider this chapter your doorway to the technology−specific chapters that begin with Chapter 6. This chapter contains the generic techniques that we’ll use in later chapters to answer specific technology

needs—the holes left in the .NET Framework’s coverage of the Win32 API. When you complete this chapter, you’ll have the basic skills for locating functions, analyzing how they work, and calling them from your managed application. However, anyone who’s worked with the Win32 API directly in the past knows that it’s anything but consistent, which is why these technology−specific chapters are so important. This chapter tells you the basic rules—the remaining chapters tell you how Microsoft broke them. They’ll also show you techniques for getting around some of the anomalies in the Win32 API when viewed from the managed environment.

An Overview of the DLLs

The Windows operating system is composed of more than a few DLLs. If you look in the System32 folder of any Windows installation, you’ll see a wealth of DLLs, many with strange−looking names. Most of these DLLs perform special tasks, and you won’t need to worry about them unless you need to perform that special task in your application. For example, my System32 folder contains a VJOY.DLL file that I’d only need to use when working with a joystick—something I’m probably not going to do any time soon. Some of the DLLs are also device specific, so you don’t need to do anything with them unless you want to work with that device in a very specific way (which usually isn’t a good idea).

This excess of DLLs leaves the question of which DLLs you need to consider open to interpretation. There are some DLLs that you’ll never use simply because you don’t write applications that require their services or the services they provide are found somewhere in the .NET Framework. It’s important to know which DLLs to look for in your search of a specific function.

Note The help file provided with Visual Studio .NET lacks some of the documentation you’ll need to understand the Win32 API. Unfortunately, this means you’ll need to download a copy of the Platform SDK to gain access to the required help files. Fortunately, Visual Studio .NET does include a complete set of C header files and all of the tools you need to work with the Win32 API. You can obtain a copy of the latest Platform SDK at

can also obtain the required information from the help files that are provided with an MSDN subscription.

There are three main DLLs you’ll need to use for general functions: USER32.DLL, KERNEL32.DLL, and GDI32.DLL. In general, USER32.DLL contains user−specific functions such as message boxes. You’ll find low−level functions such as those used for memory allocation and thread management in KERNEL32.DLL. Most graphics functions appear in GDI32.DLL. Unfortunately, Microsoft didn’t strictly adhere to these boundaries. For example, you’ll find the Beep() function in KERNEL32.DLL, not USER32.DLL as you might expect. Because the Platform SDK documentation is written with C/C++ developers in mind, it doesn’t always list the DLL where you can find a particular function—making the search akin to an egg hunt.

Some DLLs fall into the common category, but the .NET Framework already provides good coverage of the functionality they provide. For example, the COMCTL32.DLL and COMDLG32.DLL files contain functions that developers use frequently, but most of these functions have .NET Framework equivalents. The question for most developers will be whether the .NET Framework equivalents are robust enough to meet application development needs. As shown in the MessageBoxEx() example in Chapter 2, Microsoft tends to leave out special features in .NET Framework equivalents. For example, the MessageBox.Show() function doesn’t include the Help button. Likewise, you might find some special feature COMCTL32.DLL and

COMDLG32.DLL files that the .NET Framework doesn’t implement.

Many of the DLLs you’ll use fall into the esoteric category. For example, you’ll find the BATMETER.DLL and POWRPROF.DLL files helpful when writing power management code. While the .NET Framework provides access to common needs such as power events, you might find some of the DLL functions useful for power monitoring needs. Of course, most applications that do provide power management support do so by monitoring the events and leaving the grunt work to the operating system, so these functions, while useful, are also esoteric.

We’ll discuss many other DLLs as the book progresses. The purpose of this section is to help you understand where these Win32 API functions are coming from—they don’t appear out of the air as some developers might suspect. Even if you restrict your programming efforts to the functions found in the three main DLLs, you’ll find that you can patch quite a few of the obvious support holes in the .NET Framework.

Types of Win32 Access

There are two ways to access the Win32 API functions. All of the examples we’ve looked at so far in the book use a single type of access, the direct DLL approach. In most cases, you’ll want to use this approach because it’s the simplest method to use. However, in other situations, you’ll need to use the C LIB file approach due to a lack of documentation of other factors. Sometimes even C# can’t bridge the gap between the managed and unmanaged environments, making Visual C++ the language of choice. The following sections describe these two methods of Win32 API access in more detail.

Tip Working with the Win32 API often means you’ll need to access low−level details about your application such as the window handle or the device context. Visual Basic hides this information by default. To see low−level details about your application within the development environment, use the Tools Ø Options command to display the Options dialog box. Select the Text Editor\Basic folder. Clear the Hide Advanced Members option and click OK. You’ll now see features such as Me.Handle (the handle for the current window). The C# text editor also has the Hide Advanced Members option, but it’s usually cleared by default.

Direct DLL Access

As previously mentioned, you’ll generally want to use direct DLL access when using Win32 API functions. This technique enjoys the greatest level of support from the .NET Framework. For example, you can use the [DllImport] attribute to gain access to the required function. We haven’t looked at all of the features of the [DllImport] attribute yet, so you’ll gain a better appreciation of just how valuable this attribute is as the book progresses. We’ve also looked at other attributes, such as the [StructLayout] attribute, that helps make DLL access easy.

Of course, the use of DLL access assumes that you know which DLL to access and have documentation about function arguments. The arguments could be anything from pointers to data structures. Learning the names of functions within a DLL isn’t hard (we’ll see how this works in the “Dependency Walker” section of the chapter), but learning the details can prove frustrating.

There’s a hidden problem with the DLL access method. Every time your application makes a transition from the managed to unmanaged environment, CLR has to marshal the variables in the background (this is in addition to any marshaling you perform manually within the application). Consequently, there’s a

performance hit your application will experience when using the Win32 API. Sometimes it’s more efficient to use a wrapper DLL or a function substitute, rather than incur the performance penalty.

Direct DLL access can present other problems as well. For example, some of the structures used to access Win32 API functions include unions, odd variable types, and other data translation problems. Because C# and Visual Basic don’t understand these concepts, you’ll end up ripping your hair out trying to replicate the data structure. In these cases, it’s often easier to bypass the data translation problem by using a C/C++ wrapper DLL. Since managed Visual C++ .NET understands the unmanaged environment completely, you’ll

experience less frustration in the data translation process. Be warned, though, that Visual C++ presents other challenges such as a more complex programming environment.

Even if you can replicate a data structure, it often bears little resemblance to the original. For example, consider the following unmanaged data structure.

struct MyStruct {

int data[16]; }

This looks like an easy structure to replicate, and in some ways it is. However, the resulting data structure doesn’t look like the original and could cause problems for other developers trying to learn about your code. Here’s the C# equivalent of the data structure in question.

[StructLayout(LayoutKind.Sequential)] public struct MyStruct

{

[MarshalAs(UnmanagedType.ByValArray, SizeConst=64)] public int[] myField;

}

While the two data structures are equivalent, the C# version requires two attributes to accomplish the same task that the Visual C++ version does without any attributes at all. In short, the C# version is actually more complicated. When you consider that this structure is actually very simple, it’s not too hard to image how some of the complex data structures will appear within managed code. The realities of developing Win32 API code in a managed environment include added complexity because the managed environment makes

assumptions that the Win32 API environment doesn’t make.

A final direct DLL access concern is the problem of error handling. You must also import and use the rather strange error−handling functions employed by the Win32 API if you want to provide error feedback to the user. While you still have to figure out which method of error handling to use with working with Visual C++, the actual process of retrieving the error information is easier. Fortunately, error handling isn’t so difficult that it prevents you from using the direct DLL method.

C LIB Access

For some developers, the concept of DLL versus LIB function access might prove confusing at first, but the differences between the two methods are distinct and easy to understand. A C/C++ library is a set of precompiled routines that are only accessible from a C/C++ environment. As such, the files have a LIB extension and usually reside in a separate LIB folder on the hard drive.

When you view the documentation for the Win32 API and see a reference for a LIB rather than a DLL file, you’re seeing Microsoft’s choice of C/C++ as the base language for Windows. The presence of a LIB file reference in the documentation doesn’t necessarily mean there’s no access from a DLL, but you’ll have to do some research to find the name of the associated DLL (when there’s a single DLL that implements the required function). In some cases, the answer to the question of which DLL to use is quite simple. For example, the MessageBoxEx() function we used in Chapter 2 relies on User32.LIB in the documentation and User32.DLL in the example.

The following sections discuss two forms of C library access. You’ll find examples of these two techniques in the “A C LIB Wrappers Access Example” and “A C LIB Substitute Functions Example” sections of the chapter. You’ll rely on both forms of C library access from time to time. Of the two, the wrapper technique is the most common, so we discuss it first. The substitute technique is actually better when you can implement it without loss of functionality.

Using Wrappers

The most common use of a wrapper is when a managed application can’t fully duplicate the inputs required by a C library routine or when the output from such a routine contains elements the managed application can’t understand. A common example of this problem is when a data structure contains unions or other elements that are difficult to recreate in the managed environment. In some cases, it’s possible to create the data structure but not locate the required call within a DLL. If a function exists only within a C library, then you must use a wrapper to access it.

It’s important to use a wrapper DLL with care for several reasons. The most important reason is that you’re adding another layer to the calling mechanism, which increases the probability of error and increases the complexity of debugging the resulting application. Another good reason to avoid using wrapper DLLs is the complexity of using multiple languages within the application. Low−level programming with Visual C++ requires a good understanding of both C++ and the Win32 API. Many developers will want to use C# or Visual Basic as their main programming language—using Visual C++ to create a wrapper DLL might require more time than a project allows.

A few developers have also complained of language compatibility problems when working with Visual C++. In many cases, the problem is one of not understanding how the interfaces should work, rather than an actual flaw in the interface. However, these errors only serve to point out the complexity of the problem—many of these developers are seasoned programmers who have worked with Visual C++ for several years. We’ll discuss some of these interoperability problems as the book progresses, especially when we discuss the MMC

snap−in example.

For all of the problems of using the wrapper DLL technique, there are quite a few advantages. The best advantage is that you’re working with the Win32 API using a language that’s designed to interact with it. All of the documentation Microsoft provides assumes that you’re going to use Visual C++ for this type of coding. Another advantage is speed. Performing all Win32 API calls in an unmanaged black box and simply sending the result to the managed application results in fewer housekeeping tasks such as marshaling data. In addition, Visual C++ tends to provide an efficient coding environment—one where you can closely monitor the use of resources and decrease the number of steps required to perform any given task. While you might not always notice the speed difference in a small application, using a wrapper DLL does include a performance advantage in most cases.

Using Substitute Functions

It isn’t very often that you can find a substitute for a Win32 API call if that substitute doesn’t appear within the .NET Framework. However, there are times when it’s possible to duplicate a behavior if you’re willing to accept a few compromises. For example, there’s a way to add the Windows XP theme appearance to your application without making special Win32 API calls. However, the substitute technique leaves some vestiges of older programming techniques in place, such as any owner−drawn icons. The only way to make your application completely compatible with Windows XP is to use Win32 API calls.

Of course, the biggest problem with the substitute function technique is finding it. A substitute function is normally a non−obvious way of performing a task. These are the types of techniques that developers share only with friends. In some cases, you’ll find the techniques on the pages of higher−end magazines that only expert developers would attempt to read. In short, the substitute function technique is of limited help. Yes, you should always look for a way to avoid using a wrapper DLL, but it simply isn’t possible to do so in all situations.

Hidden Functions

Sometimes a function that’s relatively easy to learn about for the Win32 API is hidden within the .NET Framework. One such example is the GetDC() function, which retrieves the device context for the current window. This call is used fairly often in the unmanaged world because it represents the only way to draw something on screen. Visual Studio .NET provides rudimentary graphics in an easy−to−use package that doesn’t rely on the developer to provide a device context, so knowing how to obtain the device context would seem superfluous until you need it to make a Win32 API call. Here’s what you need to do to obtain the device context using managed code.

IntPtr hDC; // A handle for the device context. Graphics g; // A graphics object.

// Create a graphic object from the form. g = this.CreateGraphics();

// Obtain the device context from the graphics object. hDC = g.GetHdc();

As you can see, this technique isn’t nearly as easy as making a simple GetDC() function call from the Win32 API. However, it does avoid some of the performance penalties of making the GetDC() call. The right choice of technique for your application depends on how you plan to use the device context once you have it. If you

In document Sybex NET Framework Solutions pdf (Page 50-78)

Related documents