Подтвердить что ты не робот

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

Я нашел странное поведение в VS2015 Вот подробности:

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

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

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

введите описание изображения здесь

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

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

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

Я знаю подтвержденную .Net 4.6 ошибку, которая заставляет некоторые аргументы передавать неправильные значения, и я могу связать их с моей проблемой здесь, но, как говорится в статье, ошибка возникает только при компиляции для архитектуры 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 вызывается с использованием значения CORRECT по умолчанию 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 не считывает правильные значения по умолчанию из сборки.

4b9b3361

Ответ 1

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

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

Сначала я создал библиотеку interop, запустив эту команду из командной строки Visual Studio:

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

Создает библиотеку взаимодействия WbemScripting.dll. Затем написал небольшое тестовое приложение, которое вызывает этот метод, добавив библиотеку взаимодействия 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 передаются true. Оба Object Browser и 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 теперь передает false. Ваш точный сценарий. Все остальное ведет себя так, как должно, и Object Browser, и IntelliSense показывают false, как должны. Он просто не соответствует значению по умолчанию, указанному в библиотеке типа COM.


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

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

.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 не согласен с Object Browser и IntelliSense, он, безусловно, разбирает сам по себе и не полагается на интерфейсы метаданных .NET, поэтому показывает true как значение по умолчанию.

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

  // Metadata version: v2.0.50727

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

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