by Michael Fötsch
Aug 17, 2000
Table of Contents
- Installing the DirectX SDK
- Building the SDK samples
- DirectDraw on VCL Forms
- Straight WinAPI code
- The Direct3D Framework
- C++Builder and Visual C++ differences
- What else?
- Code archive
This article is meant to help Borland users get started with the DirectX SDK. It covers everything from installing the DirectX SDK to using DirectDraw and Direct3D in VCL applications, compiling the SDK samples, using the Console Wizard to create straight WinAPI applications, and other issues.
In the DirectX and C++Builder Graphics newsgroups , two questions that come up often are “Can I use DirectX with Borland compilers?” and “Do I have to buy Visual C++ in order to write DX apps?”
On Friday, August 11, a Microsoft employee posted the following reply: “Of course you are on the safe side when you use VC++. I would recommend it to you. After all its the compiler all the stuff is being build with at MS!” (I’m not sure whether that’s a real incentive… 😉 )
However, it is certainly possible to build DX apps with C++Builder (the current Borland C++ environment), and you should let noone tell you anything else! To be fair, that Microsoft employee did not conceal it, and Microsoft did make sure that Borland users can use the DirectX SDK. After all, C++Builder is a fully ANSI-compliant Win32 development environment (more than VC++!). In fact, there are just a few minor issues to take care of when using the DX SDK and BCB together. This article will help you turn your BCB IDE into a “DXDE”…
The good news: C++Builder ships with all the header files and libraries required for DX development.
The other news: New DirectX versions are likely to be released more often than you buy new versions of C++Builder.
In order to always have the latest SDK (Software Development Kit; contains help files, headers, libraries, samples, tools, etc.), your first stop will be at the Microsoft DirectX Developer Center . After new versions of the SDK are released (along with new versions of the end-user runtime), it can be downloaded (usually for a few weeks) or ordered on CD-ROM. As the SDK becomes larger and larger with every new version (the last one was 128 MB!), it is probably cheapest and easiest to order it.
However, if you do not need the samples and the utilities (the latter are not frequently updated for new SDKs), you can also download the help files (.chm), the headers (.h), and the libraries (.lib) separately .
The next step will be setting up your IDE to use the new files. It is not required and not a good idea to overwrite the old headers and libraries in the CBuilder “include” and “lib” sub-folders. Instead, change the global project options. Close all currently opened projects in C++Builder and go to “Project | Options”. On the “Folders and Definitions” tab, add the DX SDK paths:
Add “<dxsdk>include” to the include path and “<dxsdk>libBorland” to the libraries path.
Note: The DX SDK paths need to appear prior to the other paths. For example, if you had “$(BCB)include” as your include path, make it “c:dxsdkinclude;$(BCB)include”, not the other way round. Change “c:dxsdk” to whatever path you installed the SDK to.
We’ll get back to the project options soon. But first…
The SDK contains the sample applications in both pre-compiled and source code form. Usually, you will want to experiment with the code and modify it. For that, you will have to create a C++Builder project. Following are step-by-step instructions that will help you do this:
- Copy the entire folder containing the sample into your own folder (e.g. “C:My Documents”). That way, you will be able to restore the original code if you break it.
- In C++Builder, click the “New” button. Choose the “Console Wizard” and create a new “Window (GUI)” application without the VCL (which is not needed here but can be used).
Note: In the latest versions of C++Builder, you can also specify the “project source”. Select the file that contains the WinMain function (usually “winmain.cpp”) for this. Specify “C++” as the source code type.
- If you did not specify the project source in the Console Wizard, remove the body from the WinMain function that the IDE created and declare it as “extern”, so that it looks like this:
extern WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
- Then, “link to” winmain.cpp by clicking the “Add to Project…” button (“Project | Add to Project…”, Shift+F11).
Note: Alternatively, you can also copy and paste the code from winmain.cpp (or whichever file declares WinMain) into the project source (“Unit1.cpp” for apps created by the console wizard).
- Add all the other .cpp (or .c) files from the sample folder to the project. Add .res and .rc files as well.
Note: Instead of adding everything, you can also take a look into the sample “makefile” and see which files it uses. This might also bring up files from different folders, commonly <dxsdk>samplesmultimediad3dimsrcd3dframe. Link to these files as well.
- DirectX apps use functions from the DirectX system DLLs. These functions are imported through .lib files. For DirectDraw, for example, you will have to link to ddraw.lib, for Direct3D to ddraw.lib and d3dim.lib. All DirectX apps will require you to link to dxguid.lib. (See the DX SDK help files on GUIDs, dxguid.lib, and INITGUID.)
Note: Use the .lib files from the <dxsdk>libBorland folder. The others will generate an error about an “invalid OMF record”. See the Borland site for more information about the “COFF vs. OMF” issue .
- You can attempt a build. If you get “unresolved external” errors, you did not link to all the required files, either .lib or .cpp. Check the “makefile” whether the sample uses files from different folders that you forgot. See also “The Direct3D Framework” below.
Note: The only samples that you cannot build are the “Direct3DX Utility Library” samples. You cannot use D3DX with C++Builder at all. (You can use Direct3D, however.) “d3dx.lib” is a static library (as compared to an import library), which means that there is no DLL that it exports from. Someone would have to re-compile Microsoft’s D3DX source code with a Borland compiler in order to create an OMF library (or to wrap D3DX up in a VC++-created DLL). Microsoft says Borland is responsible. Borland says Microsoft is responsible…
The Visual Component Library is one of the most outstanding features of C++Builder. It would be a shame if DirectX apps could not take advantage of it! In fact, it is easy to integrate DirectDraw (and Direct3D) functionality into VCL projects. The following step-by-step instructions will show you how to do this. (You can download the project as a ZIP file from here.)
- Create a new application.
- Double-click the main form to create a handler for its OnCreate event. Add the following code:
PostMessage(Handle, WM_INITDIRECT3D, 0, 0);
Note: WM_INITDIRECT3D is not a pre-defined Windows message. It is a user-defined message that we’ll handle in a user-defined function. This is to ensure that all Form creation messages have been processed and the Form has been displayed before we attempt to initialize Direct3D.
- Add “#define WM_INITDIRECT3D (WM_USER+101)” to Unit1.h and register a custom message handler for the message by adding the following code to the declaration of class TForm1:
void __fastcall InitDirect3D(TMessage &Msg);
VCL_MESSAGE_HANDLER(WM_INITDIRECT3D, TMessage, InitDirect3D)
- Add the regular Direct3D initialization code to the function body of InitDirect3D. (It is not the purpose of this tutorial to introduce you to Direct3D. See the References section for links to DirectDraw and Direct3D tutorials .)
- At the end of InitDirect3D, add the following call:
PostMessage(Handle, WM_ENTERGAMELOOP, 0, 0);
- Register “GameLoop” as the message handler for WM_ENTERGAMELOOP (which you can #define as WM_USER+102).
- Implement GameLoop as follows:
Note: This game loop will ensure that the framerate is as high as possible. For games, TTimers would not be good enough. Calling ProcessMessages lets the app still react to messages. The regular events are fired.
- Implement RenderScene.
- Add “bool UserWantsToQuit” to class TForm1. Set it to false in the constructor and to true in the OnCloseQuery handler that you create.
- Add the regular Direct3D shutdown code to the OnDestroy handler.
- Make sure that you #include ddraw.h and d3d.h. Link to dxguid.lib, ddraw.lib, and d3dim.lib.
That’s all there is to it. Note that this is just an example of how you could use DirectDraw and the VCL together. Depending on your type of application, you might not need a “game loop”, for example.
One of the advantages of the VCL is that you do not have to handle the window creation, message dispatching, etc. yourself (although this is not difficult). Windowed applications, for example, will usually react to WM_RESIZE messages and re-create the DirectDraw surfaces. Using the VCL, you can implement an OnResize event handler instead. (Note that there is no OnMove handler. If you need one, catch the message like you caught the user-defined messages above.)
Changing the Form properties for fullscreen applications is also straight-forward: The only thing you’d have to do is set “BorderStyle” to “bsNone”. The sample project also uses TImage to load a bitmap to a surface (that’s where the VCL shows its strengths!) and it implements an enumeration callback as a TForm1 class member.
This is not specific to working with DirectX. However, what you have to do is use the Console Wizard again. Create a “Window (GUI)” application (as opposed to a console application), and write the same code that the DirectX help files tell you to write.
You can still use the non-visual VCL components in “Window (GUI)” applications (if you chose to include the VCL in the Wizard dialog).
This refers to the code that many DirectX SDK samples are built upon. The framework source code can be found in <dxsdk>samplesmultimediad3dimd3dframe. The headers are in <dxsdk>samplesmultimediad3diminclude. Whenever you wish to build one of the samples that makes use of the framework (d3dframe, d3dutil, d3dmath, d3dtextr, etc.), or if you wish to use the framework in your own apps (d3dutil and d3dmath are quite useful!), you need to link to the appropriate .cpp files (add them to your project with the “Add to Project…” button). You should also add <dxsdk>samplesmultimediad3diminclude to your include path. Again, change the global project settings as described above.
(Whew! What a long heading!)
Talking about D3DFrame…you will come across one or two problems when trying to use d3dmath, d3dtextr, and friends.
The first one is the compiler complaining about undefined functions, namely “sinf”, “cosf”, “tanf”, etc. These are special versions of the mathematical functions (“sin”, “cos”, “tan”, etc.) that return floats. These functions do not exist in C++Builder, but you can safely cast the return values of the regular math functions to “float” to emulate them. It is best to add the following definition to your (global) project options (“Project | Options”, “Folders and Definitions” tab):
Alternatively, you can add #defines to the headers of the affected units.
Note: The cast to float does not lose any precision when compared to Visual C++ apps. They would have used floats in the first place…
The second difference (by which only d3dtextr.cpp is affected, as far as I know) concerns variable scopes. In C++Builder, the following construct is valid:
for (int i=0; i < 1000; i++) DoSomething(i); for (int i=0; i < 2000; i++) DoSomething2(i);
When compiled with Visual C++, this gives an error: “Redefinition of ‘int i'”. Visual C++ treats the code as follows:
int i; for (i=0; i < 1000; i++) DoSomething(i); int i; // of course, this is a redefinition! for (i=0; i < 2000; i++) ...
According to the ANSI C++ standard, C++Builder is right and Visual C++ is wrong. However, the programmers of d3dtextr (and probably other files from the DX SDK) relied on the loop counter to be valid outside of the loop, as is the case with Visual C++:
for (int i=0; i < 1000; i++) DoSomething(i); for (i=0; i < 2000; i++) /* 'i' is undefined. It only exists in the first loop. */ DoSomething2(i); return i; /* 'i' is undefined. It only exists in the first loop. */
C++Builder will not compile this. The solution is to place “int i;” near the top of the function and to reference it in the loops:
int i; ... for (i=0...
Note: You can also emulate Visual C++ behavior by enabling “MFC compatibility” on the “Advanced compiler options” tab in “Project | Options”.
I hope this article could answer all your questions about using DirectX with C++Builder. If you have any questions, suggestions, or comments, feel free to e-mail me!
bcbddraw.zip: Complete source code for this article. A C++Builder Project demonstrating how to use Direct3D and C++Builder together.