DLL Functions Plug-Ins Must Implement

3DS Max Plug-In SDK

DLL, LIbrary Functions, and Class Descriptors

See Also: Class ClassDesc, Generate a Class_ID, List of ClassIDs, List of SuperClassIDs.

Overview

The following DLL and Library functions must always be implemented by 3ds max plug-in developers. They are called by 3ds max or Windows at load time and provide information about the DLL being loaded and the plug-in classes provided by DLL. These functions are:

DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved)

LibNumberClasses()

LibClassDesc(i)

LibDescription()

LibVersion()

This section presents information about each of these functions as well as the details of Class Descriptors. Class Descriptors provide information about plug-in classes and are used by developers in implementing the LibClassDesc() function.

The DllMain() Function

3ds max plug-ins are implemented as DLLs. DLL stands for Dynamic Link Library. DLLs are object code libraries that let multiple programs share code, data, and resources. When developers compile and link their plug-in code, the output is a DLL. The DllMain() function is implemented by the developer and called by Windows at startup when the plug-in DLL is loaded.

DLLMain()

This function is called by Windows when the DLL is loaded (as well as other times, for instance during rendering). Most plug-ins simply make two calls inside this function. These are to initalize the 3ds max custom user interface controls, and to initialize the Win95 common controls. The sample code below, which is typical for most plug-in types, shows how this is done. Developers can see the VC++ help for more details on the DllMain() function.

Note that the calls to initialize the controls are made only once. This is done by setting a global variable to TRUE when the controls have been initialized the first time, and checked each time the function is called.

As note above, this function may also be called many times during time critical operations like rendering. Therefore developers need to be careful what they do inside this function. In the code below, note how after the DLL is loaded the first time only a few statements are executed. This function should return TRUE.

int controlsInit = FALSE;

BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved)

 { 

 // Hang on to this DLL's instance handle.

 hInstance = hinstDLL;

 if (! controlsInit) {

  controlsInit = TRUE;

  

  // Initialize MAX's custom controls

  InitCustomControls(hInstance);

  

  // Initialize Win95 controls

  InitCommonControls();

 }

 return(TRUE);

 }

The Library Functions

These functions provide access to, and information about, the classes present inside the plug-in's DLL.

LibNumberClasses()

When 3ds max is first started, it looks for DLLs to load. When it finds one, it needs a way to determine the number of plug-in classes inside the DLL. The programmer provides this information to 3ds max by defining the LibNumberClasses() function. For example:

__declspec(dllexport) int LibNumberClasses() { return 1; }

The developer should return the number of plug-in classes inside the DLL.

LibClassDesc(i)

The plug-in must provide the system with a way to retrieve the Class Descriptors defined by the plug-in. Class descriptors provide the system with information about the plug-in classes in the DLL. The function LibClassDesc(i) allows the system to access the class descriptors. A DLL may have several class descriptors, one for each plug-in class. The function should return a pointer to the 'i-th' class descriptor. For example:

__declspec(dllexport) ClassDesc *LibClassDesc(int i) {

 switch(i) {

  case 0: return &MeltCD;

  case 1: return &CrumpleCD;

  default: return 0;

  }

 }

This example returns a pointer to the Melt plug-in class descriptor, or the Crumple plug-in class descriptor. See the section at the end of this topic for a discussion of the class descriptor returned by LibClassDesc(i).

LibDescription()

When a 3ds max file is loaded that contains an entity (procedural object, modifier, controller, etc.) that the system does not have access to (i.e. the DLL is not available), a message is presented to the user. The system requires that each DLL return a text string to present to the user if it is unavailable.

As an example, say a user has a Melt modifier. He applies the melt to an object in the scene, and saves the file. He then give this file to a friend who does not have the Melt DLL. When the friend loads the file, the system will put up a message indicating an entity exists in the file that relies upon a DLL that cannot be found. This message could be something like: "Melt Modifier. To obtain a copy, call 1-800-PLUG-INS". To provide this string to the system, the DLL must implement the function LibDescription(). This function returns a string to be presented if the DLL is not found. This string is also presented in the dialog presented to the 3ds max user via the File / Summary Info / Plug-In Info... command. Once a plug-in from the DLL has been used in the scene, the system stores the string in the 3ds max file (since it must be presented when the DLL is unavailable).

Note that the scene is still loaded even if the DLL is not present. 3ds max preserves any entities for which the DLL is not found. In this way, if the file is modified and saved, then the user obtains the DLL and loads the modified file, the entity is still present and linked in to the scene. Entities which are loaded without access to their DLL are referred to as orphaned entities.

