3ds Max .NET SDK and creating reference maker - c#

I have .Net DLL for Max with ui, and I want to react to parameter changes of some nodes in the viewport. The easiest solution that came up to me, was to create ReferenceMaker plugin and set reference for node I want to watch. According to the documentation it should be
public class ReferenceListener : Autodesk.Max.Plugins.ReferenceMaker{ ... }
But when I create new instance of this class and try to set reference, it crashes on "Object reference not set to an instance of an object."
When I try to debug it, I see that all baseclass attributes are null, so it seams ReferenceMaker plugin instance was not created in Max.
Finally I found MaxSharp source code here but using resulting dll let me to the same result and frankly the implementation is quiet the same as I had before. Trying to attach ReferenceListener to ReferenceTarget allways crashes beacause of nulls in base class.
So I really don't know how to solve this, but maybe someone tried to create something like this and succedded? For now I'm thinking about writing those parameter changes callbacks to maxscript, and call .net form it, but it feels hacky..
I'm using Max 2014 (and MaxSharp is for 2013) but I did not found any differences mentioned in documentation and any help would be appreciated.
Thank you
UPDATE
So, I narrowed it down to really strange problem. I've created C++/CLI plugin, made ReferenceMaker class in C++ SDK and did .net wrapper to call the plugin form C#, but it still wasn't working with same symptoms.
It seems that wrong pointer address is stored inside Autodesk.Max wrapper objects, so this is the reason why it is failing. I did a comparison of pointer returned from .Net DLL and from C++ SDK, and they are always different by 64. And it is always like that.
C++/CLI code
IINode^ al = Autodesk::Max::GlobalInterface::Instance->COREInterface->GetSelNode(0);
IReferenceTarget^ ak = (IReferenceTarget^)al;
ReferenceTarget* nativeTarget = (ReferenceTarget*)ak->Handle.ToPointer();
m_notifyListener->Test(nativeTarget);
C++ Max SDK code
void NotifyListener::Test(RefTargetHandle managedPointer)
{
Interface* ip = GetCOREInterface();
RefTargetHandle nativePointer = ip->GetSelNode(0);
intptr_t P1 = (intptr_t)managedPointer;
intptr_t P2 = (intptr_t)nativePointer ;
}
and than resulting variables are eg.
P1 = 1490452112
P2 = 1490452048
P2 is always smaller than P1 by 64.
I would understand if those pointers were totally different, but this slight shift is really strange to me.
Does anyone have any idea what is happening there? This is something I really don't get.
I need to test if the same behaviour is in Max 2013 or 2015 as I'm using 2014. I saw on some other forums that other people are complaining sample .net plugins are not working in 2014, so maybe this is the reason?
Thank you for any advice.

Related

C# Reflection inconsistency with COM objects

Having spent the last few days reading everything I can find about C# reflection on COM objects, trying many experiments in code, and analyzing sample code to try and improve my understanding, I am now forced to admit that I just don't know enough, so I am requesting help from the Community.
I need to be able to access and update the properties of a late bound COM object that is wrapped as System._COM Object.
I tried all the standard refection stuff without success and I looked through using IDispatch, but I'm not comfortable with using the pointers involved, so I'm hoping I have missed something pretty simple in the normal interface. I found papers on MSDN that DO show how to do what I need, but all the examples are in C++ and it is over my head.
It would be really helpful if someone could explain why the following simple C# code just doesn't work as expected:
try
{
// late binding:
// localCB is a COM object (System._COMObject) created by Activator.CreateInstance() from
// the ProgID of a registered COM .DLL.
//
// The original .DLL has a string PROPERTY called
// "TESTEXTERNAL1". localCB has an IDispatch Interface. The original COM .DLL has a separate Typelib,
// and, although I did not register the typelib separately, I can see from OLEView on the COM object
// that the GUID for the typelib is included in it.
// Here's the code that is puzzling me...
var vv = localCB.GetType().InvokeMember("TESTEXTERNAL1", BindingFlags.GetProperty,
null, localCB, null);
string rt = vv.ToString();
// works exactly as expected and returns the value of TESTEXTERNAL1 - OK.
// now try and update the SAME PROPERTY, on the SAME COM object...
Parameters = new Object[1];
Parameters[0] = "Hello, World!";
localCB.GetType().InvokeMember("TESTEXTERNAL1", BindingFlags.SetProperty,
null, localCB, Parameters);
// throws an (inner) exception: HRESULT 0x8002003 DISP_E_MEMBERNOTFOUND !!!
}
catch (Exception xa)
{
string xam = xa.Message;
}
Is it unreasonable to expect an object that has already found and provided a property, to be able to update the same property? Is there some "alternative update" strategy that I am not aware of?
Many thanks for any help,
Pete.
UPDATE:
in response to Jon's request, here are snippets of the OleView:
(I had to use images because Oleview would not let me cut & paste, sorry...)
OleView of the COM .DLL
OLEView typelib view
Jon, I think you have correctly identified that the problem is with a setter method. The DLL is written in Fujitsu COBOL and provides an "under the covers" GET and SET for fields identified as PROPERTY. Accessing the COM component from C# or COBOL, it works fine, but, as you can see, it doesn't work when I try and access it for SET with reflection. Because I am unfamiliar with using reflection I was doubtful whether I had the syntax right, so I tried to make the SET as close as possible to the GET. I think I will need to generate my own SET methods (for each PROPERTY) into the COBOL and then change my "BindingFlags.SetProperty" to be "BindingFlags.InvokeMember". (I did the homework on BindingFlags and found that if you specify "SetProperty" it automatically implies the other 2 flags you mentioned.)
I think the key to it all is in recognizing that the problem is with the Fujitsu *COM Class SET, and it took your experienced eye to see that. Many thanks. If you have any other comments after seeing the OLEView, or can suggest any alternative approach in order to get the properties set, I'd be very interested. (I'm not looking forward to having to generate SETter methods for every property; it smacks of brute force... :-))
Thanks again,
Pete.
Hans was correct. The problem was with the setter method. I have written code to generate a setter for each of the properties, back in the original COBOL COM component. It wasn't as tedious or ugly as I thought it would be (about 7 lines of COBOL for each PROPERTY) and it is all working very well now. Many thanks to the community and particularly Hans Passant for support.

Best way to run a string as c# code

Let's say I have:
#{
var str= "DateTime.Now";
}
I want to process this string as a c# code
#Html.Raw(App.ProcessAsCode(str));
The output should be the current date time.
Final Edit:
Based on further information - if the goal here is to simply have a formatting engine there are lots of options out there. One such option is based around the .liquid syntax from shopify (see here). You can find a .NET port of this on gitHub here: https://github.com/formosatek/dotliquid/. The main purpose of this is to turn something like:
<h2>{{product.name}}</h2>
Into something like:
<h2>Beef Jerky</h2>
I would strongly recommend reading more about the liquid engine and syntax and I believe this will lead you in the right direction. Best of luck!
Initial Answer
This is definitely possible - although as others have said you will want to be careful in what you do. Using C# the key to compiling and running code generically is the "CSharpCodeProvider" class. Here is a brief example of how that looks:
string[] references = { "System.dll" };
CompilerParams.ReferencedAssemblies.AddRange(references);
var provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, formattedCode);
In this example, "formattedCode" is a string with the C# code. Any references must be manually added. For the full example see this stack question (How to get a Type from a C# type name string?).
NOTE -- If all you are looking to do here is a format string or something simple like that you might have the user pass in a .NET format string (eg "MM/dd/yyyy"), then use that in a call to the "ToString" method. That would provide the user some configurability, while still making sure your system stays secure. In general running code on a server that hasn't been properly checked/escaped is really dangerous!
Reference - For your reference, the current msdn page for CSharpCodeProvider also has some examples.
Another option would be using a dynamic language such as IronRuby or IronPython.

A nasty COM interop problem in VSIX

