2015-09-29 1 views
24

Я нашел странное поведение в VS2015. Подробности:Почему необязательные параметры передаются неверными значениями в Visual Studio 2015?

У меня есть проект .Net 4.6, ссылающийся на сборку 3.5. Эта сборка определяет в одном из своих интерфейсов следующий метод, который я смог проверить с помощью декомпилятора Resharper.

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

Прими к сведению последнего необязательного аргумента flushAndEND, который имеет значение по умолчанию true. Проблема теперь в том, что когда я использую этот метод в своем проекте, зависание над именем метода показывает обычный VS toolTip, который детализирует подпись метода, за исключением того, что для меня это показывает неправильное значение по умолчанию необязательного аргумента flushAndEND. Вот скриншот

enter image description here

Чтобы сделать вещи еще хуже, я заметил, что во время выполнения, при вызове метода WriteString только с первым параметром, flushAndEND получает значение false, а не его значение по умолчанию определяется в DLL Я ссылаюсь. Воздействие этого на наш проект было большим, потому что оно оказало большую особенность нашего приложения бесполезным и заблокировало большую часть наших регрессионных тестов.

Мне удалось преодолеть эту проблему, вызывая значение необязательного аргумента true при вызове метода, но я боюсь, что в проекте есть другие вызовы, которые страдают от одной и той же проблемы. Поэтому мне понадобится лучшее решение для этого или, по крайней мере, понять, в чем причина такого поведения.

Мы только что обновили нашу среду несколько недель назад. До того, как мы использовали VS2013, все работало нормально.

Мне известно о confirmed .Net 4.6 bug, в результате чего некоторые аргументы передаются неверными значениями, и я могу связать их с моей проблемой здесь, но, как говорится в статье, ошибка возникает только при компиляции для архитектуры x64. Мой проект - это приложение WPF, и мы скомпилируем его как x32.

Почему WriteString вызван с неправильным аргументом по умолчанию?

Я попробую позже, чтобы изолировать проблему в небольшом проекте и посмотреть, могу ли я воспроизвести проблему.

EDIT: Мне удалось изолировать проблему и найти интересный материал!

Я создал простой .Net 4.6 консольное приложение, добавил ссылку на мой Dll и написал следующий простой код, которые состоят из посылки команды на устройство и читать ответ:

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(); 
    } 

Что я сделал следующий, открыт этот проект как с VS 2013, так и с VS 2015. В обеих версиях VS я перестраивал проект и запускал его. Вот результаты:

VS2013: WriteString была вызвана с помощью ПРАВИЛЬНОГО значения по умолчанию flushAndEND (который true значение очистки буфера и завершение команды).

VS2015: WriteString был вызван с использованием значения WRONG по умолчанию flushAndEND, которое дало исключение таймаута.

Дальнейшие проверки между двумя версиями Visual Studio показывает, что объект зритель браузер VS2013 показывает сигнатуру метода, как:

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

в то время как браузер объектов в VS2015 показывает сигнатуру метода, как:

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

Единственное объяснение этого поведения заключается в том, что проблема с компилятором VS2015 не считывает правильные значения по умолчанию из сборки.

+2

Erm, поэтому IntelliSense показывает это как * false *, и компилятор интерпретирует его как * false *, это только декомпилятор Resharper, который показывает * true *. Как это не ошибка Resharper? Похоже на COM-компонент, обновите библиотеку interop, запустив .NET-версию Tlbimp.exe. –

+0

. Верю, что правильное значение по умолчанию должно быть истинным, потому что это означает, что нужно записывать в буфер и немедленно скрывать его. И да, это компонент COM. Я попробую ваше предложение как можно скорее. – disklosr

+0

@HansPassant Можете ли вы показать, как использовать инструмент Tlbimp.exe? Благодаря! – disklosr

ответ

25

Хорошо, я нашел способ воспроизвести эту ошибку, которую каждый может увидеть сам. И, прежде всего, программисты Microsoft, которые работают над Roslyn, которые должны это исправить. Достаточно было поворота в вопросе, что это проблема, характерная для библиотек COM-взаимодействия. Это вышло.

