【MAUI】在 .NET MAUI 中调用原生 SO 库

文章目录

前言

.NET MAUI 作为跨平台框架,允许开发者通过 P/Invoke (Platform Invoke)MAUI 的特定绑定机制 调用原生动态链接库(Android 的 .so 文件)。本文聚焦 Android 平台,提供清晰可落地的实现方案。


一、准备 SO 库

将编译好的 SO 文件按架构分类放入项目:

复制代码
YourProject/
├── Platforms/
│   └── Android/
│       └── lib/               # 自定义目录存放 SO 文件
│           ├── arm64-v8a/
│           │   └── libdemo.so
│           ├── armeabi-v7a/
│           │   └── libdemo.so
│           └── x86_64/
│               └── libdemo.so

关键点 :确保 SO 文件的 Build Action 设置为 AndroidNativeLibrary,或在 .csproj 中显式声明:

xml 复制代码
<ItemGroup>
    <AndroidNativeLibrary Include="Platforms\Android\lib\arm64-v8a\libdemo.so" />
    <AndroidNativeLibrary Include="Platforms\Android\lib\armeabi-v7a\libdemo.so" />
</ItemGroup>

二、C# 绑定声明

使用 DllImport 特性声明外部函数。假设 SO 库导出了以下 C 函数:

c 复制代码
// native-lib.h
int add(int a, int b);
const char* get_version(void);

对应的 C# 绑定代码:

csharp 复制代码
using System.Runtime.InteropServices;

public static partial class NativeLib
{
    private const string LibName = "demo"; // 对应 libdemo.so
    
    [DllImport(LibName, EntryPoint = "add", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Add(int a, int b);
    
    [DllImport(LibName, EntryPoint = "get_version", CallingConvention = CallingConvention.Cdecl)]
    private static extern IntPtr GetVersionInternal();
    
    // 封装 Marshal 转换,提供类型安全的公开接口
    public static string GetVersion()
    {
        IntPtr ptr = GetVersionInternal();
        return Marshal.PtrToStringUTF8(ptr) ?? string.Empty;
    }
}

注意要点

  • EntryPoint 必须与 C 函数签名完全匹配(区分大小写)
  • 字符串/指针类型需手动 Marshal 转换
  • CallingConvention 需与编译 SO 时的设置一致(通常为 Cdecl

三、在 MAUI 中调用

csharp 复制代码
public partial class MainPage : ContentPage
{
    private void OnInvokeClicked(object sender, EventArgs e)
    {
        try 
        {
            int result = NativeLib.Add(10, 20);
            string version = NativeLib.GetVersion();
            
            ResultLabel.Text = $"计算结果: {result}\n版本: {version}";
        }
        catch (DllNotFoundException ex)
        {
            ResultLabel.Text = $"SO 库加载失败: {ex.Message}";
        }
        catch (EntryPointNotFoundException ex)
        {
            ResultLabel.Text = $"函数未找到: {ex.Message}";
        }
    }
}

四、常见陷阱与解决

问题现象 原因 解决方案
DllNotFoundException SO 文件未打包或架构不匹配 检查 .csproj 配置,确认设备架构与 SO 文件对应
EntryPointNotFoundException 函数名修饰(mangling)或签名不符 使用 extern "C" 导出 C 风格函数,核对大小写
中文/特殊字符乱码 编码不匹配 C 端使用 UTF-8,C# 端使用 Marshal.PtrToStringUTF8
Release 模式崩溃 SO 未随 APK 分发 检查 AndroidPackageFormat 和链接器设置

五、进阶:复杂类型传递

对于结构体或回调函数,需更严谨的互操作定义:

csharp 复制代码
// 传递结构体
[StructLayout(LayoutKind.Sequential)]
public struct Point
{
    public int X;
    public int Y;
}

[DllImport(LibName)]
public static extern double CalculateDistance(Point p1, Point p2);

// 回调函数(需保持委托引用防 GC)
public delegate void ProgressCallback(int percent);

[DllImport(LibName)]
public static extern void ProcessData(byte[] data, int length, ProgressCallback callback);

总结

MAUI 调用 SO 库的核心在于:

  1. 正确放置并配置 SO 文件到项目
  2. 精确声明 DllImport 的函数签名和调用约定
  3. 妥善处理 数据类型的 Marshaling

对于复杂场景,可考虑使用 Native Library Interop 或生成绑定库(如 CppSharp),但简单的 P/Invoke 足以应对大多数需求。

相关推荐
A_nanda1 天前
C#调用Quartz.NET的完整实现。
c#·.net·quartz
1314lay_10071 天前
Vue3 + Element Plus项目和C# .Net 7.0 Core后端API项目发布部署到服务器
服务器·前端·javascript·vue.js·elementui·c#·.net
专注VB编程开发20年1 天前
MC508 PLC支持多线程网络通讯,多客户端连接,要注意不要同时读写(麦格米特PLC)
服务器·网络·windows·.net·plc·mc508·麦格米特
步步为营DotNet2 天前
深度探秘.NET中的IAsyncEnumerable:异步迭代的底层奥秘与高效实践
java·数据库·.net
波波0072 天前
每日一题:.NET中volatile解决了什么问题
开发语言·.net
时光追逐者2 天前
C#/.NET/.NET Core优秀项目和框架2026年1月简报
c#·.net·.netcore
时光追逐者2 天前
一款开源、强大、简单易用的 .NET 假数据生成利器
开源·c#·.net·.net core
用户298698530142 天前
C#: 对文字段落或文本施加阴影?Spire.Doc for .NET 助你一臂之力
后端·c#·.net
小码编匠2 天前
WPF 动态生成行列可变表格的实现方法
后端·c#·.net
追逐时光者3 天前
一款开源、强大、简单易用的 .NET 假数据生成利器
后端·.net