General Best Practices
Following is a list of general best practices to follow, and pitfalls to avoid.
- Instead of using stack-allocated character buffers, it is a best practice to use the
TSTR, which in Unicode builds maps to the WStr class. For example instead of using the following:
void process_long_string(TCHAR* longstr)
{
TCHAR buf[256];
__tcscpy (buf, longstr);
}
You can use:
void process_long_string(TCHAR* longstr)
{
TSTR buf = longstr;
}
- 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 must 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 must 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 must not 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, it is a best practice to 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 certain 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 is 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 make sure that 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 type (for example, 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 are not sufficiently initialized to be evaluated. When implementing a post-load
callback (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 must not do a scene/node evaluation (for example, do not change the scene hierarchy
and do not call INode::EvalWorldState()) in a post-load callback.
- Do not place objects with non-trivial constructors, copy constructors, or destructors
in a Tab<> template.