Table of Contents

Getting Started

Prerequisites

The .NET 8 SDK or Visual Studio 2022 Update 14 (16.14).

WPF projects have additional requirements.

This source generator generates code compatible with .NET Framework, .NET Standard 2.0, and .NET, as applicable to the project that uses it. .NET Framework 4.7.2 is the oldest target framework that is officially supported, but community contributions have made a subset of the generated code work in .NET Framework 3.5 projects.

When targeting .NET Standard or .NET Framework, it is necessary to explicitly update the C# language version to at least C# 9+ (<LangVersion>9</LangVersion> in your project file). C# 11 is sometimes required depending on the code being generated. See issue #4 for more on this. Newer is generally better. Use the latest C# language version for the best results, regardless of your TargetFramework.

Installation

Consume this library via its NuGet Package. Click on the badge to find its latest version and the instructions for consuming it that best apply to your project.

NuGet (prerelease) NuGet (daily)

Install the Microsoft.Windows.CsWin32 package:

dotnet add package Microsoft.Windows.CsWin32

When targeting .NET Framework 4.5+ or .NET Standard 2.0, you should also install the System.Memory and System.Runtime.CompilerServices.Unsafe packages as these add APIs that significantly improve much of the code generated by CsWin32:

dotnet add package System.Memory
dotnet add package System.Runtime.CompilerServices.Unsafe

Projects targeting .NET do not need to add these package references, although it is harmless to do so.

Note that while the System.Memory package depends on the System.Runtime.CompilerServices.Unsafe package, referencing the latter directly is still important to get the latest version of the APIs it provides.

Your project must allow unsafe code to support the generated code that will likely use pointers. This does not automatically make all your code unsafe. Use of the unsafe keyword is required anywhere you use pointers. The source generator NuGet package sets the default value of the AllowUnsafeBlocks property for your project to true, but if you explicitly set it to false in your project file, generated code may produce compiler errors.

