Skip to content
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

Provide a static interface for the Guid on COM structs #752

Closed
JeremyKuhne opened this issue Nov 4, 2022 · 5 comments · Fixed by #766
Closed

Provide a static interface for the Guid on COM structs #752

JeremyKuhne opened this issue Nov 4, 2022 · 5 comments · Fixed by #766
Assignees
Labels
enhancement New feature or request partner

Comments

@JeremyKuhne
Copy link
Member

We don't want to use reflection to get Guid information and also want to constrain generic helpers so in WinForms we implement INativeGuid on every generated COM struct:

internal interface INativeGuid
{
    public static abstract unsafe Guid* NativeGuid { get; }
}

internal unsafe partial struct IStream : IPopulateVTable<IStream.Vtbl>, INativeGuid
{
    static Guid* INativeGuid.NativeGuid => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in Guid));
}

Here is an example of how we use it: #751 (comment)

Here is another:

picture.Value->QueryInterface(IPersistStream.NativeGuid, persist);
@JeremyKuhne JeremyKuhne added the enhancement New feature or request label Nov 4, 2022
@JeremyKuhne
Copy link
Member Author

cc: @lonitra

@AArnott AArnott added the partner label Nov 4, 2022
@AArnott AArnott self-assigned this Nov 11, 2022
@AArnott
Copy link
Member

AArnott commented Nov 11, 2022

Here is what I plan to add. It's not quite the same as what you were doing. Please advise if it deviates in an unacceptable way.

internal interface IComIID
{
    internal static abstract ref readonly Guid Guid { get; }
}

@JeremyKuhne
Copy link
Member Author

Please advise if it deviates in an unacceptable way.

We're passing Guid* directly to native APIs in most cases. As long as we can write some sort of adapter to avoid making a copy that's fine.

Had a chat with @tannergooding to vet design.

To be as safe as possible we need the Guid definition to be unmoveable. The best way to do this is to take advantage of the ReadOnly<T> = new T[] optimization which directly references the assembly metadata. If the Guids are defined as such:

public static struct IUnknown : IComIID
{
    public static ref readonly Guid Guid
    {
        // Aggressive inlining needed to get this to inline as a single address move
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get
        {
            // Little Endian- all Windows platforms
            ReadOnlySpan<byte> data = new byte[] {
                0x00, 0x00, 0x00, 0x00,
                0x00, 0x00,
                0x00, 0x00,
                0xC0,
                0x00,
                0x00,
                0x00,
                0x00,
                0x00,
                0x00,
                0x46
            };

            return ref Unsafe.As<byte, Guid>(ref MemoryMarshal.GetReference(data));
        }
    }  
}

This ends up getting you this for asm:

IUnknown.get_Guid()
    L0000: mov rax, 0x20ca0fa0d80
    L000a: ret

And we can then write something like this:

public unsafe static class IID
{
    public static Guid* Get<T>() where T : unmanaged, IComIID
    {
        return (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in T.Guid));
    }
}

See Sharplab for an example.

Note, that we would like the Guid property to be public rather than explicit implementation.

@AArnott
Copy link
Member

AArnott commented Nov 11, 2022

Note, that we would like the Guid property to be public rather than explicit implementation.

We do offer a public IID_Guid property already. The explicit implementation property is on IComIID. So with my current design, it makes it convenient in either case (generic interface parameter or specific interface type). Is there a problem with the explicit interface implementation that I've missed?

To be as safe as possible we need the Guid definition to be unmoveable.

Is a static field movable? I didn't expect that.

@tannergooding
Copy link
Member

tannergooding commented Nov 11, 2022

Is a static field movable? I didn't expect that.

Static fields do not have any guarantee of being immovable. As an implementation detail the JIT may decide to allocate the space for them on the FOH (Frozen Object Heap) or POH (Pinned Object Heap), but it is not required to do so. -- There are runtimes/GCs that may not do this optimization and scenarios like collectible assemblies where RyuJIT might not either. C# currently emits an RVA static (backing data is embedded in the .rodata section of the PE or ELF file) for the ROSpan<byte> x = new byte[] { all constant data }; pattern. This, being directly embedded into the native image, is immovable. We may get some syntax to guarantee this pattern in the future, but none exists today (so its still an implementation detail, just one that is more stable across a range of runtimes/scenarios).

AArnott added a commit that referenced this issue Nov 11, 2022
This way, the reference can be converted by others into a pointer and passed around without fear that the data will move.

See #752
AArnott added a commit that referenced this issue Nov 12, 2022
This way, the reference can be converted by others into a pointer and passed around without fear that the data will move.

See #752
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request partner
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants