7

The basic outline of my problem is shown in the code below. I'm hosting a WebBrowser control in a form and providing an ObjectForScripting with two methods: GiveMeAGizmo and GiveMeAGizmoUser. Both methods return the respective class instances:

[ComVisible]
public class Gizmo
{
    public string name { get; set; }
}

[ComVisible]
public class GizmoUser
{
    public void doSomethingWith(object oGizmo)
    {
        Gizmo g = (Gizmo) oGizmo;
        System.Diagnostics.Debug.WriteLine(g.name);
    }
}

In JavaScript, I create an instance of both classes, but I need to pass the first instance to a method on the second instance. The JS code looks a little like this:

var 
    // Returns a Gizmo instance
    gizmo = window.external.GiveMeAGizmo(),

    // Returns a GizmoUser instance
    gUser = window.external.GiveMeAGizmoUser();

gizmo.name = 'hello';

// Passes Gizmo instance back to C# code
gUser.doSomethingWith(gizmo);

This is where I've hit a wall. My C# method GizmoUser.doSomethingWith() cannot cast the object back to a Gizmo type. It throws the following error:

Unable to cast COM object of type 'System.__ComObject' to interface type 'Gizmo'

Unsure how to proceed, I tried a couple of other things:

  • Safe casting Gizmo g = oGizmo as Gizmo; (g is null)
  • Having the classes implement IDispatch and calling InvokeMember, as explained here. The member "name" is null.

I need this to work with .NET framework version lower than 4.0, so I cannot use dynamic. Does anybody know how I can get this working?

3
  • What does oGizmo look like in debugger? or is it always null? Commented Jan 14, 2014 at 17:41
  • @Abhinav: its type is System.__ComObject. If I try oGizmo.GetType().GetMembers(), it returns null (same for GetProperties() and GetMethods(). Commented Jan 14, 2014 at 17:46
  • Can you show 'GiveMeAGizmo()' code ? Commented Jan 14, 2014 at 17:48

2 Answers 2

4

How interesting. When we receive the oGizmo object back in doSomethingWith, it is of the type Windows Runtime Object. This behavior is consistent between JavaScript and VBScript.

Now, if we explicitly specify MarshalAs(UnmanagedType.IUnknown) on the return value of the GiveMeAGizmo() method, everything works fine, the object can be cast back to Gizmo inside doSomethingWith:

[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDispatch)]
public class ObjectForScripting
{
    [return: MarshalAs(UnmanagedType.IUnknown)]
    public object GiveMeAGizmo()
    {
        return new Gizmo();
    }

    public object GiveMeAGizmoUser()
    {
        return new GizmoUser();
    }
}

Still, if we specify UnmanagedType.IDispatch or UnmanagedType.Struct (the default one which marshals the object as COM VARIANT), the problem is back.

Thus, there's a workaround, but no reasonable explanation for such COM interop behavior, so far.

[UPDATE] A few more experiments, below. Note how obtaining gizmo1 is successful, while gizmo2 is not:

C#:

// pass a Gizmo object to JavaScript
this.webBrowser.Document.InvokeScript("SetGizmo", new Object[] { new Gizmo()});

// get it back, this works
var gizmo1 = (Gizmo)this.webBrowser.Document.InvokeScript("GetGizmo");

// get a new Gizmo, via window.external.GiveMeAGizmo()
// this fails
var gizmo2 = (Gizmo)this.webBrowser.Document.InvokeScript("GetGizmo2");

JavaScript:

var _gizmo;

function SetGizmo(gizmo) { _gizmo = gizmo; }

function GetGizmo() { return _gizmo; }

function GetGizmo2() { return window.external.GiveMeAGizmo(); }

It's only a guess, but I think such behavior might have something to do with .NET security permission sets, imposed by WebBrowser.ObjectForScripting.

Sign up to request clarification or add additional context in comments.

3 Comments

I'm very impressed that you went the extra mile to help out here. Your answer works perfectly as far as I can tell, so the green tick is yours.
@AndyE, no problem. WebBrowser and everything around is the area of my special interest :)
ah, I'll remember that for the next problem then ;-). The project I'm working on employs a lot of advanced techniques for hacking the WebBrowser control, but we've been stuck a couple of times on some complicated stuff (and I'm no expert in .NET, if I'm honest!).
0

You need to do two things

  1. Find out the type of object as described here.

  2. Extract the actual object outta it using Marshal.GetObjectForIUnknown (read till the end, there is an interface to implement).

1 Comment

Have you actually tried that? It doesn't work in the OP's case, because his original object of type Gizmo is already a managed object. Somehow, it gets wrapped as "Windows Runtime Object" when passed back from JavaScript. For the same reason, Gizmo g = (Gizmo)Marshal.CreateWrapperOfType(oGizmo, typeof(Gizmo)) doesn't work either.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.