It is possible to embed arrays in a managed struct that you want to pass into a C++ plugin.
This structure
[StructLayout(LayoutKind.Sequential)]
public struct ChatChannelMessageEntry
{
public uint chatMessageIndex;
public uint chatUserID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string chatUserDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string chatTimestamp;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 140)]
public string chatMessage;
}
[StructLayout(LayoutKind.Sequential)]
public struct ChatChannelMessageList
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public ChatChannelMessageEntry[] entries;
};
matches this in the plugin
struct ChatChannelMessageEntry
{
unsigned int chatMessageIndex;
unsigned int chatUserID;
char chatUserDisplayName[32];
char chatTimestamp[32];
char chatMessage[140];
};
struct ChatChannelMessageList
{
ChatChannelMessageEntry entries[32];
};
Declaring a plugin function in a script looks like this
[DllImport(DLL_NAME)]
public static extern uint NetworkGetChatChannelMessageList( uint channelID,
uint firstMessageIndex, uint maxMessageCount,
IntPtr pData, int maxDataSize);
The IntPtr pData parameter references the buffer where your struct lives.
And in the plugin
DLL_EXPORT
unsigned int NetworkGetChatChannelMessageList( unsigned int channelID,
unsigned int firstMessageIndex, unsigned int maxMessageCount,
unsigned char* pBuffer, int bufLen )
{
ChatChannelMessageList *pOutputMessageList = (ChatChannelMessageList*)pBuffer;
.
.
.
}
Calling the function from a script looks like this
byte[] dataBuffer = new byte[k_maxDataSize];
GCHandle hDataBuffer = GCHandle.Alloc( dataBuffer, GCHandleType.Pinned );
IntPtr bufPtr = hDataBuffer.AddrOfPinnedObject();
uint chat_count = NetworkGetChatChannelMessageList(m_ChatChannelID, m_LastChatMessageIndex+1, 32, bufPtr, k_maxDataSize);
ChatChannelMessageList chat_info = (ChatChannelMessageList)Marshal.PtrToStructure( bufPtr, typeof(ChatChannelMessageList) );
for( uint c = 0; c < chat_count; ++c )
{
}
hDataBuffer.Free();
I’m not sure if pinning the memory is necessary, but that’s what works for us. We use this method to pass data back and forth between C# and C++ without any noticeable performance hit. In the case I’ve shown here the memory for the buffer is allocated and then released in the same function, but there are plenty of cases where we hold a reference to a buffer for the lifetime of the application to avoid memory allocation on a per-frame basis.