文章目录
-
- 前言
- [一、准备 SO 库](#一、准备 SO 库)
- [二、C# 绑定声明](# 绑定声明)
- [三、在 MAUI 中调用](#三、在 MAUI 中调用)
- 四、常见陷阱与解决
- 五、进阶:复杂类型传递
- 总结
前言
.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 库的核心在于:
- 正确放置并配置 SO 文件到项目
- 精确声明
DllImport的函数签名和调用约定 - 妥善处理 数据类型的 Marshaling
对于复杂场景,可考虑使用 Native Library Interop 或生成绑定库(如 CppSharp),但简单的 P/Invoke 足以应对大多数需求。