views:

1087

answers:

3

I am writing a Visual Studio add in and need to marshall a managed CodeElements object to it's unmananged form. I just need the pointer in memory, as I can cast it and treat it like a CodeElement on the unmanaged side.

    [DllImport("CodeMethodsToString.dll")]
 private static extern BSTR* CodeMethodsToString(void* functionObject);

 public static void CodeMethodsToXML(XmlElement parent, CodeElements elements)
 {
  //Call CodeMethodsToString: how do I marshall CodeElements to an IntPtr?
  //set XmlElement in here
 }

I know how to deal with the XML, and I have a working version of this in C#. I created the unmanaged DLL because calling all of the various member variables at the lowest level of recursion was killing the speed of the program. I simply need to know how to use System.Runtime.Interop.Marshal to convert the CodeElements object to a pointer to the COM object in memory.

Thanks.

+1  A: 

Is CodeElements a ComVisible interface and has a GuidAttribute?

Then C# will do the marshalling of COM objects for you, and you can simply use CodeElements as argument type:

[DllImport("example.dll")]
private static extern void DoStuff(CodeElements codeElements);
dtb
Afraid it's not that simple:CodeElements is abstract. I would need a pointer to the object. How would I get that?
To clarify: when it's doing this default marshalling, is it passing by value? Is there a way to let it do it's marshalling magic but only pass a pointer?
It's passed by reference. I assume "CodeElements" refers to this: http://msdn.microsoft.com/en-us/library/envdte.codeelements.aspx So my example should just work. If not, please post the error details.
dtb
There is definitely an error, but I really appreciate this follow up: theres a System.AccessViolation, attempted to read or write to protected memory. I am doing what you wrote in the C# code, and on the C++ side (this is what is confusing me) I have DECL BSTR* CodeMethodsToString(EnvDTE::CodeElements where DECL is a mcro for dllexport. In any case it's treating the points wrong (since the class is abstract I can't simply pass by value).
dtb
Neither seem to work, I have tried both. What is it automatically marshalling this object as?
Didn't know whether it was passing by pointer or reference
What I need to know now is what this looks like on the C++ side
I'd say `void DoStuff(CodeElements *classElement);` but I'm not a C++ expert.
dtb
BTW I doubt you'll get much speed improvements just by switching to C++.
dtb
I know, but I want to try. Thanks
An access violation usually means that the pointer you're trying to pass isn't pinned to prevent the GC from potentially moving it. Check out the GCHandle class' methods on MSDN and see it helps you. I suspect that what you want is this:unsafe { GCHandle handle = GCHandle.Alloc(codeElements); void* ptr = handle.AddrOfPinnedObject().ToPointer(); // pass the object to unmanaged code handle.Free(); // always clean handles up. always. }
Jeff Tucker
+1  A: 

As soon as you see a star or ampersand you should start by converting it to ref (safe version of a pointer). I have had reference types magically start working when I used the ref keyword in the past (which is highly contradictory - but I think it's one of those interop things):

[DllImport("example.dll")]
private static extern void DoStuff(ref CodeElements codeElements);

You could also try:

[DllImport("example.dll")]
private static extern void DoStuff([In, Out] ref CodeElements codeElements);

Or one of the permutations of those attributes.

One thing you might want to try is to use the MFC (I think, been a long time since C++) to make the COM library. Don't use a native call, export the thing as a type library and add it as a reference in Visual Studio (yes, it's that easy). Thus you will land up with something like:

myCoolClass.DoStuff(codeElements);

You might also want to pin it (if you need to pin it the error will be intermittent). I can't remember if the RCW will do that for you (I am almost certain it will), so here is the code to do it:

GCHandle handle = new GCHandle();
try
{
  handle = GCHandle.Alloc(fooz, GCHandleType.Pinned);
  // Use fooz.
}
finally
{
  if (handle.IsAllocated)
    handle.Free();
}
Jonathan C Dickinson
`ref` is used if you want to pass a value type (e.g. a `struct`) by reference and not by value. `CodeElements` is a COM interface, i.e. a reference type, and therefore always passed by reference.
dtb
I tried this and it didn't work, it might work in some combination of the other marshalling I am doing. Thanks for reminding me that one of these keywords might be pertinent.
@dtb - read! It said it has made a difference in the past; even though it seems as it should not - the MSIL generated is NOT identical and the interop layer might see the keyword as pertinent. In pure .Net to .Net calls it would, agreed, have no effect.
Jonathan C Dickinson
+2  A: 

Looks like Jonathan was close. This is how I'd do it:

[DllImport("CodeMethodsToString.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string CodeMethodsToString(IntPtr functionObject);

public static void CodeMethodsToXML(XmlElement parent, CodeElements elements)
{
   GCHandle pin;
   try
   {
      pin = GcHandle.Alloc(elements, GCHandleType.Pinned);
      string methods = CodeMethodsToString(pin.AddrOfPinnedObject());
    }
    finally
    {
       pin.Free();
    }
}
scottm
This is the way. Pinning the object to prevent GC from moving the pointer is required to avoid and AcessViolationException.
Jeff Tucker
I'm confused -- I can't get this to work in my test harness. I get an ArgumentException on GCHandle.Alloc for an RCW that isn't of type CodeElements. Is Visual Studio's CodeElements not a TLBImped COM interface?
Kim Gräsman
@Kim are you still testing with the DoStuff implementation in your answer?
scottm
Yes, or a variety thereof. In order for the native side to get a COM interface pointer I'm not sure GCHandle.Alloc is enough. But it doesn't even get that far -- the call to Alloc fails for my tlbimp:ed interface.
Kim Gräsman
AFAIK, a pointer is a pointer. GCHandleType.Pinned just makes sure the garbage collector doesn't free the memory (because a native application is using it).
scottm
Yes, my concern was that GCHandle.Alloc doesn't work for RCWs. What is the native side going to do with the pointer once it gets it? It doesn't have any matching type information... The more I read about this, the more I think dnh828 is barking up the wrong tree -- there doesn't seem to be a native representation of CodeElements. I misunderstood the premise of the question.
Kim Gräsman
The native side doesn't need any type information, it's all relative. As long as the memory pointed to fits into the data structure you need on the native side and you can get what you need, it doesn't matter.
scottm
Sure, if you want to depend on CodeElements internal layout, I suppose you can get to its most basic data. But you can't really call methods on that object, can you?
Kim Gräsman
I've reread the question and I understand your confusion. I think the OP may actually be using managed c++ if he has access to the CodeElements object in the c++ code.
scottm
Hmm, yeah, that might make sense. I'm going to delete my answer, I realize it has nothing to do with the question. Cheers!
Kim Gräsman