Я искал библиотеку типов, которая широко это доступно с помощью метода, который имеет Их аргумента с дефолтом истинного. Существует ровно один, каковы коэффициенты :) Это SWbemQualifierSet.Add() method, он принимает 3 логических аргумента, которые имеют значение по умолчанию true.

Я первый сгенерировал библиотеку Interop, выполнив эту команду с визуальной строки студии Command:

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

который производит библиотеку Interop WbemScripting.dll. Затем написал небольшую тестовую программу, которая вызывает метод, добавив библиотеку Interop WbemScripting.dll в качестве ссылки:

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

Помните, что это не реально работать, мы заинтересованы только в коде он генерирует. Глядя на сборке с 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) 

проблем нет, ldc.i4.1 опкодов проходят истинной. Оба браузера объектов и IntelliSense правильно отображают true по умолчанию.


Затем я запустил самую старую версию Tlbimp.exe, которую я смог найти на своей машине. Он генерирует .NET 2.0.50727 совместимых сборки:

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

Перестроить тестовый проект, на этот раз она выглядит следующим образом:

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) 

Проблема воспроизведена, обратите внимание, как ldc.i4.0 в настоящее время проходит ложных. Ваш точный сценарий. Все остальное ведет себя так, как должно, и Object Browser, и IntelliSense показывают false, как и следовало ожидать. Он просто не соответствует значению по умолчанию, указанному в библиотеке типа COM.

Каждая другая версия Tlbimp.exe У меня есть версия SDK версии 7.1 и выше, сгенерируйте хороший код. Все они создают сборки .NET v4.0.

Характеризуя ошибку не так просто.Я не вижу очевидный недостаток, когда я декомпилировать «плохую» библиотеку Interop, он показывает Корректирует по умолчанию объявляется:

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

Так что неудивительно, что Resharper не согласна с Browser Object и IntelliSense, это, безусловно, разбирает по сам по себе и не полагается на интерфейсы метаданных .NET, поэтому в качестве значения по умолчанию отображается true.

Поэтому я должен предположить, что Roslyn чувствителен к целевой версии исполнения. Другими словами, это только пойдет не так в старых библиотеках COM-взаимодействия, которые были созданы с инструментами старше .NET 4.0. В противном случае, не дико странно, C# не начинал поддерживать аргументы по умолчанию до v4, и были несовместимые способы указать значение по умолчанию. В худшем случае приходится использовать PIA, поставляемый поставщиком. Смягчение обстоятельств заключается в том, что значения по умолчанию, отличные от 0/false/null, не так уж и важны. Самый простой способ увидеть проблемную библиотеку - посмотреть на сборку с помощью ildasm.exe, дважды щелкните манифест. Верхняя линия:

// Metadata version: v2.0.50727 

Это, безусловно, нарушающее поведение для существующих проектов, которые перестроены с VS2015, пожалуйста report the bug. Ссылка на этот Q + A, чтобы вам не пришлось повторять все.

Обходной путь прост, просто заново создайте библиотеку interop с Tlbimp.exe, как я показал. Или удалите библиотеку interop и добавьте ссылку на COM-компонент, чтобы библиотека interop генерировалась «на лету» при ее создании. Если вы зависите от PIA от поставщика, вам придется попросить их об обновлении или правильной процедуре для создания новой библиотеки interop.

+0

Спасибо, что потратили время и силы на анализ и воспроизведение ошибки. К сожалению, библиотека поступает от продавца, поэтому я не могу ее воссоздать сама. Мне нужно будет связаться с ними. Я сообщил об ошибке и привязался к этой теме. – disklosr

+0

Копаясь в некоторых статьях, похоже, что некоторые из проблем возникают только во время компиляции Release с включенным оптимизатором. Microsoft выпустила несколько официальных исправлений CLR, ноябрь 2015 года. Надеюсь, эта конкретная проблема также была исправлена. – Brain2000

+0

Проверяя это снова в обновлении VS2015 2, он теперь генерирует странное сообщение об ошибке. «ошибка CS1729:« SWbemQualifierSetClass »не содержит конструктор, который принимает 0 аргументов». Ну, он больше не генерирует плохой код. Прогресс. –