For some time now, I've observed an intermittent COM problem in my VSIX package for Visual Studio 2010. Attempting to subscribe to one of the IDE's COM-based event sinks randomly throws the following error:
"COM object that has been separated from its underlying RCW cannot be used"
A repro case boils down to this code (which must be used in VSIX, obviously):
using System;
using EnvDTE;
using EnvDTE80;
class Test
{
private readonly Events _events;
private readonly Events2 _events2;
private readonly BuildEvents _buildEvents;
private readonly ProjectItemsEvents _projectItemsEvents;
public Test(IServiceProvider provider)
{
var dte = (DTE)provider.GetService(typeof(DTE));
var dte2 = (DTE2)dte;
// Store all references in fields as a GC precaution.
_events = dte.Events;
_events2 = (Events2)dte2.Events;
_buildEvents = _events.BuildEvents;
_projectItemsEvents = _events2.ProjectItemsEvents;
// Proceed to subscribe to event sinks.
_buildEvents.OnBuildBegin += BuildBeginHandler; // BOOM!
_projectItemsEvents.ItemAdded += ItemAddedHandler;
}
private void ItemAddedHandler(ProjectItem projectItem) { }
private void BuildBeginHandler(vsBuildScope scope, vsBuildAction action) { }
}
I've learned about a possible cause from numerous descriptions of similar problems that can be found on the net. It's basically a side effect of the way Runtime Callable Wrappers and GC interact during COM interop. Here's a link to a similar problem complete with explanation.
I'm fine with that explanation, especially because it suggests an easy workaround - storing the event sink reference in a field in order to prevent it from being prematurely GC'ed. Indeed, many people seem to have solved their problem this way.
What bothers me is that it doesn't work in my case. I'm really stumped as to why. As you can plainly see, I already store all object references in fields as a precaution. Yet the error still occurs. I tried being even more explicit using GC.KeepAlive() calls at the end of the ctor, but to no avail. Is there anything else left to do?
Without a solution, my VSIX randomly fails to load, leaving the user with a single option: to restart Visual Studio and hope it doesn't happen the next time.
Any help will truly be appreciated!
Well, I gave up and simply did the only thing that crossed my mind. I figured that since this is obviously a race condition I can't affect in a predictable manner, I might as well reenter the race if I lose.
So I moved the subscription lines into a while loop that try..catch-es them and retries after a bit of Thread.Sleep(). The loop exits either when both subscriptions succeed or when I've been continuously losing the race for more than 2 seconds.
The kicker is, I haven't lost the race once since I've implemented the change. A true Heisenbug, if I ever saw one.
Anyway, I'm going to stick with this until a proper solution occurs to me or the bug reappears.
I suspect that your problem is really that you are attempting to wire up your event handlers too soon. You normally need to be doing these sorts of things in the Initialize method of your package / toolwindow / whatever - generally speaking if you need to use a service you need to do if after the Initialize method has been called, definitely don't do this in the constructor of your Package.
(This is just a hunch - your Test class doesn't implement any VSX interfaces and so I can't see from your sample when the constructor is being called)

Error calling C++/CLI constructor from C#

I am trying to save and restore state by using a StateBlock in SlimDX via the following snippet:
StateBlockMask mask = new StateBlockMask(null) { RasterizerState = true };
var sb = new StateBlock(device.Device, mask);
Both StateBlockMask and StateBlock are classes. This gives me a compilation error:
'.ctor' is not supported by the language
Reading from some other posts here on SO, it seems that this is a problem that has to do with calling the managed code with the wrong arguments. In the source of SlimDX, I find:
StateBlock::StateBlock(SlimDX::Direct3D10::Device^ device, StateBlockMask mask)
I have no experience at all with C++/CLI, so I am wondering if there is something wrong here (like a missing or extra ^), or should I concentrate of faults on my side?
(NOTE: This question has been cross-posted to gamedev.net, future users with the same question may also want to check for answers given there)
Is StateBlockMask a struct? If not, use StateBlockMask^ mask in the C++ constructor.
This looks like a bug in SlimDX. You might want to use the issue tracker to make sure it gets dealt with properly.

