Introduction
When searching the internet, it becomes painfully clear that finding a decent tutorial to implement DirectX 9.0c into .NET Forms is nearly impossible. If you do manage to find one, chances are it is meant for C#.
If you are wondering why a programmer would want to do this, here is my personal response:
Compared to the Win32 API, .NET Forms are far more simple to include in a project and far easier to manage. The only setback is that .NET Forms were designed for C# and not C++. However, with a few simple lines of code, you can change your program so that it will take advantage of C# libraries (.NET Forms in particular). This is ideal if you are trying to construct your own level editor without all the mess generated by Win32.
If you are reading this tutorial, then I expect you know how to do the following:
- Have an intermediate knowledge of C++.
- Know how to use DirectX9 SDK, if not: www.directxtutorial.com.
- I also assume you have your own copy of Visual Studio. (Note: I use the Visual C++ Express Edition from Microsoft; if you are using Visual Studio 2005, there might be minor variations.)
- Furthermore, I assume you have the DirectX SDK installed in your computer.
Writing the code
First, you need to configure the C++ compiler so it is able to use libraries from C#. To do this, go to the menu bar, select 'Project', and go to 'Properties'. A window will open up, go to 'Configuration Properties' and select 'General'. Now, go to the box labeled 'Common Language Runtime Support' and change it to /clr. Now we can use libraries and namespaces from C# in our code.
Create a new C++ file for the entry point of your program (I will refer to this as main.cpp). Then, create a new Windows Form as a header (under Visual C++ -> UI) (I will refer to this as DX9Forms.h and DX9Forms.cpp). Adding a Windows Form to your headers should have created both a header file (.h) and a code file (.cpp). If not, add a code file and remember to include your form. Now, start with the main entry point of this program, I will use WinMain()
.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
Now, include all of your headers:
#include "iostream"
#include "windows.h"
#include "DX9Form.h"
#include "d3d9.h"
using namespace Forms_DX9;
Finally, you only need one line of code to run the form:
Application::Run(gcnew DX9Form());
We will change the constructor of the class DX9Form
later; for now, leave it as it is.
main.cpp will not change much throughout the course of this tutorial.
#include "iostream"
#include "windows.h"
#include "DX9Form.h"
#include "d3d9.h"
using namespace Forms_DX9;
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
Application::Run(gcnew DX9Form());
}
Go the file 'DX9Form.h', you should be in Designer view. Create a panel in your form (this is not needed for the program, but I find it easier to use the coordinates of the panel, then to input them manually).
Now, view the code of the 'DX9Form.h'. The majority of the code has already been generated by Visual Studio. (If you have never used forms in C++ before, this might be a little overwhelming; I suggest you experiment a little with it to get comfortable using it.)
Enter this under #pragma
once:
#using "System.dll"
#include <d3d9.h>
#include <windows.h>
#pragma comment (lib, "d3d9.lib")
As you can probably see, we are using System.dll, a C# library.
Now we need to define two globals, one of LPDIRECT3D9
and the other of LPDIRECT3DDEVICE9
.
Yet, if you simply use them as traditional globals, the program won't compile, so put them both in to a namespace named 'globals
' and define them as static members.
namespace globals
{
static LPDIRECT3DDEVICE9 d3ddev;
static LPDIRECT3D9 d3d;
}
using namespace globals;
We now need to modify the constructor of our DX9Forms
class which was generated by Visual Studio.
protected:
bool mReleased;
HWND hDX9;
public:
DX9Form(HINSTANCE hInstance)
{
mReleased = false;
mInit = false;
InitializeComponent();
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hbrBackground = NULL;
wc.lpszClassName = "WindowClass";
RegisterClassEx(&wc);
hDX9 = CreateWindowEx(NULL, "WindowClass", NULL, WS_CHILD,
this->panel1->ClientRectangle.X, this->panel1->ClientRectangle.Y,
this->panel1->Width, this->panel1->Height,
(HWND)(void*)this->panel1->Handle.ToPointer(),
NULL,
hInstance,
NULL);
ShowWindow(hDX9, SW_SHOW);
}
In this segment of code, we modify the constructor as well as add a boolean value to the DX9Form
class and a Window Handle which points to our render window (hDX9
). We use panel1
to specify where we want the window to be created and what size it should be; if you have done Win32 API programming, this should look familiar to you.
Now we need to implement two functions, initD3D()
and render()
. initD3D()
creates a device for DirectX. render()
clears the window and renders a frame.
Here are the definitions:
protected: void initD3D(HWND hHandle);
public : static void render();
Write these functions in DX9Forms.cpp:
#include "DX9Form.h"
using namespace Forms_DX9;
using namespace globals;
void DX9Form::initD3D(HWND hHandle)
{
globals::d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = true;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hHandle;
globals::d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hHandle,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&globals::d3ddev);
}
void DX9Form::render(void)
{
globals::d3ddev->Clear(0, NULL, D3DCLEAR_TARGET,
D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0);
globals::d3ddev->BeginScene();
globals::d3ddev->EndScene();
globals::d3ddev->Present(NULL, NULL, NULL, NULL);
}
Now we need to write the window protocol; the DirectX device will render when the WM_PAINT
message is called.
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
break;
case WM_PAINT:
{
DX9Form::render();
return 0;
}
break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
Events
We are almost done. Now we just need to define two events; one will be triggered when the form is first shown and will call initD3D()
, and the other will be triggered when the form is closing and will release d3ddev
and d3d
.
To create an event, select your form in the form designer and then go to the Properties window, and in the Properties window, go to Events (represented by a lightning bolt). To create a new event, just double click in a box next to what you what the event by triggered by (in this case, Shown
). After the event is created, you should be redirected to its corresponding code.
Now, treat the event as if it were a function and write your code between the braces. You need to call initD3D()
and render the first frame.
private: System::Void DX9Form_Shown(System::Object^ sender, System::EventArgs^ e)
{
initD3D(hDX9);
render();
}
Now create another event for FormClosing
; we need to release d3ddev
and d3d
.
private: System::Void DX9Form_FormClosing(System::Object^ sender,
System::Windows::Forms::FormClosingEventArgs^ e)
{
if (!mReleased)
{
globals::d3ddev->Release();
globals::d3d->Release();
mReleased = true;
}
}
Finally, we need to adjust our code in main.cpp as we have changed the constructor of DX9Form
.
#include "iostream"
#include "windows.h"
#include "DX9Form.h"
#include "d3d9.h"
using namespace Forms_DX9;
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
Application::Run(gcnew DX9Form(hInstance));
}
Compile and run this; it should work; if it doesn't, I have included source code that you can download.