Create a NativeMethods.txt file in your project directory that lists the APIs to generate code for. Each line may consist of one of the following:

  • Exported method name (e.g. CreateFile). This may include the A or W suffix, where applicable. This may be qualified with a namespace but is only recommended in cases of ambiguity, which CsWin32 will prompt where appropriate.
  • A macro name (e.g. HRESULT_FROM_WIN32). These are generated into the same class with extern methods. Macros must be hand-authored into CsWin32, so let us know if you want to see a macro added.
  • A namespace to generate all APIs from (e.g. Windows.Win32.Storage.FileSystem would search the metadata for all APIs within that namespace and generate them).
  • Module name followed by .* to generate all methods exported from that module (e.g. Kernel32.*).
  • The name of a struct, enum, constant or interface to generate. This may be qualified with a namespace but is only recommended in cases of ambiguity, which CsWin32 will prompt where appropriate.
  • A prefix shared by many constants, followed by *, to generate all constants that share that prefix (e.g. ALG_SID_MD*).
  • A comment (i.e. any line starting with //) or white space line, which will be ignored.
  • A prefix - to indicate a name that should not be generated. The rest of the line can be a name (e.g. BSTR), fully namespaced name (e.g. Windows.Win32.Foundation.BSTR), or name ending in wildcard (e.g. Windows.Win32.Foundation..*)

When generating any type or member, all supporting types will also be generated.

Generated code is added directly in the compiler. An IDE may make this generated code available to view through code navigation commands (e.g. Go to Definition) or a tree view of source files that include generated source files.

Assuming default settings and a NativeMethods.txt file with content that includes CreateFile, the P/Invoke methods can be found on the Windows.Win32.PInvoke class, like this:

using Windows.Win32;

PInvoke.CreateFile(/*args*/);

Constants are defined on the same class as the p/invoke methods (by default, the Windows.Win32.PInvoke class).

Other supporting types are defined within or under the Windows.Win32 namespace. Discovery of the namespace for a given type can be done with the Go To All feature (Ctrl+T) in Visual Studio with the type name as the search query.

A project may include many NativeMethods.txt files (each one necessarily in its own directory). CsWin32 will read them all to generate APIs, provided these files are included as AdditionalFiles in the project. A NativeMethods.txt file directly in the project directory is added automatically to AdditionalFiles. Files in other directories must be added to the project file manually.

Whether API requests are all in a single NativeMethods.txt file or split across many makes no difference to the generated result. We recommend using just one NativeMethods.txt file and keeping it sorted for easy bookkeeping. Multiple files perhaps makes the most sense in a Shared Project scenario where several API requests will be common across many projects, so sharing a NativeMethods.txt file with those same projects that contain all the necessary APIs for the set of shared source files make maintenance easier.

Some APIs require targeting a specific architecture and are not available when your C# project compiles as "Any CPU". Learn more about how this manifests and what your options are.

Customizing generated code

Several aspects of the generated code can be customized, including:

  • The name of the class(es) that declare p/invoke methods
  • Whether to emit interop types as public or internal
  • Whether to emit ANSI functions as well where Wide character functions also exist
  • Set PreserveSig for COM interfaces or individual members
  • Force generation of blittable structs, COM structs instead of interfaces (for super high performance with 0 GC pressure), etc.
  • Layered composition: have multiple assemblies contribute to a single shared PInvoke symbol so callers reach every API through one discovery point. See Layered composition for the design, dispatch rules, and a migration playbook for existing multi-layer projects.

To configure these settings, create a NativeMethods.json file in your project directory. Specifying the $schema property that points to the schema adds completions, descriptions and validation in many JSON editors, and in fact is where all the documentation for the available settings is found.

{
  "$schema": "https://aka.ms/CsWin32.schema.json",
  "emitSingleFile": false
}

Most generated types include the partial modifier so you can add your own members to that type within your code.

When you need to replace a generated type, simply copy and paste it from generated code into your own source files and remove the partial modifier. Be sure to keep the name and namespace exactly the same. CsWin32 will notice that your project already declares the type and skip generating it, but generate everything else. Note that if that type is the only thing that references some other generated type, CsWin32 will stop generating that type too. To keep CsWin32 generating the referred types you need, add them explicitly to NativeMethods.txt.

Owned outputs and scoped native values

RAIIFree and compatible FreeWith annotations on return values and output parameters tell CsWin32 which cleanup function owns that particular result. Friendly overloads can return a SafeHandle for pointer-shaped allocations as well as native handles. Unannotated pointers and the raw native signatures are unchanged.

CsWin32-generated SafeHandles expose a DangerousValue property with the original native type. For example, SHGetKnownFolderPath supplies a CoTaskMemFreePWSTRSafeHandle whose DangerousValue is PWSTR, although CoTaskMemFree accepts void*. Reading this property does not acquire a reference to keep the resource alive. BCL wrappers such as SafeFileHandle retain their existing APIs.

For an owner received from another component or as a parameter, acquire a scoped reference:

using Windows.Win32;

static bool PathExists(CoTaskMemFreePWSTRSafeHandle path)
{
    using var lease = path.Lease();
    return PInvoke.PathFileExists(lease.Value);
}

Lease() pairs DangerousAddRef and DangerousRelease without an additional heap allocation. Disposal through another alias cannot release the resource until the lease ends. The lease's Value has the same native type as its owner. The lease uses the Dispose() pattern; it does not require IDisposable. Lease helpers require C# 9 or later.

An owner already held in a visible using scope can supply owner.DangerousValue directly. This assumes ordinary exclusive ownership: another alias must not explicitly dispose it during the call. A SafeHandle parameter alone is not such a lifetime guarantee. Friendly input overloads accept base SafeHandle for types with existing cleanup annotations and keep a reference alive throughout their calls. They also accept SafeHandle for Windows.Win32.Foundation.HANDLE even without a type-level cleanup annotation, preserving this behavior as ownership annotations move to outputs. This heuristic does not apply to other types such as HWND, PWSTR, or arbitrary pointers. Such resources can be passed using their owner's DangerousValue or a lease's Value. Set useSafeHandles to false in NativeMethods.json to disable SafeHandle projection.

The lifetime analyzer reports errors for leases that are not using locals, copies, field storage, parameter passing, returns, and explicit disposal (PInvoke015). It also reports errors for DangerousValue or a lease's Value without a visible scope (PInvoke016), saving or returning a raw resource (PInvoke017), and releasing an owned resource directly (PInvoke018). Use these accessors directly in calls, or copy their contents into managed data such as a string. String interpolation such as $"Path: {path.DangerousValue}" is allowed inside the owner's or lease's scope. Interpolation into FormattableString or IFormattable retains the raw arguments for later formatting and is not a string copy. C# ref structs remain copyable: keep these diagnostics enabled to enforce the supported no-copy convention.

Neither a lease nor this analyzer proves that an arbitrary native call does not retain the resource, transfer ownership, or invalidate it independently. Such APIs need their own explicit lifetime arrangement.

If an annotated cleanup function needs additional arguments, CsWin32 generates that function for you to call but cannot automatically construct a SafeHandle: it will not guess the additional cleanup arguments. A compatible abstract helper, such as DeleteTimerQueueTimerSafeHandle, can be used to supply your own cleanup context in a derived ReleaseHandle. Its typed DangerousValue is available inside that override. You can also request the helper directly in NativeMethods.txt.

Support for trimming, AOT, and/or disabling the runtime marshaler

NEW! CsWin32 now has improved support for NativeAOT by generating code from an MSBuild task rather than a source generator so that it can leverage other source generators that support [LibraryImport] and others.

If you set one of these flags:

<PublishAot>true</PublishAot>
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>
<PublishTrimmed>true</PublishTrimmed>

Then you have two options:

Enable CsWin32RunAsBuildTask

CsWin32 now supports AOT by generating code which relies on GeneratedComInterface and LibraryImport, which are source generators. To make this chaining work, you have to request CsWin32 to run as a build task to generate the source code before compile. Do this by adding this to your csproj:

<CsWin32RunAsBuildTask>true</CsWin32RunAsBuildTask>
<DisableRuntimeMarshalling>true</DisableRuntimeMarshalling>

It is also strongly recommended to set DisableRuntimeMarshalling=true as shown here to ensure that the COM source generators handle all the marshalling correctly. See https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation and https://learn.microsoft.com/en-us/dotnet/standard/native-interop/comwrappers-source-generation for more information on these .NET source generators.

Disable runtime marshalling

CsWin32 can also support these environments by avoiding code that relies on the runtime marshaler when the allowMarshaling setting is disabled in the NativeMethods.json file. For example:

{
  "$schema": "https://aka.ms/CsWin32.schema.json",
  "allowMarshaling": false
}

Optional out/ref parameters

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);

