See what's going on with flipcode!




This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Checked D3D Interfaces
  Submitted by



The Direct3D8 API is based on COM or Component Object Model. Most COM systems, like the D3D8 API use the old unstructured C-style idiom of returning an error code from (practically) every function. While this is obviously better than not reporting the errors, it places a burden on programmers using the API, because they need to remember to manually check for errors each time they make a call to the API. If you are like me, you will forget to check the return value someday, and if you are not lucky, you may lose lots of debugging time.

C++ exceptions have the advantage over error return values that the client can use structured error handling techniques. In practice, this means that the programmer only has to write try {} catch blocks only in those places that are of interest to the programmer. Instead of writing hundreds or even thousands of conditional error handling statements sprinkled everywhere in the application, the programmer can place the try {} catch blocks in well selected places and effectively separate error handling code from ordinary program logic.

So, what I would like is to make it so that if an error occures, the D3D interface would throw an exception, and I could then catch it at the point in my application where I have the best chances of reporting or working around the error. Obviously it is not possible to modify the D3D run-time libraries, but it is possible to use the Decorator pattern [see Design Patterns: Elements of Reusable Object-Oriented Software] to attach the responsibility for checking and throwing exceptions in the case of errors to the D3D interfaces.

In practise this means that we write a Checked forwarding implementation of each D3D8 interface. The class definition for implementation for a Checked_IDirect3DSwapChain8 would look something like this:

struct Checked_IDirect3DSwapChain8 : IDirect3DSwapChain8
{
  IDirect3DSwapChain8* m_unchecked;
  LONG m_ref_cnt;

Checked_IDirect3DSwapChain8(IDirect3DSwapChain8* unchecked);

STDMETHODIMP QueryInterface(REFIID riid, void** ppvObj); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); STDMETHODIMP Present(CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion); STDMETHODIMP GetBackBuffer(UINT BackBuffer, D3DBACKBUFFER_TYPE Type, IDirect3DSurface8** ppBackBuffer); };


And the implementation of a method would look like this:

// ...

STDMETHODIMP Checked_IDirect3DSwapChain8::Present(CONST RECT* pSourceRect,
  CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA*
pDirtyRegion)
{
  HRESULT result = m_unchecked-Present(pSourceRect, pDestRect,
    hDestWindowOverride, pDirtyRegion);
  Handle_D3D_Call(result);
  return result;
}

// ...


The function Handle_D3D_Call() simply checks if the function failed, and if so, throws an exception:

static void
Handle_D3D_Call(
  HRESULT hr)
{
  if (FAILED(hr))
    throw D3D_Exception(
      As_D3D_Error_Str(hr).c_str(),
      hr);
} 


We need to handle a few functions differently. Specifically each function that takes an interface pointer, that might be a checked object, needs to detach the checked object from the interface, so that D3D never needs to know about the decorator. Furthermore, each function that creates new objects, needs to attach the corresponding checked object to the interface.

Working this way, it is possible to implement a complete decorator layer on top of the entire D3D8 interface closure and finally implement a function Checked_Direct3DCreate8() that gives a pointer to the root Direct3D pointer.

IDirect3D8*
  Checked_Direct3DCreate8(
    UINT SDKVersion)
{
  IDirect3D8* result = Direct3DCreate8(SDKVersion);
  if (result) result = new Checked_IDirect3D8(result);
  return result;
} 


Now after the application first creates the Direct3D object in one place of the program, all Direct3D calls made by the application will then be checked and any error will cause an exception to be thrown.

Potential problems A solution isn't complete before we have considered the disadvantages of the solution. There are a number of known problems with this approach:

Slower run-time performance caused by extra indirection and run-time checks Slower run-time performance is a necessary evil if you want checking. If you have structured your D3D8 code well, the performance hit is small, because you are sending large patches of primitives per call. At any rate, the checking overhead will not be much greater than doing the same checking manually.

In some cases it is possible to remove the checking completely from the release build. This can be possible, for instance, while working on the X-Box, because you know exactly how much memory will be available and which features are supported by the hardware and don't have to try out various alternatives in run-time. Errors that might occure in runtime, are then either programming errors or resource exhaustion errors. Both of those should be planned for and eliminated before shipping the product.

COM specification related problems



D3D8 will never internally know what is going on. So problems should be limited to situations when someone will try to use QueryInterface() in interesting ways or try to use the objects directly as if they were the internal D3D8 implementation themselves. D3D8 applications have very little use for QueryInterface(), so the application code should not pose problems. It leaves the possibility that D3DX8 might use some a priori knowledge of the D3D8 object. This could be the case on the X-Box, for instance, because the D3D8 implementation is obviously fixed.

External libraries that do not have exception handling turned on Another problem is that you might be using a library that doesn't use exception handling, but you are passing the checked interfaces to such libraries. In such a case, throwing the exception will obviously fail.

In summary, if you can not use this technique, you shouldn't. Eventhough you might be unable to use this technique for release builds, you might benefit from using the technique during development, or debugging, when it is imperative to be notified of errors as quickly as possible.

About the source code This article comes with the source code for all checked D3D8 interfaces. You can customize it to your specific needs. For instance, you might prefer an assertion or break-point instead of throwing an exception when an error value is returned by D3D. (You'll lose the ability to respond to run-time errors though.) In that case you can change the code.



Download the source code package here: Checked_D3D_Interfaces.zip (10k)

You can try the checked interfaces simply by including them in your project and replacing the call to Direct3DCreate8 with Checked_Direct3DCreate8. You also need to change the device initialization code to respond to exceptions instead of error codes:

for (int dev_idx=0; dev_idx < sizeof(s_dev_types)/sizeof(*s_dev_types);
++dev_idx)
{
  try {
    IDirect3DDevice8* d3d_device=0;

if (SUCCEEDED(m_d3d-CreateDevice( /* ... */)) { return; } } catch (const D3D_Exception&) { // Ignore and retry with next device... } }


How the code was written "I would rather write programs to help me write programs than write programs." -- Dick Sites

The code for the checked interfaces is long and highly repeating. It is almost possible to make the checked decorators using a regular expression based search & replace tool, but instead I decided to write a simple program that reads a header file containing COM interface declarations and generates the checked interface wrappers.

The architecture of the generator is simple. The most important architectural feature is that the analysis code is separated from the synthesis code by using a very simple representation of a COM interface closure:

struct Argument
{
  string m_name;
  string m_type_name;
};

struct Method { string m_name; string m_return_type_name; vector<Argument m_arguments; };

struct Interface { string m_name; string m_base_name; vector<Method m_methods; };

struct COM_Closure { vector<Interface m_interfaces; };


The main program of the generator then first parses the header using a very simple hand-written recursive descent parser that only reads the COM declarations and creates the COM_Closure representation. Then the COM_Closure is passed to the interface generator, which outputs the header and source files for the checked interfaces:

{
  COM_Closure com_closure;

Build_COM_Closure( input_h_file, &com_closure);

Generate_Checked_Interfaces( com_closure, output_h_file, output_cpp_file); }


The length of the code for the generator is about half of the generated code for D3D8 interfaces. The same generator can be used with small modifications to generate interfaces for other COM based systems. The code for the generator is not included here mostly because it makes extensive use of our libraries.

Thanks must go to my colleagues here at Housemarque for their comments and support.

Regards,
Vesa Karvonen
Housemarque, Inc.

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

 

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.