The orphaned entity will be loaded by a generic 'stand-in' for its SuperClass. The stand in will display a minimal scene representation. For instance, if the entity is a modifier, it will display its name in the modifier list, but no parameters will be presented. Missing object-types have dummy-type representations in the scene. They can be moved, rotated, scaled, linked, grouped, deleted: anything that just involves the node. Missing controllers only provide constant default values, which are not adjustable.

Also see the Advanced Topics section on Read Only Plug-Ins. By allowing your plug-in to function in read-only mode, users can freely distribute the DLL, but unless they are authorized to run based on the ID of a specific hardware lock they will have restricted use until the user purchases their own copy. Below is a sample implementation of this function.

__declspec( dllexport ) const TCHAR *LibDescription() {

 return _T("Melt Modifier. Call 1-800-PLUG-INS to obtain a copy");

 }

LibVersion()

Developers must implement a function that allows the system to deal with obsolete versions of 3ds max plug-in DLLs. Because the 3ds max architecture supports such a close relationship to its plug-ins, the system may at some point need to prevent older plug-in DLLs from being loaded. To allow 3ds max to accomplish this, the DLL must implement a function called LibVersion(). This function simply returns a pre-defined constant indicating the version of the system under which it was compiled. Future versions of 3ds max may update this constant, yet the older DLLs will always return the previous value. With this function the system can check if an obsolete DLL is being loaded, and if so put up a message.

__declspec( dllexport ) ULONG LibVersion() { return VERSION_3DSMAX; }

Note: The meaning of VERSION_3DSMAX has been enhanced in R2 to include information about the current 3ds max release as well as the 3ds max API release number and the 3ds max SDK release number.

The upper word now contains the 3ds max release number multiplied by 1000, and the lower word is API number and SDK revision within the API version. 3ds max will only load DLL's with the API number that is current.

The version number is composed as follows:

#define VERSION_3DSMAX ((MAX_RELEASE<<16)+(MAX_API_NUM<<8)+MAX_SDK_REV)

Each part is describes below:

MAX_RELEASE

This is the 3ds max release number multiplied by 1000.

MAX_API_NUM

This is the 3ds max API number. When a change in the API requires DLLs to be recompiled, this number is incremented and MAX_SDK_REV is set to 0. This will make all DLLs with the former MAX_API_NUM unloadable.

MAX_SDK_REV

This denotes the revision of the SDK for a given API. This is incremented when the SDK functionality changes in some significant way (for instance a new GetProperty() query response is added), but the headers have not been changed.

Note: A developer may access this value using the global function:

Function:

DWORD Get3DSMAXVersion();

Remarks:

Returns the state of the VERSION_3DSMAX #define from \MAXSDK\INCLUDE\PLUGAPI.H when the running version of 3ds max was compiled.

The following macros may be used for extracting parts of VERSION_3DSMAX:

#define GET_MAX_RELEASE(x) ((x>>16)&0xffff)

#define GET_MAX_API_NUM(x) ((x>>8)&0xff)

#define GET_MAX_SDK_REV(x) (x&0xff)

#define GET_MAX_SDK_NUMREV(x) (x&0xffff)

There is also a function you can call to find out which application your plug-in is running on -- either 3ds max or VIZ.

Function:

APPLICATION_ID GetAppID();

Remarks:

This function is available in release 2.5 and later only.

This function returns the ApplicationID, either VIZ or MAX. If a plug-in is designed to work only in one product, then it could call this function inside ClassDesc::IsPublic() to switch between exposing the plug-in or not.

For example:

IsPublic() { return(GetAppID()==kAPP_VIZ?1:0); } // VIZ only

Return Value:

One of the following enum values:

kAPP_NONE

kAPP_MAX

kAPP_VIZ

Library Function Summary

The plug-in must implement these five functions: DllMain(), LibNumberClasses(), LibClassDesc(i), LibDescription(), LibVersion(). These functions allow the system to determine information about the plug-ins contained in the DLL.

The section below provides information about the Class Descriptors which must be returned by LibClassDesc(i).

Class Descriptors

Class descriptors provide the system with information about the plug-in classes in the DLL. A method of the class descriptor is also responsible for allocating new instances of the plug-in class. The developer creates a class descriptor by deriving a class from ClassDesc and implementing several of its methods. Below is a sample class descriptor and the declaration of a single static instance.

