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

[Swift Bindings] Implement Swift native handle as SafeHandle #3052

Conversation

kotlarmilos
Copy link
Member

@kotlarmilos kotlarmilos commented Mar 21, 2025

Description

To ensure thread safety and delegate Swift handle release to the native handler, SwiftHandle implements SafeHandle for a native Swift handle. Frozen structs that require memory management contain a TypeBuffer struct is passed at the P/Invoke boundary, while a corresponding SwiftHandle performs C# reference counting for the type.

Swift frozen structs that require memory management are projected as C# classes:

class SwiftStruct
{
        [StructLayout(LayoutKind.Sequential, Size = 12)]
        public unsafe struct TypeBuffer
        {
           ...
        }
        private TypeBuffer _buffer;
        public SwiftHandle Payload => _payload;
}

Swift non-frozen structs that require memory management are projected as C# classes:

class SwiftStruct
{
        public SwiftHandle Payload => _payload;
}

When Dispose is called on these types, _payload.Dispose is invoked, which calls the ValueWitnessTable->Destroy function to free native resource.

Changes

  • Implemented SwiftHandle as SafeHandle

  • Added reference counting around P/Invoke calls using DangerousAddRef and DangerousRelease

    • Instance methods retain and release the internal SwiftHandle _payload
    • For frozen struct arguments, explicit retain/release is performed
    • For non-frozen struct arguments, SwiftHandle is passed at the P/Invoke boundary with internal retain/release; the .NET runtime automatically manages reference counting at P/Invoke boundary
    • For generic arguments, a local Swift copy is made, so no C# retain/release is performed
  • Updated MarshalToSwift and MarshalFromSwift to reflect the new SwiftHandle implementation

  • MarshalToSwift updated to check for buffer overruns

  • Removed the explicit Destroy call from the type's Dispose method

  • Ensured SwiftHandle release resources by calling _metadata.ValueWitnessTable->Destroy only after all references are released

  • Updated manual bindings to reflect the SwiftHandle implementation

Out of scope

Structs projected as C# classes do not require explicit Dispose methods or finalizers, since the GC can call Dispose of the SafeHandle when the object is collected. Projecting frozen value types as structs is a UX decision and will not be made in this PR.

Fixes #3018 #2975 #2974 #2851

@kotlarmilos kotlarmilos marked this pull request as ready for review March 26, 2025 17:23
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 31 out of 31 changed files in this pull request and generated 1 comment.

Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. I'd update the PayloadBuffer as suggested in a comment and then LGTM.

Thanks!

@kotlarmilos kotlarmilos merged commit 1636f7f into dotnet:feature/swift-bindings Apr 10, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-SwiftBindings Swift bindings for .NET
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants