* SilkMarshal 2.0 w/ GlobalMemory - a HGlobal/POH abstraction

* Update src/Core/Silk.NET.Core/Native/GlobalMemory.cs

* NativeStringEncoding = UnmanagedType
This commit is contained in:
Dylan Perks 2020-10-18 20:16:05 +01:00 коммит произвёл GitHub
Родитель 63a8826e1b
Коммит 892a931f2b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 577 добавлений и 5 удалений

Просмотреть файл

@ -33,7 +33,6 @@ dotnet_style_qualification_for_field = false:warning
dotnet_style_qualification_for_method = false:warning
dotnet_style_qualification_for_property = false:warning
dotnet_style_require_accessibility_modifiers = for_non_interface_members:hint
csharp_style_expression_bodied_methods = false:hint
csharp_style_inlined_variable_declaration = true:hint
dotnet_sort_system_directives_first = true

Просмотреть файл

@ -3,7 +3,7 @@
// You may modify and distribute Silk.NET under the terms
// of the MIT license. See the LICENSE file for details.
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
using System.Reflection;
using NativeLibrary3 = System.Runtime.InteropServices.NativeLibrary;
#else
@ -303,7 +303,7 @@ namespace Silk.NET.Core.Loader
/// <returns>A LibraryLoader suitable for loading libraries.</returns>
public static LibraryLoader GetPlatformDefaultLoader()
{
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
return new NetNextNativeLibraryLoader();
#else
@ -333,7 +333,7 @@ namespace Silk.NET.Core.Loader
throw new PlatformNotSupportedException("This platform cannot load native libraries.");
}
#if NETCOREAPP3_1
#if NETCOREAPP3_1 || NET5_0
private class NetNextNativeLibraryLoader : LibraryLoader
{
protected override IntPtr CoreLoadNativeLibrary(string name)

Просмотреть файл

@ -0,0 +1,179 @@
// This file is part of Silk.NET.
//
// You may modify and distribute Silk.NET under the terms
// of the MIT license. See the LICENSE file for details.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Silk.NET.Core.Native
{
/// <summary>
/// Represents a block of global memory. That is, memory that is pinned and is valid so long as this object is alive.
/// </summary>
public sealed class GlobalMemory : IDisposable
{
// Actual object
private readonly object _memoryObject;
internal GlobalMemory(object memoryObject, int length)
{
_memoryObject = memoryObject;
Length = length;
}
/// <summary>
/// Gets the length of this block of global memory.
/// </summary>
public int Length { get; }
/// <summary>
/// Gets a reference to a specific index of this block of global memory.
/// </summary>
/// <param name="index">The index.</param>
public ref byte this[int index] => ref Unsafe.Add(ref GetPinnableReference(), index);
#if NETCOREAPP3_1 || NET5_0
/// <summary>
/// Gets a reference to a specific index of this block of global memory.
/// </summary>
/// <param name="index">The index.</param>
public ref byte this[Index index] => ref Unsafe.Add(ref GetPinnableReference(), index.GetOffset(Length));
/// <summary>
/// Gets a span representing a specific area of this block of global memory.
/// </summary>
/// <param name="range">The range.</param>
public Span<byte> this[Range range]
=> AsSpan().Slice(range.Start.GetOffset(Length), range.End.GetOffset(Length));
#endif
/// <summary>
/// Gets a handle to this block of global memory.
/// </summary>
public unsafe IntPtr Handle => (IntPtr)Unsafe.AsPointer(ref GetPinnableReference());
/// <summary>
/// Gets a span representing this block of global memory.
/// </summary>
/// <returns>A span of global memory.</returns>
public unsafe Span<byte> AsSpan() => _memoryObject is IGlobalMemory hGlobal
? new Span<byte>((byte*) hGlobal.Handle, Length)
: new Span<byte>((byte[]) _memoryObject);
/// <summary>
/// Gets a span of the given type representing this block of global memory.
/// </summary>
/// <returns>A span of global memory.</returns>
public unsafe Span<T> AsSpan<T>() where T : unmanaged
=> new Span<T>(Unsafe.AsPointer(ref GetPinnableReference()), Length / sizeof(T));
/// <summary>
/// Gets a span representing this block of global memory.
/// </summary>
/// <returns>A span of global memory.</returns>
public static implicit operator Span<byte>(GlobalMemory left) => left.AsSpan();
/// <summary>
/// Gets a handle to this block of global memory.
/// </summary>
/// <returns>A handle to this block of global memory.</returns>
public static unsafe implicit operator void*(GlobalMemory left) => left.Handle.ToPointer();
/// <summary>
/// Gets a handle to this block of global memory.
/// </summary>
/// <returns>A handle to this block of global memory.</returns>
public static implicit operator IntPtr(GlobalMemory left) => left.Handle;
/// <summary>
/// Gets a reference to the global memory. This reference is valid until this object is disposed or finalized.
/// </summary>
/// <returns>A reference to the global memory.</returns>
public unsafe ref byte GetPinnableReference()
=> ref _memoryObject is IGlobalMemory hGlobal
? ref *(byte*) hGlobal.Handle
: ref ((byte[]) _memoryObject)[0];
private void Free()
{
switch (_memoryObject)
{
case HGlobal hGlobal:
{
Marshal.FreeHGlobal(hGlobal.Handle);
break;
}
case BStr bStr:
{
Marshal.FreeBSTR(bStr.Handle);
break;
}
}
}
/// <inheritdoc />
public void Dispose()
{
Free();
GC.SuppressFinalize(this);
}
~GlobalMemory()
{
Free();
}
// Allocation methods
/// <summary>
/// Allocates a block of global memory of the given length.
/// </summary>
/// <param name="length">The number of bytes to allocate.</param>
/// <returns>A block of global memory.</returns>
public static GlobalMemory Allocate(int length) =>
#if !NET5_0
new GlobalMemory(new HGlobal(length), length);
#else
new GlobalMemory(GC.AllocateUninitializedArray<byte>(length, true), length);
#endif
// Encapsulations different kinds of memory
private interface IGlobalMemory
{
IntPtr Handle { get; }
}
private struct HGlobal : IGlobalMemory
{
public HGlobal(int length) => Handle = Marshal.AllocHGlobal(length);
public HGlobal(IntPtr val) => Handle = val;
public IntPtr Handle { get; }
}
private struct BStr : IGlobalMemory
{
public BStr(int length) => Handle = SilkMarshal.AllocBStr(length);
public BStr(IntPtr val) => Handle = val;
public IntPtr Handle { get; }
}
private struct Other : IGlobalMemory
{
// used for "unsafe" marshalling of a pointer to our neat GlobalMemory class if that's your thing.
public Other(IntPtr val) => Handle = val;
public IntPtr Handle { get; }
}
// "Unsafe" methods
internal static GlobalMemory FromHGlobal(IntPtr hGlobal, int len)
=> new GlobalMemory(new HGlobal(hGlobal), len);
internal static GlobalMemory FromBStr(IntPtr bStr, int len)
=> new GlobalMemory(new BStr(bStr), len);
internal static GlobalMemory FromAnyPtr(IntPtr val, int len)
=> new GlobalMemory(new Other(val), len);
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// This file is part of Silk.NET.
//
// You may modify and distribute Silk.NET under the terms
// of the MIT license. See the LICENSE file for details.
namespace Silk.NET.Core.Native
{
public enum NativeStringEncoding
{
BStr = UnmanagedType.BStr,
LPStr = UnmanagedType.LPStr,
LPTStr = UnmanagedType.LPTStr,
LPUTF8Str = UnmanagedType.LPUTF8Str,
LPWStr = UnmanagedType.LPWStr,
Ansi = LPStr,
Auto = LPTStr,
Uni = LPWStr,
UTF8 = LPUTF8Str
}
}

Просмотреть файл

@ -4,8 +4,10 @@
// of the MIT license. See the LICENSE file for details.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace Silk.NET.Core.Native
{
@ -20,12 +22,373 @@ namespace Silk.NET.Core.Native
/// <param name="length">The length of the string pointer, in bytes.</param>
/// <returns>A pointer to the created string.</returns>
public static IntPtr AllocBStr(int length) => Marshal.StringToBSTR(new string('\0', length));
// Store the GlobalMemory instances so that on .NET 5 the pinned object heap isn't prematurely garbage collected
// This means that the GlobalMemory is only freed when the user calls Free.
private static readonly ConcurrentDictionary<IntPtr, GlobalMemory> _marshalledMemory =
new ConcurrentDictionary<IntPtr, GlobalMemory>();
// In addition, we should keep track of the memory we allocate dedicated to string arrays. If we don't, we won't
// know to free the individual strings allocated within memory.
private static readonly ConcurrentDictionary<GlobalMemory, int> _stringArrays
= new ConcurrentDictionary<GlobalMemory, int>();
private static IntPtr RegisterMemory(GlobalMemory memory) => (_marshalledMemory[memory.Handle] = memory).Handle;
/// <summary>
/// Allocates a block of global memory of the given size.
/// </summary>
/// <remarks>
/// The allocated memory must be manually freed using <see cref="Free"/>.
/// </remarks>
/// <param name="length">The number of bytes to allocate.</param>
/// <returns>The allocated bytes.</returns>
public static IntPtr Allocate(int length) => RegisterMemory(GlobalMemory.Allocate(length));
/// <summary>
/// Frees the specific region of global memory.
/// </summary>
/// <param name="memory">The memory to free.</param>
/// <returns>
/// Whether the operation was successful or not. If false, the memory likely wasn't allocated with
/// <see cref="Allocate" />.
/// </returns>
public static bool Free(IntPtr memory)
{
var ret = _marshalledMemory.TryRemove(memory, out var val);
if (val is null)
{
return ret;
}
if (_stringArrays.TryRemove(val, out var numStrings))
{
var span = val.AsSpan<IntPtr>();
for (var i = 0; i < numStrings; i++)
{
Free(span[i]);
}
}
val.Dispose();
return ret;
}
/// <summary>
/// Gets a <see cref="GlobalMemory"/> object containing a copy of the input string marshalled per the specified
/// native string encoding.
/// </summary>
/// <param name="input">The string to marshal.</param>
/// <param name="encoding">The target native string encoding.</param>
/// <returns>The <see cref="GlobalMemory"/> object containing the marshalled string array.</returns>
public static GlobalMemory StringToMemory
(
string input,
NativeStringEncoding encoding = NativeStringEncoding.Ansi
)
{
return encoding switch
{
NativeStringEncoding.BStr => BStrToMemory(Marshal.StringToBSTR(input), input.Length),
NativeStringEncoding.LPStr => AnsiToMemory(input),
NativeStringEncoding.LPTStr => Utf8ToMemory(input),
NativeStringEncoding.LPUTF8Str => Utf8ToMemory(input),
NativeStringEncoding.LPWStr => WideToMemory(input),
_ => throw new ArgumentOutOfRangeException(nameof(encoding))
};
static unsafe GlobalMemory Utf8ToMemory(string input)
{
var memory = GlobalMemory.Allocate(Encoding.UTF8.GetMaxByteCount(input.Length) + 1);
int convertedBytes;
fixed (char* firstChar = input)
{
fixed (byte* bytes = memory)
{
convertedBytes = Encoding.UTF8.GetBytes(firstChar, input.Length, bytes, memory.Length - 1);
}
}
memory[convertedBytes] = 0;
return memory;
}
static unsafe GlobalMemory AnsiToMemory(string input)
{
var memory = GlobalMemory.Allocate((input.Length + 1) * Marshal.SystemMaxDBCSCharSize);
int convertedBytes;
fixed (char* firstChar = input)
{
fixed (byte* bytes = memory)
{
convertedBytes = Encoding.UTF8.GetBytes(firstChar, input.Length, bytes, memory.Length);
}
}
memory[convertedBytes] = 0;
return memory;
}
static unsafe GlobalMemory WideToMemory(string input)
{
var memory = GlobalMemory.Allocate((input.Length + 1) * 2);
fixed (char* firstChar = input)
{
Buffer.MemoryCopy(firstChar, (void*) memory.Handle, memory.Length, input.Length + 1);
}
return memory;
}
}
/// <summary>
/// Gets a pointer to memory containing a copy of the input string marshalled per the specified
/// native string encoding.
/// </summary>
/// <remarks>
/// The allocated memory must be manually freed using <see cref="Free"/>.
/// </remarks>
/// <param name="input">The string to marshal.</param>
/// <param name="encoding">The target native string encoding.</param>
/// <returns>A pointer to the memory containing the marshalled string array.</returns>
public static IntPtr StringToPtr(string input, NativeStringEncoding encoding = NativeStringEncoding.Ansi)
=> RegisterMemory(StringToMemory(input, encoding));
/// <summary>
/// Reads a null-terminated string from unmanaged memory, with the given native encoding.
/// </summary>
/// <param name="input">A pointer to memory containing a null-terminated string.</param>
/// <param name="encoding">The encoding of the string in memory.</param>
/// <returns>The string read from memory.</returns>
public static string PtrToString(IntPtr input, NativeStringEncoding encoding = NativeStringEncoding.Ansi)
=> encoding switch
{
NativeStringEncoding.BStr => Marshal.PtrToStringBSTR(input),
NativeStringEncoding.LPStr => Marshal.PtrToStringAnsi(input),
NativeStringEncoding.LPTStr => Marshal.PtrToStringAuto(input),
NativeStringEncoding.LPUTF8Str => Utf8PtrToString(input),
NativeStringEncoding.LPWStr => Marshal.PtrToStringUni(input),
_ => throw new ArgumentOutOfRangeException(nameof(encoding))
};
/// <summary>
/// Reads a null-terminated string from global memory, with the given native encoding.
/// </summary>
/// <param name="input">Global memory containing a null-terminated string.</param>
/// <param name="e">The encoding of the string in memory.</param>
/// <returns>The string read from memory.</returns>
public static string MemoryToString(GlobalMemory input, NativeStringEncoding e = NativeStringEncoding.Ansi)
=> PtrToString(input.Handle, e);
/// <summary>
/// Returns a copy of the given string array in global memory, marshalled using the specified encoding.
/// </summary>
/// <param name="input">The input array.</param>
/// <param name="e">The encoding of the resultant string array.</param>
/// <returns>Global memory containing the marshalled string array.</returns>
public static GlobalMemory StringArrayToMemory
(
IReadOnlyList<string> input,
NativeStringEncoding e = NativeStringEncoding.Ansi
)
{
var memory = GlobalMemory.Allocate(input.Count * IntPtr.Size);
var span = memory.AsSpan<IntPtr>();
for (var i = 0; i < input.Count; i++)
{
span[i] = StringToPtr(input[i], e);
}
return memory;
}
/// <summary>
/// Returns a copy of the given string array in global memory, marshalled using the specified custom marshaller.
/// </summary>
/// <param name="input">The input array.</param>
/// <param name="customStringMarshaller">The custom string-to-pointer marshaller to use.</param>
/// <returns>Global memory containing the marshalled string array.</returns>
public static GlobalMemory StringArrayToMemory
(
IReadOnlyList<string> input,
Func<string, IntPtr> customStringMarshaller
)
{
var memory = GlobalMemory.Allocate(input.Count * IntPtr.Size);
var span = memory.AsSpan<IntPtr>();
for (var i = 0; i < input.Count; i++)
{
span[i] = customStringMarshaller(input[i]);
}
return memory;
}
/// <summary>
/// Returns a copy of the given string array in memory, marshalled using the specified encoding.
/// </summary>
/// <param name="input">The input array.</param>
/// <param name="encoding">The encoding of the resultant string array.</param>
/// <returns>A pointer to memory containing the marshalled string array.</returns>
public static IntPtr StringArrayToPtr
(
IReadOnlyList<string> input,
NativeStringEncoding encoding = NativeStringEncoding.Ansi
)
{
var memory = StringArrayToMemory(input, encoding);
_stringArrays.TryAdd(memory, input.Count);
return RegisterMemory(memory);
}
/// <summary>
/// Returns a copy of the given string array in memory, marshalled using the given custom string marshaller.
/// </summary>
/// <param name="input">The input array.</param>
/// <param name="customStringMarshaller">The marshaller to use for the individual strings in the array.</param>
/// <returns>A pointer to memory containing the marshalled string array.</returns>
public static IntPtr StringArrayToPtr
(
IReadOnlyList<string> input,
Func<string, IntPtr> customStringMarshaller
)
{
var memory = StringArrayToMemory(input, customStringMarshaller);
_stringArrays.TryAdd(memory, input.Count);
return RegisterMemory(memory);
}
/// <summary>
/// Reads an array null-terminated string from unmanaged memory, with the given native encoding.
/// </summary>
/// <param name="input">A pointer to unmanaged memory containing a string array.</param>
/// <param name="numStrings">The number of strings contained within the string array.</param>
/// <param name="encoding">The encoding of the strings in memory.</param>
/// <returns>The read string array.</returns>
public static unsafe string[] PtrToStringArray
(
IntPtr input,
int numStrings,
NativeStringEncoding encoding = NativeStringEncoding.Ansi
)
{
var ret = new string[numStrings];
var ptrs = (IntPtr*) input;
for (var i = 0; i < numStrings; i++)
{
ret[i] = PtrToString(ptrs![i]);
}
return ret;
}
/// <summary>
/// Reads an array null-terminated string from unmanaged memory, with the given custom pointer-to-string
/// marshaller.
/// </summary>
/// <param name="input">A pointer to unmanaged memory containing a string array.</param>
/// <param name="numStrings">The number of strings contained within the string array.</param>
/// <param name="customUnmarshaller">The pointer-to-string marshaller to use.</param>
/// <returns>The read string array.</returns>
public static unsafe string[] PtrToStringArray
(
IntPtr input,
int numStrings,
Func<IntPtr, string> customUnmarshaller
)
{
var ret = new string[numStrings];
var ptrs = (IntPtr*) input;
for (var i = 0; i < numStrings; i++)
{
ret[i] = customUnmarshaller(ptrs![i]);
}
return ret;
}
/// <summary>
/// Reads an array null-terminated string from global memory, with the given native encoding.
/// </summary>
/// <param name="input">Global memory containing a string array.</param>
/// <param name="encoding">The encoding of the strings in memory.</param>
/// <returns>The read string array.</returns>
public static string[] MemoryToStringArray
(
GlobalMemory input,
NativeStringEncoding encoding = NativeStringEncoding.Ansi
) => PtrToStringArray(input, input.Length / IntPtr.Size, encoding);
/// <summary>
/// Reads an array null-terminated string from global memory, with the given pointer-to-string marshaller.
/// </summary>
/// <param name="input">Global memory containing a string array.</param>
/// <param name="customUnmarshaller">The pointer-to-string marshaller to use.</param>
/// <returns>The read string array.</returns>
public static string[] MemoryToStringArray
(
GlobalMemory input,
Func<IntPtr, string> customUnmarshaller
) => PtrToStringArray(input, input.Length / IntPtr.Size, customUnmarshaller);
private static unsafe string Utf8PtrToString(IntPtr ptr)
{
#if NETCOREAPP3_1 || NET5_0
return Marshal.PtrToStringUTF8(ptr);
#else
var span = new Span<byte>((void*) ptr, int.MaxValue);
span = span.Slice(0, span.IndexOf(default(byte)));
fixed (byte* bytes = span)
{
return Encoding.UTF8.GetString(bytes, span.Length);
}
#endif
}
// "Unsafe" methods
/// <summary>
/// Gets a <see cref="GlobalMemory"/> object representing this HGlobal.
/// </summary>
/// <param name="hGlobal">The HGlobal to wrap.</param>
/// <param name="length">The length of this HGlobal in bytes.</param>
/// <returns>An object representing this HGlobal.</returns>
public static GlobalMemory HGlobalToMemory(IntPtr hGlobal, int length)
=> GlobalMemory.FromHGlobal(hGlobal, length);
/// <summary>
/// Gets a <see cref="GlobalMemory"/> object representing this BStr.
/// </summary>
/// <param name="bStr">The BStr to wrap.</param>
/// <param name="length">The length of this BStr in bytes.</param>
/// <returns>An object representing this BStr.</returns>
public static GlobalMemory BStrToMemory(IntPtr bStr, int length)
=> GlobalMemory.FromHGlobal(bStr, length);
/// <summary>
/// Gets a <see cref="GlobalMemory"/> object representing this pointer.
/// </summary>
/// <param name="ptr">The pointer to wrap.</param>
/// <param name="length">The length of this pointer in bytes.</param>
/// <returns>An object representing this pointer.</returns>
/// <remarks>
/// This is not recommended for use as it may be implied that freeing occurs when this object goes out of scope,
/// even though this is not the case. If the pointer is a HGlobal or a BStr, use one of the other methods;
/// otherwise, this method should only be used for accessing <see cref="GlobalMemory"/>'s rich set of APIs and
/// not to manage lifetime.
/// </remarks>
public static GlobalMemory PtrToMemory(IntPtr ptr, int length)
=> GlobalMemory.FromHGlobal(ptr, length);
// TODO !!!!!!!!!!!!!!! LEGACY METHODS START HERE, DELETE THEM ONCE SILK HAS STOPPED USING THEM !!!!!!!!!!!!!!!
/// <summary>
/// Converts a C# string to an ANSI string pointer.
/// </summary>
/// <param name="str">The input string.</param>
/// <returns>A pointer to a native ANSI string.</returns>
[Obsolete]
public static IntPtr MarshalStringToPtr(string str)
{
return Marshal.StringToHGlobalAnsi(str);
@ -36,6 +399,7 @@ namespace Silk.NET.Core.Native
/// </summary>
/// <param name="str">A pointer to the ANSI string to convert.</param>
/// <returns>A C# string.</returns>
[Obsolete]
public static string MarshalPtrToString(IntPtr str)
{
return Marshal.PtrToStringAnsi(str);
@ -45,6 +409,7 @@ namespace Silk.NET.Core.Native
/// Free a string pointer.
/// </summary>
/// <param name="ptr">The pointer to free.</param>
[Obsolete]
public static void FreeStringPtr(IntPtr ptr)
{
Marshal.FreeHGlobal(ptr);
@ -55,6 +420,7 @@ namespace Silk.NET.Core.Native
/// </summary>
/// <param name="length">The length of the string pointer, in bytes.</param>
/// <returns>A pointer to the created string.</returns>
[Obsolete]
public static IntPtr NewStringPtr(int length)
{
return Marshal.AllocHGlobal(length);
@ -65,6 +431,7 @@ namespace Silk.NET.Core.Native
/// </summary>
/// <param name="length">The length of the string pointer, in bytes.</param>
/// <returns>A pointer to the created string.</returns>
[Obsolete]
public static IntPtr NewStringPtr(uint length)
{
return Marshal.AllocHGlobal((int) length);
@ -76,6 +443,7 @@ namespace Silk.NET.Core.Native
/// <param name="array">The array of strings to convert.</param>
/// <returns>The new pointer.</returns>
/// <exception cref="OutOfMemoryException">Thrown if enough memory cannot be allocated.</exception>
[Obsolete]
public static IntPtr MarshalStringArrayToPtr(IReadOnlyList<string> array)
{
var ptr = IntPtr.Zero;
@ -118,6 +486,7 @@ namespace Silk.NET.Core.Native
/// <param name="ptr">The pointer to convert.</param>
/// <param name="length">The number of strings in the pointer.</param>
/// <returns>An array of strings.</returns>
[Obsolete]
public static string[] MarshalPtrToStringArray(IntPtr ptr, int length)
{
var ret = new string[length];
@ -131,6 +500,7 @@ namespace Silk.NET.Core.Native
/// </summary>
/// <param name="ptr">The pointer to convert.</param>
/// <param name="arr">The array to fill with strings.</param>
[Obsolete]
public static void CopyPtrToStringArray(IntPtr ptr, string[] arr)
{
for (var i = 0; i < arr.Length; i++)
@ -144,6 +514,7 @@ namespace Silk.NET.Core.Native
/// </summary>
/// <param name="ptr">The pointer to free.</param>
/// <param name="length">The number of strings in the pointer.</param>
[Obsolete]
public static void FreeStringArrayPtr(IntPtr ptr, int length)
{
for (var i = 0; i < length; i++)

Просмотреть файл

@ -66,6 +66,9 @@ namespace Silk.NET.Core.Native
/// <summary>A platform-dependent character string: ANSI on Windows 98, and Unicode on Windows NT and Windows XP.</summary>
LPTStr = 22,
/// <summary>A UTF8-encoded, null-terminated character string.</summary>
LPUTF8Str = 48,
/// <summary>A platform-dependent, signed integer: 4 bytes on 32-bit, 8 bytes on 64-bit.</summary>
/// <seealso cref="System.IntPtr" />

Просмотреть файл

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netcoreapp3.1;net5.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>preview</LangVersion>
</PropertyGroup>