feat: update BMapSharp marshaler
This commit is contained in:
File diff suppressed because it is too large
Load Diff
303
Assets/BMapBindings/BMapSharp/BMapSharp/BMapMarshalers.cs
Normal file
303
Assets/BMapBindings/BMapSharp/BMapSharp/BMapMarshalers.cs
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace BMapSharp.BMapMarshalers {
|
||||||
|
|
||||||
|
// References:
|
||||||
|
// https://stackoverflow.com/questions/18498452/how-do-i-write-a-custom-marshaler-which-allows-data-to-flow-from-native-to-manag
|
||||||
|
// https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-runtime-interopservices-icustommarshaler
|
||||||
|
//
|
||||||
|
// NOTE: I do not create a member to store the object we are marshaling.
|
||||||
|
// Because my binding do not have In, Out parameter. All parameters are In OR Out.
|
||||||
|
// So there is no reason to keep that member.
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// When receiving UTF8 string pointer given by BMap as managed string,
|
||||||
|
// I don't know why Microsoft try to call ICustomMarshaler.CleanUpNativeData without calling ICustomMarshaler.MarshalManagedToNative.
|
||||||
|
// It is trying to free the pointer managed by LibCmo self (for example, it will try to free we got string when getting object name)!
|
||||||
|
// So as the compromise, we introduce 2 different marshalers for In / Out string marshaling respectively.
|
||||||
|
// BMStringMarshaler for receiving string from BMap (OUT direction), and BMPOwnedStringMarshaler for passing string to BMap (IN direction).
|
||||||
|
// The name of marshaler for string array marshaling also following this pattern.
|
||||||
|
|
||||||
|
public class BMStringMarshaler : ICustomMarshaler {
|
||||||
|
private static readonly BMStringMarshaler INSTANCE = new BMStringMarshaler();
|
||||||
|
|
||||||
|
public static ICustomMarshaler GetInstance(string pstrCookie) {
|
||||||
|
return BMStringMarshaler.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr MarshalManagedToNative(object ManagedObj) {
|
||||||
|
// For OUT direction, we do not convert any managed data into native data.
|
||||||
|
// Return nullptr instead.
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object MarshalNativeToManaged(IntPtr pNativeData) {
|
||||||
|
// Check nullptr
|
||||||
|
if (pNativeData == IntPtr.Zero) return null;
|
||||||
|
// Call self
|
||||||
|
return BMStringMarshaler.ToManaged(pNativeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUpNativeData(IntPtr pNativeData) {
|
||||||
|
// For OUT direction, we do not convert any managed data into native data.
|
||||||
|
// Do nothing here.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUpManagedData(object ManagedObj) {
|
||||||
|
// Managed data will be cleaned by C# GC.
|
||||||
|
// So we do nothing here.
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetNativeDataSize() {
|
||||||
|
// Return -1 to indicate the size of the native data to be marshaled is variable.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the length in byte of given pointer represented C style string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">The pointer for checking.</param>
|
||||||
|
/// <returns>The length of C style string (NUL exclusive).</returns>
|
||||||
|
internal static int GetCStringLength(IntPtr ptr) {
|
||||||
|
int count = 0, unit = Marshal.SizeOf<byte>();
|
||||||
|
while (Marshal.ReadByte(ptr) != (byte)0) {
|
||||||
|
ptr += unit;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract managed string from given native pointer holding C style string data.
|
||||||
|
/// This function is shared by 2 marshalers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">Native pointer holding string data. Caller must make sure this pointer is not nullptr.</param>
|
||||||
|
/// <returns>The extracted managed string data.</returns>
|
||||||
|
internal static string ToManaged(IntPtr ptr) {
|
||||||
|
// Get the length of given string.
|
||||||
|
int szStringItemCount = BMStringMarshaler.GetCStringLength(ptr);
|
||||||
|
int szStringItemSize = Marshal.SizeOf<byte>();
|
||||||
|
// Prepare cache and copy string data
|
||||||
|
byte[] encString = new byte[szStringItemCount];
|
||||||
|
Marshal.Copy(ptr, encString, 0, szStringItemCount);
|
||||||
|
// Decode string and return
|
||||||
|
return Encoding.UTF8.GetString(encString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BMOwnedStringMarshaler : ICustomMarshaler {
|
||||||
|
private static readonly BMOwnedStringMarshaler INSTANCE = new BMOwnedStringMarshaler();
|
||||||
|
|
||||||
|
public static ICustomMarshaler GetInstance(string pstrCookie) {
|
||||||
|
return BMOwnedStringMarshaler.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr MarshalManagedToNative(object ManagedObj) {
|
||||||
|
// Check requirements.
|
||||||
|
if (ManagedObj is null) return IntPtr.Zero;
|
||||||
|
string castManagedObj = ManagedObj as string;
|
||||||
|
if (castManagedObj is null)
|
||||||
|
throw new MarshalDirectiveException("BMStringMarshaler must be used on a string.");
|
||||||
|
// Call self
|
||||||
|
return BMOwnedStringMarshaler.ToNative(castManagedObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object MarshalNativeToManaged(IntPtr pNativeData) {
|
||||||
|
// For IN direction, we do not convert any native data into managed data.
|
||||||
|
// Return null instead.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUpNativeData(IntPtr pNativeData) {
|
||||||
|
// Check nullptr
|
||||||
|
if (pNativeData == IntPtr.Zero) return;
|
||||||
|
// Free native pointer
|
||||||
|
Marshal.FreeHGlobal(pNativeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUpManagedData(object ManagedObj) {
|
||||||
|
// For IN direction, we do not convert any native data into managed data.
|
||||||
|
// Do nothing here.
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetNativeDataSize() {
|
||||||
|
// Return -1 to indicate the size of the native data to be marshaled is variable.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert given string object to native data.
|
||||||
|
/// This function is shared by 2 marshalers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">String object. Caller must make sure this object is not null.</param>
|
||||||
|
/// <returns>The created native data pointer.</returns>
|
||||||
|
internal static IntPtr ToNative(string obj) {
|
||||||
|
// Encode string first
|
||||||
|
byte[] encString = Encoding.UTF8.GetBytes(obj);
|
||||||
|
// Allocate string memory with extra NUL.
|
||||||
|
int szStringItemCount = encString.Length;
|
||||||
|
int szStringItemSize = Marshal.SizeOf<byte>();
|
||||||
|
IntPtr pString = Marshal.AllocHGlobal(szStringItemSize * (szStringItemCount + 1));
|
||||||
|
// Copy encoded string data
|
||||||
|
Marshal.Copy(encString, 0, pString, szStringItemCount);
|
||||||
|
// Setup NUL
|
||||||
|
Marshal.WriteByte(pString + (szStringItemSize * szStringItemCount), (byte)0);
|
||||||
|
// Return value
|
||||||
|
return pString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// For respecting the standard of BMap,
|
||||||
|
// the native memory we created for string array is a simple array and each item is a pointer to a NULL-terminated UTF8 string.
|
||||||
|
// Please note the array self is also NULL-terminated otherwise we don't know its length.
|
||||||
|
|
||||||
|
public class BMStringArrayMarshaler : ICustomMarshaler {
|
||||||
|
private static readonly BMStringArrayMarshaler INSTANCE = new BMStringArrayMarshaler();
|
||||||
|
|
||||||
|
public static ICustomMarshaler GetInstance(string pstrCookie) {
|
||||||
|
return BMStringArrayMarshaler.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr MarshalManagedToNative(object ManagedObj) {
|
||||||
|
// For OUT direction, we do not convert any managed data into native data.
|
||||||
|
// Return nullptr instead.
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object MarshalNativeToManaged(IntPtr pNativeData) {
|
||||||
|
// Check nullptr
|
||||||
|
if (pNativeData == IntPtr.Zero) return null;
|
||||||
|
|
||||||
|
// Get the length of array
|
||||||
|
int szArrayItemCount = BMStringArrayMarshaler.GetArrayLength(pNativeData);
|
||||||
|
int szArrayItemSize = Marshal.SizeOf<IntPtr>();
|
||||||
|
// Prepare array cache and read it.
|
||||||
|
IntPtr[] apString = new IntPtr[szArrayItemCount];
|
||||||
|
Marshal.Copy(pNativeData, apString, 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 pString = apString[i];
|
||||||
|
if (pString == IntPtr.Zero) {
|
||||||
|
ret[i] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Extract string
|
||||||
|
ret[i] = BMStringMarshaler.ToManaged(pString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return result
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUpNativeData(IntPtr pNativeData) {
|
||||||
|
// For OUT direction, we do not convert any managed data into native data.
|
||||||
|
// Do nothing here.
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUpManagedData(object ManagedObj) {
|
||||||
|
// Managed data will be cleaned by C# GC.
|
||||||
|
// So we do nothing here.
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetNativeDataSize() {
|
||||||
|
// Return -1 to indicate the size of the native data to be marshaled is variable.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the length of array created by this marshaler.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">The pointer to array for checking.</param>
|
||||||
|
/// <returns>The length of array (NULL terminal exclusive).</returns>
|
||||||
|
internal static int GetArrayLength(IntPtr ptr) {
|
||||||
|
int count = 0, unit = Marshal.SizeOf<IntPtr>();
|
||||||
|
while (Marshal.ReadIntPtr(ptr) != IntPtr.Zero) {
|
||||||
|
ptr += unit;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BMOwnedStringArrayMarshaler : ICustomMarshaler {
|
||||||
|
private static readonly BMOwnedStringArrayMarshaler INSTANCE = new BMOwnedStringArrayMarshaler();
|
||||||
|
|
||||||
|
public static ICustomMarshaler GetInstance(string pstrCookie) {
|
||||||
|
return BMOwnedStringArrayMarshaler.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr MarshalManagedToNative(object ManagedObj) {
|
||||||
|
// Check nullptr object.
|
||||||
|
if (ManagedObj is null) return IntPtr.Zero;
|
||||||
|
// Check argument type.
|
||||||
|
string[] castManagedObj = ManagedObj as string[];
|
||||||
|
if (castManagedObj is null)
|
||||||
|
throw new MarshalDirectiveException("BMStringArrayMashaler must be used on an string array.");
|
||||||
|
|
||||||
|
// Allocate string items first
|
||||||
|
int szArrayItemCount = castManagedObj.Length;
|
||||||
|
int szArrayItemSize = Marshal.SizeOf<IntPtr>();
|
||||||
|
IntPtr[] apString = new IntPtr[szArrayItemCount];
|
||||||
|
for (int i = 0; i < szArrayItemCount; ++i) {
|
||||||
|
// Check null string
|
||||||
|
string stringObj = castManagedObj[i];
|
||||||
|
if (stringObj is null) apString[i] = IntPtr.Zero;
|
||||||
|
else apString[i] = BMOwnedStringMarshaler.ToNative(stringObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate array pointer now.
|
||||||
|
IntPtr pArray = Marshal.AllocHGlobal(szArrayItemSize * (szArrayItemCount + 1));
|
||||||
|
// Copy string pointer data
|
||||||
|
Marshal.Copy(apString, 0, pArray, szArrayItemCount);
|
||||||
|
// Setup NULL ternimal
|
||||||
|
Marshal.WriteIntPtr(pArray + (szArrayItemSize * szArrayItemCount), IntPtr.Zero);
|
||||||
|
|
||||||
|
// Return value
|
||||||
|
return pArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object MarshalNativeToManaged(IntPtr pNativeData) {
|
||||||
|
// For IN direction, we do not convert any native data into managed data.
|
||||||
|
// Return null instead.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUpNativeData(IntPtr pNativeData) {
|
||||||
|
// Check nullptr
|
||||||
|
if (pNativeData == IntPtr.Zero) return;
|
||||||
|
|
||||||
|
// Get the length of array
|
||||||
|
int szArrayItemCount = BMStringArrayMarshaler.GetArrayLength(pNativeData);
|
||||||
|
int szArrayItemSize = Marshal.SizeOf<IntPtr>();
|
||||||
|
// Prepare array cache and read it.
|
||||||
|
IntPtr[] apString = new IntPtr[szArrayItemCount];
|
||||||
|
Marshal.Copy(pNativeData, apString, 0, szArrayItemCount);
|
||||||
|
// Free array self
|
||||||
|
Marshal.FreeHGlobal(pNativeData);
|
||||||
|
|
||||||
|
// Iterate the string pointer array and free them one by one.
|
||||||
|
foreach (IntPtr pString in apString) {
|
||||||
|
// Free string pointer
|
||||||
|
if (pString == IntPtr.Zero) continue;
|
||||||
|
Marshal.FreeHGlobal(pString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanUpManagedData(object ManagedObj) {
|
||||||
|
// For IN direction, we do not convert any native data into managed data.
|
||||||
|
// Do nothing here.
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetNativeDataSize() {
|
||||||
|
// Return -1 to indicate the size of the native data to be marshaled is variable.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,20 @@ using BMapSharp.VirtoolsTypes;
|
|||||||
|
|
||||||
namespace BMapSharp.BMapWrapper {
|
namespace BMapSharp.BMapWrapper {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BMapSharp module specific exception.
|
||||||
|
/// </summary>
|
||||||
|
public class BMapException : Exception {
|
||||||
|
public BMapException() { }
|
||||||
|
public BMapException(string message)
|
||||||
|
: base(message) { }
|
||||||
|
public BMapException(string message, Exception inner)
|
||||||
|
: base(message, inner) { }
|
||||||
|
public static void ThrowIfFailed(bool condition) {
|
||||||
|
if (!condition) throw new BMapException("BMap operation failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The guard of native BMap environment.
|
/// The guard of native BMap environment.
|
||||||
/// This class initialize native BMap environment when constructing and free it when destructing.
|
/// This class initialize native BMap environment when constructing and free it when destructing.
|
||||||
|
|||||||
Reference in New Issue
Block a user