If the code attempts to create a devicefor a system that doesn’t support the request device type, the call will fail, so it’s important to check for a nullreturn value in your production
Trang 1Using a Mesh Example
Now that you’ve seen how a mesh is constructed and how to test the content of an X file, it’s time to see whatyou’ve learned in action The example in this section performs the simple task of loading an X file andrendering it The example code will demonstrate that some of the DirectX code is the same as you’ve used inthe past but other code is different because of the managed environment In fact, this example contains a fewsurprises that you might not have expected
Note A lot of developers were surprised to hear that there are some problems getting DirectX to run on somemachines that have AMD processors installed It turns out that the DrawIndexedPrimitive() function cancause DirectX to stop responding on these machines This problem was first reported in The Inquirer(http://www.theinquirer.net/?article=4474) Later, Microsoft uploaded a Knowledge Base article toexplain the problem (http://support.microsoft.com/search/preview.aspx?scid=kb;enưus;Q321178) TheKnowledge Base article also includes a link where you can download a patch to fix this problem
Initializing the Application
Let’s begin with application initialization Listing 16.1 contains the constructor for the form You’ll find thiscode in the \Chapter 16\C#\MeshView and \Chapter 16\VB\MeshView folders of the CD
Listing 16.1: Mesh Example Initialization
// DirectX objects.
private DirectX8 DX8; // DirectX 8 object.
private Direct3D8 D3D8; // Direct 3D object.
private Direct3DDevice8 Device; // Display device.
public frmMain()
{
D3DDISPLAYMODE Mode; // Display mode.
D3DPRESENT_PARAMETERS Params; // Display parameters.
// Required for Windows Form Designer support
InitializeComponent();
// Initialize the DirectX objects.
DX8 = new DirectX8Class();
D3D8 = DX8.Direct3DCreate();
// Create a device to use for drawing Begin by obtaining the
// current display mode Set the display parameters Finally,
// create the device.
Mode = new D3DDISPLAYMODE();
Trang 2CONST_D3DCREATEFLAGS.D3DCREATE_SOFTWARE_VERTEXPROCESSING,
ref Params);
// Set the device state.
// Turn off culling.
D3DPRESENT_PARAMETERS data structure (Params) that contains the information used to create the
device The Direct3D parameters control the operations the device can perform
The code uses the D3D8.CreateDevice() method to create the device This method accepts the parameters, adevice type, the handle to the current window, and some creation flags as input We’ll use a default adaptertype for the example In addition, the example relies on hardware support If your system only providessupport for software emulation, you’ll need to change the CONST_D3DDEVTYPE.D3DDEVTYPE_HALenumeration value to CONST_D3DDEVTYPE.D3DDEVTYPE_SW If the code attempts to create a devicefor a system that doesn’t support the request device type, the call will fail, so it’s important to check for a nullreturn value in your production code and make an alternative device creation call (or exit from the
application)
When the D3D8.CreateDevice() method returns, Device is in a default state that doesn’t work for anything.
Microsoft is assuming that you’ll configure the device for your needs The final four lines of code configure
Device for the needs of this application by setting the render state using the Device.SetRenderState() method.
The code controls the device setting using the CONST_D3DRENDERSTATETYPE enumeration members
In this case, the code sets the culling state, Z buffer, lighting, and ambient lighting color Notice that I’veactually commented the ambient color setting out We’ll see later in this example why this setting can causeproblems or fix them, depending on the situation
Loading a Mesh File
At this point, the main DirectX objects are set up However, we can’t do any more until the user opens theapplication Listing 16.2 shows the code to open an X file and process it for use This code demonstrates theprerequisites for preparing to render (display) the file, but we won’t display it yet
Listing 16.2: Loading the X File
// These are the global file−related objects.
private string File2Open; // The file we want to render.
private D3DXMesh Mesh; // The Mesh Object.
private D3DMATERIAL8[] Materials; // Array of materials.
Loading a Mesh File
Trang 3private Direct3DTexture8[] Textures; // Array of textures.
private void mnuFileOpen_Click(object sender, System.EventArgs e) {
OpenFileDialog Dlg; // File Open Dialog
D3DX8 Worker; // A worker object.
D3DXBuffer Adjacency; // Adjacency data buffer.
D3DXBuffer MatBuffer; // Materials buffer.
Int32 MatCount; // Number of materials.
String TextName; // Name of the texture file.
String FilePath; // Path to the texture file.
// Set up the File Open Dialog
Dlg = new OpenFileDialog();
Dlg.Filter = "X Format File (*.x)|*.x";
Dlg.DefaultExt = ".x";
Dlg.Title = "Open X File Dialog";
// Display the File Open Dialog and obtain the name of a file and // the file information.
ref Adjacency,
ref MatBuffer,
ref MatCount);
// Obtain a list of materials and textures.
Materials = new D3DMATERIAL8[MatCount];
Textures = new Direct3DTexture8[MatCount];
FilePath = File2Open.Substring(0,
File2Open.LastIndexOf("\\") + 1); for (int Counter = 0; Counter < MatCount; Counter++)
// Determine if there is a texture to process.
TextName = Worker.BufferGetTextureName(MatBuffer, Counter);
Trang 4image on screen The last three variables are all that application requires in the form of file objects to renderthe image on screen.
The mnuFileOpen_Click() method begins as any file opening code would: by creating the dialog box and
waiting for user input If Dlg returns DialogResult.OK, the application begins processing the resulting
filename Otherwise, the application exits to the main form and waits for the user to stop clicking Cancel (orexit the application)
The code begins by creating an instance of the D3DX8 worker class (Worker) The Worker object providesaccess to a wealth of methods that you can use to manipulate graphics files of all types, including mesh files
The code then creates two temporary buffers, Adjacency and MatBuffer, that the application will use to hold
data from the X file The LoadMeshFromX() method loads data from the X file that the user selected into the
two buffers and the device The MatCount variable holds the number of materials retrieved from the X file and stored in MatBuffer Device now contains the data required to render the image, but we still need to
perform other tasks on the buffers
Tip Listing 16.2 contains the Adjacency buffer for the LoadMeshFromX() method even though
the application will never use the data contained within the buffer C# doesn’t provide amethod to leave out optional parameters, so we’re forced to use this technique to keep thecompiler happy even when the application doesn’t need the data You can get around theoptional parameter problem, in some cases, by using either the Type.Missing or
Missing.Value value Of course, other options include using null or IntPtr.Zero as needed
The next step in the process is to create Materials and Textures arrays The code sizes these two arrays to hold the number of items specified by MatCount Given the way that an X file stores the texture information, the
Textures array will never include more items than the Materials array, so using this technique is safe The
code uses a for loop for processing It begins by obtaining the current material from MatBuffer using the
BufferGetMaterial() method The code must call this method once for each material in the buffer We’ve set
the device to display the ambient color, so the next step is to transfer the diffuse color value to the ambient
Loading a Mesh File
Trang 5color value in the Materials array.
Getting the texture comes next The code checks for a texture attached to the current MatBuffer entry using the BufferGetTextureName() method If TextName is null on return, then the material doesn’t include a texture Otherwise, the code combines the FilePath contents with TextName to create a filename and path It
uses the CreateTextureFromFile() method to place the texture within the Textures array
The code has to perform some cleanup at this point Because we don’t need MatBuffer anymore, the code sets
this object to null Remember that we’re still working with unmanaged code You’ll create a memory leak ifyou don’t perform this step The code calls Render() to render the image on screen, starts the timer used toupdate the image, and enables the Timer Ø Stop menu item At this point, the first image is displayed onscreen and the display will receive regular updates
Displaying the Image On Screen
It’s finally time to discuss the rendering process Listing 16.3 shows one way to render an image on screen.This is probably the least−code−intensive method for displaying a rotating image on screen Once you get pastthese basics, the coding can become quite intense because you start working with the mathematics required torender images precisely
Listing 16.3: Rendering the Image
// Define the D3DXMatrixRotationY() function.
[DllImport("DX8VB.DLL", CharSet=CharSet.Auto, SetLastError=true,
EntryPoint="VB_D3DXMatrixRotationY")]
public static extern void D3DXMatrixRotationY(out D3DMATRIX MOut,
float angle);
// Define the D3DXMatrixLookAtLH() function.
[DllImport("DX8VB.DLL", CharSet=CharSet.Auto, SetLastError=true,
EntryPoint="VB_D3DXMatrixLookAtLH")]
public static extern void D3DXMatrixLookAtLH(out D3DMATRIX MOut,
ref D3DVECTOR VEye,
ref D3DVECTOR VAt,
ref D3DVECTOR VUp);
// Define the D3DXMatrixPerspectiveFovLH() function.
[DllImport("DX8VB.DLL", CharSet=CharSet.Auto, SetLastError=true,
EntryPoint="VB_D3DXMatrixPerspectiveFovLH")]
public static extern void D3DXMatrixPerspectiveFovLH(
out D3DMATRIX MOut,
float fovy,
float aspect,
float zn,
float zf);
// We need to use pi for some of the values.
public const float pi = 3.1415F;
private void Render()
{
D3DMATRIX WorldView; // Drawing matrices world view.
D3DMATRIX Camera; // Drawing matrices camera view.
D3DMATRIX Projection; // Drawing matrices projection.
D3DVECTOR Vect1; // Matrix vectors.
D3DVECTOR Vect2; // Matrix vectors.
Displaying the Image On Screen
Trang 6D3DVECTOR Vect3; // Matrix vectors.
// Clear the display area.
// Set up the world view.
D3DXMatrixRotationY(out WorldView, DateTime.Now.Millisecond);
Device.SetTransform(CONST_D3DTRANSFORMSTATETYPE.D3DTS_WORLD,
ref WorldView);
// Set up the camera view.
Vect1 = new D3DVECTOR();
Vect2 = new D3DVECTOR();
Vect3 = new D3DVECTOR();
// Render the mesh Set the materials and textures, and then draw
// the mesh subset.
for (int Counter = 0; Counter < Materials.Length; Counter ++)
// Present the scene on screen.
Device.Present(IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero);
Trang 7interoperability layer doesn’t always do a good job of importing everything you need into the IDE DirectXprovides an extensive math library (among other functions) that’s totally inaccessible because the mathfunctions aren’t imported.
You should see another surprise in the code at this point This is the first time we’ve had to use the EntryPointproperty As shown in Figure 16.17, the function names listed in the DirectX help files don’t match thefunction names that actually appear in the DLL Microsoft had to write a Visual Basic compatibility layer tomake DirectX work properly with this language Consequently, the developers at Microsoft had to give theVisual Basic specific functions special names, with the result shown in Figure 16.17 This use of alternativenames is another problem to look out for when working with DLLs, especially those used to support COM
There’s one additional surprise in this DLL Notice that the function declarations define MOut as an out value
rather than a ref In addition, the other arguments are defined as ref values The reason that this is a surprisingturn of events is that the Visual Basic would lead you to believe that you can pass these arguments by value
Even the Visual C++ documentation only passes the MOut argument as a pointer (by reference), so there’s
apparently no reason to set the declarations up as we have This is one of those odd declarations that youdiscover by trial and error rather than by observation Try changing the function calls to the Visual Basic orVisual C++ documented format and you’ll find that they no longer work properly (or perhaps at all)
Figure 16.17: Exercise care when importing COM libraries because you might leave the functions behind
It’s time to look at the Render() method The code begins as you might expect It clears the display Make sureyou clear both the target area and the Z buffer as shown in the code or you’ll have unpleasant results (mainlydata corruption) The Clear() method also accepts a background color (white, in this case), the Z buffer depth(where 1.0 is farthest away and 0.0 is closest), and a stencil buffer setting
Drawing the image begins by a call to the BeginScene() method The code then sets various matrices fordisplay purposes Notice that this is where we use the math functions declared earlier in Listing 16.3 Thisbook isn’t about the math behind DirectX, so I’ll leave a complete discussion of the intricacies of matrix code
to someone else The important issue is that you need to set up a world view, camera view, and projectionview in order to see the image One item of note is that you need to provide some value in radians for theD3DXMatrixRotationY() function The example uses the current time in milliseconds
(DateTime.Now.Millisecond), which produces acceptable results if you synchronize this value with the timerinterval
After the code sets up the various matrices, it begins to render the image Direct3D requires that the code
Displaying the Image On Screen
Trang 8render the image on a material−by−material basis Consequently, the code uses a for loop to process eachmaterial and texture in turn The application relies on the SetMaterial() and SetTexture() methods to performthe processing Once the code has applied the current color and texture, it uses the Mesh.DrawSubset() todisplay image components that use that material on screen.
The rendering process ends with two steps First, the code tells the device that the scene has ended using theEndScene() method Second, the Direct3D device presents the rendered image to the real display using thePresent() method At this point, the user sees the image
The user experience with Direct3D probably won’t be the same without some form of animation To add ananimated effect to your display, you need a timer The example application uses a simple timer to render theimage each time the timer expires, which hopefully produces smooth animation Here’s the code used toimplement the timer:
private void timer1_Tick(object sender, System.EventArgs e)
private void frmMain_Closing(object sender,
A Few Words about Output
Let’s look at the output of the application as it exists now Figure 16.18 shows the standard output with color.Notice that the colors interact with the texture just as they did in the MeshView Tool
A Few Words about Output
Trang 9Figure 16.18: The standard output from the application allows color and texture to interact.
I mentioned earlier in the section that you need to exercise care in setting up your device In some cases, youmight discover to your horror that the X file loads and displays but the image lacks color This normallyoccurs because you’ve turned off lighting effects Likewise, if you add lighting when all you need is a texture,the image might not appear as anticipated—it might have odd interactions in the texture
Let’s see an example of a device setup change Remove the comments from this line of code in Listing 16.1:
Device.SetRenderState(CONST_D3DRENDERSTATETYPE.D3DRS_LIGHTING, 0);
Recompile the code and run it Load the Box4.X file and your output should look similar to that shown inFigure 16.19 This is the texture−only version of that X file If you load any of the other X files that wecreated with the MeshView Tool, you’ll notice that they all display without color (you can only see them ifyou change the background color of the application)
A Few Words about Output
Trang 10Figure 16.19: A texture−only form of the Box4.X file
The moral behind this particular piece of coding is that you need to verify that your DirectX settings arecorrect before you blame the managed environment for some oddity in your code The fact is that DirectX iscomplex even without the vagaries of managed code to worry about—using managed code only adds to thedeveloper’s burden
Where Do You Go from Here?
This chapter has shown you how to use three new DirectX tools You’ve also seen a coding example thatshows how to use the MeshView utility with your application What you’ve received is a general overview ofthe extended capabilities of DirectX—we haven’t really explored the depths of this complex API You shouldleave this chapter knowing that the tools do indeed work, that they reduce the effort required to code certaintypes of DirectX applications, and that everything works with the managed environment—at least partially
At this point, you know that DirectX does provide full functionality in the managed environment as long asyou’re willing to work around some problem areas From the examples, you should also know that DirectXdoesn’t work nearly as fast in the managed environment as it does in a native executable Finally, you shouldunderstand that the amount of work you need to perform to use DirectX will be reduced by the introduction ofDirectX 9 However, at the time of this writing, DirectX 9 is still in beta and Microsoft hasn’t provided arelease date for it
If you plan on working with DirectX in your applications, it’s time to explore further This book hasn’tcovered a lot of areas that you’ll need to learn before you can write professional−quality DirectX applications.For example, we haven’t discussed much of the math behind DirectX even though DirectX is a
math−intensive development environment We also haven’t discussed the art that goes into DirectX
development—three chapters in a book aren’t enough to cover such broad issues You need to learn moreabout the general use of DirectX
Congratulations, you’ve finished the chapters in the book However, you’re not finished yet There are still
Where Do You Go from Here?
Trang 11two appendices to view, a glossary to use, and an Extras folder on the CD to exploit In short, even though thetext is finished, there are still more avenues to explore before you can say that you’ve learned everything thisbook has to offer.
Where Do You Go from Here?
Trang 12Part V: Appendices
Appendix A: Fifty−Two Tips for Error−Free Win32 API Access Appendix B: Fixes for Common API Access Errors
Trang 13Appendix A: Fifty−Two Tips for Error−Free Win32 API Access
We all like the little bits of information that say a lot in a short space, especially when working with complexideas or concepts These tips are especially worthwhile because they often represent hours of work They willhelp you create a better environment in which to access the Win32 API Many of them are significant andshow the hours of work required for discovery; others are subtle suggestions exposed in a moment of clearthinking A few of the tips simply consist of good−to−know information of the common−sense variety Nomatter the source, there’s one tip for each week of the year The tips are presented in no apparent order andyou can skip around when reading them if you like
When working with enumerations, it’s usually best to create a single enumeration that contains allpossible values for a situation rather than create many custom enumerations for each call that requires
a portion of the entire enumeration The use of custom enumerations leads to confusion on the part ofother developers and increases the work required to use the Win32 API, without much benefit to theoriginator
1
Verify the type of variable that a handle or other pointer requires In most cases, you’ll use an IntPtr
to represent a handle or other pointer However, some pointers (especially handles) use a structureinstead In this case, use the data structure The biggest clue to look for is the use of macros whendeclaring the handle or other pointer Macros generally signal the use of a data structure or othernon−IntPtr form of variable
2
Always free unmanaged memory that you allocate using any of the methods we have discussedthroughout the book This includes memory allocated using the Marshal.AllocHGlobal() function.Failure to free memory that you allocate outside of Garbage Collector control will create a memoryleak because the Garbage Collector only works with managed resources
3
The [MarshalAs] attribute represents one of the most flexible ways to create an interface betweenmanaged and unmanaged data Generally, you’ll use the [MarshalAs] attribute more often with VisualBasic than you will with C#, but both languages require this feature for certain types of data
transmission such as COM interfaces and Char arrays
4
Sometimes the specific name of a Win32 API function will remain elusive—you remember what thefunction does but can’t quite remember the function name so you can look it up in the Platform SDKdocumentation When this happens, you can use the Search feature of the Platform SDK
documentation to look up the function name Unfortunately, Microsoft’s search mechanism isn’tknown for it’s ability to locate all of the information pertaining to a subject, so it often pays to
perform the same search using a search engine like Google
(http://www.google.com/advanced_search) In addition, you can perform searches using specifickeywords in the Platform SDK documentation index For example, many functions come in a Get or aWrite form Looking through the list of entries in these areas of the documentation might help youlocate a function that eludes other forms of search
5
You can use either an enumeration or a class as a container for a list of variable values When using aclass, declare the individual members as public const with a variable type The class method offersbetter control over the enumeration member types; an enumeration is more memory efficient
6
Use NET Framework data types whenever possible This technique avoids conflicts with nativelanguage data types and makes the code more portable In addition, some languages, such as VisualBasic, benefit from using the NET Framework data types
7
Make sure you perform the correct bit−level manipulations when working with flags In most cases,you’ll want to create an enumeration that contains the various flag bit locations and then and the flagwith the enumeration value Flags generally require use of an if statement to ensure that the
application reacts to them properly
8
Trang 14Avoid excessive data−manipulation−oriented Win32 API code by using built−in NET Framework
functionality For example, you can obtain the handle for the current object by using the this.Handle
property
9
Spy++ can provide you with valuable information about the manner in which Windows interacts with
an application For example, you can use Spy++ to determine the actual value for a window handleand use this value to check the output of any functions used by your application Spy++ also helpsyou with message handling needs and can tell you about the threads used by your application
Normally, there’s no way to implement a union within managed data structures Generally, you have
to create one version of the data structure for each member of the union However, you can get aroundthis requirement by ensuring that you understand which form of the data structure your applicationwill use—implementing only the form of the data structure that your application needs will help avoidconfusion on the part of other developers
13
Make sure you understand all features of the [DllImport] attribute This is a versatile attribute that isthe main tool used to create linkage between the managed environment and any unmanaged DLLsthat you want to use It’s also important to understand the limitations for the current [DllImport]attribute implementation For example, you can’t use it to make calls to a DLL that uses the FastCallcalling convention
Look for inconsistencies in the Win32 API call syntax when debugging your application For
example, some calls return an HRESULT value, some a Boolean value, others a numeric value, andstill others an object such as a handle Failure to detect these differences can cause errors in yourapplication code, even if the Win32 API call succeeds
16
Some functionality requires clever programming techniques rather than difficult programming
techniques For example, you can give your applications a Windows XP appearance by creating amanifest file rather than writing the drawing code directly Of course, there’s no free lunch Manyclever programming techniques have limitations that you need to consider In the case of a manifestfile, it affects only the Windows−drawn controls and won’t affect owner−drawn controls
17
Spend time learning about the System.Runtime.InteropServices namespace This namespace contains
a wealth of classes and attributes you can use to make access to both the Win32 API and COM mucheasier
18
You can use the Win32 API FormatMessage() function to interpret the numeric output of the
Marshal.GetHRForLastWin32Error() method, the Marshal.GetLastWin32Error() method, or theGetLastError() function
19
Use the PostMessage() function when you want to send a message and have the call return
immediately Likewise, use the SendMessage() function when you want the message to completebefore the function returns
20
Windows always creates a set of environmental strings for your application In most cases, the stringswill include path information The strings could also include information about the user and thecomputer system Reading and adding to these strings is always acceptable, but avoid modifying thestrings if possible
21
Appendix A: Fifty−Two Tips for Error−Free Win32 API Access
Trang 15Always use events to handle Windows messages and delegates to provide support for callback
functions The main reason you want to use events to handle Windows messages is to allow someoneinheriting from your code to access the message without worrying about the details of the Windowsmessage
23
One alternative to using the FormatMessage() function to interpret an error code is to use the ErrorLookup utility This utility accepts a number as input and an optional DLL where the associated errormessages are found and outputs the human−readable error message
24
Beware of threading problems when working with the Win32 API Always be sure that any
modifications to the data in the main window are made by functions in the main thread The sameholds true for any other graphic element—always make sure that your application handles them in athread−safe manner
25
The WM_SYSCOMMAND message is one of the more flexible messages offered by the Win32 API
It helps the developer to perform tasks such as manipulate windows and turn on the screensaver
27
To provide cues for other developers when a Win32 API function argument will accept an
enumeration value, use the enum name as the argument type In this case, however, you’ll need toensure that the enumeration provides the correct data type for the Win32 API call or the function mayfail to work as anticipated
28
Always implement data structures fully Even if a shortened form of the data structure will work forthe current project, it’s likely that you’ll need the data structure in the future In addition, you can’t besure of side effects that will occur when using an incomplete version of the data structure SometimesWindows will use reserved or unused data members for specific purposes (such as the storage ofpointers and handles)
29
Don’t assume that all undefined flag values constitute an error There are four possible reasons for anundefined flag value First, the flag value could be incorrect, in which case you should display anerror message Second, the host could support an undocumented flag value that’s designed for thatversion of Windows Third, the undefined value could mean that the call failed Fourth, an undefinedflag value could be the correct output given some environmental or application condition The callmight produce multiple outputs, one or more of which include an undefined flag value
30
Windows provides a means for creating your own custom messages Always use the
_RegisterWindowMessage() function to register a unique name for your custom message
31
Make sure you use role−based and token−based security appropriately in your applications Eachform of security has a specific purpose in the Windows environment Role−based security is bettersuited to distributed applications and situations in which you need to know what type of user isaccessing an application and associated data Token−based security works better in situations inwhich you need to maintain strict control and information about the user
32
Avoid using unsafe code whenever possible Any code that relies on the use of manual pointers (*symbol) or addresses (& symbol) is unsafe code Only C# supports this method of working with theWin32 API Unsafe code blocks double the potential for problems with the Win32 API call becausethe compiler and runtime both reduce the number of checks they make Of course, there are situations
in which you can’t avoid using pointers unless you want to create a wrapper DLL using Visual C++(an effort that comes with its own set of problems) Generally, you can avoid using pointers by
33
Appendix A: Fifty−Two Tips for Error−Free Win32 API Access
Trang 16relying on the out and ref keywords and using IntPtr variables.
Use the in and out designations in the Platform SDK documentation to determine the flow of databetween your application and the unmanaged environment Variables marked as [in] are normallypassed by value You’ll use a reference (the ref keyword in C#) for variables marked as [in, out].Finally, variables marked as [out] are normally passed in an uninitialized state using the out keyword
or the [out] attribute
34
The Platform SDK generally lists a C/C++ library as the source of a Win32 API call In most cases,there’s a corresponding DLL that the NET developer will rely on to access the function However, insome cases, the function only appears in the C/C++ library Whenever this problem occurs, you’llneed to use a wrapper DLL to access the function
35
The serial and parallel ports are examples of hardware that provide standardized access under
Windows However, if you want to access specific devices, you’ll need access to a third−party libraryand its accompanying documentation
36
Use multiple overrides of the same Win32 API function to create certain effects For example, if youwant the ability to assign a null value to a structure, you need one version of the function call with thestructure in place and a second version with an IntPtr substituted for the structure
37
Make sure you understand the use of special−purpose messaging functions For example, it’s essential
to know the purpose of the PostThreadMessage() function if you want to manipulate threads directly
38
Windows uses a message pump to deliver and process messages There are literally thousands ofWindows messages that you can use to communicate with your own application as well as otherapplications executing on the system Make sure you spend time learning about the various messagesand message classes that the Win32 API provides
42
At least a few of the Win32 API calls you want to make will require the use of a Visual C++ wrapperDLL Make sure you include Windows.H as part of STDAFX.H to make sure the Visual C++ wrapperDLL will work correctly In addition, you must include certain #defines to ensure that the compilerwill enable advanced Windows features
43
Always verify that the message function you want to use is still viable For example, the
PostAppMessage() function has been replaced by the PostThreadMessage() function Using the wrongfunction in your application can cause errors if newer versions of Windows don’t support the replacedfunction
44
Look for hidden NET Framework functions to help resolve Win32 API call problems For example,one of the most common graphics calls is GetDC() This function will work in most, but not all, caseswith a NET application In many cases, you can use the NET Framework CreateGraphics() function
to obtain the graphical presentation for the object in question and then use the GetHdc() function toobtain a handle to the device context
47
Appendix A: Fifty−Two Tips for Error−Free Win32 API Access
Trang 17of the same function to handle data in different ways The most common difference between functions
is that some use Unicode characters and some use ANSI characters
Not all flags are single−bit values Some flags consume two or more bits within the variable returned
by a Win32 API call In this case, you’ll need to set up special handling for the flag value and couldfind that transferring the flag value to another variable is well worth the effort The most importanttask is to determine the proper reaction to each flag value and to ignore undefined flag values
48
Although there’s no requirement to use the [StructLayout] attribute, using it can help you obtain betterresults when creating data structures The LayoutKind enumeration helps determine the final datapresentation to the Win32 API call In addition, you can use the CharSet, Pack, and Size fields toensure that the output of the structure is acceptable The Size field is especially useful when you want
to ensure that the Win32 API sees a structure of a specific size
49
Never use your old Visual Basic Win32 API function declarations within the managed environment.Microsoft has changed how Visual Basic works Consequently, the older code will cause problems inthe newer NET applications
50
Macros present one of the harder elements of the Win32 API to convert to the managed environment
In many cases, you can use a constant value to represent the macro Some macros will require theconstruction of bit−manipulating functions Finally, a few macros actually create other data elements,such as structures When you find such a macro, determine what type of data element it creates andthen define the data element directly
Trang 18Appendix B: Fixes for Common API Access Errors
It’s always going to be hard to create applications that use more than one system for making calls, obtainingsystem resources, and performing other tasks No matter how well you build the bridge between the twotechnologies, there are going to be problems in translating calls and data from one technology to the other Forthis reason, there is some risk in using the interoperability techniques found in this book No matter howclosely you follow the examples, there are going to be times where the Win32 API throws a monkey wrench
in your plans and makes it difficult to obtain a desired effect in your application
This appendix provides some fixes for the most common Win32 API access errors based on personal
experience and the experiences of other developers on the various NET newsgroups While these sectionsmay not fix every error that you have accessing the Win32 API, they’ll at least give you some ideas of where
to look for answers Even a hint of where to look when you’re scratching your head late at night is better than
no information at all However, I’d also like to provide updates to this appendix on my Web site If you see aproblem that doesn’t appear in this appendix (or anywhere else in the book) and you think that other
developers are also seeing this problem, feel free to write me about it at JMueller@mwt.net When you write,please include a short description of the problem, some sample code if possible, and your suggested fix for theproblem
Resolving Data Corruption in Data Structures
Several areas of the book have mentioned the fact that managed data and unmanaged data are essentiallyincompatible Whenever you pass data from the managed environment to the unmanaged environment, theCommon Language Runtime (CLR) marshals the data for you—it converts the data as needed for the twotechnologies Generally, CLR does a good job of translating the data for you assuming you provide the correctdata as input In some cases, CLR needs a little help translating the data, so you can use the [MarshalAs]attribute to tell CLR how to marshal the data For example, you might need to tell CLR to marshal data as awide (Unicode) string instead of an ANSI string in some cases
Most developers figure out how to use the [MarshalAs] attribute with functions because the feedback isimmediate In fact, Windows will often tell you that the data is in the incorrect format as an error code
However, Windows doesn’t provide such feedback with data structures In many cases, the developer has noidea that the data is in the wrong format or suffers corruption during transit Consequently, debugging thisproblem can be particularly troublesome
The first place to look for errors is the structure header definition Make sure you include the required
arguments in the [StructLayout] attribute Many developers forget to tell CLR which character set to use orhow to pack the data structure Both of these omissions can cause serious damage to your data because CLRmakes certain assumptions about the data based on the call you make and the host operating system
The second place to look is the data fields within the structure This is where the [MarshalAs] attribute comesinto play Read the Platform SDK documentation carefully to look for clues about the data in the structure.Remember that in many examples in the book you discovered inconsistencies in the Win32 API
implementation and how to learn about those inconsistencies in the documentation If the Platform SDKdocumentation doesn’t provide the required information, try looking through the C/C++ header files Oftenthe header files contain fixes that don’t appear in the documentation Finally, look online for example code,advice from fellow newsgroup members, and Microsoft Knowledge Base articles about the topic All of thesesources can help you track down a need to marshal data in a certain way when using data structures
Trang 19The [DllImport] Attribute Works Improperly
Most of the Win32 API calls that you’ll make use standard calling conventions In many situations, you canuse the [DllImport] attribute with just the name of the DLL you want to use—the compiler will find the rest ofthe required information based on your function declaration However, as you’ve seen in many of the
examples in the book, you sometimes need to specify the proper character set using the CharSet field becausethe DLL in question has more than one implementation of the same function We’ve also seen how to use theSetLastError field to retrieve any error information the DLL can provide
Unfortunately, sometimes using the basic fields won’t make the function work properly For example, older CDLLs often use alternative calling conventions You can get around this problem by using the
CallingConvention field This field tells CLR which calling convention to use An associated enumerationcontains options for the following calling conventions:
an option for the FastCall convention, this version of the NET Framework doesn’t support it In other words,you might be out of luck calling certain types of DLLs using the [DllImport] attribute for now, but Microsoft
is planning on resolving this issue in the future
In at least a few cases, you’ll need to specify an entry point to the DLL using the EntryPoint field This fieldhelps resolve problems with DLL versus common naming of functions It also helps you choose a specificfunction when Windows might choose another form of the function based on the platform (ANSI versusUnicode character support is one instance) Sometimes you’ll need to use an ordinal value in place of a string
to ensure that you get the correct function number One of the mistakes that developers make is forgetting toadd a pound sign to the ordinal value For example, you’d need to specify an ordinal value as shown here:
[DllImport("WinMM.DLL", EntryPoint="#188")]
public static extern Int32 waveOutGetDevCaps(IntPtr uDeviceID,
ref WAVEOUTCAPS pwoc,
UInt32 cbwoc);
This form of the [DllImport] attribute ensures that the function calls the waveOutGetDevCapsW() functionand not the waveOutGetDevCapsA() function The first form is the Unicode version normally used on
Windows NT, Windows 2000, and Windows XP The second form is normally used on Windows 9x
machines The important thing to remember is that without the # sign, CLR would look for the 188() function,which doesn’t exist in WinMM.DLL
Data Transfer Problems
Transferring data between the managed and unmanaged environment is problematic because you’re
essentially working with two different data management systems Most developers will find that the NET
The [DllImport] Attribute Works Improperly
Trang 20Framework provides great variable support for common values such as integers, but that things get a littleweird when you start working with arrays and handles COM presents more than a few challenges becauseyou need to work with interfaces, function pointers, and objects In sum, it’s amazing that data transfer works
at all between the managed and unmanaged environments because there are so many differences betweenthem Fortunately, the [MarshalAs] attribute can handle most of the problems—at least if you know how touse the various attribute features correctly
Throughout the book, we’ve looked at various [MarshalAs] attribute features The most common feature isusing the UnmanagedType enumeration to select the correct unmanaged data type However, this enumerationcontains more than a few data types and it’s easy to choose the wrong one In many cases, the compiler willhappily compile the application to use a data type that’s obviously incompatible with the function or methodthat the application wants to call Consequently, the first place to look for problems in your code when datatransfer problems occur is the data type selected for marshaling purposes For example, if you’re working with
a COM interface and are passing an interface pointer using an IntPtr variable, you need to marshal the
interface using the UnmanagedType.Interface enumeration member Of course, you might also need to choose
a specific type of COM pointer, such as the UnmanagedType.IDispatch or UnmanagedType.IUnknownenumeration member
It’s also important to look at the various fields you can add to the [MarshalAs] attribute For example, we’veused the SizeConst field to help marshal Char array variables Using this field saves untold grief in getting themanaged environment to work with the unmanaged array Of course, it’s not always correct to even pass anarray In more than one situation, we created the effect of a fixed length array by passing the correct number
of variables of the anticipated type No, it doesn’t look as clean as using an array, but the technique stillworks
One of the more interesting field values is MarshalCookie This field isn’t strictly necessary For example, wepassed cookies in the MMC example for the book without using it However, adding the MarshalCookie fielddoes provide additional information to the marshaler and can reduce problems in a multithreaded
environment, where the need to obtain a particular instance of the object is important
Despite the power of the [MarshalAs] attribute, there are times when you’ll need to construct a Visual C++wrapper DLL to perform the required conversion or at least create a conversion routine of your own Lookingagain at the MMC example, there isn’t any way to marshal an icon so that it’s the correct size and color depth.This is the type of data conversion that you need to perform using a custom routine The fact that the need forthe data routine is essentially hidden makes it even more important to research the data requirements of therecipient first Nowhere in the documentation does it tell you that you’ll need to translate NET icons to usethem with MMC The MMC documentation was created long before NET appeared on the scene, and MMC
is one of the features that Microsoft decided not to support, so there isn’t any documentation on the topic Ifyou try the [MarshalAs] attribute and find the support lacking, it’s time to don your detective hat and perform
a little research on the data requirements for your application In many cases, you’ll find that you overlooked a[MarshalAs] attribute feature or need to write a custom translation routine
Sometimes a data transfer problem is one of misinterpretation The NET documentation isn’t clear aboutwhen you should use a String versus a StringBuilder, so it’s easy to create a variable of the wrong type andattempt to pass it to the Win32 API function In some cases, even running the application won’t produce anytype of error message that you can use as a starting point for your detective work When this occurs, you need
to look at the data requirements for the Win32 API call closely and then match those requirements to the NETFramework variable types A String cannot change, so it’s not a good candidate for use as a buffer On theother hand, a StringBuilder can change, and developers often use it to improve application performance forthat reason Consequently, a StringBuilder is the correct choice when you need to pass a buffer to a Win32API call rather than to a constant text value
The [DllImport] Attribute Works Improperly
Trang 21It’s also important to use the [StructLayout] attribute as needed in your applications Sometimes the managedenvironment will experience problems in creating a structure that matches the Win32 API function or COMmethod requirements In this case, you can use the Size field to ensure that the structure is an appropriate size.Using the CharSet field ensures that the function will receive string variables of the correct size You can alsouse the LayoutKind.Explicit setting to set the size and location of individual fields within the data structure.
.NET Implementation of COM Interface Doesn’t Work
We discussed this particular problem lightly in the COM areas of the book, but it requires a second look.Many developers will look at the NET Framework documentation and see that it supports an interface such asIDataObject Because many COM applications rely on the IDataObject interface, many developers will fallinto this particular trap The general rule of thumb to follow is that the NET Framework has a complete lack
of COM interface support If you see an interface with a name you need in the NET Framework, it’s likelythat the interface simply has the same name, not the same support as the COM counterpart In general, you’llneed to research the interface to see if the COM and the NET Framework version are the same
This brings up another potential problem—which is one of creating classes derived from the interface Somedevelopers have also experienced a problem where they described an interface implementation, derived fromthe interface, and created all of the required methods within the class only to find that the class doesn’t work
as anticipated (or even compile in some situations) In some cases, the compiler might become confused when
it sees two interfaces with the same name and use the wrong one to compile the application Generally, usefull name qualification in the class definition when there’s a potential for confusion
Sometimes a developer can become fixated on the anticipated solution to a problem and overlook an obviousanswer—I certainly know that I have There’s a chance that you’ll implement an interface that also appears inthe NET Framework, find that it doesn’t work, and try a number of solutions to resolve the problem—all ofwhich are based on the idea that the problem is one of NET versus COM implementation In some cases, theproblem is one of implementation If you’ve tried every other method of resolving the conflict with COM,look again at the interface definition to ensure that you’ve created it correctly Pay particular attention to datatypes because many developers run into this problem Make sure you use the [MarshalAs] attribute as needed
to ensure that the interface reflects the COM version (See the section "Data Transfer Problems" for moredetails.)
Handling Memory Leaks and Other Resource Problems
Some developers are under the impression that NET applications can’t leak memory and will never haveproblems with resources because the Garbage Collector handles all of these problems The fact is that
management of unmanaged memory or resources always falls on the developer The Garbage Collector willonly manage memory and resources that it knows about—that it was involved in allocating on behalf of theapplication Because of the amount of misinformation on various Web sites and newsgroups, many developerswill fall into this trap The result is that you might see more memory and resource problems, not less—at leastuntil developers fully understand how NET memory management works
Of course, the main NET Framework class for working with unmanaged memory and other resources isMarshal The AllocCoTaskMem() and AllocHGlobal() functions are the main methods for allocating memory.Any memory you allocate within your application requires deallocation by the appropriate function, which isFreeCoTaskMem() and FreeHGlobal() in most cases The only exception to this rule is if the Win32 API call
.NET Implementation of COM Interface Doesn’t Work
Trang 22frees the memory for you The clue that the Win32 API function frees the memory for you is that you’ll see anexception from CLR when the application tries to free the memory If you see an error message, check thePlatform SDK documentation to ensure that the function you called frees the memory—the documentationnormally contains this information.
The same process that you use for memory is also used for resources Any resource that you access using a.NET Framework call must also be freed using a NET Framework call However, in this case, you mustalways free the resource None of the Win32 API calls will free the resource for you If you see an errormessage when you attempt to free the resource, it usually means that there’s some problem with your code orthat you never gained access to the resource in the first place (in which case, you’ll see other errors)
Make sure you always use the proper pointer functions for moving data between environments For example,the PtrToStructure() function will create a structure based on the date pointed at by a pointer Likewise, theStructureToPtr() function accepts a structure as input and creates a pointer to it You must allocate the
unmanaged memory to create the pointer before calling the StructureToPtr() function and deallocate thememory when the application is done using it
The problem with memory and resource leaks is subtler than many developers realize For example, any time
a Win32 API function or a COM method returns data that your application doesn’t allocate, you need toconsider whether your application will have to free the memory used by that data Visual C++ developers willremember using the LocalFree() function to free memory allocated by applications If you use a function callthat requires use of LocalFree(), then you’ll need to gain access to that function through the Win32 APIbecause the NET Framework doesn’t provide support for it The same holds true for any resources that youallocate using a Win32 API call Make sure you use the proper Win32 API call to free the resource when theapplication is done using it
Windows Doesn’t Appear to Handle Messages Correctly
Above all, Windows relies on messages to perform a multitude of tasks There’s a message for just aboutevery task you can imagine The problem is the overwhelming number of messages that developers have toconsider when creating an application In some cases, several messages have similar functionality, making itdifficult to locate just the right message to accomplish a given task For example, when you read the
descriptions of the WM_ACTIVATE and WM_ACTIVATEAPP messages, it might be hard to figure outwhich message to use
Some messages are meant to work in a sequence, so you must send more than one message to complete thetask For example, the WM_LBUTTONDOWN is always followed by the WM_LBUTTONUP message.Otherwise, Windows assumes that the user always has the left mouse button down and will perform a
continuous drag operation Of course, one cycle of a WM_LBUTTONDOWN and WM_LBUTTONUPmessage is a click Some developers might think that they should use a double sequence for a double−click,but Windows requires use of the special WM_LBUTTONDBLCLK message instead
Another point of confusion is the message itself The documentation for many messages discusses an
LPARAM and a WPARAM value Some developers will attempt to send numeric information for these twoarguments in all cases, but that won’t work in either the managed or the unmanaged environment Sometimesthese arguments contain pointers to other objects, such as a data structure To send the message, you mustcreate the data structure and fill it with information first In short, make sure you understand the requirementsfor using the message—especially the content of the two parameters associated with the message
Windows Doesn’t Appear to Handle Messages Correctly
Trang 23Choosing the correct message function is also a concern Many of the message functions are easy to identify,but many developers have a problem figuring out when to use the SendMessage() function versus the
PostMessage() function The SendMessage() function waits until an action initiated by a message completes.It’s important to use this function when your application has to wait for the task to complete before it beginsthe next task Timing and synchronization are important no matter what application environment you use TheWin32 API provides the various message functions to help synchronize application activities
A final concern for message processing is ensuring that your application actually handles the message inquestion Managed applications don’t handle many of the Windows messages, which means you’ll have toprovide code for handling them Remember to override the WndProc() function to add your own messagehandling code to the application In addition, remember to call the base.WndProc() function to ensure thatyour application handles all of the default messages as well
Windows Doesn’t Appear to Handle Messages Correctly
Trang 24A
Accelerated Graphics Port (AGP)
A special PC bus used specifically for display adapters An AGP−based display adapter can operate atmuch higher speeds than the normal ISA or PCI bus will allow What this means to the user is thatdisplay speeds are much higher In addition to making the display adapter faster, AGP also allows theadapter to directly access main memory as if it were part of the adapter’s private memory storage.This in turn allows the display adapter to store more complex objects like textures, which are used toimprove display appearance
access control entry (ACE)
Defines the object rights for a single user or group Every ACE has a header that defines its type, size,and flags Next comes an access mask that defines the rights a user or group has to the object Finally,there’s an entry for the user’s or group’s security identifier (SID)
access control list (ACL)
Part of the Windows NT security API used to determine both access and monitoring properties for anobject Each ACL contains one or more ACEs (access control entries) that define the security
properties for an individual or group There are two major ACL groups: SACL (Security AccessControl List) and DACL (Discretionary Access Control List) The SACL controls the Windows NTauditing feature The DACL controls access to the object
access token
A definition of the rights that a service or resource requestor has to the operating system This is thedata structure that tells the security system what rights a user has to access a particular object Theobject’s access requirements are contained in a security descriptor In short, the security descriptor isthe lock and the access token is the key
See Audio Compression Manager.
Adaptive Differential Pulse Code Modulation (ADPCM)
A data encoding technique used for sound systems Unlike some encoding techniques, ADPCMsupports compression Sound cards normally provide encoding and compression functionality inhardware In most cases, the sound card will support several standardized compression levels and afew custom compression levels In addition, the sound card can usually perform data encoding anddecoding without compression
ADPCM
See Adaptive Differential Pulse Code Modulation.
AGP
See Accelerated Graphics Port.
American National Standards Institute (ANSI)
An organization dedicated to creating standard implementations of common technologies For
example, this group created the American Standard Code for Information Interchange (ASCII)
character standard commonly used for application development
American Standard Code for Information Interchange (ASCII)
A standard method of equating the numeric representations available in a computer to
human−readable form The number 32 represents a space, for example The standard ASCII codecontains 128 characters (7 bits) The extended ASCII code uses 8 bits for 256 characters Display