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

Generate raw COM interface definitions to facilitate creating manual CCW #708

Closed
JeremyKuhne opened this issue Oct 4, 2022 · 5 comments · Fixed by #714
Closed

Generate raw COM interface definitions to facilitate creating manual CCW #708

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

Comments

@JeremyKuhne
Copy link
Member

In manual generation of CCW's (using ComWrappers, for example) it would help to have interfaces defined that match the raw vtables so managed objects can implement for a generically written CCW to use.

A current example of this in WinForms is for IStream.

            [UnmanagedCallersOnly]
            private static int Read(IntPtr thisPtr, byte* pv, uint cb, uint* pcbRead)
            {
                try
                {
                    Interop.Ole32.IStream instance = ComInterfaceDispatch.GetInstance<Interop.Ole32.IStream>((ComInterfaceDispatch*)thisPtr);
                    instance.Read(pv, cb, pcbRead);
                    return S_OK;
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    return ex.HResult;
                }
            }

In the above example Interop.Ole32.IStream is what the CCW is written against. The blob above is pretty generic and could easily be added to code generation in CsWin32 in the future (as an option). In the immediate term we want to write this manually using interfaces CsWin32 defines. If they are written as normal interfaces with matching inheritance (IStream : ISequentialStream : IUnknown) it would allow sharing of code generated for CCWs.

The pattern TerraFx uses for COM is the ideal we're looking for:

https://github.com/terrafx/terrafx.interop.windows/blob/main/sources/Interop/Windows/Windows/um/objidlbase/IStream.cs#L137

    public interface Interface : ISequentialStream.Interface
    {
        [VtblIndex(5)]
        HRESULT Seek(LARGE_INTEGER dlibMove, [NativeTypeName("DWORD")] uint dwOrigin, ULARGE_INTEGER* plibNewPosition);
@JeremyKuhne JeremyKuhne added the enhancement New feature or request label Oct 4, 2022
@JeremyKuhne
Copy link
Member Author

cc: @tannergooding

@AArnott AArnott added the partner label Oct 4, 2022
@AArnott
Copy link
Member

AArnott commented Oct 4, 2022

In the immediate term we want to write this manually using interfaces CsWin32 defines

You want to write this manually right now? Is there any further development you want to do on the idea before CsWin32 starts emitting this for you?

Any thoughts on whether the interfaces use PreserveSig or not? The default in CsWin32 is to not use it except where prescribed by metadata or nativemethods.json file.

The pattern TerraFx uses for COM is the ideal we're looking for

Is the fact that TerraFx uses a nested Interface type inside a type named after the interface (but is not itself an interface) part of what you particularly want to see? I haven't fully evaluated it yet nor considered alternatives, so any reasons you like this particular expression of interface and struct would be data to feed into the cswin32 solution.

@JeremyKuhne
Copy link
Member Author

You want to write this manually right now?

Yes, to vet the parts of CCW wrapping that are candidates for CsWin32 to generate. Want to be confident in implementation patterns.

Any thoughts on whether the interfaces use PreserveSig or not?

I think it is ideal to match the native implementation to allow passing results without resorting to throwing and catching.

part of what you particularly want to see

I don't think it is strictly necessary, but better allows aligning these definitions to CCW writing scenarios.

@AArnott
Copy link
Member

AArnott commented Oct 5, 2022

I think it is ideal to match the native implementation to allow passing results without resorting to throwing and catching.

It's interesting that in your own example (copied below) you don't show that. Below, the IStream interface's Read method either returns void or you discard the result anyway.

Interop.Ole32.IStream instance = ComInterfaceDispatch.GetInstance<Interop.Ole32.IStream>((ComInterfaceDispatch*)thisPtr);
instance.Read(pv, cb, pcbRead);
return S_OK;

If we were to actually use PreserveSig on these interfaces, it would look more like:

Interop.Ole32.IStream instance = ComInterfaceDispatch.GetInstance<Interop.Ole32.IStream>((ComInterfaceDispatch*)thisPtr);
return instance.Read(pv, cb, pcbRead);

In any case, I think for this request, I'll leave CsWin32 generating interfaces based on it's default behavior, and you can turn PreserveSig on wherever you want it via the .json file.

@JeremyKuhne
Copy link
Member Author

Here is an example of how we want to implement a callback in a CCW:

        [UnmanagedCallersOnly]
        private static HRESULT Load(IPersistStream* @this, IStream* pStm)
        {
            try
            {
                IPersistStream.Interface persistStream = GetManagedObject(@this);
                return persistStream is null
                    ? HRESULT.COR_E_OBJECTDISPOSED
                    : persistStream.Load(pStm);
            }
            catch (Exception ex)
            {
                return (HRESULT)ex.HResult;
            }
        }

If the interface matches the vtable signatures (or possibly the helpful overloads in the struct) then code generating this sort of block becomes possible.

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.

2 participants