C# DllImport trouble

My question is a little general, so i'm not looking for an exact answer, but possibly some directions to look into that will help me...
At my work place I program mostly in C#.
We have this 3rd party company we work with, that gave us a Native C++ dll that we need to use.
Since the C++ method I needed wasn't exposed in a manner that was easy to reference from C#, I wrapped the dll in another Native C++ Dll.
So now i have 2 Native C++ dlls, one wrapping the other.
I created a small C# console application that calls the method I created in C++.
My method signature looks like this :
[DllImport("HashMethodWrapper.dll")]
[return: MarshalAs(UnmanagedType.LPStr)]
private static extern string CreateHash(
string input,
[MarshalAs(UnmanagedType.LPStr)]StringBuilder output);
In my console application, everything works fine, and i always receive the string im expecting in the result.
But when I move it to a web service or a Web Application i created (since this is where i really need it), I see that the string im receiving is garbage and not even consistent. It seems as if im getting just some reference to memory that is lost or something like that, but this is only a guess of mine...
I don't know why this happens, since in my console application everything works fine.
Does anyone have a direction that might help me ???...
Thanks in advance,
gillyb
Edit :
I thought it might have to do with some unpinned objects, so i tried calling the method in a fixed statement, something like :
unsafe public static string CreateHashWrap(string pass)
{
String bb;
StringBuilder outPass = new StringBuilder();
fixed (char* resultStr = CreateHash(pass, outPass))
{
bb = new String(resultStr);
}
return bb;
}
...but this still didn't do it for me. Is this the right way to pin objects ?
2nd Edit :
The method signature in C++ looks like this :
extern "C" __declspec(dllexport) char *CreateRsaHash(char *inputPass, char *hashPass);
3rd Edit :
I changed the signature of the method to be
extern "C" __declspec(dllexport) bool CreateRsaHash(char *inputPass, char *hashPass);
and the return value im looking for is placed in the *hashPass parameter.
Now, I created a simple Console application to test it. When insert the DllImport in my main class, and directly call the method everything works great, but when I move the DllImport and wrap the method in a different class and call that class from the Console 'Main' method, I get a StackOverflow exception!
Anyone got any ideas why this is happening ??
Try specifying the capacity of the StringBuilder before passing it to your interop method.
It's really hard to know from the sparse information but if I had to guess I would say you need to make sure you're pinning the output object. Also I would probably change the output parameter to some other type, it seems pretty strange that StringBuilder works at all frankly.
I do know that if you allocate an object, it will get a pointer but that doesn't mean that it won't move. So if you try to pass a pointer to a managed object into an unmanaged environment you need to make sure you tell the GC to "pin" the memory so it doesn't get moved out from under you.
Here is a really rough version of what I mean by pinning:
string input = "...";
StringBuilder output = new StringBuilder();
var handle = System.Runtime.InteropServices.GCHandle.Alloc(output, GCHandleType.Pinned);
try
{
CreateHash(input, output);
}
finally
{
handle.Free();
}
I would consider to warp inside a C# shared assembly/dll instead of a c++ dll and then try to get your console application to work with the dll. It is good practice to wrap external dependencies this way anyway.
Otherwise some traditional issues are 32 vs 64 bit, the load path to the shared library. Is it really only a string or something more complex?
I found the solution to my problem, and now i feel kinda (if not really!) stupid... :-|
I used the LoadLibrary() method in C++ to dynamically invoke a method from the other native dll. The problem was that I didn't give the method any path, and just the dll filename. In .net, it would've searched in the current folder, but seems like in native code this doesn't work this way.
The bigger problem in my programming practices is obviously the fact that i didn't fully cover error handling in my native C++ dll!
All the asnwers I received on this page weren't for nothing though...
Once I found out that i had problem with the directory path, I ran into different exceptions about trying to access corrupt memory, etc. And then I needed to create pinned objects, and declare a size for my StringBuilder object.
Thanks to everyone for your help!!
:)

Categories

Resources