-
Notifications
You must be signed in to change notification settings - Fork 4.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.NET 8 ComWrappers source generator doesn't handle _VTblGap* methods #102421
Comments
Tagging subscribers to this area: @dotnet/interop-contrib |
@smourier Thanks for trying out this scenario. I'm not sure we want to continue this behavior into the new system. This is an undocumented feature that is an implicit contract between .NET Framework/CoreCLR and the C# compiler. It is an oddity that I wish was done in a better way. The concept of creating a gap makes a lot of sense, but this mechanism seems to much magic. Is the pattern itself important here or just the ability to define a vtable gap? |
It was actually documented in "CLI Partition II" (as "Microsoft Specific") back then, as well as for example in in Serge Lidin's "Expert .NET 2.0 IL Assembler" book (I'm an old COM programmer ...). It does look a bit awkward and magic but I find the feature super useful when you just consume interfaces (obviously) with lots of members, for example in one of my answer here https://stackoverflow.com/a/60112084/403671 Any mechanism that would allow (possibly multiple) gaps would be fine I guess, since we already lost source compatibility anyway with source-generation. |
In ECMA-335? I can't find this in the spec anywhere.
Yep. Serge's book is not authoritative though and discusses many implementation details that are best left avoided.
Perfect. @jkoritzinsky and @jtschuster Any preference here? Retaining the |
Oh no, when I say old, I mean it :-) https://download.microsoft.com/download/7/3/3/733ad403-90b2-4064-a81e-01035a7fe13c/ms%20partition%20ii.pdf |
Ah, this is prior to standardization. Well, I am going to defer to @jkoritzinsky or @agocke here. I personally don't like this pattern at all, but it is niche enough and something I've used as well. Regardless of whether we continue to respect the Regardless, appreciate the issue, this is a documentation area that should be improved. |
Oh wow, that's quite subtle. Never seen it before. I'm personally not opposed to supporting it as it does seem useful -- but I would probably support the existing mechanism if it's not too much trouble. It's already esoteric enough that I wouldn't want to invent a new, incompatible way of doing the same thing. |
We could also chuck it in the ECMA-335 Augments so that it doesn't get lost in the future. |
Definitely not too much trouble from the runtime side. Adding this specific support to the COM source generators might be tricky since the generator would then also need to parse the
This should remain outside the official ECMA-335. I appreciate the idea of adding documentation and that should happen in here. Adding this to ECMA-335 is elevating this pattern to a place I'd prefer to not clutter with niche historical artifacts. |
I'm pretty sure there's another issue to propose using attributes to manually define VTables? It looks to be a more elegant solution than relying on gaps. |
I also think an attribute-based solution would be the best option. The I think a "VTable offset" attribute would be the cleanest: [GeneratedComInterface, Guid("ec5ec8a9-c395-4314-9c77-54d7a935ff70")]
public partial interface IWICImagingFactory
{
[ComVTableOffset(23)]
int CreateQueryWriter(in Guid guidMetadataFormat, in Guid pguidVendor, out IWICMetadataQueryWriter ppIQueryWriter);
} Methods after one annotated with The generator would change the output: file unsafe partial interface InterfaceImplementation
{
internal static void** CreateManagedVirtualFunctionTable()
{
- void** vtable = (void**)global::System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(global::Program.IWICImagingFactory), sizeof(void*) * 5);
+ void** vtable = (void**)global::System.Runtime.CompilerServices.RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(global::Program.IWICImagingFactory), sizeof(void*) * 27);
{
nint v0, v1, v2;
global::System.Runtime.InteropServices.ComWrappers.GetIUnknownImpl(out v0, out v1, out v2);
vtable[0] = (void*)v0;
vtable[1] = (void*)v1;
vtable[2] = (void*)v2;
}
{
- vtable[3] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, int> )&ABI__VTblGap1_23;
- vtable[4] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, global::System.Guid*, global::System.Guid*, void**, int> )&ABI_CreateQueryWriter;
+ vtable[26] = (void*)(delegate* unmanaged[MemberFunction]<global::System.Runtime.InteropServices.ComWrappers.ComInterfaceDispatch*, global::System.Guid*, global::System.Guid*, void**, int> )&ABI_CreateQueryWriter;
}
return vtable;
}
} An open question would be: should one be allowed to specify an offset lower than the current? Could I do this? [GeneratedComInterface, Guid("ec5ec8a9-c395-4314-9c77-54d7a935ff70")]
public partial interface IWICImagingFactory
{
[ComVTableOffset(23)]
HRESULT CreateQueryWriter(in Guid guidMetadataFormat, ref readonly Guid pguidVendor, out IWICMetadataQueryWriter ppIQueryWriter);
[ComVTableOffset(22)]
HRESULT CreatePalette(out IWICPalette ppIPalette);
} |
I think a VTable index option looks better. |
Description
The current .NET 8 ComWrappers source generator doesn't seem to support skipping vtable slots in COM interface declarations, as described and supported by the builtin interface marshaling https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/imetadataemit-definemethod-method#slots-in-the-v-table
Reproduction Steps
This code works fine with .NET Framework or .NET 8 with built-in marshaling:
Here's an equivalent source-generated .NET 8 ComWrappers:
Expected behavior
It should work the same.
Actual behavior
.NET 8 source-generated ComWrappers version crashes with a "System.AccessViolationException: Attempted to read or write protected memory" because the code seems to consider _VtblGap1_23 as a regular method.
Note if you change
_VTblGap1_23
by_VtblGap1_23
(note the t has a different case), the error is not the same, you get a strangeRegression?
It's a regression from built-in marshaling.
Known Workarounds
I don't know any but declare dummy methods.
Configuration
Latest .NET 8, Windows 11, 64-bit
Other information
No response
The text was updated successfully, but these errors were encountered: