使用 PInvoke char** (C -> C#) 更正 return 值

Correct return value with PInvoke char** (C -> C#)

我的问题是我正在使用 C# 中的 MATLAB API,这是给我带来麻烦的函数。

C代码:

EXTERN_C char ** matGetDir(MATFile * pMF, int *num); 

我希望它能工作,但不幸的是它没有(C# 代码):

[DllImport("libmat.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern string[] matGetDir(IntPtr matFile, ref int num); 

如果我用 IntPtr 而不是 string[] 我可以调用函数,但是我不知道如何将代码从 IntPtr 转换为 string[]

编辑 1:
我也尝试使用这些属性,但它失败并出现错误:无法封送 'return value':无效的 managed/unmanaged 类型组合。

[return: MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)]
private static extern string[] matGetDir(IntPtr matFile, ref int num);

你可以这样做:

[DllImport("libmx.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void mxFree(IntPtr ptr);

[DllImport("libmat.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]        
private static extern IntPtr matGetDir(IntPtr matFile, ref int num);

public static string[] matGetDir(IntPtr matFile)
{
    // Obtain as IntPtr
    var count = 0;
    var pointers = matGetDir(matFile, ref count);

    // Handling errors as noticed by David Heffernan
    if (count < 0) { throw new Exception("Failed to obtain list of variables in selected mat file."); }
    if (pointers == IntPtr.Zero) { return new string[0]; }

    // Cast into IntPtr[]
    var ptrs = new IntPtr[count];
    Marshal.Copy(pointers, ptrs, 0, count);

    // Convert each value in IntPtr[] into string
    // NB: using System.Linq;
    var strs = ptrs.Select(x => Marshal.PtrToStringAnsi(x)).ToArray();

    // Don't forget to free memory allocated by Matlab
    // NB: Deleting global pointer only ==> see "edit([matlabroot '/extern/examples/eng_mat/matdgns.c']);" example 
    mxFree(pointers);

    // And voilà
    return strs;
}

因此,只需像您最初尝试的那样在私有方法中转换为 IntPtr,然后添加一个 public 方法以转换为 string[]。有关详细信息,请参阅代码中的注释。

您无法告诉 p/invoke 编组器如何为您编组它。你需要手动完成。关于您的问题,有趣的一件事是您没有提供任何有关此 char** 真正含义的详细信息。了解类型并没有完全定义参数的语义对您来说非常重要,或者在这种情况下是 return 值。

我们很容易猜到,但最好在文档中查找:http://uk.mathworks.com/help/matlab/apiref/matgetdir.html

Arguments

mfp

Pointer to MAT-file information

num

Pointer to the variable containing the number of mxArrays in the MAT-file

Returns

Pointer to an internal array containing pointers to the names of the mxArrays in the MAT-file pointed to by mfp. In C, each name is a NULL-terminated string. The num output argument is the length of the internal array (number of mxArrays in the MAT-file). If num is zero, mfp contains no arrays.

matGetDir returns NULL in C (0 in Fortran). If matGetDir fails, sets num to a negative number.

所以,我们这样声明 p/invoke:

[DllImport("libmat.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr matGetDir(IntPtr matFile, out int num); 

这样称呼它:

int num;
IntPtr matFile = ...;
IntPtr namesPtr = matGetDir(mayFile, out num);
if (names == IntPtr.Zero)
    // handle error
string[] names = new string[num];
for (int i = 0; i < num; i++)
{
    int offset = i * Marshal.SizeOf(typeof(IntPtr));
    names[i] = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(namesPtr, offset));
}