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 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 ofClassDesc
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 ofassert()
.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 theWM_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 anIObjParams
interface is passed in. This interface pointer is defined to be valid until (and including) the modifier'sEndEditParams()
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 toObject::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
Use
dynamic_cast
instead of using the unsafestatic_cast
and always check the result for NULL to make sure that it succeeded.Use
Interface::ReleaseViewport
after you finish with aViewExp
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 Windows Message Filtering for more information.Use
P_VERSION
for parameter block descriptors and update the version number whenever theParamBlockDesc2
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 toHold::Suspend()
andHold::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.