The DirectX programming environment presents a number of new challenges to the developer. Of course, working with DirectX itself can be a challenge because there are a number of issues to consider. However, the managed environment brings several new challenges and that's what we consider in this section.
One of the most important issues is performing the data conversions correctly. A DirectX application relies heavily on numeric data. It isn't always easy to tell when a data structure has returned the wrong information. When you convert a data structure that has at least some string data, the string serves as a means for detecting some types of errors. The pure numeric nature of many DirectX data structures makes this impossible. Consequently, you need to know the values of at least some of the numeric fields so that you can verify that the rest of the data structure contains good data. In sum, data validation is essential, but difficult.
We saw in the section "Converting the DDCAPS Data Structure" that DirectX also relies heavily on arrays that you can't dismiss, convert to something else, or define inline. This section shows one method for converting an array from a managed version to an unmanaged equivalent. However, the failure points in this conversion technique are many. For example, if you choose the wrong managed data type, the wrong ArraySubType argument value, or the wrong SizeConst argument value, the DirectX function will receive an array with incorrect data values. DirectX is very likely to go along with this error in most cases. The only error that it might detect is an incorrect SizeConst argument value. The result is that the display could act erratically, you might see data damage, or the application might not work at all.
Another problem is that you can actually overwork the data structures. One such example is the LARGE_INTEGER data type. The reason that the C/C++ headers create such a complex structure for this data type is that many C/C++ compilers don't support 64-bit integers natively. This concept is something you should keep in mind as you create data structures. For example, you might find the need to use a Currency data type in one of the structures. This is a data type that some C/C++ compilers don't support natively, yet the support is easily accessible from the .NET Framework. In sum, don't always re-create everything you find in the header files because you won't always need to do so.
Sometimes the data conversion problem isn't one of creating a managed version of an unmanaged data structure. For example, converting the elements in the DDPIXELFORMAT data structure is relatively easy. Coming up with a name for the new member is tough. Here are two examples of the unions that appear in this data structure:
// DWORD dwRGBBitCount; // DWORD dwYUVBitCount; // DWORD dwZBufferBitDepth; // DWORD dwAlphaBitDepth; // DWORD dwLuminanceBitCount; // DWORD dwBumpBitCount; //} DUMMYUNIONNAMEN(1); public UInt3 2 dwCountDepthData;
// DWORD dwRBitMask; // DWORD dwYBitMask; // DWORD dwStencilBitDepth; // DWORD dwLuminanceBitMask; // DWORD dwBumpDuBitMask; //} DUMMYUNIONNAMEN(2); public UInt32 dwMaskData1;
Needless to say, you can't use the same names as before because the union includes too many names to fit comfortably on a single line. Fortunately, all of the values are of the same type, which makes the conversion easy. In this case, I chose a generic name that appears to fit the functionality offered by each of the members. However, it's a less-than-perfect solution. Make sure you retain the original code as shown in the example when you rename a variable. Otherwise, other developers will have a difficult time reading your code.
A final area of concern for .NET developers who want to use DirectX is the use of pointers. It's important to remember how both .NET and the unmanaged environment work with pointers. In some cases, you might have to go back to a data structure and use an IntPtr type in place of the custom type you previously defined. When using any pointer, make sure you observe these rules:
• Allocate memory if the called function will write to a buffer or provide other feedback.
• Free any memory that you allocate.
• Pay close attention to the flow of pointers in a DirectX application because the function may provide you with a pointer into external memory.
• Use pinning when necessary to ensure that the Garbage Collector doesn't free memory it thinks is unused.
• Validate buffer sizes using test programs written in unmanaged Visual C++ whenever possible.
• Don't count on the documentation to provide accurate values of buffer size.
• Use the C/C++ headers to check pointers values whenever possible.
• Sometimes you'll need to use something other than an IntPtr, as in the case of some handle types used by the Win32 API.
Was this article helpful?