Writing MAXScript Plug-ins

MAXScript plug-ins have the file extension .dlx. They can extend MAXScript in several ways, including:

Note: There are several sample DLX projects in the 3ds Max sdk howto and samples directories. See the MAXScript Extension Plug-ins topic for a list of these projects. The howto\intervalarray and howto\testdlx projects are simple examples that illustrate how to add different types of extensions.

Other plug-in types use the Function Publishing method to expose themselves to MAXScript. DLX plug-ins expose functions by extending the Primitive class, and using the def_visible_primitive macro from maxscript\macros\define_instantiation_functions.h:

// Declare C++ function and register it with MAXScript
#include <maxscript\macros\define_instantiation_functions.h>
    def_visible_primitive(IntervalArray, "IntervalArray");

Note: DLX plug-ins cannot be loaded or viewed in the Plug-in Manager. When writing and testing a DLX plug-in, you need to add the path to the compiled plug-in to the 3rd Party Plug-Ins paths (under Customize > Configure User and System Paths), and re-start 3dx Max.

DEF and DLL Entry

DLX plug-ins must define a DLL entry point that sets the language locale and disables threading when the DLL is loaded. This file also exports the DLL's initialization function, the description, and the version.

MAXScript plug-ins do not generally use ClassDesc so their .cpp and .def files are simpler as they do not export the functions GetClassDesc() and GetNumClasses().

The following is a typical MAXScript plug-in .def file:

LIBRARY IntervalArray.dlx
EXPORTS
   LibDescription       @1       PRIVATE
   LibInit              @2       PRIVATE
   LibVersion           @3       PRIVATE
SECTIONS
   .data READ WRITE

The following is the implementation of the IntervalArray howto sample DllMain.cpp file (in maxsdk/howto/maxscript/intervalarray/):

//DLLMain.cpp

#include <maxscript/maxscript.h>
HINSTANCE hInstance;
extern void IntervalArrayInit ();
// ========================================================
// Grab onto this DLL's instance handle
BOOL WINAPI DllMain(HINSTANCE DLLhinst, DWORD fdwReason, LPVOID lpvReserved)
{
    switch(fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        MaxSDK::Util::UseLanguagePackLocale();
        hInstance = DLLhinst;
        DisableThreadLibraryCalls(hInstance);
        break;
    }
    return TRUE;
}
__declspec(dllexport) void LibInit()
{
    //TODO: Put any code for initializing your plugin here. 
    //In this case it is IntervalArrayInit(), which will be defined in IntervalArray.cpp,
    //However in this example IntervalArrayInit() is empty.
    IntervalArrayInit();
}
__declspec(dllexport) const TCHAR* LibDescription()
{
    //TODO: Put code in here telling what your plugin does.
    return _T("Interval Array Function");
}
__declspec(dllexport) ULONG LibVersion()
{
    //Return the version of the Max SDK
    return VERSION_3DSMAX;
}

Note: There are several sample DLX projects in the 3ds Max sdk howto and samples directories. See the MAXScript Extension Plug-ins topic for a list of these projects. The howto\intervalarray and howto\testdlx projects are simple examples that illustrate how to add different types of extensions.

Other plug-in types use the Function Publishing method to expose themselves to MAXScript. DLX plug-ins expose functions by extending the Primitive class, and using the def_visible_primitive macro from maxscript\macros\define_instantiation_functions.h:

// Declare C++ function and register it with MAXScript
#include <maxscript\macros\define_instantiation_functions.h>
    def_visible_primitive(IntervalArray, "IntervalArray");

Note: DLX plug-ins cannot be loaded or viewed in the Plug-in Manager. When writing and testing a DLX plug-in, you need to add the path to the compiled plug-in to the 3rd Party Plug-Ins paths (under Customize > Configure User and System Paths), and re-start 3dx Max.

DEF and DLL Entry

The DLL entry definition is simpler than for other plug-in types, as MAXScript plug-ins do not generally use ClassDesc so their .cpp and .def files do not export the functions GetClassDesc() and GetNumClasses().

