xamarin android C#调用so库方法
xamarin android是使用的C#语言,而C#中通过P/Invoke调用一个使用C++编写的.so库方法和java android中使用JNI去调用.so库方法是不一样的,写对应的.so库也不能去使用JNI的方式去写
现在针对不同类型的返回值来进行cpp文件的书写,并且需要在C#端进行适当的声明和内存管理
1.返回值为char* 类型的函数
这类函数通常用于C#中返回字符串数据
C++代码
假设有一个返回加密结果的函数:
其中的AES是一个加密算法
extern "C" {
char* EncryptFromJNI(const char* raw, const char* key) {
if (raw == nullptr || key == nullptr) return nullptr;
AES aes_en((unsigned char*)key);
char encrypt[1024] = {0};
aes_en.Cipher((char*)raw, encrypt); // 假设Cipher可以处理直接的char*输入输出
LOGI("Encrypt result: %s", encrypt);
return strdup(encrypt); // 返回动态分配的字符串,需要在C#端释放
}
}
C#代码
在C#中声明P/Invoke并调用该函数:
其中as_jni_project是对应上面cpp代码生成的.so库,去除了前缀lib和后缀.so
public class JNI_Wrapper
{
static JNI_Wrapper()
{
Java.Lang.JavaSystem.LoadLibrary("as_jni_project");
}
public const string DLLName = "as_jni_project";
[DllImport(DLLName, EntryPoint = "EncryptFromJNI")]
public static extern IntPtr EncryptFromJNI(string raw,string key);
[DllImport(DLLName, EntryPoint = "DecryptFromJNI")]
public static extern IntPtr DecryptFromJNI(string res, string key);
public static string Encrypt(string raw,string key)
{
IntPtr ptr = EncryptFromJNI(raw, key);
if (ptr == IntPtr.Zero) return null; // 检查空指针
try
{
string result = Marshal.PtrToStringAnsi(ptr); // 将指针转换为C#字符串
return result;
}
finally
{
Marshal.FreeCoTaskMem(ptr); // 或者使用Marshal.FreeHGlobal,取决于C++侧如何分配内存
}
}
}
PS:如果在C++中使用malloc分配内存,则应使用Marshal.FreeHGlobal来释放内存;如果使用的是strdup或类似的方式,通常也需要使用Marshal.FreeHGlobal。
2.返回值为数组类型的函数
对于返回数组可以通过指针返回数组的首地址。
C++代码
假设有一个返回整数数组的函数:
extern "C" {
int* GetIntArray(int size) {
int* arr = (int*)malloc(size * sizeof(int));
for(int i = 0; i < size; ++i) {
arr[i] = i;
}
return arr; // 注意:调用者需负责释放这块内存
}
}
C#代码
在C#中声明P/Invoke并调用该函数:
public class JNI_Wrapper
{
[DllImport("as_jni_project", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetIntArray(int size);
public static int[] GetArray(int size)
{
IntPtr ptr = GetIntArray(size);
if (ptr == IntPtr.Zero) return null;
try
{
int[] array = new int[size];
Marshal.Copy(ptr, array, 0, size); // 将非托管内存复制到托管数组
return array;
}
finally
{
Marshal.FreeHGlobal(ptr); // 释放由C++分配的内存
}
}
}
// 使用示例
class Program
{
static void Main()
{
int[] array = JNI_Wrapper.GetArray(5);
foreach(var item in array)
{
Console.WriteLine(item);
}
}
}
假设返回byte[]类型:
C++代码
unsigned关键字在C/C++中用于指定整型变量或类型为无符号(即只能表示非负数值)
1. 数据范围:char类型在C/C++中可能是有符号的也可能是无符号的,这取决于编译器和平台。当您需要确保字符值的范围是0到255(而不是-128到127)时,应该使用unsigned char。这是因为字节(byte)通常用来表示图像、音频等二进制数据,这些数据不应被解释为负数。
2. 特定用途:在这个函数中,您正在生成一个字节数组(byte array),每个元素代表一个字节(8位)。由于字节的取值范围是从0到255,使用unsigned char可以明确地表达这一点,并避免潜在的误解或错误。
extern "C" {
unsigned char* GetByteArray(int size) {
unsigned char* arr = (unsigned char*)malloc(size * sizeof(unsigned char));
for(int i = 0; i < size; ++i) {
arr[i] = i % 256; // 填充一些示例数据
}
return arr; // 注意:调用者需负责释放这块内存
}
}
C#代码:
在C#中声明P/Invoke并调用该函数:
public class JNI_Wrapper
{
[DllImport("as_jni_project", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetByteArray(int size);
public static byte[] GetByteArray(int size)
{
IntPtr ptr = GetByteArray(size);
if (ptr == IntPtr.Zero) return null;
try
{
byte[] array = new byte[size];
Marshal.Copy(ptr, array, 0, size); // 将非托管内存复制到托管数组
return array;
}
finally
{
Marshal.FreeHGlobal(ptr); // 释放由C++分配的内存
}
}
}
// 使用示例
class Program
{
static void Main()
{
byte[] byteArray = JNI_Wrapper.GetByteArray(10);
foreach(var item in byteArray)
{
Console.WriteLine(item);
}
}
}
假设返回string[]类型:
对于字符串数组的情况稍微复杂一些,因为直接返回一个指针数组(即char**)可能不是最简单的方法。一种常见的方法是使用结构体或通过传递缓冲区的方式来实现
C++代码:
typedef struct {
int count;
char* strings[10]; // 假设最多有10个字符串
} StringArray;
extern "C" {
StringArray GetStringArray() {
StringArray sa = {0};
const char* sampleStrings[] = {"Hello", "World", "From", "C++", "JNI"};
sa.count = 5; // 设置实际的字符串数量
for(int i = 0; i < sa.count; ++i) {
sa.strings[i] = strdup(sampleStrings[i]); // 动态分配每个字符串
}
return sa;
}
}
C#代码:
在C#中声明P/Invoke并调用该函数:
使用了[StructLayout(LayoutKind.Sequential)]来确保C#结构体成员的内存布局顺序与非托管代码中的结构体保持一致。此外,还使用了[MarshalAs]特性指定了数组的大小,这对于正确映射到非托管代码中的固定大小数组非常重要
[StructLayout(LayoutKind.Sequential)]
public struct StringArray
{
public int Count;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public IntPtr[] Strings;
}
public class JNI_Wrapper
{
[DllImport("as_jni_project", CallingConvention = CallingConvention.Cdecl)]
private static extern StringArray GetStringArray();
public static string[] GetStringArray()
{
StringArray sa = GetStringArray();
string[] result = new string[sa.Count];
for(int i = 0; i < sa.Count; ++i)
{
result[i] = Marshal.PtrToStringAnsi(sa.Strings[i]);
Marshal.FreeCoTaskMem(sa.Strings[i]); // 或者使用Marshal.FreeHGlobal,取决于C++侧如何分配内存
}
return result;
}
}
// 使用示例
class Program
{
static void Main()
{
string[] stringArray = JNI_Wrapper.GetStringArray();
foreach(var item in stringArray)
{
Console.WriteLine(item);
}
}
}
3.返回结构体类型的函数
需要返回复杂的结构数据时,可以使用结构体
C++代码
定义并实现一个返回结构体的函数:
typedef struct {
int id;
char name[50];
} Person;
extern "C" {
Person CreatePerson(int id, const char* name) {
Person person = {id, ""};
strncpy(person.name, name, 49); // 确保不会超出边界
person.name[49] = '\0'; // 确保字符串以null终止
return person;
}
}
C#代码
在C#中声明P/Invoke并调用该函数:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]:这个属性用于指定字符串字段Name应被视为长度为50的固定大小字符数组,并且该字符串将以ANSI编码方式进行封送处理。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Person
{
public int Id;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public string Name;
}
public class JNI_Wrapper
{
[DllImport("as_jni_project", CallingConvention = CallingConvention.Cdecl)]
private static extern Person CreatePerson(int id, string name);
public static Person CreatePersonWrapper(int id, string name)
{
return CreatePerson(id, name);
}
}
// 使用示例
class Program
{
static void Main()
{
var person = JNI_Wrapper.CreatePersonWrapper(1, "John Doe");
Console.WriteLine($"ID: {person.Id}, Name: {person.Name}");
}
}
更多推荐


所有评论(0)