Closed weltkante closed 7 months ago
The documentation for VT_INT
and VT_UINT
are not specific on the width of the value and various issues. A simple reading of the docs for VT_INT
and VT_UINT
leads to matching them to nint
and nuint
, especially with looking at other languages with similarly named integer types. As a result, I decided to add comments about the incongruity so anyone who looks at it in the future can understand why we mapped them to int/uint.
The unit test you linked to works a little differently than you think. The CreateRaw
method doesn't require a strict type match, it only validates that the size of the type is equal to the expected size. The Create
method is the only method that does an exact type match, and as you saw in the comments, we explicitly don't match nint
and nuint
to VT_INT
and VT_UINT
as it's incorrect.
We made this decision to treat them as int
/uint
to avoid the problems you raised in #36354 about the DLR's implementation.
We made this decision to treat them as int/uint to avoid the problems you raised in https://github.com/dotnet/runtime/issues/36354 about the DLR's implementation.
Forgot about that issue, making them int/uint is definitely the right thing. In that old issue I think I was working against the docs and comparing implementations, without any practical use cases, but now I've got actually TLB and IDL code where it happens in practice.
Having read the corresponding IDL that generates VT_INT/VT_UINT and VT_I4/VT_UI4 (looking at IDL via oleview32, headers/docs of the APIs and interacting with the TLB via ITypeLib) it seems pretty clear that the intention behind VT_INT/VT_UINT is the LONG vs. INT difference in C/C++, which mostly matters to 16 bit vs. 32 bit and is an ABI decision that on the amd64 architecture they are not expanded to 32/64 bit, not something languages can decide.
For the pointer sized variant types we have VT_INT_PTR and VT_UINT_PTR, I actually got them in the RDP Client ActiveX as well, and interestingly enough the old tlbimp gets those very wrong and generates bad APIs in the assembly that are not portable between x86/amd64 even though it generates an AnyCpu assembly.
Anyways, I would have commented on the PR and not made a new issue but it was already closed when I came across this, so figured I'd document it in a new issue instead to document my findings. Since this is just about comments/unit tests feel free to close the issue if you are aware of the problems and don't think they need to be further clarified in source and comments.
Description
Comments in the new Variant Marshalling code and unit test indicate a misunderstanding of VT_INT/VT_UINT as meaning IntPtr/UIntPtr, however as far as I can tell the actual implementation for unmanaged-to-managed conversions (same file) is correctly implementing it like classic interop as Int32/UInt32. The comment in the Windows headers indicates that this is the int/long difference in C/C++ which both are 32 bit on x64 Windows, unlike in C#, so always treating it as 32 bit should be correct unless you expect compatibility with different ABIs than x86/x64 (for example I don't know what the situation is on ARM64)
For what its worth these types do appear in the typelib of ActiveX controls depending on int vs. long usage, I came across it in the Microsoft RDP Client ActiveX control (mstscax.dll, part of the Windows installation) while doing a proof-of-concept implementation using it in .NET Core. For example in the OnFocusReleased event of the non-dual IDispatch event interface the argument is specified as VT_INT instead of VT_I4 while other events like OnLogonError use long instead of int and have the usual VT_I4. However I haven't yet seen VT_INT passed at runtime during IDispatch event handling, OnFocusReleased seems to get passed VT_I4 instead in my tests.
/cc @jkoritzinsky @AaronRobinsonMSFT
Reproduction Steps
Review of Variant Marshaller implementation and commit https://github.com/dotnet/runtime/pull/93635
VT_INT appearing in the type library of mstscax.dll from a normal Windows installation
Expected behavior
Unit tests and comments do not misinterpret VT_INT/VT_UINT semantics.
Actual behavior
Comments and unit test in source code indicate an incorrect mapping of VT_INT/VT_UINT.
The practical implementation of unmanaged-to-managed mappings seems to differ from comments and unit tests, so is unlikely to have introduced actual bugs.
Regression?
VT_INT/VT_UINT works in classic COM interop and map to Int32/UInt32
Known Workarounds
not necessary as far as I can tell, worst case a custom marshaller could be used
Configuration
64-bit .NET 9.0.0-alpha.1.23617.4
Other information
No response