Skip to content

Improved Unicode string marshaling #1641

@MichaelGrafnetter

Description

@MichaelGrafnetter

Is your feature request related to a problem? Please describe.

As an example, here is the LsaOpenPolicy function signature generated by CsWin32:

internal static unsafe winmdroot.Foundation.NTSTATUS LsaOpenPolicy(
    winmdroot.Security.Authentication.Identity.LSA_UNICODE_STRING? SystemName,
    in winmdroot.Security.Authentication.Identity.LSA_OBJECT_ATTRIBUTES ObjectAttributes,
    uint DesiredAccess,
    out global::Windows.Win32.LsaCloseSafeHandle PolicyHandle
)

When calling this function from C#, I must create my own unsafe wrapper for marshaling the systemName string parameter:

internal static unsafe NTSTATUS LsaOpenPolicy(
    string? systemName,
    LsaPolicyAccessMask desiredAccess,
    out LsaCloseSafeHandle policyHandle)
{
    LSA_UNICODE_STRING systemNameUnicode = default;
    LSA_OBJECT_ATTRIBUTES attributes = default;

    if (systemName is not null)
    {
        fixed (char* systemNamePointer = systemName)
        {
            // Marshal string as LSA_UNICODE_STRING
            systemNameUnicode.Length = checked((ushort)(systemName.Length * sizeof(char)));
            systemNameUnicode.MaximumLength = checked((ushort)((systemName.Length+1) * sizeof(char)));
            systemNameUnicode.Buffer = (PWSTR)systemNamePointer;
            return PInvoke.LsaOpenPolicy(
                systemNameUnicode,
                attributes,
                checked((uint)desiredAccess),
                out policyHandle);
        }
    }
    else
    {
        return PInvoke.LsaOpenPolicy(
            systemNameUnicode,
            attributes,
            checked((uint)desiredAccess),
            out policyHandle);
    }
}

These safe buffer structures are used heavily in Win32 API, e.g., UNICODE_STRING, LSA_UNICODE_STRING , OEM_STRING, CRYPT_BUFFER, etc.

Describe the solution you'd like
I think that CsWin32 should automatically emit this kind of trivial type converters, to shield developers from unsafe code. Not having to deal with this would save developers a lot of time and bugs. But I understand the complexity of generating this code automatically.

Describe alternatives you've considered
Long before CsWin32, I used to manually create wrappers like this one for UNICODE_STRING:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeString
{
    private const ushort UnicodeCharLength = 2;

    /// <summary>
    /// Maximum number of unicode characters that can fit into the buffer, excluding the trailing 0.
    /// </summary>
    public const ushort MaxLength = ushort.MaxValue / UnicodeCharLength - 1;

    public UnicodeString(string? text)
    {
        if (text == null)
        {
            this.Length = this.MaximumLength = 0;
            this.Buffer = null;
        }
        else if (text.Length > MaxLength)
        {
            throw new ArgumentOutOfRangeException(nameof(text));
        }
        else
        {
            this.Buffer = text;

            // Length of the unicode string.
            this.Length = (ushort)(text.Length * UnicodeCharLength);

            // Length of the unicode string, including the trailing \0 character.
            // Important: Some Windows components, including the Local Security Authority (LSA) do not behave properly if Length==MaximumLength.
            this.MaximumLength = (ushort)(this.Length + UnicodeCharLength);
        }
    }

    /// <summary>
    /// The length, in bytes, of the string stored in Buffer.
    /// </summary>
    /// <remarks>
    /// If the string is null-terminated, Length does not include the trailing null character.
    /// </remarks>
    public ushort Length;

    /// <summary>
    /// The length, in bytes, of Buffer.
    /// </summary>
    public ushort MaximumLength;

    /// <summary>
    /// Pointer to a buffer used to contain a string of wide characters.
    /// </summary>
    [MarshalAs(UnmanagedType.LPWStr)]
    public string? Buffer;
}

Although the code above avoids unsafe, it is incompatible with trimming and can only be used as [In] parameter, not [Out].

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions