Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Calls native libraries via P/Invoke. LibraryImport, marshalling, cross-platform resolution.
dotnet-native-interop
Platform Invoke (P/Invoke) patterns for calling native C/C++ libraries from .NET: [LibraryImport] (preferred, .NET 7+)
vs [DllImport] (legacy), struct marshalling, string marshalling, function pointer callbacks,
NativeLibrary.SetDllImportResolver for cross-platform library resolution, and platform-specific considerations for
Windows, macOS, Linux, iOS, and Android.
Version assumptions: .NET 7.0+ baseline for [LibraryImport]. [DllImport] available in all .NET versions.
NativeLibrary API available since .NET Core 3.0.
Scope
LibraryImport (.NET 7+) and DllImport declarations
Struct and string marshalling patterns
Function pointer callbacks and delegates
NativeLibrary.SetDllImportResolver for cross-platform resolution
Out of scope
AOT-specific P/Invoke concerns (direct pinvoke) -- see [skill:dotnet-native-aot]
COM interop and CsWin32 source generator -- see [skill:dotnet-winui]
WASM JavaScript interop (JSImport/JSExport) -- see [skill:dotnet-aot-wasm]
Cross-references: [skill:dotnet-native-aot] for AOT-specific P/Invoke and [LibraryImport] in publish scenarios,
[skill:dotnet-aot-architecture] for AOT-first design patterns including source-generated interop, [skill:dotnet-winui]
for CsWin32 source generator and COM interop, [skill:dotnet-aot-wasm] for WASM JavaScript interop (not native P/Invoke).
LibraryImport vs DllImport
[LibraryImport] (.NET 7+) is the preferred attribute for new P/Invoke declarations. It uses source generation to
produce marshalling code at compile time, making it fully AOT-compatible and eliminating runtime codegen overhead.
[DllImport] is the legacy attribute. It relies on runtime marshalling, which may require codegen not available in AOT
scenarios. Use [DllImport] only when targeting .NET 6 or earlier, or when the SYSLIB1054 analyzer indicates
[LibraryImport] cannot handle a specific signature.
Decision Guide
Scenario
Use
New code targeting .NET 7+
[LibraryImport]
Targeting .NET 6 or earlier
[DllImport]
SYSLIB1054 analyzer flags incompatibility
[DllImport] (with comment explaining why)
Publishing with Native AOT
[LibraryImport] (required for full AOT compat)
LibraryImport Declaration
using System.Runtime.InteropServices;
publicstaticpartialclassNativeApi
{
[LibraryImport("mylib")]
internalstaticpartialintProcessData(
ReadOnlySpan<byte> input,
int length);
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
internalstaticpartialintOpenByName(string name);
[LibraryImport("mylib", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internalstaticpartialboolCloseResource(nint handle);
}
```text
Key requirements for `[LibraryImport]`:
- Method must be `staticpartial` in a `partial` class
- Stringmarshallingmustbeexplicitlyspecifiedvia `StringMarshalling` or `[MarshalAs]` ( )
- `[: ()]`
- `<>` `<>` -- `[]` ( )
### ()
```
;
{
[]
;
[]
[]
;
}
```text
The `SYSLIB1054` analyzer suggests converting `[DllImport]` to `[LibraryImport]` provides code fixes. Key changes:
Replace `[DllImport]` `[LibraryImport]`
Change ` ` to ` `
Make the containing ``
4. `` ``
5. `` ``
6. `[]` ``
```
[("", = CharSet.Unicode, SetLastError = )]
;
[]
;
```text
---
Native library names differ across platforms. Use `NativeLibrary.SetDllImportResolver` conditional compilation to handle .
Windows uses `.dll` files. The loader searches the application directory, system directories, `PATH`.
```csharp
[]
;
```text
Windows also supports omitting the extension -- the loader appends `.dll` automatically:
```csharp
[]
;
```text
macOS uses `.dylib` files; Linux uses `.so` files. The .;
```text
.NET probing order library name ``:
`foo` (exact name)
`foo.dll`, `foo.so`, `foo.dylib` (platform extension)
`libfoo`, `libfoo.so`, `libfoo.dylib` (lib prefix + extension)
iOS does allow loading libraries at runtime. Native code must be statically linked the application binary. Use `__Internal` the library name to call functions linked the main executable:
```csharp
[]
;
```csharp
For iOS, =>
<NativeReference Include=>
<Kind>Static</Kind>
<ForceLoad></ForceLoad>
</NativeReference>
</ItemGroup>
```text
Android uses `.so` files loaded the apps lib/<abi>/ directory
[]
;
```csharp
Include platform-specific `.so` files each target ABI the project:
```xml
<ItemGroup Condition=>
<AndroidNativeLibrary Include= Abi= />
<AndroidNativeLibrary Include= Abi= />
</ItemGroup>
```text
WebAssembly does support traditional P/Invoke. Native C/C++ code cannot be called via `[LibraryImport]` `[DllImport]` browser WASM. For JavaScript interop, see [skill:dotnet-aot-wasm].
---
`NativeLibrary.SetDllImportResolver` (.NET Core +) provides runtime control over library resolution. This the recommended approach cross-platform library loading name probing insufficient.
```csharp
System.Reflection;
System.Runtime.InteropServices;
NativeLibrary.SetDllImportResolver(
Assembly.GetExecutingAssembly(),
DllImportResolver);
{
(libraryName == )
{
(OperatingSystem.IsWindows())
NativeLibrary.Load(, assembly, searchPath);
(OperatingSystem.IsMacOS())
NativeLibrary.Load(, assembly, searchPath);
(OperatingSystem.IsLinux())
NativeLibrary.Load(, assembly, searchPath);
}
.Zero;
}
```text
| Scenario | Why resolver needed |
|----------|----------------------|
| Versioned `.so` = NativeLibrary.Load();
(NativeLibrary.TryLoad(, h))
{
funcPtr = NativeLibrary.GetExport(h, );
(NativeLibrary.TryGetExport(h, , fp))
{
}
NativeLibrary.Free(h);
}
```text
---
Structs passed to native code must have a well-defined memory layout. Use `[StructLayout]` to control layout alignment.
```csharp
System.Runtime.InteropServices;
[]
Point
{
X;
Y;
}
[]
ValueUnion
{
[] IntValue;
[] FloatValue;
[] DoubleValue;
}
[]
PackedHeader
{
Magic;
Length;
Version;
}
```text
**Blittable structs** (containing only primitive types sequential/ layout) are passed directly to native code without copying. Non-blittable structs require marshalling, which incurs overhead.
Blittable primitive types: ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``, ``.
**Not blittable:** `` (marshals - `BOOL` ), `` (depends charset), ``, arrays of non-blittable types.
Specify encoding explicitly. Never rely marshalling behavior.
```csharp
[]
;
[]
;
[]
;
```text
For output buffers, use `[]` `[]` `ArrayPool` instead of `StringBuilder`:
```csharp
[]
;
[] buffer = ArrayPool<>.Shared.Rent();
{
result = GetName(buffer, buffer.Length);
name = (buffer, , result);
}
{
ArrayPool<>.Shared.Return(buffer);
}
```text
Modern .NET (.NET +) prefers function pointers over -based callbacks better performance AOT compatibility.
**Preferred: Unmanaged function pointers `[UnmanagedCallersOnly]`**
```csharp
System.Runtime.InteropServices;
[]
;
[])]
{
;
}
{
RegisterCallback(&MyCallback, .Zero);
}
```text
**Alternative: Delegate-;
[]
;
NativeCallback? s_callback;
{
s_callback = NativeCallback(MyManagedCallback);
RegisterCallbackDelegate(s_callback, .Zero);
}
{
* ;
}
```text
Use `SafeHandle` subclasses to manage native resource lifetimes instead of raw `IntPtr`/``. This prevents resource leaks use-after-free bugs.
```csharp
System.Runtime.InteropServices;
Microsoft.Win32.SafeHandles;
:
{
{ }
{
NativeApi.CloseResource(handle);
;
}
}
{
[]
;
[]
;
[]
;
}
```text
---
Map C/C++ types to .NET types carefully. Some C types have platform-dependent sizes.
| C/C++ Type | .NET Type | Size |
|------------|-----------|------|
| `int8_t` / `` | `` | |
| `uint8_t` / `unsigned ` | `` | |
| `int16_t` / `` | `` | bytes |
| `uint16_t` / `unsigned ` | `` | bytes |
| `int32_t` / `` | `` | bytes |
| `uint32_t` / `unsigned ` | `` | bytes |
| `int64_t` / ` ` | `` | bytes |
| `uint64_t` / `unsigned ` | `` | bytes |
| `` | `` | bytes |
| `` | `` | bytes |
| C/C++ Type | .NET Type | Notes |
|------------|-----------|-------|
| `size_t` / `ptrdiff_t` | `` / `` | Pointer-sized |
| `*` / pointer types | `` `*` | Pointer-sized |
| `` (C/C++) | `CLong` (.NET +) | bytes Windows, bytes Unix -bit |
| `unsigned ` | `CULong` (.NET +) | Same platform variance `` |
| Windows `BOOL` | `` | bytes ( ``) |
| Windows `BOOLEAN` | `` | |
Do use C
---
**Do use `[DllImport]` .NET + code without justification.** Use `[LibraryImport]` which generates marshalling at compile time. Only fall back to `[DllImport]` SYSLIB1054 analyzer indicates incompatibility.
**Do assume `` marshals .** .NET marshals `` a - Windows `BOOL` . Use `[MarshalAs(UnmanagedType.U1)]` C `_Bool`/``, `[MarshalAs(UnmanagedType.Bool)]` Windows `BOOL` explicitly.
**Do use C
**Do use `StringBuilder` output buffers.** `[LibraryImport]` does support `StringBuilder` at all, `[DllImport]` it allocates multiple intermediate copies. Use `[]` `[]` `ArrayPool` instead.
**Do use `[LibraryImport]` `[DllImport]` WASM.** WebAssembly does support traditional P/Invoke. For JavaScript interop WASM, see [skill:dotnet-aot-wasm].
**Do use library loading iOS.** iOS prohibits loading libraries at runtime. Use `` the library name statically linked native code.
**Do use `System.Delegate` fields interop structs.**
Code Navigation (Serena MCP)
Primary approach: Use Serena symbol operations for efficient code navigation:
Find definitions: serena_find_symbol instead of text search
Understand structure: serena_get_symbols_overview for file organization
Track references: serena_find_referencing_symbols for impact analysis
Precise edits: serena_replace_symbol_body for clean modifications
When to use Serena vs traditional tools:
Use Serena: Navigation, refactoring, dependency analysis, precise edits
Use Read/Grep: Reading full files, pattern matching, simple text operations
Fallback: If Serena unavailable, traditional tools work fine
NET runtime automatically probes common name variations (withand without `lib` prefix, with platform-specific extensions).
```csharp
// Use the logical name without extension -- .NET probes:// libsqlite3.dylib (macOS), libsqlite3.so (Linux), sqlite3.dll (Windows)
[LibraryImport("libsqlite3")]
internalstaticpartialintsqlite3_open(
[MarshalAs(UnmanagedType.LPUTF8Str)] string filename,
outnint db)
for
"foo"
1.
2.
3.
### iOS
not
dynamic
into
as
into
// Calls a function statically linked into the iOS app binary
LibraryImport("__Internal")
internalstaticpartialintNativeFunction(int input)
the native library must be compiled as a staticlibrary (`.a`) and linked during the Xcode build phase. MAUI and Xamarin handle this through native references in the project file:
```xml
<ItemGroup Condition
's native library directory. The library name typically omits the `lib` prefix and `.so` extension in the P/Invoke declaration:
```csharp
// Android loads libmynative.so from the APK'
onLinux (e.g., `libfoo.so.2`) | Default probing does not check versioned names |
| Library in a non-standard path | Load from a custom directory at runtime |
| Bundled native library per RID | Resolve to `runtimes/<rid>/native/` path |
| Feature detection at load time | Try multiple library names and fall back gracefully |
### NativeLibrary API
The `NativeLibrary` class provides low-level library management:
```csharp
// Load a library explicitlynint handle
"mylib"
// Try to load without throwing
if
"mylib"
out
nint
// Get a function pointer by name
nint
"my_function"
// Or try without throwing
if
"my_function"
out
nint
// Use function pointer
## Marshalling Patterns
### Struct Marshalling
and
using
// Sequential layout -- fields laid out in declaration order
StructLayout(LayoutKind.Sequential)
public
struct
public
int
public
int
// Explicit layout -- fields at specific byte offsets (for unions)
StructLayout(LayoutKind.Explicit)
public
struct
FieldOffset(0)
public
int
FieldOffset(0)
public
float
FieldOffset(0)
public
double
// Sequential with packing -- override default alignment
StructLayout(LayoutKind.Sequential, Pack = 1)
public
struct
public
byte
public
int
// No padding before this field
public
short
value
with
explicit
byte
sbyte
short
ushort
int
uint
long
ulong
float
double
nint
nuint
bool
as
4
byte
by
default
char
on
string
### String Marshalling
string
on
default
// UTF-8 strings (most common for cross-platform C APIs)
internalstaticpartialintReadResource(NativeResourceHandle handle,
Span<byte> buffer, int count)
## Cross-Platform Data Type Mapping
### Fixed-Size Types
char
sbyte
1
byte
char
byte
1
byte
short
short
2
short
ushort
2
int
int
4
int
uint
4
long
long
long
8
long
long
ulong
8
float
float
4
double
double
8
### Platform-Dependent Types
nint
nuint
void
nint
or
void
long
6
4
on
8
on
64
long
6
as
long
int
4
not
bool
byte
1
byte
not
# `long` for C/C++ `long` -- they have different sizes on Unix 64-bit. Use `CLong`/`CULong` for portable interop.
## Agent Gotchas
1.
not
in
new
7
when
2.
not
bool
as
1
byte
bool
as
4
byte
by
default
for
bool
or
for
3.
not
# `long` to interop with C/C++ `long`.** C `long` is 4 bytes on Windows but 8 bytes on 64-bit Unix. Use `CLong`/`CULong` (.NET 6+) for cross-platform correctness.
4.
not
for
string
not
and
with
char
or
byte
from
5.
not
or
for
not
in
6.
not
dynamic
on
dynamic
"__Internal"
as
for
7.
not
in
Use typed delegates orunmanaged function pointers (`delegate* unmanaged`). Untyped delegates can destabilize the runtime during marshalling.
8. **Do not forget to keep delegate instances alive during native use.** The GC may collect a delegate that native code still references. Store delegates in a static field or use `GCHandle` for the duration of native callbacks.
---
## Prerequisites
- .NET 7+ SDK for `[LibraryImport]` source generation
- .NET Core 3.0+ for `NativeLibrary` API
- Native libraries compiled for each target platform/architecture
- For iOS: Xcode with native static libraries linked via `NativeReference`
- For Android: native `.so` files for each target ABI (arm64-v8a, x86_64)
---
## References
- [Platform Invoke (P/Invoke)](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke)
- [Native interoperability best practices](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/best-practices)
- [LibraryImport source generation](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation)
- [Type marshalling](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/type-marshalling)
- [Customizing struct marshalling](https://learn.microsoft.com/en-us/dotnet/standard/native-interop/customize-struct-marshalling)
- [NativeLibrary class](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativelibrary)