diff --git a/FFmpeg.AutoGen.Abstractions.Test/ConstCharPtrMarshalerTest.cs b/FFmpeg.AutoGen.Abstractions.Test/ConstCharPtrMarshalerTest.cs
new file mode 100644
index 00000000..83ace8c9
--- /dev/null
+++ b/FFmpeg.AutoGen.Abstractions.Test/ConstCharPtrMarshalerTest.cs
@@ -0,0 +1,99 @@
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace FFmpeg.AutoGen.Abstractions.Test
+{
+ ///
+ /// Tests for , the custom marshaler used for
+ /// const char* return values coming from FFmpeg. It must decode the native
+ /// UTF-8 string without ever freeing the (borrowed/static) native memory.
+ ///
+ /// This project multi-targets net9.0 and net48 so both compile-time branches of the
+ /// marshaler are covered: net9.0 references the netstandard2.1 build
+ /// (Marshal.PtrToStringUTF8) and net48 references the netstandard2.0 build
+ /// (the manual UTF-8 decode fallback).
+ ///
+ [TestClass]
+ public class ConstCharPtrMarshalerTest
+ {
+ private readonly List _allocations = new();
+
+ private static ICustomMarshaler Marshaler => ConstCharPtrMarshaler.GetInstance(null!);
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ foreach (var ptr in _allocations)
+ Marshal.FreeHGlobal(ptr);
+ _allocations.Clear();
+ }
+
+ /// Allocates a NUL-terminated UTF-8 C string in unmanaged memory.
+ private IntPtr AllocUtf8(string value)
+ {
+ var bytes = Encoding.UTF8.GetBytes(value);
+ var ptr = Marshal.AllocHGlobal(bytes.Length + 1);
+ _allocations.Add(ptr);
+ Marshal.Copy(bytes, 0, ptr, bytes.Length);
+ Marshal.WriteByte(ptr, bytes.Length, 0); // NUL terminator
+ return ptr;
+ }
+
+ [TestMethod]
+ public void GetInstance_ReturnsNonNullMarshaler()
+ {
+ Assert.IsNotNull(Marshaler);
+ }
+
+ [TestMethod]
+ public void NullPointer_ReturnsNull()
+ {
+ var result = Marshaler.MarshalNativeToManaged(IntPtr.Zero);
+ Assert.IsNull(result);
+ }
+
+ [TestMethod]
+ public void EmptyString_ReturnsEmptyString()
+ {
+ var ptr = AllocUtf8(string.Empty);
+ var result = Marshaler.MarshalNativeToManaged(ptr);
+ Assert.AreEqual(string.Empty, result);
+ }
+
+ [TestMethod]
+ public void AsciiString_RoundTrips()
+ {
+ const string expected = "libavcodec 60.31.102";
+ var ptr = AllocUtf8(expected);
+ var result = Marshaler.MarshalNativeToManaged(ptr);
+ Assert.AreEqual(expected, result);
+ }
+
+ [TestMethod]
+ public void Utf8MultiByteString_RoundTrips()
+ {
+ // Mix of 2-byte (é), 3-byte (日本語) and 4-byte (emoji) UTF-8 sequences.
+ const string expected = "café — 日本語 🎬";
+ var ptr = AllocUtf8(expected);
+ var result = Marshaler.MarshalNativeToManaged(ptr);
+ Assert.AreEqual(expected, result);
+ }
+
+ [TestMethod]
+ public void CleanUpNativeData_DoesNotInvalidatePointer()
+ {
+ const string expected = "av_version_info";
+ var ptr = AllocUtf8(expected);
+
+ // CleanUpNativeData must be a no-op for borrowed FFmpeg memory: it must not
+ // free or mutate the buffer. So decoding again after it returns the same
+ // value from the still-valid pointer.
+ var first = Marshaler.MarshalNativeToManaged(ptr);
+ Marshaler.CleanUpNativeData(ptr); // documented no-op for borrowed memory
+ var second = Marshaler.MarshalNativeToManaged(ptr);
+
+ Assert.AreEqual(expected, first);
+ Assert.AreEqual(expected, second);
+ }
+ }
+}
diff --git a/FFmpeg.AutoGen.Abstractions.Test/FFmpeg.AutoGen.Abstractions.Test.csproj b/FFmpeg.AutoGen.Abstractions.Test/FFmpeg.AutoGen.Abstractions.Test.csproj
new file mode 100644
index 00000000..b6721d94
--- /dev/null
+++ b/FFmpeg.AutoGen.Abstractions.Test/FFmpeg.AutoGen.Abstractions.Test.csproj
@@ -0,0 +1,37 @@
+
+
+
+
+ net9.0
+ net9.0;net48
+ enable
+ enable
+ false
+ preview
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/FFmpeg.AutoGen.Abstractions.Test/Usings.cs b/FFmpeg.AutoGen.Abstractions.Test/Usings.cs
new file mode 100644
index 00000000..540383dc
--- /dev/null
+++ b/FFmpeg.AutoGen.Abstractions.Test/Usings.cs
@@ -0,0 +1 @@
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
diff --git a/FFmpeg.AutoGen.Abstractions/ConstCharPtrMarshaler.cs b/FFmpeg.AutoGen.Abstractions/ConstCharPtrMarshaler.cs
index 47bd8e71..41ca9c94 100644
--- a/FFmpeg.AutoGen.Abstractions/ConstCharPtrMarshaler.cs
+++ b/FFmpeg.AutoGen.Abstractions/ConstCharPtrMarshaler.cs
@@ -6,7 +6,27 @@ namespace FFmpeg.AutoGen.Abstractions;
public class ConstCharPtrMarshaler : ICustomMarshaler
{
private static readonly ConstCharPtrMarshaler Instance = new();
- public object MarshalNativeToManaged(IntPtr pNativeData) => Marshal.PtrToStringAnsi(pNativeData);
+
+ public object MarshalNativeToManaged(IntPtr pNativeData)
+ {
+#if NETSTANDARD2_1_OR_GREATER
+ return Marshal.PtrToStringUTF8(pNativeData);
+#else
+ if (pNativeData == IntPtr.Zero)
+ return null;
+
+ var length = 0;
+ while (Marshal.ReadByte(pNativeData, length) != 0)
+ length++;
+
+ if (length == 0)
+ return string.Empty;
+
+ var buffer = new byte[length];
+ Marshal.Copy(pNativeData, buffer, 0, length);
+ return System.Text.Encoding.UTF8.GetString(buffer);
+#endif
+ }
public IntPtr MarshalManagedToNative(object managedObj) => IntPtr.Zero;
diff --git a/FFmpeg.AutoGen.CppSharpUnsafeGenerator/Processing/FunctionProcessor.cs b/FFmpeg.AutoGen.CppSharpUnsafeGenerator/Processing/FunctionProcessor.cs
index 5ae286e9..e14486f0 100644
--- a/FFmpeg.AutoGen.CppSharpUnsafeGenerator/Processing/FunctionProcessor.cs
+++ b/FFmpeg.AutoGen.CppSharpUnsafeGenerator/Processing/FunctionProcessor.cs
@@ -12,8 +12,6 @@ internal class FunctionProcessor
{
private const string ReturnMarshalAsConstCharPtr = "[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ConstCharPtrMarshaler))]";
- private const string ReturnMarshalAsLPUTF8Str = "[return: MarshalAs(UnmanagedType.LPUTF8Str)]";
-
private const string MarshalAsUTF8Macros =
" \r\n" +
" #if NETSTANDARD2_1_OR_GREATER\r\n" +
@@ -169,10 +167,17 @@ private TypeDefinition GetReturnType(Type type, string name)
{
return builtinType.Type switch
{
+ // For const char* return values we must always use ConstCharPtrMarshaler and never
+ // [return: MarshalAs(UnmanagedType.LPUTF8Str)]. LPUTF8Str on a return value makes the
+ // CLR call CoTaskMemFree on the pointer after copying the string, but FFmpeg returns
+ // pointers to static/borrowed memory (e.g. av_version_info, avcodec_get_name,
+ // av_get_pix_fmt_name, AVClass names). Freeing that memory corrupts the native heap
+ // and leads to AccessViolationException. The NoCustomStringMarshal option only makes
+ // sense for parameters, where the CLR owns the marshalled buffer.
PrimitiveType.Char => new TypeDefinition
{
Name = "string",
- Attributes = new[] { _context.NoCustomStringMarshal ? ReturnMarshalAsLPUTF8Str : ReturnMarshalAsConstCharPtr }
+ Attributes = new[] { ReturnMarshalAsConstCharPtr }
},
PrimitiveType.Void => new TypeDefinition
{
diff --git a/FFmpeg.AutoGen.sln b/FFmpeg.AutoGen.sln
index 0fb39f25..e4b7e306 100644
--- a/FFmpeg.AutoGen.sln
+++ b/FFmpeg.AutoGen.sln
@@ -38,48 +38,138 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3. Legacy", "3. Legacy", "{
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2. Abstractions", "2. Abstractions", "{1B9CC2D2-096F-4D35-8940-DA6344450430}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFmpeg.AutoGen.Abstractions.Test", "FFmpeg.AutoGen.Abstractions.Test\FFmpeg.AutoGen.Abstractions.Test.csproj", "{931DC899-6676-4884-A46C-060A4786DE78}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A536B02A-B0B1-4753-8328-17369EF09976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A536B02A-B0B1-4753-8328-17369EF09976}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A536B02A-B0B1-4753-8328-17369EF09976}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A536B02A-B0B1-4753-8328-17369EF09976}.Debug|x64.Build.0 = Debug|Any CPU
+ {A536B02A-B0B1-4753-8328-17369EF09976}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A536B02A-B0B1-4753-8328-17369EF09976}.Debug|x86.Build.0 = Debug|Any CPU
{A536B02A-B0B1-4753-8328-17369EF09976}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A536B02A-B0B1-4753-8328-17369EF09976}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A536B02A-B0B1-4753-8328-17369EF09976}.Release|x64.ActiveCfg = Release|Any CPU
+ {A536B02A-B0B1-4753-8328-17369EF09976}.Release|x64.Build.0 = Release|Any CPU
+ {A536B02A-B0B1-4753-8328-17369EF09976}.Release|x86.ActiveCfg = Release|Any CPU
+ {A536B02A-B0B1-4753-8328-17369EF09976}.Release|x86.Build.0 = Release|Any CPU
{5BD870D6-27B4-4208-ACBF-496F2809326A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BD870D6-27B4-4208-ACBF-496F2809326A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5BD870D6-27B4-4208-ACBF-496F2809326A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5BD870D6-27B4-4208-ACBF-496F2809326A}.Debug|x64.Build.0 = Debug|Any CPU
+ {5BD870D6-27B4-4208-ACBF-496F2809326A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5BD870D6-27B4-4208-ACBF-496F2809326A}.Debug|x86.Build.0 = Debug|Any CPU
{5BD870D6-27B4-4208-ACBF-496F2809326A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BD870D6-27B4-4208-ACBF-496F2809326A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5BD870D6-27B4-4208-ACBF-496F2809326A}.Release|x64.ActiveCfg = Release|Any CPU
+ {5BD870D6-27B4-4208-ACBF-496F2809326A}.Release|x64.Build.0 = Release|Any CPU
+ {5BD870D6-27B4-4208-ACBF-496F2809326A}.Release|x86.ActiveCfg = Release|Any CPU
+ {5BD870D6-27B4-4208-ACBF-496F2809326A}.Release|x86.Build.0 = Release|Any CPU
{2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Debug|x64.Build.0 = Debug|Any CPU
+ {2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Debug|x86.Build.0 = Debug|Any CPU
{2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Release|x64.ActiveCfg = Release|Any CPU
+ {2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Release|x64.Build.0 = Release|Any CPU
+ {2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Release|x86.ActiveCfg = Release|Any CPU
+ {2A8E06C6-5A68-4FB4-AE0C-F43B644E3737}.Release|x86.Build.0 = Release|Any CPU
{4557AF4F-4680-4764-A7A0-F739664B4DA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4557AF4F-4680-4764-A7A0-F739664B4DA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4557AF4F-4680-4764-A7A0-F739664B4DA1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4557AF4F-4680-4764-A7A0-F739664B4DA1}.Debug|x64.Build.0 = Debug|Any CPU
+ {4557AF4F-4680-4764-A7A0-F739664B4DA1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4557AF4F-4680-4764-A7A0-F739664B4DA1}.Debug|x86.Build.0 = Debug|Any CPU
{4557AF4F-4680-4764-A7A0-F739664B4DA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4557AF4F-4680-4764-A7A0-F739664B4DA1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4557AF4F-4680-4764-A7A0-F739664B4DA1}.Release|x64.ActiveCfg = Release|Any CPU
+ {4557AF4F-4680-4764-A7A0-F739664B4DA1}.Release|x64.Build.0 = Release|Any CPU
+ {4557AF4F-4680-4764-A7A0-F739664B4DA1}.Release|x86.ActiveCfg = Release|Any CPU
+ {4557AF4F-4680-4764-A7A0-F739664B4DA1}.Release|x86.Build.0 = Release|Any CPU
{07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Debug|x64.Build.0 = Debug|Any CPU
+ {07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Debug|x86.Build.0 = Debug|Any CPU
{07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Release|x64.ActiveCfg = Release|Any CPU
+ {07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Release|x64.Build.0 = Release|Any CPU
+ {07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Release|x86.ActiveCfg = Release|Any CPU
+ {07F3A6D4-5599-4D77-9197-A666E8FC8EEC}.Release|x86.Build.0 = Release|Any CPU
{1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Debug|x64.Build.0 = Debug|Any CPU
+ {1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Debug|x86.Build.0 = Debug|Any CPU
{1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Release|x64.ActiveCfg = Release|Any CPU
+ {1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Release|x64.Build.0 = Release|Any CPU
+ {1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Release|x86.ActiveCfg = Release|Any CPU
+ {1294EC3A-9667-40A2-8189-C9EC12ECFDD6}.Release|x86.Build.0 = Release|Any CPU
{ED2E790B-9233-40BC-BA66-E3291AAA9878}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED2E790B-9233-40BC-BA66-E3291AAA9878}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ED2E790B-9233-40BC-BA66-E3291AAA9878}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {ED2E790B-9233-40BC-BA66-E3291AAA9878}.Debug|x64.Build.0 = Debug|Any CPU
+ {ED2E790B-9233-40BC-BA66-E3291AAA9878}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {ED2E790B-9233-40BC-BA66-E3291AAA9878}.Debug|x86.Build.0 = Debug|Any CPU
{ED2E790B-9233-40BC-BA66-E3291AAA9878}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED2E790B-9233-40BC-BA66-E3291AAA9878}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ED2E790B-9233-40BC-BA66-E3291AAA9878}.Release|x64.ActiveCfg = Release|Any CPU
+ {ED2E790B-9233-40BC-BA66-E3291AAA9878}.Release|x64.Build.0 = Release|Any CPU
+ {ED2E790B-9233-40BC-BA66-E3291AAA9878}.Release|x86.ActiveCfg = Release|Any CPU
+ {ED2E790B-9233-40BC-BA66-E3291AAA9878}.Release|x86.Build.0 = Release|Any CPU
{C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Debug|x64.Build.0 = Debug|Any CPU
+ {C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Debug|x86.Build.0 = Debug|Any CPU
{C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Release|x64.ActiveCfg = Release|Any CPU
+ {C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Release|x64.Build.0 = Release|Any CPU
+ {C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Release|x86.ActiveCfg = Release|Any CPU
+ {C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0}.Release|x86.Build.0 = Release|Any CPU
{028113A5-39CE-40A2-9118-56E99F30B4F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{028113A5-39CE-40A2-9118-56E99F30B4F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {028113A5-39CE-40A2-9118-56E99F30B4F8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {028113A5-39CE-40A2-9118-56E99F30B4F8}.Debug|x64.Build.0 = Debug|Any CPU
+ {028113A5-39CE-40A2-9118-56E99F30B4F8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {028113A5-39CE-40A2-9118-56E99F30B4F8}.Debug|x86.Build.0 = Debug|Any CPU
{028113A5-39CE-40A2-9118-56E99F30B4F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{028113A5-39CE-40A2-9118-56E99F30B4F8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {028113A5-39CE-40A2-9118-56E99F30B4F8}.Release|x64.ActiveCfg = Release|Any CPU
+ {028113A5-39CE-40A2-9118-56E99F30B4F8}.Release|x64.Build.0 = Release|Any CPU
+ {028113A5-39CE-40A2-9118-56E99F30B4F8}.Release|x86.ActiveCfg = Release|Any CPU
+ {028113A5-39CE-40A2-9118-56E99F30B4F8}.Release|x86.Build.0 = Release|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Debug|x64.Build.0 = Debug|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Debug|x86.Build.0 = Debug|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Release|Any CPU.Build.0 = Release|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Release|x64.ActiveCfg = Release|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Release|x64.Build.0 = Release|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Release|x86.ActiveCfg = Release|Any CPU
+ {931DC899-6676-4884-A46C-060A4786DE78}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -92,6 +182,7 @@ Global
{07F3A6D4-5599-4D77-9197-A666E8FC8EEC} = {E9CC1A7F-ADD1-486B-BEB3-B6E388B35731}
{F969EEDD-27A8-434B-A438-39C48BBAEFE6} = {1B9CC2D2-096F-4D35-8940-DA6344450430}
{1294EC3A-9667-40A2-8189-C9EC12ECFDD6} = {1B9CC2D2-096F-4D35-8940-DA6344450430}
+ {931DC899-6676-4884-A46C-060A4786DE78} = {1B9CC2D2-096F-4D35-8940-DA6344450430}
{ED2E790B-9233-40BC-BA66-E3291AAA9878} = {F969EEDD-27A8-434B-A438-39C48BBAEFE6}
{C272CD0A-E13B-4E80-8CA2-2BEC8396D8D0} = {F969EEDD-27A8-434B-A438-39C48BBAEFE6}
{028113A5-39CE-40A2-9118-56E99F30B4F8} = {F969EEDD-27A8-434B-A438-39C48BBAEFE6}
diff --git a/FFmpeg.AutoGen/ConstCharPtrMarshaler.cs b/FFmpeg.AutoGen/ConstCharPtrMarshaler.cs
index 3bcca9c6..5424b7f5 100644
--- a/FFmpeg.AutoGen/ConstCharPtrMarshaler.cs
+++ b/FFmpeg.AutoGen/ConstCharPtrMarshaler.cs
@@ -6,7 +6,27 @@ namespace FFmpeg.AutoGen;
public class ConstCharPtrMarshaler : ICustomMarshaler
{
private static readonly ConstCharPtrMarshaler Instance = new();
- public object MarshalNativeToManaged(IntPtr pNativeData) => Marshal.PtrToStringAnsi(pNativeData);
+
+ public object MarshalNativeToManaged(IntPtr pNativeData)
+ {
+#if NETSTANDARD2_1_OR_GREATER
+ return Marshal.PtrToStringUTF8(pNativeData);
+#else
+ if (pNativeData == IntPtr.Zero)
+ return null;
+
+ var length = 0;
+ while (Marshal.ReadByte(pNativeData, length) != 0)
+ length++;
+
+ if (length == 0)
+ return string.Empty;
+
+ var buffer = new byte[length];
+ Marshal.Copy(pNativeData, buffer, 0, length);
+ return System.Text.Encoding.UTF8.GetString(buffer);
+#endif
+ }
public IntPtr MarshalManagedToNative(object managedObj) => IntPtr.Zero;