How to access COM variant properties with F#

Viewed 102

I have access to a legacy system through an ActiveX control. I can create an instance of this control using it's ProgID in F# and then call various methods successfully. However, I'm having problems trying to use it's properties. These have been declared as Variant types.

I am able to do this successfully in C# like this:

using System.Runtime.InteropServices;

// asuming I have already created an instance of the Legacy ActiveX control - axLegacy
object v = new VariantWrapper(0.0);
axLegacy.Get("Color", ref v);

In F# I can get everything else to work but the property:

open System
open System.Reflection
open System.Runtime.InteropServices

let getParamVal (legacyType: Type, instance: obj, propertyName: string, x: byref<obj>)  =
    let res = legacyType.InvokeMember(name = "Get", invokeAttr = (BindingFlags.InvokeMethod ||| BindingFlags.Instance ||| BindingFlags.Public), binder = null, target = instance, args = [|propertyName, x|])
    res

[<EntryPoint>]
let main argv =
    let axLegacyType = Type.GetTypeFromProgID("Legacy.ProgID.1")
    let axLegacy = Activator.CreateInstance(axLegacyType)  // create an instance of the type
    // calling a method with no parameters - works
    let res = axLegacyType.InvokeMember(name = "MethodNoParams", invokeAttr = (BindingFlags.InvokeMethod ||| BindingFlags.Instance ||| BindingFlags.Public), binder = null, target = axLegacy, args = null)
    // calling a method with parameters - works
    let res2 = axLegacyType.InvokeMember(name = "MethodWithParam", invokeAttr = (BindingFlags.InvokeMethod ||| BindingFlags.Instance ||| BindingFlags.Public), binder = null, target = axLegacy, args = [|"method param value goes here"|])
    // accessing a property - does not work
    let mutable propertyVal = new VariantWrapper(0.0) :> obj  // downcast to an Object
    // this throws the COM exception
    let res = getParamVal(axLegacyType, instance, "PropertyName", &magVal)

I'm almost there and if anyone can help me that would be gratefully appreciated. I know one obvious answer is to just use C#, but F# actually fits my requirements a lot better. Plus, F# is a cool language to use :)

=EDIT= I managed to sort out the byref bit and added the getParamVal function. Following comments I have also generated the IDL for the control and am including the important bits below:

    [
      uuid(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX),
      helpstring("Dispatch interface for AxLegacy Control"),
      hidden
    ]
    dispinterface AxLegacy {
        methods:
            [id(0x00000002)]
            long Get(
                            BSTR lpszParam, 
                            VARIANT* vValue);
    };

And the actual error I receive is this:

System.Reflection.TargetInvocationException
  HResult=0x80131604
  Message=Exception has been thrown by the target of an invocation.
  Source=System.Private.CoreLib
  StackTrace:
   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)
   at System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args)
   at Program.getParamVal(Type legacyType, Object instance, String cmd, Object& x) in C:\Source\F#\AxLegacyConsole\AxLegacyConsole\Program.fs:line 12
   at Program.main(String[] argv) in C:\Source\F#\AxLegacyConsole\AxLegacyConsole\Program.fs:line 25

Inner Exception 1:
COMException: Type mismatch. (0x80020005 (DISP_E_TYPEMISMATCH))
1 Answers

I actually solved this and it took quite a bit of work! Here's a brief outline:

  • I had to create things in Visual Studio 2017 first (which has complete support for F# .NET Framework projects)
  • I created an F# Console app (.NET Framework)
  • Created a dummy C# Library (.NET Framework)
  • Added the ActiveX control to the C# project
  • Copied the section to build the Interop assembly from the CSPROJ to the FSPROJ file
  • Copied the generated interop DLL to the build output folder of the F# Console app

I've posted in more detail about how I did it here: Using an ActiveX Control in F#

Related