如何将使用 Shell COM 的 C# 代码转换为 F#?

How to convert C# code that uses Shell COM to F#?

我有以下 C# 方法:

private static bool IsLink(string shortcutFilename)
{
    var pathOnly = Path.GetDirectoryName(shortcutFilename);
    var filenameOnly = Path.GetFileName(shortcutFilename);

    var shell = new Shell32.Shell();
    var folder = shell.NameSpace(pathOnly);
    var folderItem = folder.ParseName(filenameOnly);
    return folderItem != null && folderItem.IsLink;
}

我尝试将其转换为 F# 为:

let private isLink filename =
    let pathOnly = Path.GetDirectoryName(filename)
    let filenameOnly = Path.GetFileName(filename)
    let shell = new Shell32.Shell()
    let folder = shell.NameSpace(pathOnly)
    let folderItem = folder.ParseName(filenameOnly)
    folderItem <> null && folderItem.IsLink

然而,它报告 let shell = new Shell32.Shell() 行的错误,表示 new 不能用于接口类型。

我只是犯了一个愚蠢的语法错误,还是需要额外的工作才能从 F# 访问 COM?

我对 F# 编译器的了解还不够多,但是您的评论已经很明显了。 C# 和 VB.NET 编译器对 COM 内置有相当数量的显式支持。请注意,您的语句在接口类型上使用 new 运算符,互操作库中的 Shell32.Shell 如下所示:

[ComImport]
[Guid("286E6F1B-7113-4355-9562-96B7E9D64C54")]
[CoClass(typeof(ShellClass))]
public interface Shell : IShellDispatch6 {}

IShellDispatch6是真正的接口类型,通过IShellDispatch5接口也可以看到IShellDispatch。这是过去 20 年工作中的版本控制,COM 接口定义是不可变的,因为更改它们几乎总是会在运行时导致无法诊断的硬崩溃。

[CoClass] 属性是这个故事的重要属性,这就是 C# 编译器要查找的内容,您在 [ComImport] 接口类型上使用 new。告诉它通过创建 Shell32.ShellClass 实例的实例并获取 Shell 接口来创建对象。 F# 编译器不执行的操作。

ShellClass 是一个 fake class,它是由类型库导入程序自动生成的。 COM 从不公开具体的 classes,它使用基于超纯接口的编程范例。对象总是由对象工厂创建的,CoCreateInstance() is the workhorse for that. Itself a convenience function, the real work is done by the universal IClassFactory interface,超纯风格。每个 COM coclass 实现其 CreateInstance() 方法。

类型库导入器使 ShellClass 看起来像这样:

[ComImport]
[TypeLibType(TypeLibTypeFlags.FCanCreate)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("13709620-C279-11CE-A49E-444553540000")]
public class ShellClass : IShellDispatch6, Shell {
    // Methods
    [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType=MethodCodeType.Runtime), DispId(0x60040000)]
    public virtual extern void AddToRecent([In, MarshalAs(UnmanagedType.Struct)] object varFile, [In, Optional, MarshalAs(UnmanagedType.BStr)] string bstrCategory);
    // Etc, many more methods...
}

大量的火焰和移动,none 应该永远使用。唯一 真正 重要的是 [Guid] 属性,它提供 CoCreateInstance() 需要的 CLSID。它还需要IID,即接口的[Guid],由接口声明提供。

因此 F# 中的解决方法是创建 Shell32.ShellClass 对象,就像 C# 编译器隐式执行的那样。虽然从技术上讲,您可以将引用保存在 ShellClass 变量中,但您应该强烈支持接口类型。 COM方式,纯粹的方式,它避免了this kind of problem。最终是 CLR 完成了工作,它在其 new 运算符实现中识别 ShellClass class 声明中的 [ClassInterface] 属性. .NET 中更明确的方法是使用 Type.GetTypeFromCLSID() 和 Activator.CreateInstance(),当您只有 coclass.

的 Guid 时很方便