我在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之前,一切正常。
我知道confirmed .Net 4.6 bug会导致某些参数被传递错误的值,我可以在这里将它与我的问题联系起来,但正如文章所述,只有在编译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:使用CORRECT默认值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编译器无法从程序集中读取正确的默认值。
答案 0 :(得分:25)
好的,我找到了一种方法来重现这个任何人都可以看到的错误。最重要的是,在Roslyn上工作的微软程序员需要解决这个问题。在问题中有足够的领先优势,这是一个特定于COM互操作库的问题。那个淘汰了。
我搜索了一个广泛使用的类型库,其方法具有 bool 参数,默认值为 true 。只有一个,赔率是多少:)它是SWbemQualifierSet.Add() method,它需要3个布尔参数,它们的默认值都是真的。
我首先通过从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
操作码传递 true 。对象浏览器和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 。你的具体情况。其他一切行为都应该如此,对象浏览器和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不同意对象浏览器和智能感知并不令人惊讶,它肯定会自行反汇编并且不依赖于.NET元数据接口,因此将 true 显示为默认值。
因此,我必须假设Roslyn对目标运行时版本很敏感。换句话说,只有使用早于.NET 4.0的工具创建的旧COM互操作库才会出错。否则不是很奇怪,C#在v4之前没有开始支持默认参数,并且存在指定默认值的不兼容方式。最坏情况是必须使用由供应商提供的PIA。减轻情况是除了0 / false / null之外的默认值并不常见。查看有问题的库的最简单方法是使用ildasm.exe查看程序集,双击Manifest。第一行: // Metadata version: v2.0.50727
对于使用VS2015重建的现有项目,这肯定会破坏行为,请report the bug。链接到此Q + A,因此您无需重复所有内容。
解决方法很简单,只需用Tlbimp.exe重新创建互操作库,如我所示。或者删除互操作库并添加对COM组件的引用,以便在构建时即时生成互操作库。如果您依赖供应商的PIA,那么您将不得不要求他们更新或创建新的互操作库的正确程序。