3

I'm trying to expose a fairly simple C# class to COM which should be usable from VBScript (among others). Some objects need to be created via COM calls and will be used in furter calls later on. The definition of the exposed classes and interfaces looks like this:

namespace Test 
{
  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
  public interface IComInterface
  {
      IFoo CreateFoo();
      void UseFoo(int x, IFoo f);
  }

  [ClassInterface(ClassInterfaceType.None)]
  public sealed class CComInterface : IComInterface
  {
      public CComInterface() {}
      public IFoo CreateFoo() { return new Foo(); }
      public void UseFoo(int x, IFoo f) { f.Bar(); }
  }

  [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
  public interface IFoo
  {
      void Bar();
  }

  [ClassInterface(ClassInterfaceType.None)]
  public class Foo : IFoo
  {
      internal Foo() {}
      public void Bar() {}
  } 
}

The simplest thinkable COM client in VBScript does this:

Dim ci
Set ci = WScript.CreateObject("Test.CComInterface")
Dim foo
Set foo = ci.CreateFoo
foo.Bar
ci.UseFoo 0, foo

While the call to Bar succeeds, calling UseFoo fails with "Error 5: invalid procedure call or invalid argument"

The generated IDL seems ok to me:

dispinterface IComInterface {
    properties:
    methods:
        [id(0x60020003)]
        IFoo* CreateFoo();
        [id(0x60020004)]
        void UseFoo(
                        [in] long x, 
                        [in] IFoo* f);
};

The vbs call succeeds when I wrap the second parameter in parentheses like this:

ci.UseFoo 0, (foo)

As far as I understand (I'm no VBScript expert however) this forces the reference to foo to be passed by value, i.e. a copy of the reference is being made.

How can I change the interface so that it can be called with the foo reference directly? Since this will be a public interface used by customers I don't like the idea of having to explain why all the objects created need to be passed back in an extra pair of parentheses...

nopopem
  • 81
  • 5
  • I don't see a [ProgId] in this snippet. How close is this to the real code? Does CreateFoo() actually return a different interface? – Hans Passant Jun 07 '10 at 16:58
  • This is real code I used to reproduce the problem from a larger project. It has no explicit ProgId, it is generated from namespace and class names. – nopopem Jun 08 '10 at 08:29

2 Answers2

3

Louis.fr on msdn social helped me out - the trick is to tell the marshaller how to marshal the parameter like this:

public void UseFoo(int x, [MarshalAs(UnmanagedType.IDispatch)] IFoo f) { f.Bar(); }
nopopem
  • 81
  • 5
1

Try changing the C# interface to take a ref parameter.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Already tried that, result is that the tlb lists the parameter as void UseFoo([in] long x, [in, out] IFoo** f); but I still need to wrap it in parentheses in VBScript. – nopopem Jun 08 '10 at 08:40