機器碼無法存取 Windows Forms 物件

從 .NET 5 開始,您無法再從機器碼中存取 Windows Forms 物件。

變更描述

在舊版 .NET 版本中,某些 Windows Forms 型別已被修飾為對 COM Interop 是可見的,因此機器碼可以進行存取。 從 .NET 5 開始,Windows Forms API 對 COM Interop 是不可見的,或無法從機器碼存取。 .NET 執行階段不再支援立即建立自訂型別程式庫。 而且,.NET 執行階段不能相依於 .NET Framework 的型別程式庫 (這會需要維持類別的圖形,就像它們在 .NET Framework 中一樣)。

變更原因

  • 從用於型別程式庫 (TLB 檔案) 產生和查閱的列舉中移除 ComVisible(true):因為 .NET Core 沒未提供 WinForms TLB,所以保留此屬性沒有任何價值。
  • AccessibleObject 類別中移除 ComVisible(true):類別不是 CoCreateable (它們沒有無參數建構函式),而且向 COM 公開已存在的執行個體不需要該屬性。
  • ControlComponent 中移除 ComVisible(true):這是用來允許透過 OLE/ActiveX 裝載 WinForms 控制項,例如在 VB6 或 MFC 中。 但是,這需要一個適用於 WinForms 的 TLB (不再提供),以及登錄型的啟用 (這也無法立即運作)。 通常,不會維護以 COM 為基礎的 WinForms 控制項裝載,因此移除了支援,而不是讓它處於不受支援的狀態。
  • 從控制項中移除 ClassInterface 屬性:如果不支援透過 OLE/ActiveX 進行裝載,則不再需要這些屬性。 它們會保留在物件仍公開給 COM 且屬性可能相關的其他位置中。
  • EventArgs 中移除 ComVisible(true):它們最有可能與不再受支援的 OLE/ActiveX 裝載搭配使用。 它們也不是 CoCreateable,因此該屬性沒有任何用途。 而且,在不提供 TLB 的情況下公開現有的執行個體是沒有意義的。
  • 從委派中移除 ComVisible(true):用途不明,但因為不再支援 WinForms 控制項的 ActiveX 裝載,所以不太可能有任何實用性。
  • 從某個非公用程式代碼中移除 ComVisible(true):唯一可能的取用者是新的 Visual Studio 設計工具,但若未指定 GUID,就不太可能仍需要它。
  • 從某些任意公用設計工具類別中移除 ComVisible(true):舊的 Visual Studio 設計工具可能已正在使用 COM Interop 來與這些類別交談。 但是,因為舊的設計工具不支援 .NET Core,所以很少有人會需要這些做為 ComVisible
  • IWin32Window 定義了在 .NET Framework 中定義的相同 GUID,這會產生危險的後果。 如果您需要與 .NET Framework 相互運作,請使用 ComImport
  • WinForms 受控的 IDataObject 變成了 ComVisible。 這不是必要的,IDataObject COM interop 有一個個別的 ComImport 介面宣告。 將受控的 IDataObject 設為 ComVisible 會適得其反,因為沒有提供 TLB,封送處理一律會失敗。 而且,GUID 未指定且不同於 .NET Framework,因此移除未記載的 IID 不太可能會對客戶產生負面的影響。
  • 移除 ComVisible(false):這些會被放在看似任意的位置,而且在預設不向 COM Interop 公開類別時是多餘的。

導入的版本

.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 和更新版本上運作:

  • 引入支援 IDispatch 的使用者宣告的 ObjectForScripting 物件 (依預設會套用,除非在專案層級明確變更)。

    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 Forms API。