class MeltClassDesc : public ClassDesc {

 public:

 int     IsPublic() { return TRUE; }

 void *    Create(BOOL loading=FALSE) { return new MeltMod(); }

 const TCHAR *  ClassName() { return _T("Melt"); }

 SClass_ID   SuperClassID() { return OSM_CLASS_ID; }

 Class_ID   ClassID() { return Class_ID(0xA1C8E1D1, 0xE7AA2BE5); }

 const TCHAR*  Category() { return _T(""); }

 };

static MeltClassDesc MeltCD;

The following six methods of the class descriptor are described below: IsPublic(), Create(), ClassName(), SuperClassID(), ClassID() and Category(). Developers wishing to review the entire set of methods should see Class ClassDesc.

IsPublic()

This method returns a Boolean value. If the plug-in can be picked and assigned by the user, as is usually the case, return TRUE. Certain plug-ins may be used privately by other plug-ins implemented in the same DLL and should not appear in lists for the user to choose from. These plug-ins would return FALSE.

Create(BOOL loading=FALSE)

3ds max calls this method when it needs a pointer to a new instance of the plug-in class. For example, if 3ds max is loading a file from disk containing a previously used plug-in (procedural object, modifier, controller, etc.), it will call the plug-in's Create() method. The plug-in responds by allocating a new instance of its plug-in class. As the sample implementation above shows, simply use the new operator.

The optional parameter passed to Create() is a flag indicating if the class being created is going to be loaded from a disk file. If the flag is TRUE, the plug-in may not have to perform any initialization of the object because the loading process will take care of it. See the Advanced Topics section on Loading and Saving for more information.

When the system needs to delete an instance of a plug-in class, it usually calls a method named DeleteThis(). Plug-in developers must implement this method as well. Since a developer uses the new operator to allocate memory, he or she should use the delete operator to de-allocate it. For instance, the developer would implement DeleteThis() as follows:

void DeleteThis() { delete this; }

See the Advanced Topics section on Memory Allocation for additional details.

ClassName()

This method returns the name of the class. This name appears in the button for the plug-in in the 3ds max user interface.

SuperClassID()

This method returns a system-defined constant describing the class that this plug-in class was derived from. For example, the Bend modifier returns OSM_CLASS_ID. This super class ID is used by all object space modifiers. Some other example super class IDs are: CAMERA_CLASS_ID, LIGHT_CLASS_ID, SHAPE_CLASS_ID, HELPER_CLASS_ID, and SYSTEM_CLASS_ID. See List of Super Class IDs for the entire list of available super class IDs.

ClassID()

This method must return the unique ID for the plug-in object. A program is provided with the SDK to generate these ClassIDs. It is VERY important you use this program to create the ClassIDs for your plug-ins. If you use one of the source code examples to create your plug-in, you MUST change the existing Class_ID. If you don't, you'll get a conflict. If two ClassIDs conflict, the system will only load the first one it finds (and will post a message when it attempts to load the second one noting that there is a Class_ID conflict).

A Class_ID consists of two unsigned 32-bit quantities. The constructor assigns a value to each of these, for example Class_ID(0xA1C864D1, 0xE7AA2BE5). See Class Class_ID for reference information.

Note that the sample code plug-ins used in 3ds max use 0 as the second 32-bit quantity of the Class_ID. Only the built-in classes (those that ship with MAX) should have the second 32 bits equal to 0. All plug-in developers should use both 32 bit quantities. Again, make sure you use the program provided to create your ClassIDs. This will ensure no conflicts arise between plug-ins. To generate a random Class_ID and optionally copy it to the clipboard, click Generate a Class_ID.

Category()

The category is selected in the bottom-most drop down list in the create branch of the command panel. If this is set to be an existing category (i.e. "Standard Primitives", "Particle Systems", etc.) then the plug-in will appear in that category. Developers should NOT add to the categories provided by 3ds max (see the note below). If the category doesn't yet exist then it is created. If the plug-in does not need to appear in the list, it may simply return a null string as in _T(""). Category() is also used for modifiers to classify them in the button sets dialog.

Important Note: The 3ds max architecture has a limit of 12 plug-ins per category in the Create branch. To prevent a problem with too many plug-ins per category, developers should ALWAYS create a new Category for their plug-ins, rather than using one of the existing ones used by the standard 3ds max plug-ins. Note that versions of 3ds max prior to release 1.2 would crash if there were more than 12 buttons per category.