/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
$ npx -y skills add managedcode/dotnet-skills --skill dotnet-pinvoke --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/dotnet-pinvoke
Context preview
The summary Claude sees to decide when to auto-load this skill.
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
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` | `SetLastPInvokeError` | | **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("mRead 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` | `SetLastPInvokeError` | | **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("mStop explaining .NET to your AI. Start building. We've all been there: asking Claude to use Entity Framework, only to get EF6 patterns in a .NET 8 project. Explaining to Copilot that Blazor Server and Blazor WebAssembly aren't the same thing.
Repo: managedcode/dotnet-skills
Other skills on dotnet-skills.
- /aspnet-core
Build, debug, modernize, or review ASP.NET Core applications with correct hosting, middleware, security, configuration, logging, and deployment patterns on current .NET. USE FOR: working on ASP.NET Core apps, services, or middleware; changing auth, routing, configuration,
Open skill - /aspire
Build, upgrade, and operate Aspire 13.4.x C# or TypeScript application hosts with the current CLI, AppHost, ServiceDefaults, integrations, dashboard, testing, MCP, and deployment patterns for distributed apps. USE FOR: Aspire.AppHost.Sdk, Aspire.Hosting.*,
Open skill - /azure-functions
Build, review, or migrate Azure Functions in .NET with correct execution model, isolated worker setup, bindings, DI, and Durable Functions patterns. USE FOR: working on Azure Functions in .NET; migrating from the in-process model to the isolated worker model; adding Durable
Open skill - /blazor
Build and review Blazor applications across server, WebAssembly, web app, and hybrid scenarios with correct component design, state flow, rendering, and hosting choices. USE FOR: building interactive web UIs with C# instead of JavaScript; choosing between Server, WebAssembly, or
Open skill - /entity-framework6
Maintain or migrate EF6-based applications with realistic guidance on what to keep, what to modernize, and when EF Core is or is not the right next step. USE FOR: EF6 codebases; runtime versus ORM migration decisions; EDMX, code-first, ObjectContext, and legacy data-access
Open skill - /entity-framework-core
Design, tune, or review EF Core data access with proper modeling, migrations, query translation, performance, and lifetime management for modern .NET applications. USE FOR: DbContext, migrations, model configuration, EF queries, tracking, loading, performance, transactions, and
Open skill

