2015-09-29 3 views
24

Ich fand ein seltsames Verhalten in VS2015 Hier sind die Details:Warum werden optionale Parameter in Visual Studio 2015 falsch übergeben?

Ich habe ein .Net 4.6-Projekt Verweis auf eine 3.5-Assembly. Diese Assembly definiert in einer ihrer Schnittstellen die folgende Methode, die ich mit Resharper Decompiler überprüfen konnte.

void WriteString([MarshalAs(UnmanagedType.BStr), In] string data, [In] bool flushAndEND = true); 

Beachten Sie die letzte optionale Argument flushAndEND, die einen Standardwert von true hat. Das Problem ist jetzt, wenn ich diese Methode in meinem Projekt benutze, den Mauszeiger über dem Methodennamen den üblichen VS-ToolTip zeigt, der die Methodensignatur angibt, außer dass er für mich einen falschen Standardwert des optionalen Arguments flushAndEND anzeigt. Hier ist ein Screenshot

enter image description here

Um die Dinge noch schlimmer zu machen, ich habe bemerkt, dass während der Laufzeit, wenn das Verfahren WriteString nur mit dem ersten Parameter aufrufen, flushAndEND-false gesetzt wird und nicht den Standardwert in der DLL definiert Ich referenziere. Die Auswirkung auf unser Projekt war groß, weil es ein großes Merkmal unserer App nutzlos machte und einen großen Teil unserer Regressionstests blockierte.

Ich konnte dieses Problem überwinden, indem ich den Wert des optionalen Arguments beim Aufruf der Methode auf true setzte, aber ich fürchte, es gibt andere Aufrufe an einem anderen Ort im Projekt, die unter dem gleichen Problem leiden. Ich werde dafür eine bessere Lösung brauchen oder zumindest verstehen, was die Ursache für dieses Verhalten ist.

Wir haben gerade unsere Umgebung vor einigen Wochen aufgerüstet. Bevor wir VS2013 benutzt haben und alles hat gut funktioniert.

Ich bin mir der confirmed .Net 4.6 bug bewusst, die verursacht, dass einige Argumente falsche Werte übergeben werden, und ich kann es auf mein Problem hier beziehen, aber wie es in dem Artikel gesagt wird, tritt der Fehler nur beim Kompilieren für x64-Architektur auf. Mein Projekt ist eine WPF-Anwendung und wir kompilieren es als x32.

Warum wird WriteString mit falschem Standardargument aufgerufen?

Ich werde später versuchen, das Problem in einem kleinen Projekt zu isolieren und sehen, ob ich das Problem reproduzieren kann.

EDIT: Ich habe es geschafft, das Problem zu isolieren, und einige interessante Sachen gefunden!

habe ich eine einfache .NET 4.6-Konsole-Anwendung, hat einen Verweis auf meine Dll und schrieb den folgenden einfachen Code, der sich auf eine Vorrichtung zum Senden eines Befehls aus und liest die Antwort:

private static void Main(string[] args) 
    { 

     //Init managers 
     ResourceManager ioMgr = new ResourceManagerClass(); 
     FormattedIO488 instrument = new FormattedIO488Class(); 

     //Connect to the USB device 
     instrument.IO = (IMessage)ioMgr.Open("USB0::0x0957::0x0909::MY46312358::0::INSTR"); 


     string cmd = "*IDN?"; 

     //This is the problematic method from my dll 
     instrument.WriteString(cmd); 

     //Read the response 
     string responseString = instrument.ReadString(); 
     Console.WriteLine(responseString); 
     Console.ReadKey(); 
    } 

Was ich tat, Als nächstes wird dieses Projekt sowohl von VS 2013 als auch von VS 2015 geöffnet. In beiden Versionen von VS habe ich das Projekt neu erstellt und ausgeführt. Hier sind die Ergebnisse:

VS2013: WriteString wurde mit dem richtigen Standardwert von flushAndEND genannt (die true Bedeutung der Puffer spülen und den Befehl beenden).

VS2015: WriteString wurde mit dem WRONG-Standardwert flushAndEND aufgerufen, der eine Zeitüberschreitungsausnahme ergab.

Weitere Kontrollen zwischen den beiden Versionen von Visual Studio zeigt, dass der Viewer-Objekt-Browser in VS2013 die Methodensignatur wie zeigt:

