diff --git a/BMapBindings/BMapSharp/.editorconfig b/BMapBindings/BMapSharp/.editorconfig new file mode 100644 index 0000000..ca51e64 --- /dev/null +++ b/BMapBindings/BMapSharp/.editorconfig @@ -0,0 +1,364 @@ +root = true + +# All files +[*] +indent_style = space + +# Xml files +[*.xml] +indent_size = 2 + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### +[*.{cs,vb}] + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:warning + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +#### C# Coding Conventions #### +[*.cs] + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = false +csharp_new_line_before_else = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = none +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### +[*.{cs,vb}] + +# Naming rules + +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion +dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces +dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase + +dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion +dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters +dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase + +dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods +dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties +dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.events_should_be_pascalcase.symbols = events +dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables +dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase + +dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants +dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase + +dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion +dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters +dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase + +dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields +dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion +dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields +dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase + +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase + +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums +dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase + +# Symbol specifications + +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interfaces.required_modifiers = + +dotnet_naming_symbols.enums.applicable_kinds = enum +dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.enums.required_modifiers = + +dotnet_naming_symbols.events.applicable_kinds = event +dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.events.required_modifiers = + +dotnet_naming_symbols.methods.applicable_kinds = method +dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.methods.required_modifiers = + +dotnet_naming_symbols.properties.applicable_kinds = property +dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.properties.required_modifiers = + +dotnet_naming_symbols.public_fields.applicable_kinds = field +dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_fields.required_modifiers = + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_fields.required_modifiers = + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum +dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types_and_namespaces.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +dotnet_naming_symbols.type_parameters.applicable_kinds = namespace +dotnet_naming_symbols.type_parameters.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters.required_modifiers = + +dotnet_naming_symbols.private_constant_fields.applicable_kinds = field +dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_constant_fields.required_modifiers = const + +dotnet_naming_symbols.local_variables.applicable_kinds = local +dotnet_naming_symbols.local_variables.applicable_accessibilities = local +dotnet_naming_symbols.local_variables.required_modifiers = + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.applicable_accessibilities = local +dotnet_naming_symbols.local_constants.required_modifiers = const + +dotnet_naming_symbols.parameters.applicable_kinds = parameter +dotnet_naming_symbols.parameters.applicable_accessibilities = * +dotnet_naming_symbols.parameters.required_modifiers = + +dotnet_naming_symbols.public_constant_fields.applicable_kinds = field +dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_constant_fields.required_modifiers = const + +dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function +dotnet_naming_symbols.local_functions.applicable_accessibilities = * +dotnet_naming_symbols.local_functions.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascalcase.required_prefix = +dotnet_naming_style.pascalcase.required_suffix = +dotnet_naming_style.pascalcase.word_separator = +dotnet_naming_style.pascalcase.capitalization = pascal_case + +dotnet_naming_style.ipascalcase.required_prefix = I +dotnet_naming_style.ipascalcase.required_suffix = +dotnet_naming_style.ipascalcase.word_separator = +dotnet_naming_style.ipascalcase.capitalization = pascal_case + +dotnet_naming_style.tpascalcase.required_prefix = T +dotnet_naming_style.tpascalcase.required_suffix = +dotnet_naming_style.tpascalcase.word_separator = +dotnet_naming_style.tpascalcase.capitalization = pascal_case + +dotnet_naming_style._camelcase.required_prefix = _ +dotnet_naming_style._camelcase.required_suffix = +dotnet_naming_style._camelcase.word_separator = +dotnet_naming_style._camelcase.capitalization = camel_case + +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.capitalization = camel_case + +dotnet_naming_style.s_camelcase.required_prefix = s_ +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.capitalization = camel_case + diff --git a/BMapBindings/BMapSharp/BMapSharp/BMap.cs b/BMapBindings/BMapSharp/BMapSharp/BMap.cs index 549b0a9..00b9894 100644 --- a/BMapBindings/BMapSharp/BMapSharp/BMap.cs +++ b/BMapBindings/BMapSharp/BMapSharp/BMap.cs @@ -23,16 +23,18 @@ namespace BMapSharp { /// The message content need to be printed. public delegate void OutputCallback([In, MarshalAs(UnmanagedType.LPUTF8Str)] string msg); + #region Custom Marshalers + + // 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. + /// The custom marshaler for BMap string array. public class BMStringArrayMarshaler : ICustomMarshaler { - // 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. - private static readonly BMStringArrayMarshaler g_Instance = new BMStringArrayMarshaler(); public static ICustomMarshaler GetInstance(string pstrCookie) => g_Instance; @@ -48,13 +50,9 @@ namespace BMapSharp { // 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(); - private static readonly int szArrayItemSize = Marshal.SizeOf(); - private static readonly int szStringItemSize = Marshal.SizeOf(); - - public IntPtr MarshalManagedToNative(object ManagedObj) { + public nint MarshalManagedToNative(object ManagedObj) { // Check nullptr object. - if (ManagedObj is null) return IntPtr.Zero; + if (ManagedObj is null) return nint.Zero; // Check argument type. string[] castManagedObj = ManagedObj as string[]; if (castManagedObj is null) @@ -62,97 +60,73 @@ namespace BMapSharp { // Allocate string items first int szArrayItemCount = castManagedObj.Length; - IntPtr[] apStrings = new IntPtr[szArrayItemCount]; + int szArrayItemSize = Marshal.SizeOf(); + nint[] apString = new nint[szArrayItemCount]; for (int i = 0; i < szArrayItemCount; ++i) { - // Encode string first. - byte[] encString = Encoding.UTF8.GetBytes(castManagedObj[i]); - // Allocate string memory with extra NULL terminal. - int szStringItemCount = encString.Length; - IntPtr pString = Marshal.AllocHGlobal(szStringItemSize * (szStringItemCount + 1) + szLengthHeaderSize); - // Setup length field - Marshal.WriteInt32(pString, 0, szStringItemCount); - // Copy string data with offset. - IntPtr pFakeString = pString + szLengthHeaderSize; - Marshal.Copy(encString, 0, pFakeString, szStringItemCount); - // Set NULL terminal. - Marshal.WriteByte(pFakeString, szStringItemSize * szStringItemCount, 0); - // Set item in string pointer - apStrings[i] = pFakeString; + // Check null string + string stringObj = castManagedObj[i]; + if (stringObj is null) apString[i] = nint.Zero; + else apString[i] = BMStringMarshaler.ToNative(stringObj); } // Allocate array pointer now. - 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); + nint pArray = Marshal.AllocHGlobal(szArrayItemSize * (szArrayItemCount + 1)); + // Copy string pointer data + Marshal.Copy(apString, 0, pArray, szArrayItemSize * szArrayItemCount); + // Setup NULL ternimal + Marshal.WriteIntPtr(pArray + (szArrayItemSize * szArrayItemCount), nint.Zero); // Return value - return pFakeArray; + return pArray; } - public object MarshalNativeToManaged(IntPtr pNativeData) { + public object MarshalNativeToManaged(nint pNativeData) { // Check nullptr - if (pNativeData == IntPtr.Zero) return null; + if (pNativeData == nint.Zero) return null; - // Get real array pointer - IntPtr pFakeArray = pNativeData; - IntPtr pArray = pFakeArray - szLengthHeaderSize; - - // Get the count of array and read string pointers - int szArrayItemCount = Marshal.ReadInt32(pArray, 0); - IntPtr[] apStrings = new IntPtr[szArrayItemCount]; - Marshal.Copy(pFakeArray, apStrings, 0, szArrayItemCount); + // Get the length of array + int szArrayItemCount = BMStringArrayMarshaler.GetArrayLength(pNativeData); + int szArrayItemSize = Marshal.SizeOf(); + // Prepare array cache and read it. + nint[] apString = new nint[szArrayItemCount]; + Marshal.Copy(pNativeData, apString, 0, szArrayItemSize * 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) { + nint pString = apString[i]; + if (pString == nint.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); + // Extract string + ret[i] = BMStringMarshaler.ToManaged(pString); } // Return result return ret; } - public void CleanUpNativeData(IntPtr pNativeData) { + public void CleanUpNativeData(nint pNativeData) { // Check nullptr - if (pNativeData == IntPtr.Zero) return; + if (pNativeData == nint.Zero) return; - // Get real array pointer - IntPtr pFakeArray = pNativeData; - IntPtr pArray = pFakeArray - szLengthHeaderSize; + // Get the length of array + int szArrayItemCount = BMStringArrayMarshaler.GetArrayLength(pNativeData); + int szArrayItemSize = Marshal.SizeOf(); + // Prepare array cache and read it. + nint[] apString = new nint[szArrayItemCount]; + Marshal.Copy(pNativeData, apString, 0, szArrayItemSize * szArrayItemCount); + // Free array self + Marshal.FreeHGlobal(pNativeData); - // Get the count of array and read string pointers - int szArrayItemCount = Marshal.ReadInt32(pArray, 0); - IntPtr[] apStrings = new IntPtr[szArrayItemCount]; - Marshal.Copy(pFakeArray, apStrings, 0, szArrayItemCount); - - // Iterate the array and free them one by one. - for (int i = 0; i < szArrayItemCount; ++i) { - // Get string pointer - IntPtr pFakeString = apStrings[i]; - if (pFakeString == IntPtr.Zero) continue; - IntPtr pString = pFakeString - szLengthHeaderSize; + // Iterate the string pointer array and free them one by one. + foreach (nint pString in apString) { // Free string pointer + if (pString == nint.Zero) continue; Marshal.FreeHGlobal(pString); } - - // Free array self - Marshal.FreeHGlobal(pArray); } public void CleanUpManagedData(object ManagedObj) { @@ -164,8 +138,112 @@ namespace BMapSharp { return -1; } + /// + /// Return the length of array created by this marshaler. + /// + /// The pointer to array for checking. + /// The length of array (NULL terminal exclusive). + internal static int GetArrayLength(nint ptr) { + int count = 0, unit = Marshal.SizeOf(); + while (Marshal.ReadIntPtr(ptr) != nint.Zero) { + ptr += unit; + ++count; + } + return count; + } } + public class BMStringMarshaler : ICustomMarshaler { + private static readonly BMStringMarshaler g_Instance = new BMStringMarshaler(); + public static ICustomMarshaler GetInstance(string pstrCookie) => g_Instance; + + public nint MarshalManagedToNative(object ManagedObj) { + // Check requirements. + if (ManagedObj is null) return nint.Zero; + string castManagedObj = ManagedObj as string; + if (castManagedObj is null) + throw new MarshalDirectiveException("BMStringMarshaler must be used on a string."); + // Call self + return BMStringMarshaler.ToNative(castManagedObj); + } + + public object MarshalNativeToManaged(nint pNativeData) { + // Check nullptr + if (pNativeData == nint.Zero) return null; + // Call self + return BMStringMarshaler.ToManaged(pNativeData); + } + + public void CleanUpNativeData(nint pNativeData) { + // Check nullptr + if (pNativeData == nint.Zero) return; + // Free native pointer + Marshal.FreeHGlobal(pNativeData); + } + + public void CleanUpManagedData(object ManagedObj) { + // Do nothing, because managed data do not need any clean up. + } + + public int GetNativeDataSize() { + // Return -1 to indicate the managed type this marshaler handles is not a value type. + return -1; + } + + /// + /// Return the length in byte of given pointer represented C style string. + /// + /// The pointer for checking. + /// The length of C style string (NUL exclusive). + internal static int GetCStringLength(nint ptr) { + int count = 0, unit = Marshal.SizeOf(); + while (Marshal.ReadByte(ptr) != (byte)0) { + ptr += unit; + ++count; + } + return count; + } + + /// + /// Convert given string object to native data. + /// This function is shared by 2 marshalers. + /// + /// String object. Caller must make sure this object is not null. + /// The created native data pointer. + internal static nint 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(); + nint pString = Marshal.AllocHGlobal(szStringItemSize * (szStringItemCount + 1)); + // Copy encoded string data + Marshal.Copy(encString, 0, pString, szStringItemSize * szStringItemCount); + // Setup NUL + Marshal.WriteByte(pString + (szStringItemSize * szStringItemCount), (byte)0); + // Return value + return pString; + } + /// + /// Extract managed string from given native pointer holding C style string data. + /// This function is shared by 2 marshalers. + /// + /// Native pointer holding string data. Caller must make sure this pointer is not nullptr. + /// The extracted managed string data. + internal static string ToManaged(nint ptr) { + // Get the length of given string. + int szStringItemCount = BMStringMarshaler.GetCStringLength(ptr); + int szStringItemSize = Marshal.SizeOf(); + // Prepare cache and copy string data + byte[] encString = new byte[szStringItemCount]; + Marshal.Copy(ptr, encString, 0, szStringItemSize * szStringItemCount); + // Decode string and return + return Encoding.UTF8.GetString(encString); + } + } + + #endregion + // Decide the file name of loaded DLL. #if BMAP_OS_WINDOWS diff --git a/BMapBindings/BMapSharp/BMapSharpTestbench/BMapSharpTestbench.csproj b/BMapBindings/BMapSharp/BMapSharpTestbench/BMapSharpTestbench.csproj index 2150e37..90c53ef 100644 --- a/BMapBindings/BMapSharp/BMapSharpTestbench/BMapSharpTestbench.csproj +++ b/BMapBindings/BMapSharp/BMapSharpTestbench/BMapSharpTestbench.csproj @@ -4,7 +4,6 @@ Exe net8.0 enable - enable