views:

152

answers:

1

I have a custom plugin for 3ds Max that interfaces with some managed code on the back end. In some circumstances, I'd like to forward along a managed object to MAXScript for direct interaction, i.e. return a wrapped object from one of my functions.

MAXScript is able to manipulate managed objects directly relatively well through another plugin (msxdotNet) included with Max (I'm using 3ds Max 2008). It basically wraps an object and uses reflection for late bound calls, but it is totally self contained and doesn't have any sdk exposure. The plugin dll itself also does not expose anything more than the minimum interface required by Max for adding a few top level scripted classes.

The scripted classes allow one to instantiate a new object via a constructor

local inst = (dotNetObject "MyPlugin.MyClass" 0 0 "arg3")

In my case I already have an instance of an object that I'd like to use.

Is there a way to construct an instance of a dotNetObject wrapper from within my plugin to return to Max?


Idealy, I'd like to have a helper function with a (C++/CLI) signature similar to:

Value* WrapObject(System::Object ^obj);

Some basic guarantees that I can make:

  • The msxdotNet plugin is already loaded.
  • The msxdotNet plugin and my managed assemblies are in the same AppDomain.

The source for the msxdotNet plugin is included as an sdk sample, but for management/sanity's sake, modifying it and recompiling it is not an option.

A: 

I've solved this by leveraging the fact that any CLR object wrapped by dotNetObject will automatically wrap return values (method results and property values) with another wrapper. This even applies to static methods and properties on CLR types wrapped with dotNetClass.

Lets say I've already got a method in my plugin that lets me execute arbitrary MAXScript:

Value* EvalScript(System::String ^script);

Now I just need to serialize an object into a string and back again to an active object (a reference to the same object, not just a copy!).

I do this by grabbing the GCHandle of the object, using GCHandle::ToIntPtr to convert it to something blittable and using GCHandle::FromIntPtr to materialize the same object in a different context. Of course I am doing this in process (and in the same app domain), this wouldn't work otherwise.

Value* WrapObject(System::Object ^obj)
{
    GCHandle handle = GCHandle::Alloc(obj)
    try
    {
        return EvalScript(System::String::Format(
            L"((dotNetClass \"System.Runtime.InteropServices.GCHandle\").FromIntPtr (dotNetObject \"System.IntPtr\" {0})).get_Target()",
            GCHandle::ToIntPtr(handle));
    }
    finally
    {
        handle.Free();
    }
}

The comment I have explaining this in the real code is more that 10x as long as the actual code.

Corey Ross