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 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:
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.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 Windows 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:
INode::EvalWorldState()
) in a post-load callback.Do not place objects with non-trivial constructors, copy constructors, or destructors in a Tab<>
template.