Hello again, I’m trying to pass MAXScript objects to my assembly, but I’m not having much luck. For example I’d like to pass my selection (just 1 node) to my assembly. At first I created a method that looked like this:
public void Test(IINode node) {...} //I've tried INativeObject too
if I try it in Maxscript I get an error: MyAssembly.Test $ -- Runtime error: No method found which matched argument list I looked at the C++ SDK samples and they use Value**, I tried to use IValue as parameter too but that didn’t work. I assume it’s because in the C++ SDK Value is the base class of all objects, but this isn’t the case with the the .net version (I think?). I then had a probably-not-so-good idea of writing an extension method in C++ SDK to retrieve the address of the node, so I could pass it back to my assembly and use: GlobalInterface.Instance.INode.Marshal(handle); This is my C++ method: Value* MyFunction_cf(Value** arg_list, int count) { INT_PTR testAddress = (INT_PTR)&(*(arg_list[0])); return IntegerPtr::intern(testAddress); } I think this is correct as I get the first element in the Value* array, get the object it’s pointing to and then get its address. I cast this to an INT_PTR which is a __int64. Then in my assembly I have a method: public void Test(long nodeHandle) { IntPtr handleFromSDK = new IntPtr(nodeHandle); IINode node = GlobalInterface.Instance.INode.Marshal(handleFromSDK); string name = node.Name; } MAXScript: testPtrValue = MyFunction $ myAssembly.Test(testPtrValue) This crashes (System.AccessViolationException) this is probably because the address I have is wrong. The handleFromSDK isn’t the same as when I get get it from a Node I get by name. Do you know what I might’ve done wrong here?
I hope I’m overthinking this and there is a nicer solution? :) I'd like to pass BitArrays and stuff like that as well, not just Nodes like this example.
Thanks in advance, Vin |
| Just wanted to post an update, I've been able to get "better" results by first casting the Value* to an inode: INode* nodeTest = arg_list[0]->to_node(); INT_PTR nativePoint = (INT_PTR)(&(*(nodeTest))); return IntegerPtr::intern(nativePoint); However when I compare this address with the NativePointer address there is a 9 byte(?) difference. (e.g. c#: 764393721, C++: 764393712). Why is this the case? Would it be safe to juse 'offset' the C++ address?
Thanks! |
| Final update ;) I solved it by not casting it to a node after all. Returning the IValue object memory address. Then in my C# Assembly I do the following with the address: IValue value = global.Value.Marshal(new IntPtr(nodePointer)); IINode node = value.ToNode; Incase there is a better way to do this I'd be happy to hear it! :) |
| Glad to hear you found the solution. I took me a few readings over the code to understand it too. Anything consumed by and returned by a MaxScript function will be an IValue, it will never be an actual pointer to a raw data type. Therefore, you will always need to interpret the result as IValue first and then use its internal "to" functions to get the wrapped data type. IValue is a MaxScript wrapper around Max objects, much like .NET also wraps things in its own wrappers. Marsel Khadiyev (Software Developer, EPHERE Inc.) |
| Hey Marsel, I’m wondering how stable my way to pass MAXScript objects to my C# assembly is. I’m a bit worried the garbage collector might release the object and therefore making the address I get from C++ invalid. I’m not sure if this can ever happen because I’d still be holding a reference to my object so it probably wouldn’t be collected right?
This might not be an actual problem, but if I do this in Maxscript: test = 5 GetMemoryAddress test 1117854848
memAddressTestWindow.MyTestMethod 1117854848
This works nicely, but then I change the address so it's no longer valid, I'm able to catch the exception the value.To... Method throws, but after this Max is sort of broken. If I try to print my variable test I get a *System exception* and when I run the GC I get a "unknown error occured" messagebox. Do you know why this would be happening and if there is a way around this? Thanks in advance, Vin |
| You should never reuse a pointer to an IValue because its lifetime is controlled by MXS and it can be collected right after function execution (though usually it will hang around until garbase collector is called). What you should hold on to is the pointer to the object which it wraps. For example, myValue.ToNode will get you the node wrapper whose pointer you can safely store. It will be around until you delete the node in the scene (and restore object for its deletion is not deleted) or until the scene is reset. Marsel Khadiyev (Software Developer, EPHERE Inc.) |
| Hey Marsel, thanks for the reply!
I thought the IValue would hang around until the object it wraps would be collected (oops!). However I'm a bit confused how I have to proceed now. Right now in C++ I return a pointer to an IValue and then pass this to the c# assembly to marshal it back to an IValue, to then call the .ToSomething methods. But if I understand correctly I should, from C++, return a pointer to for example a BitArray BitArray bitArray = arg_list[0]->to_Bitarray(); INT_PTR nativePoint = (INT_PTR)(&(bitArray)); and then pass that pointer to C# but how would I then marshal that pointer back to an IBitArray? IBitArray doesn't have the .Marshal method. EDIT: I've checked a few more interfaces now that I got home and almost none of them have a Marshal method. (IMesh, IPoint3,...) EDIT2: I've changed my C++ extension method now as a test to return the address of an Mesh object instead of a Value wrapper object: Mesh* mesh = arg_list[0]->to_mesh(); INT_PTR testAddress = (INT_PTR)&(*(mesh)); I've compared this address to the one I can get from C# and it's the same. var node = GlobalInterface.Instance.COREInterface.GetSelNode(0); ITriObject triObj = node.ObjectRef.FindBaseObject() as ITriObject; IMesh mesh = triObj.Mesh; IntPtr meshPtr = mesh.NativePointer; //same as my C++ pointer - so it's correct This is just a test of course, I don't want to select the object in my assembly, I'd like to pass a mesh object to it instead. But how would I marshal this pointer to an IMesh object? |
| Hey Marsel, Sorry to keep bumping this, but I’m a bit lost here. I’m trying to store a pointer to the data the Value object is wrapping rather than a pointer to the Value (because of what you said). However, I’m not even sure how to get a pointer for the wrapped objects. //Get the Point3 value the Value* object is wrapping Point3 value = arg_list[0]->to_point3(); “value” is just a new object on the stack so I can’t take the address of that one as it’ll go out of scope after this method call. I’ve tried to change (but probably shouldn’t) the “to_point3” method from:
virtual Point3 to_point3() { ABSTRACT_CONVERTER(Point3, Point3); }
to virtual Point3* to_point3_2() { ABSTRACT_CONVERTER(Point3*, Point3); }
The X and Y properties are correct but Z is always zero, but I’m not sure if I can just change the methods like that.
Do you know how I could get a pointer to the object the value object is wrapping around? Thanks in advance, Vin |
| Hi Vin, I was referring to storing pointers for complex objects like INodes and ReferenceTargets. Point3/int/string/etc. are copied by value so you shouldn't have to worry about their lifetime or storing their pointers. Just use those as is. Marsel Khadiyev (Software Developer, EPHERE Inc.) |
| Hey Marsel,
Thanks! So I can store a Value object that is wrapping a point3/int/etc., wouldn't the Value object still be managed by MAXScript? The main goal was to pass MAXScript Point3 values to C#. I get the Value* and then pass that to C#, marshal it back to an IValue and then call .ToPoint3(), is this valid for simple types like that?
For INodes I'd store the Node pointer and marshal that one in C#, instead of the Value*.
Thanks again, Vin |
| Not the Value instances themselves, but their to_point3, to_integer, etc. Those values are safe o store. You're right, Value objects themselves are managed by MXS garbage collector. Marsel Khadiyev (Software Developer, EPHERE Inc.) |
| Thanks a lot for all your help! I won't be storing a Value*! :) One last question, how would I send a simple object like Point3 from MAXScript to C#? I assume the easiest would be to just pass the 3 values? (Gets a bit tricky with BitArray then I would think...).
For more complex objects I'll use the to_... Methods and store that pointer and marshal it in C# to the correct type. |
| Maybe I don't understand correctly, but couldn't you just do var myCSharpObject = mxsValue.to_point3; ? Marsel Khadiyev (Software Developer, EPHERE Inc.) |
| mxsValue being a IValue object correct? How would I get this object without storing a pointer to it from in C++ and then marshalling it in C# to a IValue object. Originally I had this: C++: Return the Value* MXS: Pass the Value* to C# assembly C#: Marshal Value* to IValue object. Use one of the to_... methods like you just showed to get the object. Is there another way to pass an IValue object from MXS directly to C#, without the C++ in between? I've tried having an IValue object as parameter like this: public void Test(Ivalue valueObject) and then using the .to methods on that one, but it doesn't work in MAXScript: myPoint = [5,5,5] [5,5,5] myAssembly.Test myPoint -- Runtime error: No method found which matched argument list |
| Ok, I think I understand better now. In this case marshaling to IValud through C++ is the only way I can think of for now. Or rather it is the simplest away to go about it. Marsel Khadiyev (Software Developer, EPHERE Inc.) |
| Thanks! Although the bad part would be that the Value* could become invalid between calls (C++ and C#), when it gets collected by MAXScript potentially. Anyway, thanks for all the help with this! |
| No problem. I don' think MXS GC would be invoked in the middle of your call. It is single threaded so it shouldn't be an issue. At least from what i know. Marsel Khadiyev (Software Developer, EPHERE Inc.) |
| But in theory it could be called between these 2 calls?: myValuePointer = GetMemoryAddress myPoint --get the memory address of the Value object wrapper around myPoint MXS GC? MyCSharpAssembly.SomeMethod myValuePointer --this one will marshal the address to IValue object |
| As long as myPoint is there in MXS it will not be collected so the example above should be fine. MXS will only collect those values that go out of scope and are no longer referenced by MXS itself. For example, if you declare a global variable in the listener it will never be collected until you close max or change its value to undefined. Marsel Khadiyev (Software Developer, EPHERE Inc.) |
| Aha! That clears a lot up about the garbage collector. Thanks a lot for all the info! :) |