
This chapter will introduce the remaining DirectX.NET classes, which implement the infrastructure of most DirectX.NET applications: the IdleForm and Utility classes, as well as the DirectX.DirectXException class.
One of the first steps in the DirectX tutorial was to alter the message loop to allow us to run code in the idle time between messages. The .NET framework offers the System.Windows.Forms.Application.Idle event. This event, unfortunately, is only called once, when the system becomes idle, and so is unsuitable for continuous idle-time processing (like frame updates). IdleForm solves this problem.
__gc public class IdleForm : public Form { public: IdleForm(); // Start the application main loop. virtual void Run(); protected: // Called when objects should be initialised (trapped). __event EventHandler *Init; // Called when the application is idle (i.e. no messages are being processed, and // the output device is not paused). __event EventHandler *Idle; };
IdleForm offers one public member: Run, which starts the application message-processing loop.
The two protected events are also of interest. Init is fired just before the message processing begins. This makes it suitable for initialising DirectX objects within the form. Idle is called continuously while messages are not being processed.
IdleForm contains a single method, Run. This implements a simple message loop:
// Start the application main loop. void IdleForm::Run() { try { __raise Init(this, new EventArgs()); while (Created) { Application::DoEvents(); __raise Idle(this, new EventArgs()); } }
Application.DoEvents simply implements the PeekMessage - DispatchMessage logic familiar from the original, handling any messages currently in the queue. Unfortunately, .NET doesn't allow any further customisation of the message handling logic, but this should be sufficient.
Exceptions thrown in the application tend to result in problems, since the message displayed is often hidden behind the DirectX Graphics front-end. This can be a real problem, so IdleForm catches all unhandled exceptions and closes the device first, before displaying the message.
catch (Exception *e) { // Catch any unhandled exception and kill the device, because we won't see it otherwise... Close(); System::Diagnostics::Debug::WriteLine(String::Format(S"Exception \"{0}\" occurred in {1}.", e->Message, e->Source)); System::Diagnostics::Debug::WriteLine(String::Format(S"Stack trace: {0}", e->StackTrace)); MessageBox::Show(e->Message, e->Source, MessageBoxButtons::OK, MessageBoxIcon::Error); } }
The Utility class differs from most of the classes in DirectX .NET in that it is entirely static; that is, it contains only static members.
__sealed __gc public class Utility { public: static Utility() { #ifdef _UNICODE LPWSTR tszAppPath = (LPWSTR)(void *) System::Runtime::InteropServices::Marshal::StringToCoTaskMemUni( System::Windows::Forms::Application::ExecutablePath); #else LPSTR tszAppPath = (LPSTR)(void *) System::Runtime::InteropServices::Marshal::StringToCoTaskMemAnsi( System::Windows::Forms::Application::ExecutablePath); #endif PathRemoveFileSpec(tszAppPath); AppPath = new String(tszAppPath); PathAddBackslash(tszAppPath); AppPathWithBackslash = new String(tszAppPath); CoTaskMemFree(tszAppPath); } static String *AppPath; static String *AppPathWithBackslash; };
Utility offers two static members: AppPath, which contains the path to the directory containing the application, and AppPathWithBackslash, which contains the same path with a backslash appended (if appropriate). This makes the process of accessing files in the application directory considerably easier.
The DirectXException class extends Exception to represent an exception thrown by a DirectX interface method call.
__gc public class DirectXException : public Exception { public: DirectXException(String *Source, HRESULT Code); ~DirectXException(); static String *GetExceptionString(HRESULT h); };
DirectXException offers little of public interest over and above that which Exception offers.
The primary interesting section of DirectXException is GetExceptionString, which returns the exception description.
String *DirectXException::GetExceptionString(HRESULT h)
{
TCHAR sError[256];
if (FAILED(D3DXGetErrorString(h, sError, 256)) || (sError[0] == 0))
return String::Format(S"Unknown exception 0x{1:x8}", __box(h));
return new String(sError);
}
This calls D3DXGetErrorString to attempt to translate the HRESULT into an error string (since DirectX tends not to return standard error codes, FormatMessage is fairly useless).
This concludes our look at the code behind DirectX .NET. From here, the next chapter introduces the first of the sample programs, in either C# or VB.NET.