From 3D Studio MAX to Direct 3D, Part 2: How to Create a Plug-In
The series continues as we examine how to integrate MFC inside the plugin. A simple example of a Utility Plugin is included.
This time we're going to talk about how to develop a MAX plug-in. Enclosed with this article are two samples of a simple Utility plug-in -- one for the Visual C++ 4.x / Max 1.x, and the other one for Visual C++ 5.x / Max x.x. These samples will give you an overview of an exporter plug-in's skeleton. To save time, you can personalize them for your own Utility plug-ins.
plug-ins Overview
MAX offers many types of plug-ins. In MAX, a plug-in is a DLL which is explicitly loaded during the MAX's startup. Every type of plug-in has a specific file extension (i.e. .DLU for Utility plug-ins, .DLM for Modifier plug-ins, .DLO for procedural Object plug-ins, etc…). This extension is used by MAX to differentiate common DLLs from plug-ins.
Normally, plug-ins written by third party developers are stored in the \plug-ins directory of MAX. To generate a .DLU DLL stored in the \plug-ins directory of MAX, a VC++ DLL project should be used. Each time a plug-in is recompiled, you have to take care that MAX is not running (or the new DLL version won't be generated as the previous is already running). MAX's startup won't last too long if your computer owns enough RAM for everything (VC++, your project and MAX) to stay continuously inside. For people who never used DLLs here's a brief reminder.
Windows DLLs
A Dynamic Linked Library can be seen as an executable (.EXE) which contains functions callable by another executable. Like an executable, it can also contain its own resources (dialog template, string, icons, etc…). Standard DLLs only give you the possibility to export C-like functions. With Visual C++'s MFCs you can create a new kind of DLL called 'MFC Extension' where it's possible to export C++ classes. When a client program uses a DLL's functions, the DLL's header file must be included in the .cpp files that use the functions, and the DLL's library (file extension .lib) must be linked to the project. Only functions that were declared to exportation can be used by a client program. Exportation declarations can be done using .def file (for both regular and extension DLLs) or using Microsoft's specific tokens (for extension DLLs only).
As MAX is a pure Win32 program, DLLs are not supposed to use MFC and must be of the Regular type only. Using the .def file of your plug-in's project, four functions have to be declared in exportation. You'll have to edit the .def to make it looks like :
LIBRARY themaxplug-in
EXPORTS
LibDescription @1
LibNumberClasses @2
LibClassDesc @3
LibVersion @4
SECTIONS
.data READ WRITE
Each exported functions are associated with a number that will be used to make the dynamic link when the client (MAX) will call the DLL's functions. The most important function is the LibClassDesc(). It's used to returned an object that will be used by MAX to create the plug-in.
MAX is not a MFC based program, which leads to some difficulties when you want to use MFC inside your plug-in. As I'm a lazy programmer, I like to use MFC in my programs, so I'm going to describe the steps for the creation of a MFC based plug-in.
Typical steps to create a MFC based utility plug-in
There's two ways to create a MFC based plug-in: the first with static linking of MFC DLLs, and the second with shared MFC DLLs. Both have advantages and disadvantages. For static linking, the MFC functions and resources will be statically linked to your plug-in. This will, however, make your plug-in bigger in size. The advantage is that you can use MFC transparently, like a typical MFC program. For shared MFC DLLs (also called dynamic linking of MFC DLLs), the advantage is you keep a short DLL, but as your plug-in is also a DLL, the utilization of MFC resources needs some extra code, as nested DLLs are not managed transparently concerning resources. I'll explain the first method only, as its implementation is easier, and more understandable. Your plug-in will increase in size by about 100kb more than with the second method.
For the following steps, you can check the samples provided for more details.
1: | Create a new project based on a MFC AppWizard DLL, choose static linked of MFC. |
2: | Edit the generated .def file as described below. |
3: | Create a Dialog Resource Template for the plug-in's panel, the width must be 104 units. |
4: | Include the max.h header file in the DLL's main file. |
5: | Override (using class wizard) the InitInstance and ExitInstance of the CApp class create by the AppWizard. Add the following code to the InitInstance method: //Get the DLL's HINSTANCE HINSTANCE hInstance = AfxGetResourceHandle(); //Init 3DS Max Custom Controls InitCustomControls(hInstance); //Init Win32 ControlsInitCommonControls(); return TRUE; |
6 | Change compilation settings to generate the DLL in the MAX's \plug-ins directory. Don't forget to change the DLL's extension to .DLU. |
7: | Add the MAX's "core.lib" and "util.lib" library files to your project. |
8: | Create a plug-in's description class. Look at the PlugDesc class of the sample. |
9: | Create a plug-in's panel class. Refer to the sample's PlugPanel class. |
10: | Generate a new Class ID using the Kinetix's "gencid" program and define a constant of it in your plug-in's header file. i.e. : #define TESTPLUG_CLASS_ID Class_ID(0x24f21f8d, 0x59140b2b) |
11: | For the Panel class and miscellaneous code implementations, refer to the sample's "PlugTest.cpp" and "PlugTest.h" files. |
Once you succeed at compiling your plug-in, you have to test it, and if somehow it hangs (it happens, sometimes…) you may want to debug to see what's going on.
Debugging is an inevitable step of programming, and even if your program doesn't hang, it's always interesting to trace a program to see how everything works. Debugging a MAX's plug-in is not as easy as debugging a program. Normally, you choose the "Debug" compilation mode when you're in the debugging phase of your project, but it's not something you can do with MAX's plug-ins. Visual C++ proposes two compilation modes : Debug and Release. Typically, you develop your project using the Debug Mode, because it offers many advantages like source level debugging, special memory management, crash protection/checking, etc… Once you finish your project, you compile it using the Release Mode which optimizes your code and suppresses debug information and debug purpose checking to make your project shorter and faster at execution.
So, where's the problem? Memory heaps are not managed the same way if you compile in Release or Debug mode. And, if a Release compiled project frees a heap allocated with a Debug compiled project, a crash will occur. The version of MAX you're using was compiled using the Release Mode. It's impossible to compile your plug-in using the Debug Mode. The only choice your have is to create a new compilation Mode that manages heaps the same way as the Release Mode, and which generates Debug Information for being debugged. For the sample, I've called this new compilation mode "Special 3DSMax". To view the setting use the "Settings…" command of the VC++'s "Project" menu.
Provided are the steps to create this new compilation mode:
1: | Select the "Configurations…" command of the "Build" menu. |
2: | For your plug-in's project, choose "Add…" |
3: | Choose a name for your new compilation mode, copy settings from the Release Mode, keep the Win32 platform. |
4: | Edit the Setting (ALT+F7) of your plug-in. Choose your new compilation mode. |
5: | In the "C/C++" tab, change the "Debug Info" to "Program Database", set the "Optimizations" to "Disable(Debug)". |
6: | In the "Link" tab, check the "Generate Debug Info" check box. |
As the Debug Compilation Mode can't be used for the plug-in, you can remove it by repeating Step 1, selecting the Debug Mode of your plug-in, and clicking the "Remove" button. Some features that were available in Debug Mode won't be used with this mode:
- | Special memory management, memory leak display at debug exit |
- | TRACE macros. |
- | ASSERT, ASSERTVALID macros. |
As the setting was taken from the Release Mode, the Preprocessing define "NDEBUG" is declared. You can remove it ("C/C++" tab of the Project setting panel, "Preprocessor definitions") and set a new define (ie "SPECIALMAX" )
The Sample
There's two zip files :
- | PlugTest_VC4.zip, for Visual C++ 4.x, with the MAX 1.x SDK. |
- | PlugTest_VC5.zip, for Visual C++ 5.x, with any version of the MAX SDK. |