dis*_*osr 24 .net c# visual-studio visual-studio-2015 .net-4.6
我在VS2015中发现了一个奇怪的行为.以下是详细信息:
我有一个引用3.5程序集的.Net 4.6项目.这个程序集在其中一个接口中定义了我能够使用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调用错误的默认参数?
我稍后会尝试在一个小项目中隔离问题,看看我是否可以重现这个问题.
编辑:我设法隔离了问题,发现了一些有趣的东西!
我创建了一个简单的.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编译器无法从程序集中读取正确的默认值.
Han*_*ant 25
好吧,我找到了一种方法来重现这个任何人都能看到的错误.最重要的是,在Roslyn上工作的微软程序员需要解决这个问题.在问题中有足够的领先优势,这是一个特定于COM互操作库的问题.淘汰了.
我搜索了一个可以广泛使用的类型库,其方法具有默认值为true的bool参数.只有一个,几率是多少 :)它是SWbemQualifierSet.Add()方法,它需要3个布尔参数,它们都有一个默认值为true.
我首先通过从Visual Studio命令提示符运行此命令来生成interop库:
   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操作码传递正确.这两个对象浏览器和智能感知正确显示真正的为默认值.
然后我运行了我能在我的机器上找到的最老版本的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现在如何传递虚假.你的具体情况.其他一切行为都应该如此,对象浏览器和智能感知都应该显示出错误.它与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不同意对象浏览器和智能感知并不令人惊讶,它肯定会自行拆解并且不依赖于.NET元数据接口,因此默认显示为true.
因此,我必须假设Roslyn对目标运行时版本很敏感.换句话说,只有使用早于.NET 4.0的工具创建的旧COM互操作库才会出错.否则不是很奇怪,C#在v4之前没有开始支持默认参数,并且存在指定默认值的不兼容方式.最坏情况是必须使用由供应商提供的PIA.减轻情况是除了0/false/null之外的默认值并不常见.查看有问题的库的最简单方法是使用ildasm.exe查看程序集,双击Manifest.顶线:
  // Metadata version: v2.0.50727
对于使用VS2015重建的现有项目,这肯定是破坏行为,请报告错误.链接到此Q + A,因此您无需重复所有内容.
解决方法很简单,只需用Tlbimp.exe重新创建互操作库,如我所示.或者删除互操作库并添加对COM组件的引用,以便在构建时即时生成互操作库.如果您依赖供应商的PIA,那么您将不得不要求他们更新或创建新的互操作库的正确程序.