Skip to content

Conversation

@wu-yafeng
Copy link

NativeAot support #34

@wu-yafeng wu-yafeng marked this pull request as ready for review December 23, 2025 15:02
@wu-yafeng
Copy link
Author

wu-yafeng commented Dec 23, 2025

I conducted manual testing based on the FIDOUI program and it looks good.

I think it's necessary to enable Github Action for PR

@wu-yafeng
Copy link
Author

Consider upgrade to net10 for IsAotCompatible assembly metadata support.

see https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=windows%2Cnet8#verify-referenced-assemblies-are-aot-compatible

The IsAotCompatible assembly metadata was introduced in .NET 10. Libraries that were published targeting earlier versions of .NET won't have this attribute, even if they were built with true.

@MichaelGrafnetter
Copy link
Owner

Thanks for the PR, @wu-yafeng . I suspect that migration from DllImport to LibraryImport would need to be done as well to support AOT.

@wu-yafeng
Copy link
Author

I am not good at the P/Invoke, but I have reviewed some articles and complied some work that needs tobe done to migrate from DllImport to LibraryImport:

  • replace DllImport to LibraryImport
  • Implement CustomMarshaller for non-bittable class (such as RelyingPartyInformation.)

Example:

 [LibraryImport(WebAuthn, EntryPoint = "WebAuthNAuthenticatorMakeCredential")]
 [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
 public static partial HResult AuthenticatorMakeCredential(
     WindowHandle windowHandle,
     [MarshalUsing(typeof(ReplyingPartyInformationMarshaler))] RelyingPartyInformation rpInformation,
     ... // other parameter with MarshalUsing
 );


[CustomMarshaller(typeof(RelyingPartyInformation), MarshalMode.Default, typeof(ReplyingPartyInformationMarshaler))]
internal static unsafe class ReplyingPartyInformationMarshaler
{
    internal static IntPtr ConvertToUnmanaged(RelyingPartyInformation rpInfo)
    {
        var structure = new WEBAUTHN_RP_ENTITY_INFORMATION()
        {
            dwVersion = (uint)rpInfo.Version,
            pwszIcon = Utf16StringMarshaller.ConvertToUnmanaged(rpInfo.Icon),
            pwszId = Utf16StringMarshaller.ConvertToUnmanaged(rpInfo.Id),
            pwszName = Utf16StringMarshaller.ConvertToUnmanaged(rpInfo.Name),
        };

        IntPtr result = Marshal.AllocHGlobal(Marshal.SizeOf<WEBAUTHN_RP_ENTITY_INFORMATION>());

        Marshal.StructureToPtr(structure, result, false);

        return result;
    }

    public static void Free(IntPtr ptr)
    {
        Marshal.FreeHGlobal(ptr);
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    partial struct WEBAUTHN_RP_ENTITY_INFORMATION
    {
        internal uint dwVersion;
        internal ushort* pwszIcon;
        internal ushort* pwszId;
        internal ushort* pwszName;
    }
}

@MichaelGrafnetter
Copy link
Owner

@wu-yafeng You are right. P/Invoke without memory leaks was the hardest thing to implement. Duplicate method signatures are needed as well, see a code snippet from a different project of mine:

#if NET7_0_OR_GREATER
    [LibraryImport("Advapi.dll", SetLastError = true)]
    internal static partial NtStatus LsaClose(IntPtr handle);
#else
    [DllImport("Advapi.dll", SetLastError = true)]
    internal static extern NtStatus LsaClose(IntPtr handle);
#endif

@wu-yafeng wu-yafeng marked this pull request as draft December 31, 2025 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants