NET cross-platform local libraries introduced into actual combat

NET cross-platform local libraries introduced into actual combat

In-depth analysis of how. NET projects gracefully introduce third-party local libraries, support multiple platforms for Windows and Linux, and guide to avoid traps

最后更新 4/20/2026 11:18 PM
沙漠尽头的狼
预计阅读 21 分钟
分类
.NET
标签
.NET C# cross-platform Native Library

When developing. NET, you occasionally need to call native libraries provided by third parties, such as hardware SDKs, cryptographic libraries, or underlying communication components. This article shares my two major solutions and experience in avoiding traps when introducing cross-platform local libraries through an actual Demo project.

1. project preparation

Demo项目使用了一个简单的C++动态库 TimeMeaning,它提供了一个API:

// 传入秒级时间戳,返回一段人生/时间意境文案
const char* GetTimeMeaning(int timestampSecond);

The corresponding copy is returned after the timestamp is taken for 10 years, implying that each moment has different life insights:

static const char* TIME_MEANINGS[] = {
    "黎明破晓,万物苏醒,新的一天带来新的希望",
    "晨光熹微,思绪清晰,适合规划一天的行程",
    "日出东方,阳光灿烂,充满活力与朝气",
    "上午时光,精力充沛,专注做事效率高",
    "正午时分,阳光明媚,适合休息片刻",
    "午后暖阳,慵懒惬意,时光静静流淌",
    "夕阳西下,余晖满天,美好的黄昏时分",
    "夜幕降临,星光点点,思绪开始沉淀",
    "夜深人静,皓月当空,适合反思与冥想",
    "午夜时分,万籁俱寂,梦想在黑暗中萌芽"
};

Third-party libraries usually provide versions for different platforms, and the directory structure is as follows:

Lib/
├── x64/
│   ├── TimeMeaning.dll        # 64位 Windows
│   └── (lib)TimeMeaning.so    # 64位 Linux
├── x86/
│   └── TimeMeaning.dll        # 32位 Windows
└── arm64/
    └── (lib)TimeMeaning.so    # ARM64 Linux

We hope:

  • ** Keep the code uniform **: There is no need to write different calling code for each platform
  • ** Automatic adaptation **: Automatically select library files for the corresponding platform when compiling or publishing

Directory.Build.props global macro definition

首先,我们在解决方案根目录创建 Directory.Build.props,默认定义 PLATFORM_WIN_X86 条件编译宏:

<Project>
	<PropertyGroup>
		<DefineConstants>$(DefineConstants);PLATFORM_WIN_X86</DefineConstants>
	</PropertyGroup>
</Project>

然后通过 publish.bat 发布脚本调用 SetPlatformMacro.ps1,在发布前根据目标平台修改全局宏定义:

# SetPlatformMacro.ps1
$macro = ""
switch ($Platform) {
    "linux-x64" { $macro = "PLATFORM_LINUX_X64" }
    "linux-arm64" { $macro = "PLATFORM_LINUX_ARM64" }
    "win-x64" { $macro = "PLATFORM_WIN_X64" }
    "win-x86" { $macro = "PLATFORM_WIN_X86" }
}
$content = $content -replace '<DefineConstants>.*?</DefineConstants>', "<DefineConstants>`$(DefineConstants);$macro</DefineConstants>"

This makes it easy to distinguish the currently compiled platform version in the code:

#if PLATFORM_WIN_X64
var platform = "Windows X64";
#elif PLATFORM_WIN_X86
var platform = "Windows X86";
#elif PLATFORM_LINUX_X64
var platform = "Linux X64";
#elif PLATFORM_LINUX_ARM64
var platform = "Linux ARM64";
#else
var platform = "Unknown";
#endif

2. Overview of the two major plans

Introducing local libraries is mainly divided into two major solutions **:

  1. 动态加载:使用 NativeLibrary API 运行时手动加载
  2. 静态加载:使用 DllImport 特性声明(做了3种情况测试)

VC-LTL and YY-Thunder (common to all examples)

All four example programs incorporate the following two NuGet packages. The examples currently tested all support Windows versions Win7 and above, as well as the Linux platform:

Windows 7运行原理:虽然微软官方.NET 10已不再支持Windows 7,但通过使用 net10.0-windows 目标框架配合 AOT 发布,可让程序在Windows 7上正常运行。AOT将.NET代码静态编译为原生可执行文件,完全摆脱对.NET运行时的依赖;配合VC-LTL和YY-Thunks分别提供轻量C运行时支持和旧Windows API兼容层,实现跨版本兼容。

<PackageReference Include="VC-LTL" Version="5.3.1" />
<PackageReference Include="YY-Thunks" Version="1.2.1-Beta.4" />
  • VC-LTL:使用开源的 VC 运行时库,无需安装系统补丁,大幅减少程序体积,兼容旧系统。配合AOT( PublishAot=true )发布可摆脱.NET运行时依赖,直接生成原生可执行文件
  • ** YY-Thunder **: Provides a compatibility layer for old versions of Windows with new APIs, allowing modern code to run normally on Win7/XP (examples of XP untested articles)

3. Plan 1: Dynamic loading (Success)

动态加载是最灵活的方式,使用 NativeLibrary API 在运行时手动加载本地库。这种方式的优势是可以完全自定义库的加载逻辑,能够处理相同库但存储在不同目录、使用不同文件命名的复杂场景。对于某些特殊需求,比如需要在运行时根据条件选择加载不同的版本,或者库的路径需要动态计算,动态加载是最好的选择。

code implementation

using System.Runtime.InteropServices;

namespace csharp.test.dynamic;

internal static class TimeMeaningNative
{
    // 根据当前操作系统判断库文件名:Windows用dll,Linux用so
    private static readonly string DllName = OperatingSystem.IsWindows()
        ? "TimeMeaning.dll"
        : "libTimeMeaning.so";

    // 定义与C++函数相同调用约定的委托,用于后续转换
    [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    private delegate IntPtr GetTimeMeaningDelegate(int timestampSecond);

    // 用于存储转换后的委托(调用时会用到)
    private static GetTimeMeaningDelegate? _getTimeMeaning;
    // 用于存储库的句柄(用于后续释放)
    private static IntPtr _handle;

    // 静态构造函数,在第一次使用该类时自动执行
    static TimeMeaningNative()
    {
        // 拼接库的完整路径:应用程序根目录 + Lib子目录 + 文件名
        var dllPath = Path.Combine(AppContext.BaseDirectory, "Lib", DllName);

        // NativeLibrary.Load:加载指定路径的本地库,返回库句柄
        _handle = NativeLibrary.Load(dllPath);

        // NativeLibrary.GetExport:从已加载的库中获取指定名称的函数地址
        var funcPtr = NativeLibrary.GetExport(_handle, "GetTimeMeaning");

        // Marshal.GetDelegateForFunctionPointer:将函数指针转换为可调用的委托
        _getTimeMeaning = Marshal.GetDelegateForFunctionPointer<GetTimeMeaningDelegate>(funcPtr);
    }

    // 提供手动释放库的方法,避免内存泄漏
    public static void Free()
    {
        if (_handle == IntPtr.Zero) return;
        NativeLibrary.Free(_handle);
        _handle = IntPtr.Zero;
    }

    // 封装对外的调用接口,返回字符串结果
    public static string GetTimeMeaningString(int timestampSecond)
    {
        if (_getTimeMeaning == null)
        {
            throw new InvalidOperationException("动态库未正确加载");
        }

        // 调用委托,得到C++函数返回的指针
        var ptr = _getTimeMeaning(timestampSecond);
        // 将UTF8编码的字符指针转换为C#字符串
        return Marshal.PtrToStringUTF8(ptr) ?? string.Empty;
    }
}

** Code description **:

  • NativeLibrary.Load - 核心API,传入完整路径加载本地库
  • NativeLibrary.GetExport - 从已加载库中获取导出函数的指针
  • Marshal.GetDelegateForFunctionPointer - 将非托管函数指针转换为.NET委托,这样就可以像调用普通方法一样调用本地函数了
  • Marshal.PtrToStringUTF8 - 将C++返回的UTF8字符串指针转换为C#字符串

Project Configuration (csproj)

<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFrameworks>net10.0;net10.0-windows</TargetFrameworks>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>

		<WindowsSupportedOSPlatformVersion>6.1</WindowsSupportedOSPlatformVersion>
		<TargetPlatformMinVersion>6.1</TargetPlatformMinVersion>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="CodeWF.Log.Core" Version="11.3.14" />
		<PackageReference Include="VC-LTL" Version="5.3.1" />
		<PackageReference Include="YY-Thunks" Version="1.2.1-Beta.4" />
	</ItemGroup>

