本机代码无法访问 Windows Forms 对象

从 .NET 5 开始,你无法再从本机代码访问 Windows 窗体对象。

更改描述

在旧版 .NET 中,某些 Windows 窗体类型被修饰为对 COM 互操作可见,因此可通过本机代码访问。 从 .NET 5 开始,Windows 窗体 API 对 COM 互操作不可见或不可通过本机代码访问。 .NET 运行时不再支持现装创建自定义类型库。 此外,.NET 运行时不能依赖于 .NET Framework 的类型库(因为这需要保持类在 .NET Framework 中的原有结构)。

更改原因

  • 删除枚举中用于类型库(TLB 文件)生成和查找的 ComVisible(true):由于 .NET Core 不提供 WinForms TLB,因此保留此属性没有意义。
  • ComVisible(true)类中移除AccessibleObject:这些类不可共同创建(它们没有无参数构造函数),并且向 COM 公开已存在的实例不需要该属性。
  • ComVisible(true)Control类中删除Component:这用于允许通过 OLE/ActiveX 托管 WinForms 控件,例如在 VB6 或 MFC 中。 但是,这需要用于 WinForms 的 TLB (不再提供),以及基于注册表的激活(这也不能开箱即用)。 通常,没有对基于 COM 的 WinForms 控件托管进行维护,因此删除了支持,而不是使其处于不受支持的状态。
  • ClassInterface从控件中删除属性:如果不支持通过 OLE/ActiveX 进行托管,则不再需要这些属性。 它们保留在对象仍向 COM 公开的其他位置,并且该属性可能相关。
  • ComVisible(true)中删除EventArgs:它们很可能曾用于 OLE/ActiveX 托管,而这种托管不再受支持。 它们也不能进行共同创建,因此该属性没有意义。 此外,在不提供 TLB 的情况下公开现有实例毫无意义。
  • 从委托中删除 ComVisible(true):具体目的不详,但由于不再支持使用 ActiveX 托管 WinForms 控件,因此其作用可能不大。
  • 从某些非公共代码中删除 ComVisible(true):唯一的潜在使用者是新的 Visual Studio 设计器,但如果没有指定 GUID,就不太可能仍然需要它。
  • ComVisible(true)从一些任意公共设计器类中删除:旧的 Visual Studio 设计器可能一直在使用 COM 互作来与这些类通信。 然而,旧版设计器不支持 .NET Core,所以很少有人会需要将这些设置为 ComVisible
  • IWin32Window 定义了在 .NET Framework 中定义的相同 GUID,这会产生危险后果。 如果需要与 .NET Framework 互作,请使用 ComImport
  • WinForms 管理的 IDataObject 被设置为 ComVisible。 这不是必需的,对于 ComImport COM 互操作,有一个单独的 IDataObject 接口声明。 将管理的 IDataObject 设置为 ComVisible 会适得其反,因为没有提供 TLB,封送处理总是会失败。 此外,GUID 未指定,与 .NET Framework 不同,因此删除未记录的 IID 不太可能对客户产生负面影响。
  • 删除 ComVisible(false):当默认不向 COM 互操作公开类时,它们被放置在看似任意的位置上,并且是多余的。

已引入的版本

.NET 5.0

以下示例适用于 .NET Framework 和 .NET Core 3.1。 此示例依赖于 .NET Framework 类型库,该库允许 JavaScript 通过反射调用回表单子类。

[PermissionSet(SecurityAction.Demand, Name="FullTrust")]
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public class Form1 : Form
{
    private WebBrowser webBrowser1 = new WebBrowser();

    protected override void OnLoad(EventArgs e)
    {
        webBrowser1.AllowWebBrowserDrop = false;
        webBrowser1.IsWebBrowserContextMenuEnabled = false;
        webBrowser1.WebBrowserShortcutsEnabled = false;
        webBrowser1.ObjectForScripting = this;

        webBrowser1.DocumentText =
            "<html><body><button " +
            "onclick=\"window.external.Test('called from script code')\">" +
            "call client code from script code</button>" +
            "</body></html>";
    }

    public void Test(String message)
    {
        MessageBox.Show(message, "client code");
    }
}

可通过两种可能的方法使示例适用于 .NET 5 和更高版本:

  • 引入一个用户声明的ObjectForScripting对象,该对象支持IDispatch(默认情况下应用,除非在项目级别显式更改)。

    public class MyScriptObject
    {
        private Form1 _form;
    
        public MyScriptObject(Form1 form)
        {
            _form = form;
        }
    
        public void Test(string message)
        {
            MessageBox.Show(message, "client code");
        }
    }
    
    public partial class Form1 : Form
    {
        protected override void OnLoad(EventArgs e)
        {
            ...
    
            // Works correctly.
            webBrowser1.ObjectForScripting = new MyScriptObject(this);
    
            ...
        }
    }
    
  • 声明一个接口,并定义要公开的方法。

    public interface IForm1
    {
        void Test(string message);
    }
    
    [ComDefaultInterface(typeof(IForm1))]
    public partial class Form1 : Form, IForm1
    {
        protected override void OnLoad(EventArgs e)
        {
            ...
    
            // Works correctly.
            webBrowser1.ObjectForScripting = this;
    
            ...
        }
    }
    

受影响的 API

所有 Windows 窗体 API。