Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions FFmpeg.AutoGen.Abstractions.Test/ConstCharPtrMarshalerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Runtime.InteropServices;
using System.Text;

namespace FFmpeg.AutoGen.Abstractions.Test
{
/// <summary>
/// Tests for <see cref="ConstCharPtrMarshaler"/>, the custom marshaler used for
/// <c>const char*</c> 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).
/// </summary>
[TestClass]
public class ConstCharPtrMarshalerTest
{
private readonly List<IntPtr> _allocations = new();

private static ICustomMarshaler Marshaler => ConstCharPtrMarshaler.GetInstance(null!);

[TestCleanup]
public void Cleanup()
{
foreach (var ptr in _allocations)
Marshal.FreeHGlobal(ptr);
_allocations.Clear();
}

/// <summary>Allocates a NUL-terminated UTF-8 C string in unmanaged memory.</summary>
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<!--
Multi-targeted on purpose: net9.0 resolves the netstandard2.1 build of
FFmpeg.AutoGen.Abstractions (Marshal.PtrToStringUTF8 path) while net48
resolves the netstandard2.0 build (manual UTF-8 decode path). This way the
ConstCharPtrMarshaler tests exercise both compile-time branches.

net48 is only added on Windows: the .NET Framework test host cannot run on
Linux/macOS, so on non-Windows hosts we build and run net9.0 only (which
still covers the netstandard2.1 branch). The netstandard2.0 branch is
validated on Windows.
-->
<TargetFrameworks>net9.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OS)' == 'Windows_NT'">net9.0;net48</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FFmpeg.AutoGen.Abstractions\FFmpeg.AutoGen.Abstractions.csproj" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions FFmpeg.AutoGen.Abstractions.Test/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Microsoft.VisualStudio.TestTools.UnitTesting;
22 changes: 21 additions & 1 deletion FFmpeg.AutoGen.Abstractions/ConstCharPtrMarshaler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" +
Expand Down Expand Up @@ -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
{
Expand Down
91 changes: 91 additions & 0 deletions FFmpeg.AutoGen.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}
Expand Down
22 changes: 21 additions & 1 deletion FFmpeg.AutoGen/ConstCharPtrMarshaler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down