feat: finish c sharp custom marshaler.

- Finish BMapStringArrayMarshaler but not test.
This commit is contained in:
yyc12345 2024-09-21 22:01:25 +08:00
parent 7c88b3614a
commit 5e5eed03f5
2 changed files with 89 additions and 47 deletions

View File

@ -27,81 +27,122 @@ namespace BMapSharp {
return g_Instance; return g_Instance;
} }
public IntPtr MarshalManagedToNative(object ManagedObj) { // For respecting the standard of BMap,
if (ManagedObj is null) return IntPtr.Zero; // the native memory we created is a simple array and each item is a pointer to a NULL-terminated UTF8 string.
// Please note the array self is not NULL-terminated because its length is provided by another argument in function calling.
// However, this memory layout is not good for our marshaling.
// We can not know the size of array we created. Because we need iterate it when freeing or fetching data.
// We also can not know the size of string we created because we need read them when parsing them to C# string.
//
// So the solution we made is adding an uint32_t header before the array to indicate the size of array.
// And also add an uint32_t header for each string to indicate the length of string (in bytes, NULL exclusive).
// So the pointer put in array is not the address we allocated, it has an offset.
// Also we return native pointer is not the address we allocated, it also has an offset.
private static readonly int szLengthHeaderSize = Marshal.SizeOf<int>();
private static readonly int szArrayItemSize = Marshal.SizeOf<IntPtr>();
private static readonly int szStringItemSize = Marshal.SizeOf<byte>();
public IntPtr MarshalManagedToNative(object ManagedObj) {
// Check nullptr object.
if (ManagedObj is null) return IntPtr.Zero;
// Check argument type.
string[] castManagedObj = ManagedObj as string[]; string[] castManagedObj = ManagedObj as string[];
if (castManagedObj is null) if (castManagedObj is null)
throw new MarshalDirectiveException("BMStringArrayMashaler must be used on an string array."); throw new MarshalDirectiveException("BMStringArrayMashaler must be used on an string array.");
// Allocate array pointer first. // Allocate string items first
// We need allocated memory with extra item and set it to zero later. int szArrayItemCount = castManagedObj.Length;
// Because we don't have any idea to check the length of this array when free it. IntPtr[] apStrings = new IntPtr[szArrayItemCount];
// Although native BMap library do not need this extra NULL terminal. for (int i = 0; i < szArrayItemCount; ++i) {
int szArrayItemSize = Marshal.SizeOf<IntPtr>();
IntPtr pArray = Marshal.AllocHGlobal(szArrayItemSize * (castManagedObj.Length + 1));
// The allocate string data one by one.
for (int i = 0; i < castManagedObj.Length; ++i) {
// Encode string first. // Encode string first.
byte[] encString = Encoding.UTF8.GetBytes(castManagedObj[i]); byte[] encString = Encoding.UTF8.GetBytes(castManagedObj[i]);
// Allocate string memory with extra NULL terminal. // Allocate string memory with extra NULL terminal.
int szStringItemSize = Marshal.SizeOf<byte>(); int szStringItemCount = encString.Length;
IntPtr pString = Marshal.AllocHGlobal(szStringItemSize * (encString.Length + 1)); IntPtr pString = Marshal.AllocHGlobal(szStringItemSize * (szStringItemCount + 1) + szLengthHeaderSize);
// Copy data into it. // Setup length field
Marshal.Copy(encString, 0, pString, szStringItemSize * encString.Length); Marshal.WriteInt32(pString, 0, szStringItemCount);
// Copy string data with offset.
IntPtr pFakeString = pString + szLengthHeaderSize;
Marshal.Copy(encString, 0, pFakeString, szStringItemCount);
// Set NULL terminal. // Set NULL terminal.
Marshal.WriteByte(pString, szStringItemSize * encString.Length, 0); Marshal.WriteByte(pFakeString, szStringItemSize * szStringItemCount, 0);
// Add into pointer array // Set item in string pointer
Marshal.WriteIntPtr(pArray, szArrayItemSize * i, pString); apStrings[i] = pFakeString;
} }
// Write array NULL terminal // Allocate array pointer now.
Marshal.WriteIntPtr(pArray, szArrayItemSize * castManagedObj.Length, IntPtr.Zero); IntPtr pArray = Marshal.AllocHGlobal(szArrayItemSize * szArrayItemCount + szLengthHeaderSize);
// Setup length field
Marshal.WriteInt32(pArray, 0, szArrayItemCount);
// Copy string pointer data with offset.
IntPtr pFakeArray = pArray + szLengthHeaderSize;
Marshal.Copy(apStrings, 0, pFakeArray, szArrayItemCount);
// Return value // Return value
return pArray; return pFakeArray;
} }
public object MarshalNativeToManaged(IntPtr pNativeData) { public object MarshalNativeToManaged(IntPtr pNativeData) {
// Check nullptr
if (pNativeData == IntPtr.Zero) return null; if (pNativeData == IntPtr.Zero) return null;
// Iterate the array until we reach the NULL terminal. // Get real array pointer
// And save all we meet string. IntPtr pFakeArray = pNativeData;
int szArrayItemSize = Marshal.SizeOf<IntPtr>(); IntPtr pArray = pFakeArray - szLengthHeaderSize;
IntPtr pArray = pNativeData;
int index = 0;
while (true) {
// Get the pointer of this string.
IntPtr pString = Marshal.ReadIntPtr(pArray, szArrayItemSize * index);
// If it is NULL terminal, break the while.
if (pString == IntPtr.Zero) break;
// Read string into buffer and // Get the count of array and read string pointers
Marshal.FreeHGlobal(pString); int szArrayItemCount = Marshal.ReadInt32(pArray, 0);
++index; IntPtr[] apStrings = new IntPtr[szArrayItemCount];
Marshal.Copy(pFakeArray, apStrings, 0, szArrayItemCount);
// Iterate the array and process each string one by one.
string[] ret = new string[szArrayItemCount];
for (int i = 0; i < szArrayItemCount; ++i) {
// Get string pointer
IntPtr pFakeString = apStrings[i];
if (pFakeString == IntPtr.Zero) {
ret[i] = null;
continue;
}
IntPtr pString = pFakeString - szLengthHeaderSize;
// Read string length
int szStringItemCount = Marshal.ReadInt32(pString, 0);
// Read string body
byte[] encString = new byte[szStringItemCount];
Marshal.Copy(pFakeString, encString, 0, szStringItemCount);
// Decode string with UTF8
ret[i] = Encoding.UTF8.GetString(encString);
} }
// Return result
return ret;
} }
public void CleanUpNativeData(IntPtr pNativeData) { public void CleanUpNativeData(IntPtr pNativeData) {
// Check nullptr
if (pNativeData == IntPtr.Zero) return; if (pNativeData == IntPtr.Zero) return;
// Iterate the array until we reach the NULL terminal. // Get real array pointer
// And free every string we meet. IntPtr pFakeArray = pNativeData;
int szArrayItemSize = Marshal.SizeOf<IntPtr>(); IntPtr pArray = pFakeArray - szLengthHeaderSize;
IntPtr pArray = pNativeData;
int index = 0; // Get the count of array and read string pointers
while (true) { int szArrayItemCount = Marshal.ReadInt32(pArray, 0);
// Get the pointer of this string. IntPtr[] apStrings = new IntPtr[szArrayItemCount];
IntPtr pString = Marshal.ReadIntPtr(pArray, szArrayItemSize * index); Marshal.Copy(pFakeArray, apStrings, 0, szArrayItemCount);
// If it is NULL terminal, break the while.
if (pString == IntPtr.Zero) break; // Iterate the array and free them one by one.
// Free string and move index to next one. for (int i = 0; i < szArrayItemCount; ++i) {
// Get string pointer
IntPtr pFakeString = apStrings[i];
if (pFakeString == IntPtr.Zero) continue;
IntPtr pString = pFakeString - szLengthHeaderSize;
// Free string pointer
Marshal.FreeHGlobal(pString); Marshal.FreeHGlobal(pString);
++index;
} }
// Now free the array self. // Free array self
Marshal.FreeHGlobal(pArray); Marshal.FreeHGlobal(pArray);
} }

View File

@ -85,6 +85,7 @@ namespace BMapSharp.VirtoolsTypes {
public VxVector4 w; public VxVector4 w;
} }
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
public struct CKFaceIndices { public struct CKFaceIndices {
[MarshalAs(UnmanagedType.U4)] [MarshalAs(UnmanagedType.U4)]
public uint I1; public uint I1;