void WriteString(string data, [bool flushAndEND = True]) 

, während das Objekt-Browser in VS2015 zeigt die Methodensignatur als:

void WriteString(string data, [bool flushAndEND = False]) 

Die einzige Erklärung für dieses Verhalten ist, dass ein Problem mit VS2015 Compiler nicht richtige Standardwerte aus der Assembly lesen.

+2

Ähm, also IntelliSense zeige es als * false * und der Compiler interpretiert es als * false *, es ist nur der Resharper Decompiler, der * true * anzeigt. Wie ist das kein Resharper-Fehler? Sieht aus wie eine COM-Komponente, aktualisieren Sie die Interop-Bibliothek, indem Sie die .NET 4-Version von Tlbimp.exe –

+0

Ich glaube, dass der richtige Standardwert sollte wahr sein, weil dies bedeutet, in den Puffer schreiben und sofort flush. Und ja, es ist eine COM-Komponente. Ich werde Ihren Vorschlag so schnell wie möglich versuchen. – disklosr

+0

@HansPassant Können Sie zeigen, wie Sie das Tool Tlbimp.exe verwenden? Vielen Dank! – disklosr

Antwort

25

Okay, ich habe einen Weg gefunden, diesen Fehler zu reproduzieren, den jeder selbst sehen kann. Und vor allem die Microsoft-Programmierer, die an Roslyn arbeiten, die das beheben müssen. Es gab genug Anhaltspunkte in der Frage, dass dies ein Problem ist, das spezifisch für COM-Interop-Bibliotheken ist. Das hat ausgereicht.

ich für eine Art Bibliothek gesucht, die weithin mit einer Methode, die ein Bool Argument mit einem Standard von wahr hat zur Verfügung steht. Es gibt genau eine, was die Chancen sind :) Es ist SWbemQualifierSet.Add() method, es dauert 3 Boolean Argumente, die alle einen Standardwert von True haben.

I erzeugt zuerst die Interop-Bibliothek mit diesem Befehl aus der Visual Studio-Eingabeaufforderung ausgeführt wird:

tlbimp C:\Windows\SysWOW64\wbem\wbemdisp.tlb 

, die eine WbemScripting.dll Interop-Bibliothek erzeugt. Dann schrieb eine kleine Test-Anwendung, die die Methode aufruft, Hinzufügen der WbemScripting.dll Interop-Bibliothek als Referenz:

class Program { 
    static void Main(string[] args) { 
     var obj = new WbemScripting.SWbemQualifierSet(); 
     object val = null; 
     obj.Add("foo", ref val); 
    } 
} 

Beachten Sie, dass es nicht wirklich läuft, sind wir daran interessiert ist nur in dem Code erzeugt es. Mit Blick auf die Montage mit ildasm.exe:

IL_001e: ldstr  "foo" 
    IL_0023: ldloca.s val 
    IL_0025: ldc.i4.1 
    IL_0026: ldc.i4.1 
    IL_0027: ldc.i4.1 
    IL_0028: ldc.i4.0 
    IL_0029: callvirt instance class WbemScripting.SWbemQualifier WbemScripting.ISWbemQualifierSet::Add(string, 
                             object&, 
                             bool, 
                             bool, 
                             bool, 
                             int32) 

Keine Probleme, die ldc.i4.1 Opcodes passieren wahr. Sowohl der Objektbrowser als auch IntelliSense zeigen standardmäßig true als Standard an.


Dann lief ich die älteste Version von Tlbimp.exe, die ich auf meinem Computer finden konnte. Es erzeugt eine .NET 2.0.50727 kompatible Baugruppe:

"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\TlbImp.exe" c:\windows\syswow64\wbem\wbemdisp.tlb 

das Testprojekt neu aufbauen, diesmal sieht es so aus:

IL_001e: ldstr  "foo" 
    IL_0023: ldloca.s val 
    IL_0025: ldc.i4.0 
    IL_0026: ldc.i4.0 
    IL_0027: ldc.i4.0 
    IL_0028: ldc.i4.0 
    IL_0029: callvirt instance class WbemScripting.SWbemQualifier WbemScripting.ISWbemQualifierSet::Add(string, 
                             object&, 
                             bool, 
                             bool, 
                             bool, 
                             int32) 

