Scripter-Callable Functions (SDK)

3DS Max Plug-In SDK

Scripter-Callable Functions (SDK)

Scripter-callable functions are represented by instances of the Function family of classes. These descend from Value and so are all first-class values in MAXScript. For the moment, this document only describes non-polymorphic primitive functions, of which there are three kinds.

Primitive functions are defined using one of the three declaration macros:

def_visible_primitive(fn, name);

def_mapped_primitive(fn, name);

def_lazy_primitive(fn, name);

corresponding to the 3 primitive function types. Mapped primitives are automatically mapped over collection first arguments and lazy primitives have their arguments delivered as unevaluated code fragments (see the Access to the Compiler and Interpreter topic for more details).

The first argument to these macros is the implementing C++ function, the second is a string literal giving the scripter-visible name for the function. This string winds up naming a MAXScript global variable that contains the Primitive instance representing the function.

All the def_x macros have several replaceable definitions in different header files. One is used to statically instantiate the Primitive object that represents the function, another to create externs in a header file. The convention throughout the MAXScript codebase is to gather sets of related scripter function definitions in 'protocol' files (eg, mathpro.h) and include in various places with different def_x macro definitions in force.

The def_x macro definition headers relevant to Primitive function defs are as follows:

"defextfn.h" - generate externs from def_x macros

"definsfn.h" - generate static instances from def_x macros

A side-effect of using the instantiating form of the def_ macros is that a scripter global variable named by the second argument is automatically created and loaded with the representing instance; you don't have to do anything else to make them visible in the scripter.

Example:

#include "definsfn.h"

def_visible_primitive( open_file,"openFile"); 

def_visible_primitive( query_box,"queryBox"); 

def_lazy_primitive ( quote,"quote"); 

def_mapped_primtive ( dump,"dump"); 

The implementing C++ functions you provide to these macros must have the following signature to conform to scripter calling conventions:

Value* func_cf(Value** arg_list, int count);

Further, the C++ function name is a derivative of the name given in the def_x macro (to avoid some naming conflicts). It is constructed by appending '_cf' to the name give, so:

def_visible_primitive( open_file, "openFile");

would refer to:

Value* open_file_cf(Value** arg_list, int count) { ... }

The arguments from the script call are delivered in call order in the arg_list array which is count Value*'s long. The function must return a Value*, which for otherwise void functions, should be the distinguished value ok.

Note that the caller-protects-new-values convention means that values in the arg_list array are already protected from the collector.

There is no implicit type or argument checking in this calling mechanism - you have to do it yourself in the called function. There are several macros to help with this:

check_arg_count(fn_name, wanted_count, got_count);

type_check(val, class, fn_name_or_description);

Example:

Value* open_file_cf(Value** arg_list, int count)

{

// check we have 1 arg and that it's a string

check_arg_count(openFile, 1, count);

type_check(arg_list[0], String, "openFile filename");

... 

char* fn = arg_list[0]->to_string();

... 

}

In this example, you could have left out the type_check() and let the to_string() converter complain if it didn't have a string, but the error message might be less descriptive.

MAXScript functions also support keyword argument passing. The keyword arguments are placed in the arg_list after all the positional args, preceded by a marker value (accessible in the Value* global, keyarg_marker). The arguments are passed in pairs, a keyword Name and the argument's value, one after the other in the arg_list array.

There are several macros to help extract keyword arguments. All assume that the C++ function parameter names are 'arg_list' and 'count' as shown in the above examples.

key_arg(key);

get the named arg value or &unsupplied if not present

 

key_arg_or_default(key, def);

get the named argument or the default value 'def' if not supplied

int_key_arg(key, var, def);

get named key arg into Value* 'var' variable and return it as a C++ int, return 'def' if not supplied.

float_key_arg(key, var, def);

get named key arg into Value* 'var' variable and return it as a C++ float, return 'def' if not supplied.

bool_key_arg(key, var, def);

get named key arg into Value* 'var' variable and return it as a C++ BOOL, return 'def' if not supplied.

There is also a macro for checking positional argument counts in the presence of keyword args:

check_arg_count_with_keys(fn, wanted, got);

Examples:

The following 'delete_file' example shows an arg count check, conversion of first argument (arg_list[0]) to a string and the return of MAXScript 'true' or 'false' values.

def_visible_primitive(delete_file, "deleteFile");

 

... 

 

Value*

delete_file_cf(Value** arg_list, int count)

{

// deleteFile "file_name"

 

check_arg_count("deleteFile", 1, count);

BOOL result = DeleteFile(arg_list[0]->to_string());

return result ? &true_value : &false_value;

}

The 'file_in' example shows working with an optional keyword argument 'quiet'. The required arg count is checked with check_arg_count_with_keys() in this case.

Two typed value locals are declared, one to hold the temporary FileStream instance and the other to hold the fileIn result.

def_visible_primitive(file_in, "fileIn");

 

... 

 

Value*

file_in_cf(Value** arg_list, int count)

{

// fileIn "filename" [quiet:true]

 

check_arg_count_with_keys("fileIn", 1, count);

two_typed_value_locals(FileStream* file, Value* result);

Value* quiet = key_arg_or_default(quiet, &true_value);

type_check(quiet, Boolean, "fileIn quiet:");

Value* result;

 

// open a temp fileStream

vl.file = (new FileStream)->open(arg_list[0]->to_string(), "rt");

if (vl.file == (FileStream*)&undefined)

throw RuntimeError (GetString(IDS_FILEIN_CANT_OPEN_FILE),

arg_list[0]); 

 

// pass it to the stream-based file_in

try

{

vl.result = file_in(vl.file, (quiet == &true_value));

}

catch (...)

{

// catch any errors and close the temp filestream

vl.file->close();

throw;

}

 

// pop value locals & return fileIn result

return_value(vl.result);

}

The implementation of the file_in() function is given as another example later in these notes.

Useful Globals

Interface* MAXScript_interface; -- MAX interface object

Exported Value* Virtual Functions

void print(); -- print representation to listener

void sprin1(CharStream* s); -- print representation on stream with no
-- terminating \n

Listener Window I/O

mputs(char*); -- print string to listener

mprintf(char* fmt, ...); -- printf to listener

MAXScript Exception Classes

Compile and runtime errors are reported in MAXScript using the C++ exception-handling mechanism. A MAXScript exception class hierarchy is defined for these errors, as follows:

class MAXScriptException

class UnknownSystemException

class SignalException

class CompileError

class SyntaxError

class TypeError

class NoMethodError

class AccessorError

class AssignToConstError

class ArgCountError

class RuntimeError

class IncompatibleTypes

class ConversionError

Common exception constructors:

AccessorError (Value* target, Value* prop)

ArgCountError (char* fn_name, int wanted, int got);

ConversionError (Value* val, char* typename);

IncompatibleTypes (Value* v1, Value* v2);

RuntimeError (char* description);

RuntimeError (char* desc_part1, char* desc_part1);

RuntimeError (char* description, Value* implicated_value);

RuntimeError (char* desc_part1, char* desc_part1,

Value* implicated_value); 

RuntimeError (Value* implicated_value);

TypeError (char* descr, Value* wrong_val,

ValueMetaClass* should_be = NULL); 

An error is signalled by throwing one of these exceptions, for example:

if (arg_list[0]->to_int() > max_index)

throw RuntimeError ("Index out of range: ", arg_list[0]);