4

I have the following legacy VB6 function that I want to call from C#.

Public Function CreateMiscRepayment(ByRef objMiscRepayment As MiscRepayment) As Variant
   ' Code that sets objMiscRepayment here
End Function

I'm using the following code in C# but getting an exception:

dynamic vb6ComObject = Activator.CreateInstance(Type.GetTypeFromProgID(progId));
dynamic miscRepayment = null;
dynamic result = vb6ComObject.CreateMiscRepayment(ref miscRepayment);

The exception is:

System.ArgumentException: Could not convert argument 0 for call to CreateMiscRepayment.
at System.Dynamic.ComRuntimeHelpers.CheckThrowException(Int32 hresult, ExcepInfo& excepInfo, UInt32 argErr, String message)
at CallSite.Target(Closure , CallSite , ComObject , Object& )
at CallSite.Target(Closure , CallSite , ComObject , Object& )
at CallSite.Target(Closure , CallSite , Object , Object& )
at CallSite.Target(Closure , CallSite , Object , Object& )
Application\ApplicationClasses.cs(65,0): at ApplicationClasses.CanInstantiateMiscRepayment()

I've tried changing ref to out but get the same error. If I omit ref, the method executes without error, but of course miscRepayment is still null rather than containing the object that was supposed to be passed out.


Update

I've tried some other ways, including using VB.NET (since it has always been more COM friendly than C#).

With the following VB.NET code:

Dim vb6ComObject = Activator.CreateInstance(System.Type.GetTypeFromProgID(progId))
Dim miscRepayment = Nothing
Dim result = vb6ComObject.CreateMiscRepayment(miscRepayment)

It throws the following similar, but different exception:

System.Runtime.InteropServices.COMException: Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))
    at Microsoft.VisualBasic.CompilerServices.LateBinding.LateGet(Object o, Type objType, String name, Object[] args, String[] paramnames, Boolean[] CopyBack)
    at Microsoft.VisualBasic.CompilerServices.NewLateBinding.LateGet(Object Instance, Type Type, String MemberName, Object[] Arguments, String[] ArgumentNames, Type[] TypeArguments, Boolean[] CopyBack)
    UnitTest1.vb(19,0): at TestProject1.UnitTest1.TestMethod1()

Interestingly, if I change the call in either the C# or VB.NET example code to use null/Nothing instead of miscRepayment then the code executes without throwing an exception. I've even set a breakpoint in the code of the VB6 COM object and can confirm that the code has executed properly on that end. Obviously, with setting the miscRepayment parameter to null/Nothing, there is then no way in .NET to receive the created object. The problem must be to do with the marshalling of parameters.

I've also tried using Type.InvokeMember with a ParameterModifier argument that marks miscRepayment as being a ref parameter, but get the following exception:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException: Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))

     --- End of inner exception stack trace ---
    at System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters)
    at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams)
    UnitTest1.vb(18,0): at TestProject1.UnitTest1.TestMethod1()

Lastly, I've tried the following VB.NET code:

Dim vb6ComObject = Activator.CreateInstance(System.Type.GetTypeFromProgID(progId))
Dim args(0) As Object
Microsoft.VisualBasic.CompilerServices.LateBinding.LateCall(vb6ComObject, type, "CreateMiscRepayment", args, Nothing, New Boolean() {True})

It throws the following exception:

System.Runtime.InteropServices.COMException: Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))
    at Microsoft.VisualBasic.CompilerServices.LateBinding.InternalLateCall(Object o, Type objType, String name, Object[] args, String[] paramnames, Boolean[] CopyBack, Boolean IgnoreReturn)
    at Microsoft.VisualBasic.CompilerServices.LateBinding.LateCall(Object o, Type objType, String name, Object[] args, String[] paramnames, Boolean[] CopyBack)
    UnitTest1.vb(17,0): at TestProject1.UnitTest1.TestMethod1()

With all the code that throws an exception, the VB6 COM object is never invoked. The COM interop code must be choking when trying to marshall the ref parameter.

In my Google searches I've come across some examples using Type.InvokeMember, but the ref parameters are always for simple types such as integers and strings.

John Mills
  • 10,020
  • 12
  • 74
  • 121
  • What if you change `dyanmic miscRepayment` to `object miscRepayment`? (And treat it as `dynamic` later if that works ... but I don't use `dynamic` or C# 4. Also, is there a way to get a "dynamic method group"? e.g. force an Action/Function/delegate out of it?) –  Dec 16 '11 at 03:23
  • @pst: I tried changing from `dynamic` to `object` as you suggested but it still throws the same exception. Not sure about your last bit there. – John Mills Dec 16 '11 at 03:30
  • I was wondering if it was possible to `((MyDelegate)it.method)(...);`, but it is not -- at least like that due to it being an expression -- it appears. Can you get it working with just "regular" reflection? That would at least mean "hey, it works somehow!" (or not). –  Dec 16 '11 at 03:54
  • Perhaps some hints: http://stackoverflow.com/questions/2475310/c-sharp-4-0-dynamic-doesnt-set-ref-out-arguments –  Dec 16 '11 at 03:58
  • 2
    Its been a while since I have dealt with COM objects but shouldnt you be able to get a definition of the MiscRepayment instead of dynamic. Also could I please see how you have referenced vb6ComObject, I am sure it will be standard, just want to make sure. – Adam Dec 16 '11 at 04:25
  • @pst: I tried using reflection, but no luck. `type.GetMethod("CreateMiscRepayment")` returns null. `type.GetMethods()` returns 7 methods, but these are the public methods on MarshalByRefObject. – John Mills Dec 16 '11 at 04:29
  • @Adam: I instantiated `vb6ComObject` by calling `Activator.CreateInstance(Type.GetTypeFromProgID(progId)`. I'm not referencing an interop assembly; was hoping dynamic would allow me to get by without doing that. – John Mills Dec 16 '11 at 04:32
  • Can you use reflection then to get the actual type of the object you need. So can you use Activator.CreateInstance to create an instance of the object you need. Then use reflection to get and fill the properties, such as instance.GetType().GetProperty("nameofproperty") Looking at the error it is just having trouble matching up the signatures of the method you want to call as the parameter needs to be of its asking type. – Adam Dec 16 '11 at 04:45
  • @Adam: I gave that a try when Pst suggested using reflection but it didn't show any of the methods defined in the VB 6 code. My guess is that an `IDispatch` interface is all that we have so only late binding will work. I'll try querying the type returned by `GetTypeFromProgID(progId)` as well to see if that contains the methods. – John Mills Dec 16 '11 at 04:57

2 Answers2

2

There doesn't seem to be a way in .NET to call a method on a COM object that takes a ref parameter with a complex type.

I've filed a bug with Microsoft. Vote it up if this problem is also affecting you.

Update 30/4/2013

There is a comment on the bug report from Microsoft saying that it has been fixed. No mention of which version of .NET has been affected though.

John Mills
  • 10,020
  • 12
  • 74
  • 121
  • 1
    Unlikely to be the case. While I haven't tried this with COM objects, I have called a class via the Activator.CreateInstance with a ref parameter in the constructor. There was actually no need to note that is was a ref as it was automatically accounted for. If you are dealing with method parameters you can use the typeof(TheType).MakeByRefType(). Then again I could be wrong :) – Adam Dec 19 '11 at 05:21
  • I'm sure it does work fine for reflecting over .NET objects. It doesn't work (for me) with COM though. Only too happy to be proven wrong though and quite happy to change accepted answer if someone can show how to make it work. – John Mills Dec 19 '11 at 21:38
  • Any news on this. I am struggling with the same issue and there is no clear way around this. MS with there usual crap... – MoonKnight Mar 07 '14 at 16:21
0

Not actually an answer, but a workaround.

Seems to me, that when you change the way of accessing COM objects from dynamic to static, the problem goes away. By static way I mean to prepare a dll for the COM object using C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\TlbImp.exe. I guess this is early binding.

Jarekczek
  • 7,456
  • 3
  • 46
  • 66