【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 足以应对大多数需求。

相关推荐
mudtools18 小时前
一份声明,多份产物:飞书 SDK 的 AI 工具面是怎么生成的
人工智能·.net·ai编程
云中飞鸿18 小时前
.Net和C#有什么关系?(从C++视角观察)
开发语言·c#·.net
2401_885885042 天前
国际语音C#接口怎么写?.NET环境下实现国际语音验证码批量下发
开发语言·c#·.net
1-1=02 天前
.NET Core 本地化 (L10n)
.net·.netcore
1-1=02 天前
.NET Core 健康检查
.net·.netcore·1024程序员节
波力海苔夹心脆6752 天前
C# string 字符串方法大全:判空、查找、截取、分割、替换一篇讲透
经验分享·c#·.net·字符串·string·入门教程
小羊没烦恼!2 天前
.NET无侵入自动化探针原理和主流实现
运维·开发语言·javascript·python·自动化·.net·1024程序员节
IT规划师2 天前
MQTT实战 - 遗嘱消息+心跳机制,让你的设备永远不会“假在线”(七)
物联网·.net
公子小六3 天前
基于.NET的Windows窗体编程之WinForms自定义控件(3)
windows·c#·.net·音视频·winforms