Assuming the function something like GetSongInfo(int songID, LPInfostring songinfo), you can define the struct as having an array of IntPtr. However, you will have to watch out for memory leaks as the calling function may expect you to free the memory allocated for the strings returned.
target.h:
typedef struct SongInfo
{
char* infoString[10];
} *LPSongInfo;
extern "C" __declspec(dllexport) int GetSongInfo(int songID, LPSongInfo info);
target.c:
extern "C" __declspec(dllexport) int GetSongInfo(int songID, LPSongInfo demo)
{
demo->infoString[0] = "Hello world";
demo->infoString[1] = "Hello 1";
demo->infoString[2] = "Hello 2";
return TRUE;
}
P/Invoke Signature:
[DllImport("PInvokeDll.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int GetSongInfo(int songID, out SongInfo ts);
[StructLayout(LayoutKind.Sequential)]
struct SongInfo
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public IntPtr[] infoString;
};
Example Use:
SongInfo siStruct;
var num2 = GetSongInfo(101, out siStruct);
// copy the results to managed memory
var results = new string[10];
for (int i = 0; i < 10; i++)
{
if (siStruct.infoString[i] != IntPtr.Zero)
{
// if these were Unicode strings, this would change to PtrToSTringUni
results[i] = Marshal.PtrToStringAnsi(siStruct.infoString[i]);
}
}
// results now holds the .Net strings
// if there is an expectation of the caller to free the struct
// strings, that should happen now
As an alternative for functions which do not allocate memory, you can use a struct like the following to marshal the strings automatically. However, it will free the unmanaged memory unconditionally, which may or may not be desirable.
[StructLayout(LayoutKind.Sequential)]
struct SongInfo2
{
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPStr, SizeConst = 10)]
public string[] infoString;
};