Following is a list of general best practices to follow, and pitfalls to avoid.
- Compile your plug-in with the supported version of Visual C++. See 3ds Max SDK Requirements for a list of supported compilers.
- Controller plug-ins should implement ILockedTrack. See Animation Track Locking for more information.
- Use Interface::GetAppHFont() to get the appropriate default font settings.
- Use ClassDesc2 instead of ClassDesc to take advantage of the Function Publishing API.
- Never store asset paths as strings. Instead use an instance of MaxSDK::AssetManagement::AssetUser.
- Use DbgAssert() instead of assert().
- Plug-ins should always store parameters and serializable data in a parameter block.
- Always use gencid.exe to generate new class ids for code based on samples and tutorials.
- Pay special attention to interpreting correctly the lParam parameter send with the WM_NOTIFY message passed to hook procedures registered for file I/O dialogs. See Handling WM_NOTIFY Messages for more information.
- Plug-in finalization code should never make a call to a global utility plug-in (a class derived from GUP) because these are unloaded first.
- When writing objects that support interfaces we recommend that you derive from FPInterface so that you can provide function publishing functionality either immediately or in the future.
- Be careful when storing interfaces passed to a method by 3ds Max. In some cases the validity of these interfaces is limited
to the duration of the function call. Some examples include:
- When a modifier becomes active in the modify branch of the command panel, its BeginEditParams() method is called and an IObjParams interface is passed in. This interface pointer is defined to be valid until (and including) the modifier's EndEditParams() method is called. If a plug-in were to hang on to the pointer outside of this interval and then call one of its methods,
the result would be undefined.
- The INode interface pointer, which when passed in to Object::Display(), is valid for only the duration of the function call.
- When a modifier becomes active in the modify branch of the command panel, its BeginEditParams() method is called and an IObjParams interface is passed in. This interface pointer is defined to be valid until (and including) the modifier's EndEditParams() method is called. If a plug-in were to hang on to the pointer outside of this interval and then call one of its methods,
the result would be undefined.
- The INode interface pointer, which when passed in to Object::Display(), is valid for only the duration of the function call.
- Use dynamic_cast instead of using the unsafe static_cast and always check the result for NULL to be sure it succeeded.
- Use Interface::ReleaseViewport after you finish with a ViewExp pointer to avoid causing a memory leak.
- Be careful to always use the ParamID to access individual parameters in a parameter block (IParamBlock).
- Only use a simple value types (e.g. primitive types or classes that do not manage memory) or pointers in a Tab<> collection.
- Use MaxSDK::WindowsMessageFilter for filtering windows messages in progress dialogs. See Progress Dialog Message Filtering for more information.
- Use P_VERSION for parameter block descriptors and update the version number whenever the ParamBlockDesc2 is changed
- Never change parameter IDs, instead just add new parameter IDs and deprecate old ones
- When writing modifiers avoid using PART_ALL when specifying the channels required and modified. Instead explicitly set the correct channel flags.
- Do not throw uncaught exceptions in 3ds Max plug-ins.
- Surround all calls to ShapeObject::CopyBaseData() with calls to Hold::Suspend() and Hold::Resume(). See Calling CopyBaseData().
- If a scene evaluation is performed in a post-load callback, it is possible that other objects aren't sufficiently initialized
to be evaluated. When implementing a post-load callback (i.e. a class derived from PostLoadCallBack) there are two considerations to assure 3ds Max stability during scene file loads:
- Your object must be safe to evaluate if it is evaluated before your post-load callback is run.
- You should not do a scene/node evaluation (e.g. perform no changes to a scene hierarchy and don't call INode::EvalWorldState()) in a post-load callback .
- Your object must be safe to evaluate if it is evaluated before your post-load callback is run.
- You should not do a scene/node evaluation (e.g. perform no changes to a scene hierarchy and don't call INode::EvalWorldState()) in a post-load callback .
- Do not place objects with non-trivial constructors, copy constructors, or destructors in a Tab<> template.