dimich
dimich

Reputation: 91

How to annotate interface property get and set with attributes in F#

How to translate the following COM interface to F#? I cannot figure out how to annotate get and set of a property.

Plus, for COM interop, do I need to annotate both a property itself and its get with DispId?

[ComImport, TypeLibType((short)0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")]
private interface IWshShortcut
{
    [DispId(0)]
    string FullName { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0)] get; }
    [DispId(0x3e8)]
    string Arguments { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3e8)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3e8)] set; }
    [DispId(0x3ec)]
    string RelativePath { [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ec)] set; }
    [DispId(0x3ee)]
    int WindowStyle { [DispId(0x3ee)] get; [param: In] [DispId(0x3ee)] set; }
    [DispId(0x3ef)]
    string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)] [DispId(0x3ef)] set; }
    [TypeLibFunc((short)0x40), DispId(0x7d0)]
    void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink);
    [DispId(0x7d1)]
    void Save();
}

Upvotes: 2

Views: 673

Answers (1)

ildjarn
ildjarn

Reputation: 62975

Here's a correct, though not literal, translation:

[<ComImport; Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B"); TypeLibType(0x1040s)>]
type private IWshShortcut =
  [<DispId(0)>]
  abstract member FullName : [<MarshalAs(UnmanagedType.BStr)>] string with get

  [<DispId(0x3e8)>]
  abstract member Arguments : [<MarshalAs(UnmanagedType.BStr)>] string with get, set

  [<DispId(0x3ec)>]
  abstract member RelativePath : [<MarshalAs(UnmanagedType.BStr)>] string with set

  [<DispId(0x3ee)>]
  abstract member WindowStyle : int with get, set

  [<DispId(0x3ef)>]
  abstract member WorkingDirectory : [<MarshalAs(UnmanagedType.BStr)>] string with get, set

  [<DispId(0x7d0); TypeLibFunc(0x40s)>]
  abstract member Load : [<MarshalAs(UnmanagedType.BStr)>] PathLink:string -> unit

  [<DispId(0x7d1)>]
  abstract member Save : unit -> unit

As you found, you cannot add attributes to an abstract property's underlying getter/setter methods in F#, only to the property itself, but it doesn't matter for this particular interface:

  • String properties with both get and set need the same MarshalAs for both anyway.
  • In is the default directionality for string parameters, so specifying it would be redundant anyway.
  • Applying DispId to the property getter as your C# code does is legal but pointless – while DispId can be applied to both methods and properties, and property getters and setters technically happen to be methods, the attribute only has an effect for the property itself.

N.b. because the CLR marshals string parameters for COM methods as BStrs by default, we can omit all the MarshalAs directives as well and make this look a bit more trim (albeit less explicit):

[<ComImport; Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B"); TypeLibType(0x1040s)>]
type private IWshShortcut =
  [<DispId(0)>] abstract member FullName:string with get
  [<DispId(0x3e8)>] abstract member Arguments:string with get, set
  [<DispId(0x3ec)>] abstract member RelativePath:string with set
  [<DispId(0x3ee)>] abstract member WindowStyle:int with get, set
  [<DispId(0x3ef)>] abstract member WorkingDirectory:string with get, set
  [<DispId(0x7d0); TypeLibFunc(0x40s)>] abstract member Load : PathLink:string -> unit
  [<DispId(0x7d1)>] abstract member Save : unit -> unit

Of course, all of this applies to the C# implementation as well, so it can be similarly simplified:

[ComImport, Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B"), TypeLibType((short)0x1040)]
private interface IWshShortcut
{
    [DispId(0)] string FullName { get; }
    [DispId(0x3e8)] string Arguments { get; set; }
    [DispId(0x3ec)] string RelativePath { set; }
    [DispId(0x3ee)] int WindowStyle { get; set; }
    [DispId(0x3ef)] string WorkingDirectory { get; set; }
    [DispId(0x7d0), TypeLibFunc((short)0x40)] void Load(string PathLink);
    [DispId(0x7d1)] void Save();
}

Upvotes: 6

Related Questions