From ae0bcdbff642302f45c0a63e5049d4c7d1bd521f Mon Sep 17 00:00:00 2001 From: Drew Noakes Date: Mon, 29 Jan 2024 15:40:02 +1100 Subject: [PATCH] Polyfill non-allocating encoding span->string without byte[] Originally proposed by @iamcarbon in https://github.com/drewnoakes/metadata-extractor-dotnet/pull/380#discussion_r1468653528 This requires allowing unsafe blocks for `net462` and `netstandard1.3`. Co-authored-by: Jason Nelson --- MetadataExtractor/IO/IndexedReader.cs | 8 +++---- MetadataExtractor/IO/SequentialReader.cs | 13 ++++++----- MetadataExtractor/MetadataExtractor.csproj | 2 ++ MetadataExtractor/Util/EncodingExtensions.cs | 24 ++++++++++++++++++++ 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 MetadataExtractor/Util/EncodingExtensions.cs diff --git a/MetadataExtractor/IO/IndexedReader.cs b/MetadataExtractor/IO/IndexedReader.cs index 0a7e323e9..16a9b2237 100644 --- a/MetadataExtractor/IO/IndexedReader.cs +++ b/MetadataExtractor/IO/IndexedReader.cs @@ -319,17 +319,15 @@ public double GetDouble64(int index) /// public string GetString(int index, int bytesRequested, Encoding encoding) { -#if NET462 || NETSTANDARD1_3 - var bytes = GetBytes(index, bytesRequested); + // This check is important on .NET Framework + if (bytesRequested is 0) + return ""; - return encoding.GetString(bytes, 0, bytes.Length); -#else Span bytes = bytesRequested < 256 ? stackalloc byte[bytesRequested] : new byte[bytesRequested]; GetBytes(index, bytes); return encoding.GetString(bytes); -#endif } /// diff --git a/MetadataExtractor/IO/SequentialReader.cs b/MetadataExtractor/IO/SequentialReader.cs index 3267e1fc6..97f82b746 100644 --- a/MetadataExtractor/IO/SequentialReader.cs +++ b/MetadataExtractor/IO/SequentialReader.cs @@ -220,14 +220,15 @@ public float GetS15Fixed16() /// public string GetString(int bytesRequested, Encoding encoding) { -#if NETSTANDARD2_1 - Span bytes = bytesRequested > 2048 ? new byte[bytesRequested] : stackalloc byte[bytesRequested]; + // This check is important on .NET Framework + if (bytesRequested is 0) + return ""; + + Span bytes = bytesRequested < 256 ? stackalloc byte[bytesRequested] : new byte[bytesRequested]; + GetBytes(bytes); + return encoding.GetString(bytes); -#else - var bytes = GetBytes(bytesRequested); - return encoding.GetString(bytes, 0, bytes.Length); -#endif } public StringValue GetStringValue(int bytesRequested, Encoding? encoding = null) diff --git a/MetadataExtractor/MetadataExtractor.csproj b/MetadataExtractor/MetadataExtractor.csproj index fcda68924..3779ba6fc 100644 --- a/MetadataExtractor/MetadataExtractor.csproj +++ b/MetadataExtractor/MetadataExtractor.csproj @@ -16,6 +16,8 @@ Camera manufacturer specific support exists for Agfa, Canon, Casio, DJI, Epson, true true README.md + + true diff --git a/MetadataExtractor/Util/EncodingExtensions.cs b/MetadataExtractor/Util/EncodingExtensions.cs new file mode 100644 index 000000000..b195a74a7 --- /dev/null +++ b/MetadataExtractor/Util/EncodingExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) Drew Noakes and contributors. All Rights Reserved. Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +#if NETFRAMEWORK || NETSTANDARD1_3 + +internal static class EncodingExtensions +{ + /// + /// Converts a span of bytes into a string, following the specified encoding's rules. + /// + /// The encoding to follow. + /// The bytes to convert. + /// The decoded string. + public unsafe static string GetString(this Encoding encoding, ReadOnlySpan bytes) + { + // Poly-fill for method available in newer versions of .NET + + fixed (byte* pBytes = bytes) + { + return encoding.GetString(pBytes, bytes.Length); + } + } +} + +#endif