Skip to content

Conversation

@jevansaks
Copy link
Member

@jevansaks jevansaks commented Oct 28, 2025

This change addresses a few problems with projection of certain parameters:

1. Improve projection of [optional, out] parameters

Currently cswin32 projects [optional, out] parameters as pointer, which makes it harder to work with. After discussion in #1446, I propose the following new behavior:

Some parameters in win32 are [optional, out] or [optional, in, out]. C# does not have an idiomatic way to represent this concept, so for any method that
has such parameters, CsWin32 will generate two versions: one with all ref or out parameters included, and one with all such parameters omitted. For example:

// Omitting the optional parameter:
IsTextUnicode(buffer);

// Passing ref for optional parameter:
IS_TEXT_UNICODE_RESULT result = default;
IsTextUnicode(buffer, ref result);

2. Project struct-typed [MemorySize] parameters as Span<byte>

The handling of struct-typed [MemorySize] parameters in cswin32 is in some cases incorrect: #1456, and there's no way to correctly call the friendly method since you cannot pass a buffer larger than the struct. In such cases, these parameters are like FlexibleArrays but the struct doesn't have an embedded variable-sized array, the struct is more like the header of some variable-sized payload. A good example is InitializeAcl (from the linked issue).

The ideal would be Span<ACL> where you can express the size of the Span in a number of bytes. However, Span has no way to do that, Span is always a whole number of elements. So, we need to type these parameters as Span<byte>. I added the following blurb to the getting-started docs to explain how these work:

In the Win32 APIs there are many functions where one parameter is a buffer (void* or byte*) and another parameter is the size of that buffer. When generating for a target framework that supports Spans, there will be overloads of these functions that take a Span<byte> which represents both of these parameters, since a Span refers to a chunk of memory and a length. For example, an API like IsTextUnicode has a void* parameter whose length is described by the iSize parameter in the native signature. The CsWin32 projection of this method will be:

BOOL IsTextUnicode(ReadOnlySpan<byte> lpv, Span<IS_TEXT_UNICODE_RESULT> lpiResult)

Instead of passing the buffer and length separately, in this projection you pass just one parameter. Span is a flexible type with many things that can be converted to it safely. You will also see Span parameters for things that may look like a struct but are variable sized. For example, InitializeAcl looks like it returns an ACL struct but the parameter is annotated with a [MemorySize] attribute in the metadata, indicating it is variable-sized based on another parameter. Thus, the cswin32 projection of this method will project this parameter as a Span<byte> since the size of the parameter is variable:

// The cswin32 signature:
static BOOL InitializeAcl(Span<byte> pAcl, ACE_REVISION dwAclRevision) { ... }

And you would call this by creating a buffer to receive the ACL. Then, after the call you can reinterpret the buffer as an ACL:

// Make a buffer
Span<byte> buffer = new byte[CalculateAclSize(...)];
InitializeAcl(buffer, ACE_REVISION.ACL_REVISION);

// The beginning of the buffer is an ACL, so cast it to a ref:
ref ACL acl = ref MemoryMarshal.AsRef<ACL>(buffer);

// Or treat it as a Span:
Span<ACL> aclSpan = MemoryMarshal.Cast<byte, ACL>(buffer);

CsWin32 will also generate a struct-typed parameter for convenience but this overload will pass sizeof(T) for the length parameter to the underlying Win32 API, so this only makes sense in some overloads such as SHGetFileInfo where the parameter has an annotation indicating it's variable-sized, but the size is only ever sizeof(SHFILEINFOW):

// Span<byte> overload:
static nuint SHGetFileInfo(string pszPath, FILE_FLAGS_AND_ATTRIBUTES dwFileAttributes, Span<byte> psfi, SHGFI_FLAGS uFlags)
// ref SHGETFILEINFOW overload:
static nuint SHGetFileInfo(string pszPath, FILE_FLAGS_AND_ATTRIBUTES dwFileAttributes, ref SHFILEINFOW psfi, SHGFI_FLAGS uFlags)

Fixes #1446, Fixes #1456, Fixes #1453.

@jevansaks jevansaks enabled auto-merge (squash) October 29, 2025 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants