1

I have a VB.NET winforms app which uses an ActiveX control (ocx). Via interop, I have a reference to an object defined in the ocx and call various functions on it. This works ok for the most part.

I had some issues with one function call causing a memory leak (my other question was related to this) and the developer suggested I use a different function to get the same data. The difference is that the first function returns the data, and the second function is passed an object by ref in which it puts the data.

I call the original (leaky) function which returns the data like this:

// from generated documentation of axobject
// function name obfuscated
public virtual object A();

Called from VB.NET

Dim result = CType(axobject.A(), UInt16())

That works but memory leaks. So I will try to use the other function

This is the suggested function to use

// C++ definition, names obfuscated
long axobject::B(VARIANT* var)
// from generated documentation of axobject
// function name obfuscated
public virtual int B(out object var);

Calling it from VB.NET like this

Dim o As Object = Nothing
Dim i As Integer
i = axobject.B(o) ' exception here
Dim result = CType(o, UInt16())

produces an execption

System.Runtime.InteropServices.COMException: 'Parameter not optional. (Exception from HRESULT: 0x8002000F (DISP_E_PARAMNOTOPTIONAL))'

ok, so it seems like the null object is not passed to the function. So I assign something to it something beforehand

Dim o As Object = New Object()
Dim i As Integer
i = axobject.B(o)
Dim result = CType(o, UInt16())

and now this exception

System.Runtime.InteropServices.COMException: 'Type mismatch. (Exception from HRESULT: 0x80020005 (DISP_E_TYPEMISMATCH))'

Even tried boxing a UInt array

Dim o As Object = {1US}

but I get the same error.

Well my first inclination when I saw the definition was something like C# dynamic but it is not available in VB.NET and I've heard many times to use Object with Option Strict Off and Option Infer On. These are the options I am using, and it doesn't work. But I tested it in a C# project, and it works

dynamic o = null;
int res = axobject.B(ref o);
UInt16[] result = (UInt16[])o;
// everything here works

Here is a screenshot of the debugger with working code in C#.

enter image description here

The definition from the perspective of the C# project has ref instead of out when coming from VB.NET. I am 100% sure the ocx is the same version in both projects. This is strange

// from generated documentation of axobject in C# project
public virtual int B(ref object var);

Well I'm going to make an extension method in C# and see how it goes. Any suggestions on how to make this call purely from VB.NET would be very helpful.

Edit 1

I tried making an extension method in C# and calling from VB.NET. I get the same errors as VB.NET in the C# extension method

public static int B_ex(this axobject obj, ref object var)
{
    int res = obj.B(out dynamic o);
    var = o;
    return res;
}

DISP_E_PARAMNOTOPTIONAL

or when o is assigned to new object() first,

DISP_E_TYPEMISMATCH

Edit 2

Adding results of Ildasm

Interop.LibName.dll:

.method public hidebysig newslot virtual
instance int32 B(object& marshal( struct) var) runtime managed preservesig internalcall
{
.custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 D1 00 00 00 00 00 )
.override LibName.ClassName::B
} // end of method ClassName::B

AxInterop.LibName.dll:

.method public hidebysig newslot virtual
instance int32 B(object& var) cil managed
{
// Code size 35 (0x23)
.maxstack 2
.locals init (int32 V_0)
IL_0000: ldarg.0
IL_0001: ldfld class [Interop.LibName]LibName.ClassName LibName.ClassName::ocx
IL_0006: brtrue.s IL_0014
IL_0008: ldstr "B"
IL_000d: ldc.i4.0
IL_000e: newobj instance void [System.Windows.Forms]System.Windows.Forms.AxHost/InvalidActiveXStateException::.ctor(string, valuetype [System.Windows.Forms]System.Windows.Forms.AxHost/ActiveXInvokeKind)
IL_0013: throw
IL_0014: ldarg.0
IL_0015: ldfld class [Interop.LibName]LibName.ClassName LibName.AxGetData::ocx
IL_001a: ldarg.1
IL_001b: callvirt instance int32 [Interop.LibName]LibName.ClassName::B(object&)
IL_0020: stloc.0
IL_0021: ldloc.0
IL_0022: ret
} // end of method ClassName::B

djv
  • 15,168
  • 7
  • 48
  • 72
  • Active-X controls Microsoft tried to make obsolete.Some were made obsolete in VB5 (2005) and some in VB6 (2006).But people are still using these controls.In Window XP I have used some of these controls but had to add the libraries (dll) using reg32srv.You are having issues with early/late bindings.It works well with VB but late binding you have issues in c#.I would stick with VB.The best way of solving these issues is to put a break point where the exception occurs and then add variable to a watch to see the type that is returned.Usually object is the best type.You may have an array. – jdweng Apr 23 '18 at 16:53
  • @jdweng I have no choice but use this ocx - it is the only API provided by the hardware manufacturer unfortunately. So A() returns a uint16[], and this is fine in VB, but the function leaks memory. B() is supposed to take a variable, and put the uint16[] in it (ref / ByRef). This works in C# using dynamic but I can't find a way to make it work in VB. I agree VB was better with late bound objects until C# introduced dynamic. – djv Apr 23 '18 at 16:59
  • Do you need to type the variable? IIRC it's valid to just do `Dim o = axobject` and that's equivalent to C# `dynamic` – Mgetz Apr 23 '18 at 17:14
  • You are dealing with late bindings. So see following : https://stackoverflow.com/questions/8283505/late-binding-and-option-strict – jdweng Apr 23 '18 at 17:22
  • It might be helpful if you could show the method signature from the interop library so that we could see how the passed objected is decorated with marshaling directives. Taking a wild guess, you could try wrapping the Uint16 array in a [VariantWrapper](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.variantwrapper.variantwrapper(v=vs.110).aspx) and pass that wrapper to the method. – TnTinMn Apr 23 '18 at 18:52
  • @TnTinMn I think I have, `long axobject::B(VARIANT* var)`. For anything in addition to this I would need some direction. – djv Apr 23 '18 at 18:53
  • @Mgetz I tried `Dim o` `B(o)` and still get `COMException: 'Parameter not optional...'`. And with `o = New Object()` in between, another `COMException: 'Type mismatch...'` – djv Apr 23 '18 at 19:06
  • @djv, I could be misunderstanding your situation, but my perception is that you added the ActiveX control to a WinForm via a COM ToolBox item. This should have invoked the library importer and added an interop library (typically AxSomething to the project references. Looking at that library in a decompiler is what I meant so that we can see the .Net signature. Something along the lines of (from MediaPlayer ActiveX control) : `ReadOnly Property preset(ByVal nIndex As Integer) As Object` where we can inspect the `MarshalAs` stuff. – TnTinMn Apr 23 '18 at 19:06
  • @TnTinMn yes it is imported via the toolbox into an interop library. Is there something in Visual Studio which could do what you are talking about or do I need another tool? – djv Apr 23 '18 at 19:10
  • @jdweng I don't even get to the part where the object is late-bound, which would be after a successful call to `B(object)`. Check the question again. Casting from the result of `object A()` is trivial and works, but `B(object)` doesn't even accept the object as an argument so the ref argument is never changed. – djv Apr 23 '18 at 19:13
  • @TnTinMn This is in the Object Browser: `Public Overridable Function B(ByRef var As Object) As Integer`, and the metada (go to definition) shows c#: `public virtual int B(ref object var);`, but I suspect there is something else I need to do to see the `MarshalAs` stuff – djv Apr 23 '18 at 19:17
  • 1
    You can use the IL disassembler of your choice. Something like Redgate Reflector or the open source ILSpy will do. Or in a pinch, MS's ILDASM. – TnTinMn Apr 23 '18 at 19:19
  • @TnTinMn I ran ildasm and updated my question – djv Apr 23 '18 at 19:46
  • Interesting results. Active-X I believe is before WinXp so integers were only 16 bits, not 32. The Ildasm says 32 bit which must mean the Active-X was returning a 32 bit long. That may have something to do with the error. Not sure. May be you have to cast an integer to an integer. Classes and properties have a type in the binary that may be causing run-time errors if they do not match. I believe the int32 is really a pointer to the structure and not a return value. – jdweng Apr 23 '18 at 20:32
  • @jdweng Please see the screenshot I added. It shows the function call when it works. In VB.NET I can't even get the function to return so forget about the return value; it happens to be 0 when the function succeeds. – djv Apr 23 '18 at 20:49
  • @djv, It just occurred to me that your use of `Dim o As Object = {1US}` will create a standard .Net array type and you probably need to pass the less derived `System.Array` that maps to SafeArray. i.e.: `Dim returnArray As System.Array = System.Array.CreateInstance(GetType(UInt16), 0)` or perhaps `Dim returnArray As System.Array = System.Array.CreateInstance(GetType(Object), 0)` – TnTinMn Apr 23 '18 at 22:55
  • @TnTinMn the System.Array method doesn't work. The "hacky" methods in the chat don't work either (I just get the same exceptions). Unless the developer gives me some more direction, I may need to put this code into a c# project. – djv Apr 24 '18 at 20:26
  • Just out of curiosity, if with the _leaky_ method you do: `Dim obj as Object = axobject.A()` and inspect `obj` in the debugger, does it indicate obj is a `ComObject`. `Object` or array? – TnTinMn Apr 24 '18 at 21:21
  • @TnTinMn `System.UInt16[]`, {Length=1048576} – djv Apr 24 '18 at 21:37
  • @djv, thanks for satisfying my curiosity, but I have nothing to to offer on that front. It is probably best to work with the developer. However, if that yields no joy, you may be able to remap the interop marshaling to something you can control. This depends on knowing the method's IDL from the type library and the containing interface guid and type. If needed, get familiar with using [the OLE/COM Object Viewer](https://msdn.microsoft.com/en-us/library/d0kh9f4c.aspx) so that you could post the pertinent IDL. – TnTinMn Apr 25 '18 at 15:33
  • In OLE/COM Object Viewer: `long B(VARIANT* var);` and `VARIANT A();`. Not much help. – djv Apr 27 '18 at 14:47

0 Answers0