/dotnet-pinvoke
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime, SafeHandle, and cross-platform patterns. USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging
Claude Code
$ npx -y skills add dotnet/skills --skill dotnet-pinvoke --agent claude-code
Codex
$ npx skills add dotnet/skills --skill dotnet-pinvoke -a codex -y
Cursor
$ npx skills add dotnet/skills --skill dotnet-pinvoke -a cursor -y
Gemini CLI
$ npx skills add dotnet/skills --skill dotnet-pinvoke -a gemini-cli -y
GitHub Copilot
$ npx skills add dotnet/skills --skill dotnet-pinvoke -a github-copilot -y
OpenCode
$ npx skills add dotnet/skills --skill dotnet-pinvoke -a opencode -y
Windsurf
$ npx skills add dotnet/skills --skill dotnet-pinvoke -a windsurf -y
SKILL.md
dotnet-pinvoke.SKILL.mdname: dotnet-pinvoke
description: >
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport.
Covers function signatures, string marshalling, memory lifetime, SafeHandle, and
cross-platform patterns.
USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging
existing native interop code, wrapping a C or C++ library for use in .NET, diagnosing
crashes, memory leaks, or corruption at the managed/native boundary.
DO NOT USE FOR: COM interop, C++/CLI mixed-mode assemblies, or pure managed code with
no native dependencies.
license: MIT
.NET P/Invoke
Calling native code from .NET is powerful but unforgiving. Incorrect signatures, garbled strings, and leaked or freed memory are the most common sources of bugs — all can manifest as intermittent crashes, silent data corruption, or access violations far from the actual defect.
This skill covers both `DllImport` (available since .NET Framework 1.0) and `LibraryImport` (source-generated, .NET 7+). When targeting .NET Framework, always use `DllImport`. When targeting .NET 7+, prefer `LibraryImport` for new code. When native AOT is a requirement, `LibraryImport` is the only option.
When to Use This Skill
- Writing a new `[DllImport]` or `[LibraryImport]` declaration from a C/C++ header
- Reviewing P/Invoke signatures for correctness (type sizes, calling conventions, string encoding)
- Wrapping an entire C library for use from .NET
- Debugging `AccessViolationException`, `DllNotFoundException`, or silent data corruption at the native boundary
- Migrating `DllImport` declarations to `LibraryImport` for AOT/trimming compatibility
- Diagnosing memory leaks or heap corruption involving native handles or buffers
Stop Signals
- **Single function?** Map the signature (Steps 1-3), handle strings/memory only if relevant, skip tooling and migration sections.
- **Don't migrate** existing `DllImport` to `LibraryImport` unless the user asks or AOT/trimming is an explicit requirement.
- **Don't recommend CsWin32** unless the target is specifically Win32 APIs.
- **Don't generate callbacks** (Step 8) unless the native API requires function pointers.
- **Review request?** Use the validation checklist — don't rewrite working code.
Inputs
| Input | Required | Description | |-------|----------|-------------| | Native header or documentation | Yes | C/C++ function signatures, struct definitions, calling conventions | | Target framework | Yes | Determines whether to use `DllImport` or `LibraryImport` | | Target platforms | Recommended | Affects type sizes (`long`, `size_t`) and library naming | | Memory ownership contract | Yes | Who allocates and who frees each buffer or handle |
**Agent behavior:** When documentation and native headers diverge, always trust the header. Online documentation (including official Win32 API docs) frequently omits or simplifies details about types, calling conventions, and struct layout that are critical for correct P/Invoke signatures.
---
Workflow
Step 1: Choose DllImport or LibraryImport
| Aspect | `DllImport` | `LibraryImport` (.NET 7+) | |--------|-------------|---------------------------| | **Mechanism** | Runtime marshalling | Source generator (compile-time) | | **AOT / Trim safe** | No | Yes | | **String marshalling** | `CharSet` enum | `StringMarshalling` enum | | **Error handling** | `SetLastError` | `SetLastError` | | **Availability** | .NET Framework 1.0+ | .NET 7+ only |
Step 2: Map Native Types to .NET Types
The most dangerous mappings — these cause the majority of bugs:
| C / Win32 Type | .NET Type | Why | |----------------|-----------|-----| | `long` | **`CLong`** | 32-bit on Windows, 64-bit on 64-bit Unix. With `LibraryImport`, requires `[assembly: DisableRuntimeMarshalling]` | | `size_t` | `nuint` / `UIntPtr` | Pointer-sized. Use `nuint` on .NET 8+ and `UIntPtr` on earlier .NET. Never use `ulong` | | `BOOL` (Win32) | `int` | Not `bool` — Win32 `BOOL` is 4 bytes | | `bool` (C99) | `[MarshalAs(UnmanagedType.U1)] bool` | Must specify 1-byte marshal | | `HANDLE`, `HWND` | `SafeHandle` | Prefer over raw `IntPtr` | | `LPWSTR` / `wchar_t*` | `string` | UTF-16 on Windows (lowest cost for `in` strings). Avoid in cross-platform code — `wchar_t` width is compiler-defined (typically UTF-32 on non-Windows) | | `LPSTR` / `char*` | `string` | Must specify encoding (ANSI or UTF-8). Always requires marshalling cost for `in` parameters |
**For the complete type mapping table, struct layout, and blittable type rules**, see [references/type-mapping.md](references/type-mapping.md).
> ❌ **NEVER** use `int` or `long` for C `long` — it's 32-bit on Windows, 64-bit on Unix. Always use `CLong`. > ❌ **NEVER** use `ulong` for `size_t` — causes stack corruption on 32-bit. Use `nuint` or `UIntPtr`. > ❌ **NEVER** use `bool` without `MarshalAs` — the default marshal size is wrong.
Step 3: Write the Declaration
Given a C header:
int32_t process_records(const Record* records, size_t count, uint32_t* out_processed);
**DllImport:**
[DllImport("mylib")]
private static extern int ProcessRecords(
[In] Record[] records, UIntPtr count, out uint outProcessed);**LibraryImport:**
[LibraryImport("mylib")]
internal static partial int ProcessRecords(
[In] Record[] records, nuint count, out uint outProcessed);Calling conventions only need to be specified when targeting Windows x86 (32-bit), where `Cdecl` and `StdCall` differ. On x64, ARM, and ARM64, there is a single calling convention and the attribute is unnecessary.
**Agent behavior:** If you detect that Windows x86 is a target — through project properties (e.g., `<PlatformTarget>x86</PlatformTarget>`), runtime identifiers (e.g., `win-x86`), build scripts, comments, or developer instructions — flag this to the developer and recommend explicit calling conventions on all P/Invoke declarations.
// DllImport (x86 targets)
[DllImport("mylib",Read more
name: dotnet-pinvoke description: > Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime, SafeHandle, and cross-platform patterns. USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging existing native interop code, wrapping a C or C++ library for use in .NET, diagnosing crashes, memory leaks, or corruption at the managed/native boundary. DO NOT USE FOR: COM interop, C++/CLI mixed-mode assemblies, or pure managed code with no native dependencies. license: MIT
.NET P/Invoke
Calling native code from .NET is powerful but unforgiving. Incorrect signatures, garbled strings, and leaked or freed memory are the most common sources of bugs — all can manifest as intermittent crashes, silent data corruption, or access violations far from the actual defect.
This skill covers both `DllImport` (available since .NET Framework 1.0) and `LibraryImport` (source-generated, .NET 7+). When targeting .NET Framework, always use `DllImport`. When targeting .NET 7+, prefer `LibraryImport` for new code. When native AOT is a requirement, `LibraryImport` is the only option.
When to Use This Skill
- Writing a new `[DllImport]` or `[LibraryImport]` declaration from a C/C++ header
- Reviewing P/Invoke signatures for correctness (type sizes, calling conventions, string encoding)
- Wrapping an entire C library for use from .NET
- Debugging `AccessViolationException`, `DllNotFoundException`, or silent data corruption at the native boundary
- Migrating `DllImport` declarations to `LibraryImport` for AOT/trimming compatibility
- Diagnosing memory leaks or heap corruption involving native handles or buffers
Stop Signals
- **Single function?** Map the signature (Steps 1-3), handle strings/memory only if relevant, skip tooling and migration sections.
- **Don't migrate** existing `DllImport` to `LibraryImport` unless the user asks or AOT/trimming is an explicit requirement.
- **Don't recommend CsWin32** unless the target is specifically Win32 APIs.
- **Don't generate callbacks** (Step 8) unless the native API requires function pointers.
- **Review request?** Use the validation checklist — don't rewrite working code.
Inputs
| Input | Required | Description | |-------|----------|-------------| | Native header or documentation | Yes | C/C++ function signatures, struct definitions, calling conventions | | Target framework | Yes | Determines whether to use `DllImport` or `LibraryImport` | | Target platforms | Recommended | Affects type sizes (`long`, `size_t`) and library naming | | Memory ownership contract | Yes | Who allocates and who frees each buffer or handle |
**Agent behavior:** When documentation and native headers diverge, always trust the header. Online documentation (including official Win32 API docs) frequently omits or simplifies details about types, calling conventions, and struct layout that are critical for correct P/Invoke signatures.
---
Workflow
Step 1: Choose DllImport or LibraryImport
| Aspect | `DllImport` | `LibraryImport` (.NET 7+) | |--------|-------------|---------------------------| | **Mechanism** | Runtime marshalling | Source generator (compile-time) | | **AOT / Trim safe** | No | Yes | | **String marshalling** | `CharSet` enum | `StringMarshalling` enum | | **Error handling** | `SetLastError` | `SetLastError` | | **Availability** | .NET Framework 1.0+ | .NET 7+ only |
Step 2: Map Native Types to .NET Types
The most dangerous mappings — these cause the majority of bugs:
| C / Win32 Type | .NET Type | Why | |----------------|-----------|-----| | `long` | **`CLong`** | 32-bit on Windows, 64-bit on 64-bit Unix. With `LibraryImport`, requires `[assembly: DisableRuntimeMarshalling]` | | `size_t` | `nuint` / `UIntPtr` | Pointer-sized. Use `nuint` on .NET 8+ and `UIntPtr` on earlier .NET. Never use `ulong` | | `BOOL` (Win32) | `int` | Not `bool` — Win32 `BOOL` is 4 bytes | | `bool` (C99) | `[MarshalAs(UnmanagedType.U1)] bool` | Must specify 1-byte marshal | | `HANDLE`, `HWND` | `SafeHandle` | Prefer over raw `IntPtr` | | `LPWSTR` / `wchar_t*` | `string` | UTF-16 on Windows (lowest cost for `in` strings). Avoid in cross-platform code — `wchar_t` width is compiler-defined (typically UTF-32 on non-Windows) | | `LPSTR` / `char*` | `string` | Must specify encoding (ANSI or UTF-8). Always requires marshalling cost for `in` parameters |
**For the complete type mapping table, struct layout, and blittable type rules**, see [references/type-mapping.md](references/type-mapping.md).
> ❌ **NEVER** use `int` or `long` for C `long` — it's 32-bit on Windows, 64-bit on Unix. Always use `CLong`. > ❌ **NEVER** use `ulong` for `size_t` — causes stack corruption on 32-bit. Use `nuint` or `UIntPtr`. > ❌ **NEVER** use `bool` without `MarshalAs` — the default marshal size is wrong.
Step 3: Write the Declaration
Given a C header:
int32_t process_records(const Record* records, size_t count, uint32_t* out_processed);
**DllImport:**
[DllImport("mylib")]
private static extern int ProcessRecords(
[In] Record[] records, UIntPtr count, out uint outProcessed);**LibraryImport:**
[LibraryImport("mylib")]
internal static partial int ProcessRecords(
[In] Record[] records, nuint count, out uint outProcessed);Calling conventions only need to be specified when targeting Windows x86 (32-bit), where `Cdecl` and `StdCall` differ. On x64, ARM, and ARM64, there is a single calling convention and the attribute is unnecessary.
**Agent behavior:** If you detect that Windows x86 is a target — through project properties (e.g., `<PlatformTarget>x86</PlatformTarget>`), runtime identifiers (e.g., `win-x86`), build scripts, comments, or developer instructions — flag this to the developer and recommend explicit calling conventions on all P/Invoke declarations.
// DllImport (x86 targets)
[DllImport("mylib",This repository contains the .NET team's curated set of portable skills and host-specific custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io.
Other skills on skills.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about dotnet-pinvoke.
- Which coding agents can use dotnet-pinvoke?
- dotnet-pinvoke installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

