Compare commits

..

No commits in common. "d90ebb826f7849173f49e027852532d59c651611" and "e3321ff2793346241d0756005a9378c04a153b91" have entirely different histories.

4 changed files with 12 additions and 100 deletions

View file

@ -5,19 +5,19 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{46E05E34-8A87-4986-87D3-FE0DE4E05F44}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine", "src/TeamsISO.Engine/TeamsISO.Engine.csproj", "{F0D24EAE-9225-4DC4-B3D2-6966077287A0}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine", "src\TeamsISO.Engine\TeamsISO.Engine.csproj", "{F0D24EAE-9225-4DC4-B3D2-6966077287A0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.NdiInterop", "src/TeamsISO.Engine.NdiInterop/TeamsISO.Engine.NdiInterop.csproj", "{E737E54B-73DE-4F74-909C-1F0F5CF82AC6}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.NdiInterop", "src\TeamsISO.Engine.NdiInterop\TeamsISO.Engine.NdiInterop.csproj", "{E737E54B-73DE-4F74-909C-1F0F5CF82AC6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DBDF4A1D-4215-42D5-B456-2CE7159DF848}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.Tests", "src/tests/TeamsISO.Engine.Tests/TeamsISO.Engine.Tests.csproj", "{F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.Tests", "src\tests\TeamsISO.Engine.Tests\TeamsISO.Engine.Tests.csproj", "{F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.App", "src/TeamsISO.App/TeamsISO.App.csproj", "{80DCE039-3BBC-4D3F-B44B-51F324591C29}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.App", "src\TeamsISO.App\TeamsISO.App.csproj", "{80DCE039-3BBC-4D3F-B44B-51F324591C29}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.IntegrationTests", "src/tests/TeamsISO.Engine.IntegrationTests/TeamsISO.Engine.IntegrationTests.csproj", "{A85E331D-026E-4BDE-B89C-0CC4C95001CE}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.IntegrationTests", "src\tests\TeamsISO.Engine.IntegrationTests\TeamsISO.Engine.IntegrationTests.csproj", "{A85E331D-026E-4BDE-B89C-0CC4C95001CE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Console", "src/TeamsISO.Console/TeamsISO.Console.csproj", "{C3254998-9428-4264-A8FB-EAC9E1F9F432}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Console", "src\TeamsISO.Console\TeamsISO.Console.csproj", "{C3254998-9428-4264-A8FB-EAC9E1F9F432}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -4,30 +4,13 @@ namespace TeamsISO.Engine.NdiInterop;
/// <summary>
/// P/Invoke declarations for the NewTek/Vizrt NDI SDK 6 native library.
/// On Windows the import target is <c>Processing.NDI.Lib.x64.dll</c>. Resolution of
/// the DLL is handled by <see cref="NdiNativeLibraryResolver"/>, which loads it from
/// the NDI Runtime installation directory exposed by the
/// <c>NDI_RUNTIME_DIR_V&lt;n&gt;</c> environment variables — the NDI installer sets
/// these but does not always add the runtime directory to <c>PATH</c>, so a default
/// loader-based resolution would fail with <c>0x8007007E</c> on otherwise correctly
/// installed machines.
/// On Windows the import target is <c>Processing.NDI.Lib.x64.dll</c>; the loader resolves it
/// from the NDI Runtime installation path or from the application directory.
/// </summary>
internal static class NdiNative
{
private const string LibName = "Processing.NDI.Lib.x64";
/// <summary>
/// Registers <see cref="NdiNativeLibraryResolver"/> with the runtime before any
/// P/Invoke against <see cref="LibName"/> fires. The static constructor is
/// guaranteed to run before the first access to any member of this type, which
/// includes the very first <c>[DllImport]</c> call site, so the resolver is
/// always in place when the loader needs it.
/// </summary>
static NdiNative()
{
NdiNativeLibraryResolver.Register();
}
// ---- Lifecycle ----
[DllImport(LibName, EntryPoint = "NDIlib_initialize", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.U1)]

View file

@ -1,66 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
namespace TeamsISO.Engine.NdiInterop;
/// <summary>
/// Resolves the NDI native library (<c>Processing.NDI.Lib.x64.dll</c>) from the
/// NDI Runtime install directory exposed via <c>NDI_RUNTIME_DIR_V&lt;n&gt;</c>
/// environment variables.
///
/// The NDI installer creates these env vars but does not always add the runtime
/// directory to <c>PATH</c>, so a default <c>[DllImport("Processing.NDI.Lib.x64")]</c>
/// can fail with <c>0x8007007E</c> ("the specified module could not be found") on
/// otherwise correctly installed machines. This resolver patches that gap by
/// pre-loading the DLL from the install dir before the runtime falls back to its
/// default search algorithm.
///
/// The resolver is registered from the static constructor of <see cref="NdiNative"/>,
/// which guarantees it runs before the first P/Invoke on that type fires. On
/// non-Windows the resolver short-circuits; the assembly's P/Invokes are gated by
/// <c>[SupportedOSPlatform("windows")]</c> and won't fire there in any case.
/// </summary>
internal static class NdiNativeLibraryResolver
{
private const string NdiX64LibraryName = "Processing.NDI.Lib.x64";
private const string NdiX64LibraryFile = "Processing.NDI.Lib.x64.dll";
/// <summary>
/// NDI runtime install-dir environment variables, in preference order.
/// V6 is what TeamsISO is built against; V5/V4 are listed as graceful fallbacks
/// for installs that pre-date V6 — the runtime probe will still report a
/// version mismatch, but at least the DLL will load and the engine can surface
/// a clear alert instead of dying with DllNotFoundException.
/// </summary>
private static readonly string[] EnvVarFallbacks =
{
"NDI_RUNTIME_DIR_V6",
"NDI_RUNTIME_DIR_V5",
"NDI_RUNTIME_DIR_V4",
};
internal static void Register()
{
NativeLibrary.SetDllImportResolver(typeof(NdiNativeLibraryResolver).Assembly, Resolve);
}
private static IntPtr Resolve(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName != NdiX64LibraryName) return IntPtr.Zero;
if (!OperatingSystem.IsWindows()) return IntPtr.Zero;
foreach (var envVar in EnvVarFallbacks)
{
var dir = Environment.GetEnvironmentVariable(envVar);
if (string.IsNullOrEmpty(dir)) continue;
var path = Path.Combine(dir, NdiX64LibraryFile);
if (!File.Exists(path)) continue;
if (NativeLibrary.TryLoad(path, out var handle))
return handle;
}
// Fall through to default loader (PATH, app dir, etc.) — preserves the
// chance that someone added the NDI dir to PATH manually.
return IntPtr.Zero;
}
}

View file

@ -12,14 +12,9 @@ public static class NdiVersion
public const string SdkFamily = "NDI 6";
/// <summary>
/// Prefix of the runtime version banner we expect. The shipping NDI 6 runtime
/// reports its version as a build banner of the form
/// "NDI SDK WIN64 13:07:00 Jun 2 2025 6.2.0.3"
/// where the architecture token ("WIN64" / "WIN32") is stable across patch
/// releases and confirms we loaded the binary the engine P/Invokes against
/// (Processing.NDI.Lib.x64.dll). The trailing four-part token is the SDK's
/// numeric version. The probe checks this prefix; a stricter probe could
/// additionally enforce the leading "6." in the trailing version token.
/// Prefix of the runtime version string we expect (NDI runtime reports e.g.
/// "NDI SDK for Windows v6.0.1.0"). Any major change of the leading "v6" is treated
/// as a mismatch.
/// </summary>
public const string ExpectedRuntimeVersionPrefix = "NDI SDK WIN64";
public const string ExpectedRuntimeVersionPrefix = "NDI SDK for Windows v6";
}