Il codice nativo non può accedere agli oggetti Windows Forms

A partire da .NET 5, non è più possibile accedere agli oggetti Windows Forms dal codice nativo.

Descrizione delle modifiche

Nelle versioni precedenti di .NET alcuni tipi di Windows Forms sono stati decorati come visibili all'interoperabilità COM e pertanto erano accessibili al codice nativo. A partire da .NET 5, nessuna API Windows Forms è visibile all'interoperabilità COM o accessibile al codice nativo. Il runtime .NET non supporta più la creazione di librerie dei tipi personalizzate predefinite. Inoltre, il runtime .NET non può dipendere dalla libreria dei tipi per .NET Framework ( che richiederebbe la gestione della forma delle classi così come erano in .NET Framework).

Motivo della modifica

  • Rimozione di ComVisible(true) dalle enumerazioni usate per la generazione e la ricerca della libreria dei tipi (file TLB): poiché non esiste alcun TLB WinForms fornito da .NET Core, non esiste alcun valore per mantenere questo attributo.
  • Rimozione di ComVisible(true) dalle classi AccessibleObject: le classi non sono CoCreateable (non hanno un costruttore senza parametri) e l'esposizione di un'istanza già esistente a COM non richiede tale attributo.
  • Rimozione di ComVisible(true) dalle classi Control e Component : questa operazione è stata usata per consentire all'hosting di effettuare controlli WinForms tramite OLE/ActiveX, ad esempio in VB6 o MFC. Tuttavia, questo richiede un TLB per WinForms che non è più fornito, nonché l'attivazione basata sul Registro di sistema, che anch’esso non funzionerebbe per impostazione predefinita. In genere, non è stata eseguita alcuna manutenzione dell'hosting basato su COM dei controlli WinForms, quindi il supporto è stato rimosso invece di lasciarlo in uno stato non supportato.
  • Rimozione degli attributi di ClassInterface dai controlli: se l'hosting tramite OLE/ActiveX non è più supportato, questi attributi non sono più necessari. Vengono mantenuti in altre posizioni in cui gli oggetti sono ancora esposti a COM e l'attributo può essere rilevante.
  • Rimozione di ComVisible(true) da EventArgs: sono stati usati più probabilmente con l'hosting OLE/ActiveX, che non è più supportato. Non sono nemmeno CoCreateable, quindi l'attributo non ha alcun scopo. Inoltre, l'esposizione di istanze esistenti senza fornire un bilanciamento del carico di rete non ha senso.
  • Rimozione di ComVisible(true) dai delegati: lo scopo è sconosciuto, ma poiché l'hosting ActiveX di controlli WinForms non è più supportato, è improbabile che abbia alcuna utilità.
  • Rimozione di ComVisible(true) da codice non pubblico: l'unico potenziale consumer sarà la nuova finestra di progettazione di Visual Studio, ma senza un GUID specificato è improbabile che sia ancora necessario.
  • Rimozione di ComVisible(true) da alcune classi di progettazione pubblica arbitrarie: la vecchia finestra di progettazione di Visual Studio potrebbe aver usato l'interoperabilità COM per comunicare con queste classi. Tuttavia, la finestra di progettazione precedente non supporta .NET Core, quindi poche persone ne avrebbero bisogno come ComVisible.
  • IWin32Window ha definito lo stesso GUID definito in .NET Framework, che ha conseguenze pericolose. Se è necessaria l'interoperabilità con .NET Framework, usare ComImport.
  • Il IDataObject gestito WinForms è stato reso ComVisible. Questa operazione non è necessaria, esiste una dichiarazione di interfaccia ComImport separata per l'interoperabilità COM IDataObject. È controproducente che IDataObject gestito sia ComVisible, poiché non viene fornito alcun bilanciamento del carico di rete e il marshalling avrà sempre esito negativo. Inoltre, il GUID non è stato specificato e differisce da .NET Framework, quindi è improbabile che la rimozione di un IID non documentato influirà negativamente sui clienti.
  • Rimozione di ComVisible(false): tali elementi vengono inseriti in posizioni apparentemente arbitrarie e sono ridondanti quando l'impostazione predefinita non espone le classi all'interoperabilità COM.

Versione di introduzione

.NET 5.0

L'esempio seguente funziona in .NET Framework e .NET Core 3.1. Questo esempio si basa sulla libreria dei tipi di .NET Framework, che consente a JavaScript di richiamare la sottoclasse del modulo tramite reflection.

[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");
    }
}

Esistono due modi possibili per rendere l'esempio funzionante in .NET 5 e versioni successive:

  • Introdurre un oggetto ObjectForScripting dichiarato dall'utente che supporta IDispatch (che viene applicato per impostazione predefinita, a meno che non sia stato modificato in modo esplicito a livello di progetto).

    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);
    
            ...
        }
    }
    
  • Dichiarare un'interfaccia con i metodi da esporre.

    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 interessate

Tutte le API di Windows Forms.