mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
MSVC documents C++/CLI members with ECMA-372-style ID strings that
differ from Roslyn's in signatures: custom modifiers are rendered after
the modified type (a 'const int' parameter becomes
System.Int32!System.Runtime.CompilerServices.IsConst), arity markers
stay on generic instantiations (List`1{System.Int32}.Enumerator), and
the default indexed property is called 'default'. Roslyn ignores
modifiers entirely, so one generated string cannot match both
compilers' xml files: GetIdString keeps producing the C#/Roslyn form,
and the new GetIdStringCandidates additionally yields the C++/CLI form,
most specific first, for lookup code to try in order.
The dialect is pinned by IdStringProbe.il/.xml, the trimmed disassembly
of an MSVC-compiled probe assembly together with the unmodified xml MSVC
generated for it. Notable observed deviations from MSVC's documented
format: modreq is generated, but only modreq(IsVolatile) uses the
documented '|'; modreq(IsByValue) on conversion operator operands is
rendered with '!'.
Assisted-by: Claude:claude-fable-5:Claude Code
pull/3941/head
6 changed files with 713 additions and 14 deletions
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
/IdStringProbe.dll |
||||
/IdStringProbe.pdb |
||||
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
// Probe assembly for MSVC's xml doc ID string generator (the ECMA-372-style
|
||||
// dialect ILSpy's IdStringProvider must reproduce for C++/CLI assemblies).
|
||||
// Every documented member below lands in the generated .xml; the members are
|
||||
// independent, so if one fails to compile, delete it and rebuild.
|
||||
//
|
||||
// Build (VS Developer Command Prompt):
|
||||
// msbuild IdStringProbe.vcxproj -p:Configuration=Debug -p:Platform=x64
|
||||
// Return: x64\Debug\IdStringProbe.dll and x64\Debug\IdStringProbe.xml
|
||||
|
||||
using namespace System; |
||||
using namespace System::Collections::Generic; |
||||
|
||||
/// <summary>Probe class.</summary>
|
||||
public ref class IdProbe |
||||
{ |
||||
public: |
||||
/// <summary>const int parameter; known baseline: System.Int32!System.Runtime.CompilerServices.IsConst</summary>
|
||||
static int ConstValue(const int x) { return x; } |
||||
|
||||
/// <summary>long parameter; modopt(IsLong) on Int32</summary>
|
||||
static void TakesLong(long x) { (void)x; } |
||||
|
||||
/// <summary>unsigned long parameter; modopt(IsLong) on UInt32</summary>
|
||||
static void TakesULong(unsigned long x) { (void)x; } |
||||
|
||||
/// <summary>char pointer parameter; modopt(IsSignUnspecifiedByte) under a pointer</summary>
|
||||
static void TakesCharPtr(char* p) { (void)p; } |
||||
|
||||
/// <summary>const char pointer parameter; IsConst and IsSignUnspecifiedByte together (modifier ordering)</summary>
|
||||
static void TakesConstCharPtr(const char* p) { (void)p; } |
||||
|
||||
/// <summary>volatile int pointer parameter; modreq(IsVolatile): the open question is whether MSVC renders '|', '!', or omits it</summary>
|
||||
static void TakesVolatilePtr(volatile int* p) { (void)p; } |
||||
|
||||
/// <summary>const volatile int pointer parameter; modreq and modopt mixed on one type</summary>
|
||||
static void TakesConstVolatilePtr(const volatile int* p) { (void)p; } |
||||
|
||||
/// <summary>tracking reference parameter; expected System.Int32@</summary>
|
||||
static void TakesTrackingRef(int% r) { r = 0; } |
||||
|
||||
/// <summary>const tracking reference parameter; modifier placement relative to the '@'</summary>
|
||||
static void TakesConstTrackingRef(const int% r) { (void)r; } |
||||
|
||||
/// <summary>native reference parameter under /clr; representation evidence</summary>
|
||||
static void TakesNativeRef(int& r) { r = 0; } |
||||
|
||||
/// <summary>two-dimensional managed array parameter; expected System.Int32[0:,0:]</summary>
|
||||
static void TakesArray2(array<int, 2>^ a) { (void)a; } |
||||
|
||||
/// <summary>nested type of a generic instantiation; how does MSVC distribute the type arguments?</summary>
|
||||
static void TakesEnumerator(List<int>::Enumerator e) { (void)e; } |
||||
|
||||
/// <summary>generic method; expected arity marker and grave-accent parameter encoding</summary>
|
||||
generic<typename T> static void Gen(T t) { (void)t; } |
||||
|
||||
/// <summary>conversion operator with a by-value ref-class parameter; the MSVC docs show IdProbe!IsByValue and a '~' return type</summary>
|
||||
static explicit operator int(IdProbe x) { (void)x; return 0; } |
||||
|
||||
/// <summary>indexed property with a long parameter; modopt inside the indexer parentheses</summary>
|
||||
property int default[long] |
||||
{ |
||||
int get(long i) { return (int)i; } |
||||
void set(long i, int value) { (void)i; (void)value; } |
||||
} |
||||
}; |
||||
|
||||
/// <summary>Generic ref class; arity in the type ID.</summary>
|
||||
generic<typename T> public ref class GBox |
||||
{ |
||||
public: |
||||
/// <summary>method on a generic type taking T; expected grave-accent zero</summary>
|
||||
void Hold(T item) { (void)item; } |
||||
}; |
||||
|
||||
/// <summary>Consumer of an instantiated generic type.</summary>
|
||||
public ref class GBoxUser |
||||
{ |
||||
public: |
||||
/// <summary>generic instantiation in a signature; expected GBox{System.Int32}</summary>
|
||||
static void Use(GBox<int>^ b) { (void)b; } |
||||
}; |
||||
@ -0,0 +1,351 @@
@@ -0,0 +1,351 @@
|
||||
// Trimmed disassembly of an assembly compiled with MSVC from IdStringProbe.cpp |
||||
// (next to this file) with /clr /doc. The signatures are verbatim from the |
||||
// MSVC-produced metadata; the accompanying IdStringProbe.xml is the unmodified |
||||
// output of MSVC's xml doc generator for the same compilation and serves as the |
||||
// reference for the C++/CLI (ECMA-372-style) ID string dialect. |
||||
|
||||
.assembly extern mscorlib |
||||
{ |
||||
.ver 4:0:0:0 |
||||
} |
||||
.assembly IdStringProbe |
||||
{ |
||||
.ver 0:0:0:0 |
||||
} |
||||
.module IdStringProbe.dll |
||||
|
||||
.class public auto ansi beforefieldinit IdProbe |
||||
extends [mscorlib]System.Object |
||||
{ |
||||
.custom instance void [mscorlib]System.Reflection.DefaultMemberAttribute::.ctor(string) = ( |
||||
01 00 04 49 74 65 6d 00 00 |
||||
) |
||||
// Methods |
||||
.method public hidebysig static |
||||
int32 ConstValue ( |
||||
int32 modopt([mscorlib]System.Runtime.CompilerServices.IsConst) x |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x10e0 |
||||
// Header size: 12 |
||||
// Code size: 4 (0x4) |
||||
.maxstack 1 |
||||
.locals ( |
||||
[0] int32 |
||||
) |
||||
|
||||
IL_0000: ldarg.0 |
||||
IL_0001: stloc.0 |
||||
IL_0002: ldloc.0 |
||||
IL_0003: ret |
||||
} // end of method IdProbe::ConstValue |
||||
|
||||
.method public hidebysig static |
||||
void TakesLong ( |
||||
int32 modopt([mscorlib]System.Runtime.CompilerServices.IsLong) x |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x10f0 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesLong |
||||
|
||||
.method public hidebysig static |
||||
void TakesULong ( |
||||
uint32 modopt([mscorlib]System.Runtime.CompilerServices.IsLong) x |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1100 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesULong |
||||
|
||||
.method public hidebysig static |
||||
void TakesCharPtr ( |
||||
int8 modopt([mscorlib]System.Runtime.CompilerServices.IsSignUnspecifiedByte)* p |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1110 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesCharPtr |
||||
|
||||
.method public hidebysig static |
||||
void TakesConstCharPtr ( |
||||
int8 modopt([mscorlib]System.Runtime.CompilerServices.IsSignUnspecifiedByte) modopt([mscorlib]System.Runtime.CompilerServices.IsConst)* p |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1120 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesConstCharPtr |
||||
|
||||
.method public hidebysig static |
||||
void TakesVolatilePtr ( |
||||
int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile)* p |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1130 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesVolatilePtr |
||||
|
||||
.method public hidebysig static |
||||
void TakesConstVolatilePtr ( |
||||
int32 modreq([mscorlib]System.Runtime.CompilerServices.IsVolatile) modopt([mscorlib]System.Runtime.CompilerServices.IsConst)* p |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1140 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesConstVolatilePtr |
||||
|
||||
.method public hidebysig static |
||||
void TakesTrackingRef ( |
||||
int32& r |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1150 |
||||
// Header size: 12 |
||||
// Code size: 4 (0x4) |
||||
.maxstack 2 |
||||
|
||||
IL_0000: ldarg.0 |
||||
IL_0001: ldc.i4.0 |
||||
IL_0002: stind.i4 |
||||
IL_0003: ret |
||||
} // end of method IdProbe::TakesTrackingRef |
||||
|
||||
.method public hidebysig static |
||||
void TakesConstTrackingRef ( |
||||
int32 modopt([mscorlib]System.Runtime.CompilerServices.IsConst)& r |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1160 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesConstTrackingRef |
||||
|
||||
.method public hidebysig static |
||||
void TakesNativeRef ( |
||||
int32* modopt([mscorlib]System.Runtime.CompilerServices.IsImplicitlyDereferenced) r |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1170 |
||||
// Header size: 12 |
||||
// Code size: 4 (0x4) |
||||
.maxstack 2 |
||||
|
||||
IL_0000: ldarg.0 |
||||
IL_0001: ldc.i4.0 |
||||
IL_0002: stind.i4 |
||||
IL_0003: ret |
||||
} // end of method IdProbe::TakesNativeRef |
||||
|
||||
.method public hidebysig static |
||||
void TakesArray2 ( |
||||
int32[0..., 0...] a |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1180 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesArray2 |
||||
|
||||
.method public hidebysig static |
||||
void TakesEnumerator ( |
||||
valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> e |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1190 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::TakesEnumerator |
||||
|
||||
.method public hidebysig static |
||||
void Gen<T> ( |
||||
!!T t |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x11a0 |
||||
// Header size: 12 |
||||
// Code size: 3 (0x3) |
||||
.maxstack 1 |
||||
.locals ( |
||||
[0] !!T |
||||
) |
||||
|
||||
IL_0000: ldarg.0 |
||||
IL_0001: stloc.0 |
||||
IL_0002: ret |
||||
} // end of method IdProbe::Gen |
||||
|
||||
.method public hidebysig specialname static |
||||
int32 op_Explicit ( |
||||
class IdProbe modreq([mscorlib]System.Runtime.CompilerServices.IsByValue) x |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x11b0 |
||||
// Header size: 12 |
||||
// Code size: 4 (0x4) |
||||
.maxstack 1 |
||||
.locals ( |
||||
[0] int32 |
||||
) |
||||
|
||||
IL_0000: ldc.i4.0 |
||||
IL_0001: stloc.0 |
||||
IL_0002: ldloc.0 |
||||
IL_0003: ret |
||||
} // end of method IdProbe::op_Explicit |
||||
|
||||
.method public hidebysig specialname |
||||
instance int32 get_Item ( |
||||
int32 modopt([mscorlib]System.Runtime.CompilerServices.IsLong) i |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x11c0 |
||||
// Header size: 12 |
||||
// Code size: 4 (0x4) |
||||
.maxstack 1 |
||||
.locals ( |
||||
[0] int32 |
||||
) |
||||
|
||||
IL_0000: ldarg.1 |
||||
IL_0001: stloc.0 |
||||
IL_0002: ldloc.0 |
||||
IL_0003: ret |
||||
} // end of method IdProbe::get_Item |
||||
|
||||
.method public hidebysig specialname |
||||
instance void set_Item ( |
||||
int32 modopt([mscorlib]System.Runtime.CompilerServices.IsLong) i, |
||||
int32 'value' |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x11d0 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method IdProbe::set_Item |
||||
|
||||
.method public hidebysig specialname rtspecialname |
||||
instance void .ctor () cil managed |
||||
{ |
||||
// Method begins at RVA 0x11e0 |
||||
// Header size: 12 |
||||
// Code size: 7 (0x7) |
||||
.maxstack 1 |
||||
|
||||
IL_0000: ldarg.0 |
||||
IL_0001: call instance void [mscorlib]System.Object::.ctor() |
||||
IL_0006: ret |
||||
} // end of method IdProbe::.ctor |
||||
|
||||
// Properties |
||||
.property instance int32 Item( |
||||
int32 modopt([mscorlib]System.Runtime.CompilerServices.IsLong) i |
||||
) |
||||
{ |
||||
.get instance int32 IdProbe::get_Item(int32 modopt([mscorlib]System.Runtime.CompilerServices.IsLong)) |
||||
.set instance void IdProbe::set_Item(int32 modopt([mscorlib]System.Runtime.CompilerServices.IsLong), int32) |
||||
} |
||||
|
||||
} // end of class IdProbe |
||||
.class public auto ansi beforefieldinit GBox`1<T> |
||||
extends [mscorlib]System.Object |
||||
{ |
||||
// Methods |
||||
.method public hidebysig |
||||
instance void Hold ( |
||||
!T item |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x11f4 |
||||
// Header size: 12 |
||||
// Code size: 3 (0x3) |
||||
.maxstack 1 |
||||
.locals ( |
||||
[0] !T |
||||
) |
||||
|
||||
IL_0000: ldarg.1 |
||||
IL_0001: stloc.0 |
||||
IL_0002: ret |
||||
} // end of method GBox`1::Hold |
||||
|
||||
.method public hidebysig specialname rtspecialname |
||||
instance void .ctor () cil managed |
||||
{ |
||||
// Method begins at RVA 0x1204 |
||||
// Header size: 12 |
||||
// Code size: 7 (0x7) |
||||
.maxstack 1 |
||||
|
||||
IL_0000: ldarg.0 |
||||
IL_0001: call instance void [mscorlib]System.Object::.ctor() |
||||
IL_0006: ret |
||||
} // end of method GBox`1::.ctor |
||||
|
||||
} // end of class GBox`1 |
||||
.class public auto ansi beforefieldinit GBoxUser |
||||
extends [mscorlib]System.Object |
||||
{ |
||||
// Methods |
||||
.method public hidebysig static |
||||
void Use ( |
||||
class GBox`1<int32> b |
||||
) cil managed |
||||
{ |
||||
// Method begins at RVA 0x1218 |
||||
// Header size: 12 |
||||
// Code size: 1 (0x1) |
||||
.maxstack 0 |
||||
|
||||
IL_0000: ret |
||||
} // end of method GBoxUser::Use |
||||
|
||||
.method public hidebysig specialname rtspecialname |
||||
instance void .ctor () cil managed |
||||
{ |
||||
// Method begins at RVA 0x1228 |
||||
// Header size: 12 |
||||
// Code size: 7 (0x7) |
||||
.maxstack 1 |
||||
|
||||
IL_0000: ldarg.0 |
||||
IL_0001: call instance void [mscorlib]System.Object::.ctor() |
||||
IL_0006: ret |
||||
} // end of method GBoxUser::.ctor |
||||
|
||||
} // end of class GBoxUser |
||||
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0"?> |
||||
<doc> |
||||
<assembly> |
||||
"IdStringProbe" |
||||
</assembly> |
||||
<members> |
||||
<member name="M:GBoxUser.Use(GBox`1{System.Int32})"> |
||||
<summary>generic instantiation in a signature; expected GBox{System.Int32}</summary> |
||||
</member> |
||||
<member name="T:GBoxUser"> |
||||
<summary>Consumer of an instantiated generic type.</summary> |
||||
</member> |
||||
<member name="M:GBox`1.Hold(`0)"> |
||||
<summary>method on a generic type taking T; expected grave-accent zero</summary> |
||||
</member> |
||||
<member name="T:GBox`1"> |
||||
<summary>Generic ref class; arity in the type ID.</summary> |
||||
</member> |
||||
<member name="P:IdProbe.default(System.Int32!System.Runtime.CompilerServices.IsLong)"> |
||||
<summary>indexed property with a long parameter; modopt inside the indexer parentheses</summary> |
||||
</member> |
||||
<member name="M:IdProbe.op_Explicit(IdProbe!System.Runtime.CompilerServices.IsByValue)~System.Int32"> |
||||
<summary>conversion operator with a by-value ref-class parameter; the MSVC docs show IdProbe!IsByValue and a '~' return type</summary> |
||||
</member> |
||||
<member name="M:IdProbe.Gen``1(``0)"> |
||||
<summary>generic method; expected arity marker and grave-accent parameter encoding</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesEnumerator(System.Collections.Generic.List`1{System.Int32}.Enumerator)"> |
||||
<summary>nested type of a generic instantiation; how does MSVC distribute the type arguments?</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesArray2(System.Int32[0:,0:])"> |
||||
<summary>two-dimensional managed array parameter; expected System.Int32[0:,0:]</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesNativeRef(System.Int32*!System.Runtime.CompilerServices.IsImplicitlyDereferenced)"> |
||||
<summary>native reference parameter under /clr; representation evidence</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesConstTrackingRef(System.Int32!System.Runtime.CompilerServices.IsConst@)"> |
||||
<summary>const tracking reference parameter; modifier placement relative to the '@'</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesTrackingRef(System.Int32@)"> |
||||
<summary>tracking reference parameter; expected System.Int32@</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesConstVolatilePtr(System.Int32|System.Runtime.CompilerServices.IsVolatile!System.Runtime.CompilerServices.IsConst*)"> |
||||
<summary>const volatile int pointer parameter; modreq and modopt mixed on one type</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesVolatilePtr(System.Int32|System.Runtime.CompilerServices.IsVolatile*)"> |
||||
<summary>volatile int pointer parameter; modreq(IsVolatile): the open question is whether MSVC renders '|', '!', or omits it</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesConstCharPtr(System.SByte!System.Runtime.CompilerServices.IsSignUnspecifiedByte!System.Runtime.CompilerServices.IsConst*)"> |
||||
<summary>const char pointer parameter; IsConst and IsSignUnspecifiedByte together (modifier ordering)</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesCharPtr(System.SByte!System.Runtime.CompilerServices.IsSignUnspecifiedByte*)"> |
||||
<summary>char pointer parameter; modopt(IsSignUnspecifiedByte) under a pointer</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesULong(System.UInt32!System.Runtime.CompilerServices.IsLong)"> |
||||
<summary>unsigned long parameter; modopt(IsLong) on UInt32</summary> |
||||
</member> |
||||
<member name="M:IdProbe.TakesLong(System.Int32!System.Runtime.CompilerServices.IsLong)"> |
||||
<summary>long parameter; modopt(IsLong) on Int32</summary> |
||||
</member> |
||||
<member name="M:IdProbe.ConstValue(System.Int32!System.Runtime.CompilerServices.IsConst)"> |
||||
<summary>const int parameter; known baseline: System.Int32!System.Runtime.CompilerServices.IsConst</summary> |
||||
</member> |
||||
<member name="T:IdProbe"> |
||||
<summary>Probe class.</summary> |
||||
</member> |
||||
</members> |
||||
</doc> |
||||
Loading…
Reference in new issue