Loading...
Loading...
Compare original and translation side by side
DllImportLibraryImportDllImportLibraryImportLibraryImportDllImportLibraryImportDllImportLibraryImportLibraryImport[DllImport][LibraryImport]AccessViolationExceptionDllNotFoundExceptionDllImportLibraryImport[DllImport][LibraryImport]AccessViolationExceptionDllNotFoundExceptionDllImportLibraryImportDllImportLibraryImportDllImportLibraryImport| Input | Required | Description |
|---|---|---|
| Native header or documentation | Yes | C/C++ function signatures, struct definitions, calling conventions |
| Target framework | Yes | Determines whether to use |
| Target platforms | Recommended | Affects type sizes ( |
| Memory ownership contract | Yes | Who allocates and who frees each buffer or handle |
| 输入项 | 是否必填 | 描述 |
|---|---|---|
| 原生头文件或文档 | 是 | C/C++函数签名、结构体定义、调用约定 |
| 目标框架 | 是 | 决定使用 |
| 目标平台 | 推荐 | 影响类型大小( |
| 内存所有权约定 | 是 | 明确每个缓冲区或句柄的分配方和释放方 |
| Aspect | | |
|---|---|---|
| Mechanism | Runtime marshalling | Source generator (compile-time) |
| AOT / Trim safe | No | Yes |
| String marshalling | | |
| Error handling | | |
| Availability | .NET Framework 1.0+ | .NET 7+ only |
| 维度 | | |
|---|---|---|
| 实现机制 | 运行时封送 | 源代码生成器(编译时) |
| AOT/裁剪安全 | 否 | 是 |
| 字符串封送 | | |
| 错误处理 | | |
| 可用版本 | .NET Framework 1.0+ | 仅.NET 7+ |
| C / Win32 Type | .NET Type | Why |
|---|---|---|
| | 32-bit on Windows, 64-bit on 64-bit Unix. With |
| | Pointer-sized. Use |
| | Not |
| | Must specify 1-byte marshal |
| | Prefer over raw |
| | UTF-16 on Windows (lowest cost for |
| | Must specify encoding (ANSI or UTF-8). Always requires marshalling cost for |
❌ NEVER useorintfor Clong— it's 32-bit on Windows, 64-bit on Unix. Always uselong. ❌ NEVER useCLongforulong— causes stack corruption on 32-bit. Usesize_tornuint. ❌ NEVER useUIntPtrwithoutbool— the default marshal size is wrong.MarshalAs
| C / Win32类型 | .NET类型 | 原因 |
|---|---|---|
| | Windows上为32位,64位Unix上为64位。使用 |
| | 指针大小。.NET 8+使用 |
| | 不要用 |
| | 必须指定1字节封送 |
| | 优先于原生 |
| | Windows上为UTF-16( |
| | 必须指定编码(ANSI或UTF-8)。 |
❌ 绝不要用或int对应C的long——Windows上是32位,Unix上是64位。始终使用long。 ❌ 绝不要用CLong对应ulong——会导致32位平台上的栈损坏。使用size_t或nuint。 ❌ 绝不要不添加UIntPtr就使用MarshalAs——默认封送大小错误。bool
int32_t process_records(const Record* records, size_t count, uint32_t* out_processed);[DllImport("mylib")]
private static extern int ProcessRecords(
[In] Record[] records, UIntPtr count, out uint outProcessed);[LibraryImport("mylib")]
internal static partial int ProcessRecords(
[In] Record[] records, nuint count, out uint outProcessed);CdeclStdCall<PlatformTarget>x86</PlatformTarget>win-x86// DllImport (x86 targets)
[DllImport("mylib", CallingConvention = CallingConvention.Cdecl)]
// LibraryImport (x86 targets)
[LibraryImport("mylib")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]EntryPointEntryPointNotFoundException// DllImport
[DllImport("mylib", EntryPoint = "process_records")]
private static extern int ProcessRecords(
[In] Record[] records, UIntPtr count, out uint outProcessed);
// LibraryImport
[LibraryImport("mylib", EntryPoint = "process_records")]
internal static partial int ProcessRecords(
[In] Record[] records, nuint count, out uint outProcessed);int32_t process_records(const Record* records, size_t count, uint32_t* out_processed);[DllImport("mylib")]
private static extern int ProcessRecords(
[In] Record[] records, UIntPtr count, out uint outProcessed);[LibraryImport("mylib")]
internal static partial int ProcessRecords(
[In] Record[] records, nuint count, out uint outProcessed);CdeclStdCall<PlatformTarget>x86</PlatformTarget>win-x86// DllImport (x86目标)
[DllImport("mylib", CallingConvention = CallingConvention.Cdecl)]
// LibraryImport (x86目标)
[LibraryImport("mylib")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]EntryPointEntryPointNotFoundException// DllImport
[DllImport("mylib", EntryPoint = "process_records")]
private static extern int ProcessRecords(
[In] Record[] records, UIntPtr count, out uint outProcessed);
// LibraryImport
[LibraryImport("mylib", EntryPoint = "process_records")]
internal static partial int ProcessRecords(
[In] Record[] records, nuint count, out uint outProcessed);WACharSet.AutoStringBuilder❌ NEVER rely onor omit string encoding — there is no safe default.CharSet.Auto
// DllImport — Windows API (UTF-16)
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetModuleFileNameW(
IntPtr hModule, [Out] char[] filename, int size);
// DllImport — Cross-platform C library (UTF-8)
[DllImport("mylib")]
private static extern int SetName(
[MarshalAs(UnmanagedType.LPUTF8Str)] string name);
// LibraryImport — UTF-16
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastPInvokeError = true)]
internal static partial int GetModuleFileNameW(
IntPtr hModule, [Out] char[] filename, int size);
// LibraryImport — UTF-8
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
internal static partial int SetName(string name);CoTaskMemAllocCoTaskMemFreeNativeMemoryWACharSet.AutoStringBuilder❌ 绝不要依赖或省略字符串编码——没有安全的默认值。CharSet.Auto
// DllImport — Windows API (UTF-16)
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int GetModuleFileNameW(
IntPtr hModule, [Out] char[] filename, int size);
// DllImport — 跨平台C库 (UTF-8)
[DllImport("mylib")]
private static extern int SetName(
[MarshalAs(UnmanagedType.LPUTF8Str)] string name);
// LibraryImport — UTF-16
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16,
SetLastPInvokeError = true)]
internal static partial int GetModuleFileNameW(
IntPtr hModule, [Out] char[] filename, int size);
// LibraryImport — UTF-8
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
internal static partial int SetName(string name);CoTaskMemAllocCoTaskMemFreeNativeMemory❌ NEVER free with a mismatched allocator —onMarshal.FreeHGlobal'd memory is heap corruption.malloc
[LibraryImport("mylib")]
private static partial int GetName(
Span<byte> buffer, nuint bufferSize, out nuint actualSize);
public static string GetName()
{
Span<byte> buffer = stackalloc byte[256];
int result = GetName(buffer, (nuint)buffer.Length, out nuint actualSize);
if (result != 0) throw new InvalidOperationException($"Failed: {result}");
return Encoding.UTF8.GetString(buffer[..(int)actualSize]);
}[LibraryImport("mylib")]
private static partial IntPtr GetVersion();
[LibraryImport("mylib")]
private static partial void FreeString(IntPtr s);
public static string GetVersion()
{
IntPtr ptr = GetVersion();
try { return Marshal.PtrToStringUTF8(ptr) ?? throw new InvalidOperationException(); }
finally { FreeString(ptr); } // Must use the library's own free function
}Marshal.FreeHGlobalMarshal.FreeCoTaskMemmallocSafeHandle// Synchronous: use fixed
public static unsafe void ProcessSync(byte[] data)
{
fixed (byte* ptr = data) { ProcessData(ptr, (nuint)data.Length); }
}
// Asynchronous: use GCHandle
var gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
// Must keep pinned until native processing completes, then call gcHandle.Free()❌ 绝不要使用不匹配的分配器释放内存——用释放Marshal.FreeHGlobal分配的内存会导致堆损坏。malloc
[LibraryImport("mylib")]
private static partial int GetName(
Span<byte> buffer, nuint bufferSize, out nuint actualSize);
public static string GetName()
{
Span<byte> buffer = stackalloc byte[256];
int result = GetName(buffer, (nuint)buffer.Length, out nuint actualSize);
if (result != 0) throw new InvalidOperationException($"调用失败: {result}");
return Encoding.UTF8.GetString(buffer[..(int)actualSize]);
}[LibraryImport("mylib")]
private static partial IntPtr GetVersion();
[LibraryImport("mylib")]
private static partial void FreeString(IntPtr s);
public static string GetVersion()
{
IntPtr ptr = GetVersion();
try { return Marshal.PtrToStringUTF8(ptr) ?? throw new InvalidOperationException(); }
finally { FreeString(ptr); } // 必须使用库自身的释放函数
}Marshal.FreeHGlobalMarshal.FreeCoTaskMemmallocSafeHandle// 同步场景:使用fixed
public static unsafe void ProcessSync(byte[] data)
{
fixed (byte* ptr = data) { ProcessData(ptr, (nuint)data.Length); }
}
// 异步场景:使用GCHandle
var gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
// 必须保持固定直到原生处理完成,然后调用gcHandle.Free()IntPtrSafeHandleinternal sealed class MyLibHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// Required by the marshalling infrastructure to instantiate the handle.
// Do not remove — there are no direct callers.
private MyLibHandle() : base(ownsHandle: true) { }
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
private static partial MyLibHandle CreateHandle(string config);
[LibraryImport("mylib")]
private static partial int UseHandle(MyLibHandle h, ReadOnlySpan<byte> data, nuint len);
[LibraryImport("mylib")]
private static partial void DestroyHandle(IntPtr h);
protected override bool ReleaseHandle() { DestroyHandle(handle); return true; }
public static MyLibHandle Create(string config)
{
var h = CreateHandle(config);
if (h.IsInvalid) throw new InvalidOperationException("Failed to create handle");
return h;
}
public int Use(ReadOnlySpan<byte> data) => UseHandle(this, data, (nuint)data.Length);
}
// Usage: SafeHandle is IDisposable
using var handle = MyLibHandle.Create("config=value");
int result = handle.Use(myData);IntPtrSafeHandleinternal sealed class MyLibHandle : SafeHandleZeroOrMinusOneIsInvalid
{
// 封送基础设施需要此构造函数来实例化句柄。
// 不要删除——没有直接调用方。
private MyLibHandle() : base(ownsHandle: true) { }
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
private static partial MyLibHandle CreateHandle(string config);
[LibraryImport("mylib")]
private static partial int UseHandle(MyLibHandle h, ReadOnlySpan<byte> data, nuint len);
[LibraryImport("mylib")]
private static partial void DestroyHandle(IntPtr h);
protected override bool ReleaseHandle() { DestroyHandle(handle); return true; }
public static MyLibHandle Create(string config)
{
var h = CreateHandle(config);
if (h.IsInvalid) throw new InvalidOperationException("创建句柄失败");
return h;
}
public int Use(ReadOnlySpan<byte> data) => UseHandle(this, data, (nuint)data.Length);
}
// 使用方式:SafeHandle实现了IDisposable
using var handle = MyLibHandle.Create("config=value");
int result = handle.Use(myData);// Win32 APIs — check SetLastError
[LibraryImport("kernel32", SetLastPInvokeError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool CloseHandle(IntPtr hObject);
if (!CloseHandle(handle))
throw new Win32Exception(Marshal.GetLastPInvokeError());
// HRESULT APIs
int hr = NativeDoWork(context);
Marshal.ThrowExceptionForHR(hr);// Win32 API — 检查SetLastError
[LibraryImport("kernel32", SetLastPInvokeError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool CloseHandle(IntPtr hObject);
if (!CloseHandle(handle))
throw new Win32Exception(Marshal.GetLastPInvokeError());
// HRESULT API
int hr = NativeDoWork(context);
Marshal.ThrowExceptionForHR(hr);UnmanagedCallersOnly[UnmanagedCallersOnly]
private static void LogCallback(int level, IntPtr message)
{
string msg = Marshal.PtrToStringUTF8(message) ?? string.Empty;
Console.WriteLine($"[{level}] {msg}");
}
[LibraryImport("mylib")]
private static unsafe partial void SetLogCallback(
delegate* unmanaged<int, IntPtr, void> cb);
unsafe { SetLogCallback(&LogCallback); }static[UnmanagedFunctionPointer(CallingConvention.Cdecl)] // Only needed on Windows x86
private delegate void LogCallbackDelegate(int level, IntPtr message);
// CRITICAL: prevent delegate from being garbage collected
private static LogCallbackDelegate? s_logCallback;
public static void EnableLogging(Action<int, string> handler)
{
s_logCallback = (level, msgPtr) =>
{
string msg = Marshal.PtrToStringUTF8(msgPtr) ?? string.Empty;
handler(level, msg);
};
SetLogCallback(s_logCallback);
}GC.KeepAliveMarshal.GetFunctionPointerForDelegateGC.KeepAlivevar callback = new LogCallbackDelegate((level, msgPtr) =>
{
string msg = Marshal.PtrToStringUTF8(msgPtr) ?? string.Empty;
Console.WriteLine($"[{level}] {msg}");
});
IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(callback);
NativeUsesCallback(fnPtr);
GC.KeepAlive(callback); // prevent collection — fnPtr does not root the delegateUnmanagedCallersOnly[UnmanagedCallersOnly]
private static void LogCallback(int level, IntPtr message)
{
string msg = Marshal.PtrToStringUTF8(message) ?? string.Empty;
Console.WriteLine($"[{level}] {msg}");
}
[LibraryImport("mylib")]
private static unsafe partial void SetLogCallback(
delegate* unmanaged<int, IntPtr, void> cb);
unsafe { SetLogCallback(&LogCallback); }static[UnmanagedFunctionPointer(CallingConvention.Cdecl)] // 仅Windows x86需要
private delegate void LogCallbackDelegate(int level, IntPtr message);
// 关键:防止委托被垃圾回收
private static LogCallbackDelegate? s_logCallback;
public static void EnableLogging(Action<int, string> handler)
{
s_logCallback = (level, msgPtr) =>
{
string msg = Marshal.PtrToStringUTF8(msgPtr) ?? string.Empty;
handler(level, msg);
};
SetLogCallback(s_logCallback);
}GC.KeepAliveMarshal.GetFunctionPointerForDelegateGC.KeepAlivevar callback = new LogCallbackDelegate((level, msgPtr) =>
{
string msg = Marshal.PtrToStringUTF8(msgPtr) ?? string.Empty;
Console.WriteLine($"[{level}] {msg}");
});
IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(callback);
NativeUsesCallback(fnPtr);
GC.KeepAlive(callback); // 防止回收——fnPtr不会根住委托NativeLibrary.SetDllImportResolverCLongCULonglongunsigned longCLongCULongLibraryImport[assembly: DisableRuntimeMarshalling]// Simple: conditional compilation
// WINDOWS, LINUX, MACOS are predefined only when targeting an OS-specific TFM
// (e.g., net8.0-windows). For portable TFMs (e.g., net8.0), these symbols are
// not defined — use the runtime resolver approach below instead.
#if WINDOWS
private const string LibName = "mylib.dll";
#elif LINUX
private const string LibName = "libmylib.so";
#elif MACOS
private const string LibName = "libmylib.dylib";
#endif
// Complex: runtime resolver
NativeLibrary.SetDllImportResolver(typeof(MyLib).Assembly,
(name, assembly, searchPath) =>
{
if (name != "mylib") return IntPtr.Zero;
string libName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "mylib.dll"
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
? "libmylib.dylib" : "libmylib.so";
NativeLibrary.TryLoad(libName, assembly, searchPath, out var handle);
return handle;
});NativeLibrary.SetDllImportResolverlongunsigned longCLongCULongLibraryImportCLongCULong[assembly: DisableRuntimeMarshalling]// 简单方式:条件编译
// WINDOWS、LINUX、MACOS仅在针对特定OS的TFM时预定义
// (如net8.0-windows)。对于可移植TFM(如net8.0),这些符号未定义——请改用下面的运行时解析器方式。
#if WINDOWS
private const string LibName = "mylib.dll";
#elif LINUX
private const string LibName = "libmylib.so";
#elif MACOS
private const string LibName = "libmylib.dylib";
#endif
// 复杂方式:运行时解析器
NativeLibrary.SetDllImportResolver(typeof(MyLib).Assembly,
(name, assembly, searchPath) =>
{
if (name != "mylib") return IntPtr.Zero;
string libName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "mylib.dll"
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX)
? "libmylib.dylib" : "libmylib.so";
NativeLibrary.TryLoad(libName, assembly, searchPath, out var handle);
return handle;
});partialstatic partial[DllImport][LibraryImport]CharSetStringMarshallingSetLastError = trueSetLastPInvokeError = trueCallingConventionSYSLIB1054SYSLIB1057<PropertyGroup>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
</PropertyGroup>partialstatic partial[DllImport][LibraryImport]CharSetStringMarshallingSetLastError = trueSetLastPInvokeError = trueCallingConventionSYSLIB1054SYSLIB1057<PropertyGroup>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<EnableAotAnalyzer>true</EnableAotAnalyzer>
</PropertyGroup>NativeMethods.txtdotnet add package Microsoft.Windows.CsWin32NativeMethods.txtdotnet add package Microsoft.Windows.CsWin32.winmd.winmdCharSet.AutoSafeHandleIntPtrSetLastErrorSetLastPInvokeErrorCLongCULonglongunsigned longCLongCULongLibraryImport[assembly: DisableRuntimeMarshalling]boolMarshalAsUnmanagedType.BoolUnmanagedType.U1CharSet.AutoSafeHandleIntPtrSetLastErrorSetLastPInvokeErrorlongunsigned longCLongCULongLibraryImportCLongCULong[assembly: DisableRuntimeMarshalling]boolMarshalAsUnmanagedType.BoolUnmanagedType.U1SYSLIB1054SYSLIB1057<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<EnableAotAnalyzer>true</EnableAotAnalyzer>Marshal.SizeOf<T>()sizeofSYSLIB1054SYSLIB1057<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<EnableAotAnalyzer>true</EnableAotAnalyzer>Marshal.SizeOf<T>()sizeof