2015-10-26 58 views
1

我的問題是,我正在使用C#的MATLAB API,這是給我麻煩的功能。正確的返回值與PInvoke char **(C - > C#)

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: MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] 
private static extern string[] matGetDir(IntPtr matFile, ref int num); 
+0

考慮到C中的'char'是一個字節,而C#中的'char'是兩個字節。 –

+0

在這裏可以找到更多的信息:[鏈接](http://www.codeproject.com/Articles/17450/Marshal-an-Array-of-Zero-Terminated-Strings-or-Str) –

回答

2

你不能告訴p/invoke marshaller如何爲你編組這個。你需要手動完成。關於你的問題有一點很有意思,那就是你沒有給出這個char**真的是什麼細節。瞭解類型沒有完全定義參數的語義,或者在這種情況下是返回值,這對你來說非常重要。

我們可以猜測很輕鬆了,但最好是在文檔中查找這件事: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)); 
} 
+0

是的......與我的答案+錯誤檢查相同 – CitizenInsane

+1

@CitizenInsane是的。關於文檔和參數語義的觀點很重要。作爲回答很多互動問題的人,這是跨越最大的一點。 'char **'可以是很多不同的東西,所以你總是要定義它的含義。雖然我喜歡你使用LINQ。很可愛。我一定會記下這一點! –

+0

我修改了我的答案中的代碼以處理特殊情況+添加了對'mxFree' *的調用(matGetDir使用mxCalloc爲內部字符串數組分配內存,當您完成數組時,使用mxFree釋放內存。)* – CitizenInsane

1

你可以這樣來做:

[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在一個私有方法,你initally嘗試,然後添加要轉換的公共方法string[]。查看代碼中的註釋以獲取更多詳細信息。

+0

謝謝你們的答案。但是我選擇了另一個,因爲它更好地解釋了。但我也會使用你提到的mxFree。 – Renesis