They only need to export:

The following is a typical MAXScript plug-in .def file:

LIBRARY IntervalArray.dlx
EXPORTS
   LibDescription       @1       PRIVATE
   LibInit              @2       PRIVATE
   LibVersion           @3       PRIVATE
SECTIONS
   .data READ WRITE

DLX plug-ins must define a DLL entry point that sets the language locale and disables threading when the DLL is loaded.

The following is the implementation of the IntervalArray howto sample DllMain.cpp file (in maxsdk/howto/maxscript/intervalarray/):

//DLLMain.cpp

#include <maxscript/maxscript.h>
HINSTANCE hInstance;
extern void IntervalArrayInit ();
// ========================================================
// Entry point for this DLL.  See https://docs.microsoft.com/en-us/windows/desktop/dlls/dllmain
// Here we set up the user language, store the DLL handle, and disable threading for system calls
// because 3dsmax.exe is not multi-threaded.
BOOL WINAPI DllMain(HINSTANCE DLLhinst, DWORD fdwReason, LPVOID lpvReserved)
{
    switch(fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        MaxSDK::Util::UseLanguagePackLocale();
        hInstance = DLLhinst;
        DisableThreadLibraryCalls(hInstance);
        break;
    }
    return TRUE;
}
__declspec(dllexport) void LibInit()
{
    // This function is called when the DLL is loaded to do any initialization. 
    // In this case it is IntervalArrayInit(), which is defined in IntervalArray.cpp,
    IntervalArrayInit();
}
__declspec(dllexport) const TCHAR* LibDescription()
{
    // Return a short description string here
    return _T("Interval Array Function");
}
__declspec(dllexport) ULONG LibVersion()
{
    // Returns the version of the Max SDK, used to check compatibility when loaded by Max
    return VERSION_3DSMAX;
}

Finally, here is the actual implementation of the IntervalArray type in IntervalArray.cpp:

#include <maxscript/maxscript.h>
#include <maxscript/foundation/numbers.h>
#include <maxscript/foundation/arrays.h>

void IntervalArrayInit()
{
    //Todo: Place initialization code here. This gets called when Maxscript goes live
    //during max startup.
}

// Declare C++ function and register it with MAXScript
#include <maxscript\macros\define_instantiation_functions.h>
    def_visible_primitive(IntervalArray, "IntervalArray");

Value* IntervalArray_cf(Value **arg_list, int count)
{
    //--------------------------------------------------------
    //Maxscript usage:
    //--------------------------------------------------------
    // <array> IntervalArray <Start:Number> <End:Number> <steps:Integer>
    check_arg_count(IntervalArray, 3, count);
    Value* pBegin = arg_list[0];
    Value* pEnd = arg_list[1];
    Value* pStep = arg_list[2];

    //First example of how to type check an argument
    if ( ! (is_number(pBegin)))
    {
        throw RuntimeError(_T("Expected a Number for the first argument, in function IntervalArray"));
    }
    if ( ! (is_number(pEnd)))
    {
        throw RuntimeError(_T("Expected a Number for the second argument, in function IntervalArray"));
    }
    //Second example of how to type check an argument
    integer_type_check(pStep, _T("Expected an Integer for the step size" ));

    float begin = pBegin->to_float();
    float end   = pEnd->to_float();
    int steps   = pStep->to_int();


    if (steps <= 0) { throw RuntimeError(_T("Expected a positive Number for Steps, in function IntervalArray"));}
    MAXScript_TLS* _tls = (MAXScript_TLS*)TlsGetValue(thread_locals_index);
    one_typed_value_local_tls(Array* rArray);
    vl.rArray = new Array (steps);

    float data = begin;
    float increment = ((end-begin)/steps);

    for (int i = 0 ; i <= steps ; i++)
    {
        Value* aTempNumber = Float::heap_intern(data);
        vl.rArray->append(aTempNumber);
        data += increment;
    }
    return_value_tls(vl.rArray);
}

See Also