Problem reproduziert, beachten Sie, wie ldc.i4.0 jetzt falsche gibt. Dein genaues Szenario. Alles andere verhält sich wie es sollte, sowohl Objektkatalog als auch IntelliSense zeigen false wie sie sollten. Es stimmt einfach nicht mit dem Standardwert überein, der in der COM-Typbibliothek angegeben ist.


Jede andere Version von Tlbimp.exe ich zur Verfügung habe, SDK-Version 7.1 und höher guten Code generieren. Sie generieren alle .NET v4.0-Assemblys.

Die Charakterisierung des Fehlers ist nicht so einfach.Ich habe keinen offensichtlichen Fehler sehen, wenn ich die „schlechte“ Interop-Bibliothek dekompilieren, es zeigt die Standardwerte korrigiert erklärt werden:

.method public hidebysig newslot virtual instance class WbemScripting.SWbemQualifier marshal(interface) Add([in] string marshal(bstr) strName, [in] object& marshal(struct) varVal, [in][opt] bool bPropagatesToSubclass, [in][opt] bool bPropagatesToInstance, [in][opt] bool bIsOverridable, [in][opt] int32 iFlags) runtime managed internalcall 
{ 
    .custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = { int32(2) } 
    .param [3] = bool(true) 
    .param [4] = bool(true) 
    .param [5] = bool(true) 
    .param [6] = int32(0) 
    .override WbemScripting.ISWbemQualifierSet::Add 
} 

es also wenig überraschend ist, dass ReSharper nicht einverstanden mit Object Browser und IntelliSense, es auseinanderbauen sicher durch selbst und nicht auf .NET-Metadaten-Schnittstellen angewiesen, so zeigt true als Standard.

Ich muss daher davon ausgehen, dass Roslyn empfindlich auf die Ziellaufzeitversion reagiert. Mit anderen Worten, das wird nur mit alten COM-Interop-Bibliotheken schiefgehen, die mit älteren Werkzeugen als .NET 4.0 erstellt wurden. Sonst nicht sonderlich komisch, C# hat nicht begonnen, Standardargumente bis v4 zu unterstützen, und es gab inkompatible Möglichkeiten, den Standardwert anzugeben. Worst-Case-Szenario ist die Verwendung einer PIA, die von einem Anbieter geliefert wird. Mildernde Umstände sind, dass andere Standardwerte als 0/false/null nicht üblich sind. Der einfachste Weg, eine problematische Bibliothek zu sehen, besteht darin, die Assembly mit ildasm.exe zu betrachten und doppelt auf das Manifest zu klicken. Top line:

// Metadata version: v2.0.50727 

Dies ist sicherlich für bestehende Projekte zu brechen Verhalten, das mit VS2015 neu erstellt werden, bitte report the bug. Verknüpfen Sie mit diesem Q + A, damit Sie nicht alles wiederholen müssen.

Die Problemumgehung ist einfach, erstellen Sie einfach die Interop-Bibliothek mit Tlbimp.exe, wie ich gezeigt habe. Oder entfernen Sie die Interop-Bibliothek und fügen Sie einen Verweis auf die COM-Komponente hinzu, so dass die Interop-Bibliothek während des Builds direkt generiert wird. Wenn Sie von einer PIA eines Herstellers abhängig sind, müssen Sie sie nach einer Aktualisierung oder dem korrekten Verfahren zum Erstellen einer neuen Interop-Bibliothek fragen.

+0

Danke, dass Sie sich die Zeit genommen haben, den Fehler zu analysieren und zu reproduzieren. Leider kommt die Bibliothek von einem Hersteller, so dass ich sie nicht selbst erstellen kann. Ich werde sie kontaktieren müssen. Ich habe den Fehler gemeldet und mit diesem Thread verknüpft. – disklosr

+0

In einige der Artikel eingrabend, sieht es so aus, als ob einige der Probleme nur während Release Compiles mit dem Optimizer auftreten. Microsoft hat im November 2015 mehrere offizielle CLR-Patches veröffentlicht. Hoffentlich wurde dieses spezielle Problem ebenfalls behoben. – Brain2000

+0

Wenn Sie es in VS2015 Update 2 erneut überprüfen, wird jetzt eine bizarre Fehlermeldung generiert. msgstr "Fehler CS1729: 'SWbemQualifierSetClass' enthält keinen Konstruktor, der 0 Argumente akzeptiert". Nun, es erzeugt keinen schlechten Code mehr. Fortschritt. –