Reputation: 899
My problem is that I'm using the MATLAB API from C# and this is the function that is giving me trouble.
C code:
EXTERN_C char ** matGetDir(MATFile * pMF, int *num);
I expected this to work, but unfortunately it doesn't (C# code):
[DllImport("libmat.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern string[] matGetDir(IntPtr matFile, ref int num);
If I put IntPtr
instead of string[]
I can call the function, but then I don't know how to convert the code from IntPtr
to string[]
Edit1:
I also tried using these attributes, but it fails with error: Cannot marshal 'return value': Invalid managed/unmanaged type combination.
[return: MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)]
private static extern string[] matGetDir(IntPtr matFile, ref int num);
Upvotes: 1
Views: 485
Reputation: 4855
You can do it this way:
[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;
}
So just making conversion into IntPtr
in a private method as you initally tried and then adding a public method for conversion to string[]
. See comments in the code for more details.
Upvotes: 1
Reputation: 613063
You cannot tell the p/invoke marshaller how to marshal this for you. You need to do it manually. One thing that is interesting about your question is that you don't give any details of what this char**
really is. It's really important for you to learn that a type does not fully define the semantics of a parameter, or in this case a return value.
We can guess easily enough, but it is better to look this up in the documentation: 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.
So, we declare the p/invoke like this:
[DllImport("libmat.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr matGetDir(IntPtr matFile, out int num);
Call it like this:
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));
}
Upvotes: 2