Working with Span-typed and MemorySize-d parameters

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)

Buffers containing interior pointers

Some native APIs write records whose pointer fields refer to other data inside the caller's output buffer. When metadata marks a buffer parameter with ContainsInteriorPointersAttribute, CsWin32 keeps that parameter as a native pointer instead of generating a span, managed array, or in/ref/out buffer projection. The buffer's capacity parameter remains explicit even when it is optional, and other buffers sharing that capacity also keep their pointer types. Unrelated parameters can still receive friendly projections, including omission of optional outputs.

For example, the Unicode GetPrinterW API is generated as GetPrinter with the default settings. Its output buffer remains a byte*, while the handle and scalar output still have friendly projections (namespaces, attributes, and the method body are omitted here):

static unsafe BOOL GetPrinter(
    SafeHandle hPrinter, uint Level,
    byte* pPrinter, uint cbBuf, out uint pcbNeeded);

With Level = 2, the buffer contains a PRINTER_INFO_2W whose pPrinterName and other pointer fields refer to data in that same buffer.

The caller must keep the buffer alive and at the same address throughout both the native call and consumption of the returned pointers. Use unmanaged storage, stack storage, or an outer fixed scope that covers the call and all decoding or copying. A span overload's temporary pin would cover only the native call; GC.KeepAlive alone does not prevent relocation. Copying a pointer-bearing record or buffer does not rebase its pointers or make the pointed-to data independently owned.

This behavior depends on the annotation being present in the selected metadata. Pointer fields alone do not establish that they borrow the containing buffer. Offset-based self-relative data, such as a self-relative security descriptor, does not have the same relocation restriction until raw pointers into it are extracted.

Newer metadata

To update the metadata used as the source for code generation, you may install a newer Microsoft.Windows.SDK.Win32Metadata package:

dotnet add package Microsoft.Windows.SDK.Win32Metadata --prerelease

CsWin32 also consumes the WDK from a similarly named package: Microsoft.Windows.WDK.Win32Metadata.

Consuming daily builds

Can't wait for the next release to try out a bug fix? Follow these steps to consume directly from our daily build.

Just add this package feed to your nuget.config file:

<add key="winsdk" value="https://pkgs.dev.azure.com/azure-public/winsdk/_packaging/CI/nuget/v3/index.json" />