Inbyggd kod kan inte komma åt Windows Forms-objekt

Från och med .NET 5 kan du inte längre komma åt Windows Forms-objekt från inbyggd kod.

Ändra beskrivning

I tidigare .NET-versioner dekorerades vissa Windows Forms-typer som synliga för COM-interop och var därför tillgängliga för inbyggd kod. Från och med .NET 5 är inget Windows Forms-API synligt för COM-interop eller tillgänglig för inbyggd kod. .NET-runtime har inte längre stöd för att direkt skapa anpassade typbibliotek. Dessutom kan .NET-körningen inte vara beroende av typbiblioteket för .NET Framework (vilket skulle kräva att formen på klasserna bibehålls som de var i .NET Framework).

Orsak till ändring

  • Borttagning av ComVisible(true) från uppräkningar som användes för generering och sökning av typbibliotek (TLB-fil): Eftersom det inte finns någon WinForms TLB som tillhandahålls av .NET Core finns det inget värde för att behålla det här attributet.
  • Borttagning av ComVisible(true) från AccessibleObject klasser: Klasserna är inte CoCreateable (de har ingen parameterlös konstruktor) och att exponera en redan befintlig instans för COM kräver inte det attributet.
  • Borttagning av ComVisible(true) från Control och Component klasser: Detta användes för att tillåta värdskap för WinForms-kontroller via OLE/ActiveX, till exempel i VB6 eller MFC. Detta kräver dock en TLB för WinForms, som inte längre tillhandahålls, samt registerbaserad aktivering, vilket inte heller skulle fungera direkt. I allmänhet fanns det inget underhåll av COM-baserad värd för WinForms-kontroller, så supporten togs bort i stället för att lämna den i ett tillstånd som inte stöds.
  • Borttagning av ClassInterface attribut från kontroller: Om värd via OLE/ActiveX inte stöds behövs inte dessa attribut längre. De förvaras på andra platser där objekt fortfarande exponeras för COM och attributet kan vara relevant.
  • Borttagning av ComVisible(true) från EventArgs: De användes troligen med OLE/ActiveX-hosting, vilket inte längre stöds. De är inte CoCreateable heller, så attributet har inget syfte. Att exponera befintliga instanser utan att tillhandahålla en TLB är också inte meningsfullt.
  • Borttagning av ComVisible(true) från delegater: Syftet är okänt, men eftersom ActiveX-värd för WinForms-kontroller inte längre stöds, kommer det sannolikt inte att ha någon användbarhet.
  • Borttagning av ComVisible(true) från viss icke-offentlig kod: Den enda potentiella konsumenten skulle vara den nya Visual Studio-designern, men utan ett angivet GUID är det osannolikt att det fortfarande behövs.
  • Borttagning av ComVisible(true) från vissa godtyckliga offentliga designerklasser: Den gamla Visual Studio-designern kan ha använt COM-interop för att prata med dessa klasser. Den gamla designern stöder dock inte .NET Core, så få personer skulle behöva dessa som ComVisible.
  • IWin32Window definierade samma GUID som definierades i .NET Framework, vilket har farliga konsekvenser. Om du behöver interop med .NET Framework använder du ComImport.
  • WinForms-hanterade IDataObject skapades ComVisible. Detta krävs inte, det finns en separat ComImport gränssnittsdeklaration för IDataObject COM-interop. Det är kontraproduktivt att den hanterade IDataObject blir ComVisible, eftersom ingen TLB tillhandahålls och marshalling alltid kommer att misslyckas. Dessutom angavs inte GUID och skilde sig från .NET Framework, så det är osannolikt att borttagning av ett odokumenterat IID kommer att påverka kunderna negativt.
  • Borttagning av ComVisible(false): De placeras på till synes godtyckliga platser och är redundanta när standardvärdet är att inte exponera klasser för COM-interop.

Version lanserad

.NET 5.0

Följande exempel fungerar på .NET Framework och .NET Core 3.1. Det här exemplet förlitar sig på .NET Framework-typbiblioteket, vilket gör att JavaScript kan anropa tillbaka till formulärunderklassen via reflektion.

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

Det finns två möjliga sätt att få exemplet att fungera på .NET 5 och senare versioner:

  • Introducera ett användardeklarerad ObjectForScripting objekt som stöder IDispatch (som används som standard, såvida det inte uttryckligen ändras på projektnivå).

    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);
    
            ...
        }
    }
    
  • Deklarera ett gränssnitt med de metoder som ska exponeras.

    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;
    
            ...
        }
    }
    

Berörda API:er

Alla Windows Forms-API:er.