	<ItemGroup>
		<!-- 调试状态默认复制Win x64的库,便于本地调试 -->
		<None Update="Lib\x64\TimeMeaning.dll" Condition="'$(Configuration)' == 'Debug'">
			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
			<Link>Lib\TimeMeaning.dll</Link>
		</None>
	</ItemGroup>
	<ItemGroup>
		<None Update="Lib\x64\libTimeMeaning.so" Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
			<Link>Lib\libTimeMeaning.so</Link>
		</None>
		<None Update="Lib\x64\TimeMeaning.dll" Condition="'$(RuntimeIdentifier)' == 'win-x64'">
			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
			<Link>Lib\TimeMeaning.dll</Link>
		</None>
		<None Update="Lib\x86\TimeMeaning.dll" Condition="'$(RuntimeIdentifier)' == 'win-x86'">
			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
			<Link>Lib\TimeMeaning.dll</Link>
		</None>
	</ItemGroup>

</Project>

**csproj configuration instructions **:

  • Condition="'$(Configuration)' == 'Debug'" - 在Debug模式下,默认复制Windows x64版本的库,方便直接在Visual Studio中调试运行
  • Condition="'$(RuntimeIdentifier)' == 'linux-x64'" - 当使用-r linux-x64发布时,复制Linux版本的库
  • Link属性指定了库在输出目录中的路径,确保与代码中加载的路径一致

Key point description

** Dynamic loading process **:

  • The runtime determines the library file name based on the operating system
  • Splice complete path loading library
  • Get the export function address
  • Convert to delegate call

4. Option 2: Static loading

静态加载使用 DllImport 特性声明,这是.NET中调用本地库的标准方式。我们做了3种情况测试:主要是测试三方库封装代码是直接放在主工程,还是提取出来通过NuGet分发时,使用条件编译宏是否可行、路径灵活度如何。

Two ways to define conditional compilation macros

使用条件编译宏前,需要先定义 PLATFORM_XXX 宏,有两种方式:

Method 1: Define in the release configuration file (pubxml)

Properties/PublishProfiles/*.pubxml 文件的 <PropertyGroup> 中添加:

<PropertyGroup>
    <DefineConstants>$(DefineConstants);PLATFORM_WIN_X64</DefineConstants>
</PropertyGroup>

Method 2: Define in the project file (csproj)

在项目文件的 <PropertyGroup> 中添加:

<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
    <DefineConstants>$(DefineConstants);PLATFORM_LINUX_X64</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'linux-arm64'">
    <DefineConstants>$(DefineConstants);PLATFORM_LINUX_ARM64</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64'">
    <DefineConstants>$(DefineConstants);PLATFORM_WIN_X64</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x86'">
    <DefineConstants>$(DefineConstants);PLATFORM_WIN_X86</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="'$(RuntimeIdentifier)' == ''">
    <DefineConstants>$(DefineConstants);PLATFORM_EMPTY</DefineConstants>
</PropertyGroup>

重要说明:方式2在工程文件中添加的条件编译宏定义,仅对主工程有效。因为子工程编译时 $(RuntimeIdentifier) 是空的(不会继承主工程的 RID),所以子工程会匹配到 $(RuntimeIdentifier) == '' 条件,定义 PLATFORM_EMPTY 宏。这意味着在子工程中无法通过这种方式正确区分平台。如果需要在子工程中使用平台宏,需要配合发布脚本动态修改 Directory.Build.props 全局宏定义(见情况2)。

Scenario 1: Single project + conditional compilation (Successful)

直接在主工程中使用 DllImport,通过条件编译宏设置库路径,适合不封装为类库的场景,比如小工具或者不需要复用率低的项目。

静态加载使用条件编译宏的优势:可以灵活处理不同平台库名完全不同的情况,当然也包括不同目录(实际场景有可能),比如 Windows 用 Lib/Windows x64/TimeMeaning.dll,Linux 用 Lib/Linux x64/libTimeMeaning.so,这与方案一的动态加载有点像,都能处理复杂的路径差异。

code implementation

using System.Runtime.InteropServices;

namespace csharp.test.static_;

internal static class TimeMeaningNative
{
#if PLATFORM_WIN_X64 || PLATFORM_WIN_X86
    const string DLL = "Lib/TimeMeaning.dll";
#elif PLATFORM_LINUX_X64 || PLATFORM_LINUX_ARM64
    const string DLL = "Lib/libTimeMeaning.so";
#else
    const string DLL = "Lib/TimeMeaning.dll";
#endif

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    private static extern IntPtr GetTimeMeaning(int timestampSecond);

    public static string GetTimeMeaningString(int timestampSecond)
    {
        var ptr = GetTimeMeaning(timestampSecond);
        return Marshal.PtrToStringUTF8(ptr) ?? string.Empty;
    }
}

results

** Success **: In a single project scenario, the conditional compilation macro works normally, and both Windows and Linux can correctly load the corresponding library files.


将库调用封装到独立的类库工程,再由主工程引用。通过 publish.bat 发布脚本在发布前调用 SetPlatformMacro.ps1 修改全局宏定义,解决了类库不继承条件编译宏的问题。

Class library code (TimeMeaningNative.csproj)

using System.Runtime.InteropServices;

namespace TimeMeaningNative;

public static class TimeMeaningApi
{
#if PLATFORM_WIN_X64 || PLATFORM_WIN_X86
    const string DLL = "Lib/TimeMeaning.dll";
#elif PLATFORM_LINUX_X64 || PLATFORM_LINUX_ARM64
    const string DLL = "Lib/libTimeMeaning.so";
#else
    const string DLL = "Lib/TimeMeaning.dll";
#endif

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    private static extern IntPtr GetTimeMeaning(int timestampSecond);

    public static string GetTimeMeaningString(int timestampSecond)
    {
        var ptr = GetTimeMeaning(timestampSecond);
        return Marshal.PtrToStringUTF8(ptr) ?? string.Empty;
    }
}

Publish script (publish.bat)

for %%p in (%platforms%) do (
    set "tfm="
    set "pubxml="

    if "%%p"=="linux-x64" set "tfm=net10.0" & set "pubxml=FolderProfile_linux-x64.pubxml"
    ...

    powershell -ExecutionPolicy Bypass -File "SetPlatformMacro.ps1" -Platform "%%p"

    for %%d in (%project_paths%) do (
        dotnet publish "%%d" -f !tfm! /p:PublishProfile="%%d\Properties\PublishProfiles\!pubxml!"
    )
)

success cause

成功publish.bat 发布前调用 SetPlatformMacro.ps1 脚本修改 Directory.Build.props 中的全局宏定义,使子工程也能正确获取平台宏定义(如 PLATFORM_LINUX_X64),而不是依赖编译时的 RuntimeIdentifier

** Note **: The specific script can be viewed in the test warehouse, and the warehouse address is at the end of the text.

ˇImportant: NuGet packaging restrictions

** Note **: This scheme only applies to multiple project references in the same source code warehouse. If such library projects are packaged into NuGet packages and distributed to other projects, the following problems will arise:

  1. Directory.Build.props 不会被打包:NuGet 包中不会包含仓库根目录的 Directory.Build.props,上游用户无法继承宏定义
  2. ** Macros have been fixed at compile time **: When packaging, the code has completed branch pruning according to the macro conditions at that time, and the generated dll is no longer affected by upstream macros
  3. 上游无法改变行为:安装 NuGet 包的项目,即使定义了自己的 PLATFORM_XXX 宏,也无法改变已打包库的代码分支

** Solution **: If you need to distribute cross-platform libraries through NuGet, it is recommended to use Option 4 (multi-project + library name only), or use the NuGet build props mechanism (see the supplementary explanation below for details).


This is the ** most recommended ** solution. Compared with the dynamic loading method, it reduces the difficulty of use. Conditional compilation macros are not used in the class library, and only the library name (without extension) is specified to solve the cross-platform library reference problem.

Class library code (TimeMeaningNative.csproj)

using System.Runtime.InteropServices;

namespace TimeMeaningNative;

public static class TimeMeaningApi
{
    const string DLL = "Lib/TimeMeaning";
    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    private static extern IntPtr GetTimeMeaning(int timestampSecond);

    public static string GetTimeMeaningString(int timestampSecond)
    {
        var ptr = GetTimeMeaning(timestampSecond);
        return Marshal.PtrToStringUTF8(ptr) ?? string.Empty;
    }
}

Main engineering configuration (csproj)

这里有一个关键技巧:如果Linux库有lib等前缀,需要去掉,和Windows dll改为相同文件名,Linux下复制时去掉 lib 前缀!

<ItemGroup>
    <!-- Linux 下关键:复制时去掉 lib 前缀! -->
    <None Update="Lib\x64\libTimeMeaning.so" Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        <Link>Lib\TimeMeaning.so</Link>
    </None>
    ...
</ItemGroup>

working principle

  • WindowsDllImport("Lib/TimeMeaning") 自动查找 Lib/TimeMeaning.dll
  • LinuxDllImport("Lib/TimeMeaning") 会查找 Lib/TimeMeaningLib/TimeMeaning.so不会查找 Lib/libTimeMeaning.so
  • 所以Linux下需要通过 <Link>Lib\TimeMeaning.so</Link>libTimeMeaning.so 复制为 TimeMeaning.so

results

** Success **: This is the most recommended solution. It is simple and reliable. It is too lazy to define conditional compilation macros. It only needs to ensure that library file names are unified on different platforms (remove the lib prefix under Linux).


Comparison summary of protocols

category programme practices results applicable scenarios
dynamic loading NativeLibrary dynamic loading Manually determine the operating system in the code and load the library Available on all platforms Need flexible control over the loading path
static loading Single project + conditional compilation #if PLATFORM_WIN_X64 条件编译 Success Only used by main projects, does not encapsulate class libraries, and supports different path libraries
static loading Multi-engineering + conditional compilation Globally set macros through scripts before publishing ** Success (cooperated with release of script)** Recommended, different platform libraries need different paths
static loading Multi-project + library name only (no extension) Class library only writes library name + csproj conditional copy (Linux removes lib prefix) ** Perfect success across platforms ** ** Most recommended **, simple and reliable, I hope to unify library file names

6. Core experience

  1. ** It is recommended to use the DllImport constant library name (without extension)**. This is the most stable and reliable solution, and the focus is on simplicity and ease of understanding. Option 1 dynamically loading is also feasible, but it is a little more troublesome to use
  2. ** Static loading and using conditional compilation macro can handle situations where library names are different **, suitable for single projects and multiple projects (multiple projects need to cooperate with the release script to set the macro globally)
  3. 多工程场景下宏不继承的问题可以通过发布脚本解决:使用 publish.bat + SetPlatformMacro.ps1 在发布前修改全局宏
  4. ** Don't rely on the RuntimeIdentifier at the compilation time of the class library **, because the class library may not have a RuntimeIdentifier context at the compilation time, resulting in the conditional compilation macro not taking effect
  5. Linux 下注意去掉 lib 前缀,通过 csproj 的 <Link> 机制重命名
  6. ** When you need to support Windows 7 **, install VC-LTL and YY-Thunder NuGet packages
  7. ** You can place the library files in the Lib subdirectory **, not necessarily in the root directory
  8. **ˇ Important: The Directory.Build.props global macro does not support NuGet distribution **: If you package a class library that uses conditional compilation macros as NuGet, upstream projects will not inherit the macro at all, and the internal code of the NuGet package will also fix the compilation branch at the time of packaging. If you need to distribute through NuGet, plan 4 is recommended (library name only, does not rely on conditional compilation macros)

** Supplementary note **: For beginners, it is best to master Situation 3 (multiple projects + library names only) first. This is the safest and easy-to-understand way. If you do need to flexibly handle path differences, consider dynamic loading or Case 2.

7. Supplement: NuGet Packaging and Conditional Compilation Macros

7.1 Directory.Build.props and NuGet restrictions

结论Directory.Build.props 是源码仓库级别的构建配置,不属于项目本身,更不属于 NuGet 包。

is effective

scene Whether it takes effect description
Local multi-engineering development Yes MSBuild automatically traverses up and loads from the project directory
Compiled at packaging Yes Macros are applied during packaging and the compilation phase to prune branches
NuGet package internal (runtime) No The macro disappeared during compilation and the dll branch was fixed
Upstream project after installation No NuGet 不包含 Directory.Build.props,无法继承

core reason

  1. **NuGet is essentially a compilation product **: Contains dll + metadata, but does not contain build configuration
  2. 宏是编译时概念#if 在编译时已完成分支裁剪,运行时不再存在
  3. **Directory.Build.props is not packaged **: only takes effect in the local warehouse structure

7.2 Alternatives to NuGet Distribution

Scenario A: Use NuGet build props (pass macros downstream)

如果确实需要通过 NuGet 向下游项目传递宏定义,可以在库项目中添加 build/ 文件夹:

<!-- build/YourPackageName.props -->
<Project>
  <PropertyGroup>
    <!-- 基于 RuntimeIdentifier 自动定义宏 -->
    <DefineConstants Condition="'$(RuntimeIdentifier)'=='win-x86'">
      $(DefineConstants);PLATFORM_WIN_X86
    </DefineConstants>
    <DefineConstants Condition="'$(RuntimeIdentifier)'=='win-x64'">
      $(DefineConstants);PLATFORM_WIN_X64
    </DefineConstants>
    <DefineConstants Condition="'$(RuntimeIdentifier)'=='linux-x64'">
      $(DefineConstants);PLATFORM_LINUX_X64
    </DefineConstants>
    <DefineConstants Condition="'$(RuntimeIdentifier)'=='linux-arm64'">
      $(DefineConstants);PLATFORM_LINUX_ARM64
    </DefineConstants>
  </PropertyGroup>
</Project>

Then configure packaging in the csproj of the library project:

<ItemGroup>
  <None Include="build\**" Pack="true" PackagePath="build\" />
</ItemGroup>

** Note **: This only allows downstream projects to obtain macro definitions, but cannot change code branches within the packaged library (because the library dll is fixed at compile time).

The most secure NuGet distribution solution is to avoid using conditional compilation macros in library code and use option 4 (library name only) or runtime judgment:

// 推荐:库内使用运行时判断或仅库名方式
public static class TimeMeaningApi
{
    const string DLL = "Lib/TimeMeaning"; // 不加扩展名

    [DllImport(DLL, CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr GetTimeMeaning(int timestampSecond);

    // ...
}

Scheme C: Pack NuGet separately according to RID

Package NuGet separately for each platform, using different package IDs or versions:

  • YourLibrary.win-x64
  • YourLibrary.linux-x64
  • wait

However, this will increase maintenance costs and is usually not recommended.

7.3 final recommendations

  • 同一仓库内多工程:可以使用 Directory.Build.props + 发布脚本(情况2)
  • ** NuGet distribution is required **: Option 4 is strongly recommended (only library name, does not rely on conditional compilation macros)
  • ** Must use conditional compilation + NuGet**: Consider the combination of NuGet build props + runtime judgment

8. Frequently Asked Questions Q&A

Q1: Why should I remove the lib prefix under Linux?

A: There are two situations:

  • 库在根目录DllImport("TimeMeaning") 在 Linux 下会查找 TimeMeaningTimeMeaning.solibTimeMeaning.so,无需去掉前缀
  • 库在子目录DllImport("Lib/TimeMeaning") 在 Linux 下仅会查找 Lib/TimeMeaningLib/TimeMeaning.so不会查找 Lib/libTimeMeaning.so**,因此需要通过 csproj 的 机制将libTimeMeaning.so复制为TimeMeaning.so`

Q2: Must library files be in the same directory as executable files?

A: 不需要!可以放在子目录(如 Lib/),只需要在 DllImport 中指定子目录路径,如 DllImport("Lib/TimeMeaning")。注意使用子目录时Linux不会查找带lib前缀的库。

Q3: What does CallingConvention mean?

A: defines how parameters are passed and stack cleaned when a function is called. Common ones are:

  • Cdecl:C 语言默认约定,调用者清理栈(Linux 常用)
  • StdCall:Windows API 常用,被调用者清理栈
  • Winapi:平台默认(Windows 下是 StdCall,Linux 下是 Cdecl)

Q4: Does macOS support it?

A: 支持!macOS 使用 .dylib 后缀,同样可以用 DllImport("Lib/TimeMeaning"),系统会自动查找 Lib/TimeMeaning.dylib。csproj 配置中增加 osx-x64osx-arm64 的配置即可。

Q5: How to know if the library file is loaded correctly during debugging?

A: You can use the following methods:

  1. 检查输出目录的 Lib/ 子目录是否有正确的库文件
  2. Monitor library file loading using Process Monitor (Windows) or lsof (Linux)
  3. 在代码中调用 NativeLibrary.TryLoad 测试加载是否成功

The above content is based on the actual Demo project and includes the C++ library code and complete code examples of the four major solutions. If there are any mistakes or better solutions, please leave a message in the comment area to correct them!

Open source project address: github.com/dotnet9/DotnetCrossPlatformNativeLibrary

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 4/22/2026

Support for. NET by operating system versions (250707 update)

Use virtual machines and test machines to test the support of each version of the operating system for. NET. After installing the operating system, it is passed by measuring the corresponding running time of the installation and being able to run the Stardust Agent.

继续阅读
同分类 / 同标签 2/7/2026

Summary of experience in using AOT

From the very beginning of project creation, you should develop a good habit of conducting AOT release testing in a timely manner whenever new features are added or newer syntax is used.

继续阅读