diff --git a/cmake/external/onnx b/cmake/external/onnx index 2bb50465112fe..be2b5fde82d9c 160000 --- a/cmake/external/onnx +++ b/cmake/external/onnx @@ -1 +1 @@ -Subproject commit 2bb50465112feca9003e1ed654d77f01ff1415ca +Subproject commit be2b5fde82d9c8874f3d19328bdfe3b6962dc67b diff --git a/cmake/onnxruntime_providers_openvino.cmake b/cmake/onnxruntime_providers_openvino.cmake index 1584d9f703575..60575c3b9b931 100644 --- a/cmake/onnxruntime_providers_openvino.cmake +++ b/cmake/onnxruntime_providers_openvino.cmake @@ -98,3 +98,280 @@ set_target_properties(onnxruntime_providers_openvino PROPERTIES MAP_IMPORTED_CONFIG_RELEASE RelWithDebInfo MAP_IMPORTED_CONFIG_DEBUG RelWithDebInfo ) + +# --------------------------------------------------------------------------- +# Windows SxS (Side-by-Side) loading support +# +# Embeds private-assembly manifests into OV/TBB DLLs and the provider DLL so +# that Windows loads them from the EP's own directory, avoiding DLL collisions +# when another application in the same process also uses OpenVINO. +# +# Build-time approach: OV/TBB DLLs are staged into /sxs_staging//, +# Authenticode signatures are stripped, and SxS dep manifests are embedded. +# The provider DLL gets its dep manifest via a POST_BUILD step. +# --------------------------------------------------------------------------- +if(WIN32) + # --- Locate Windows SDK tools --- + # Locate a tool in the newest available Windows SDK x64 bin directory. + function(ort_find_winsdk_tool out_var exe_name) + if(${out_var}) + return() + endif() + set(_hints "") + if(DEFINED ENV{WDKBinRoot}) + list(APPEND _hints "$ENV{WDKBinRoot}/x64") + endif() + file(GLOB _glob_hints + "C:/Program Files (x86)/Windows Kits/10/bin/10.*/x64" + "C:/Program Files/Windows Kits/10/bin/10.*/x64") + list(SORT _glob_hints COMPARE NATURAL ORDER DESCENDING) + list(APPEND _hints ${_glob_hints}) + find_program(${out_var} "${exe_name}" HINTS ${_hints} NO_DEFAULT_PATH) + if(NOT ${out_var}) + message(FATAL_ERROR + "${exe_name}.exe not found in known Windows SDK paths.\n" + "Set -D${out_var}= to override.") + endif() + message(STATUS "Found ${exe_name}.exe: ${${out_var}}") + endfunction() + + ort_find_winsdk_tool(CMAKE_MT mt) + ort_find_winsdk_tool(ORT_SIGNTOOL_EXE signtool) + + # --- Enumerate OV and TBB DLL filenames for the SxS assembly manifest --- + # Glob patterns selecting the OV binaries needed by the EP. + set(_ORT_OV_PATTERNS + "cache.json" + "*openvino.*" + "*openvinod.*" + "*openvino*plugin*" + "*openvino*compiler*" + "*openvino_ir_frontend*" + "*openvino_onnx_frontend*") + + function(_ort_glob_ov _out_var _dir) + set(_result "") + foreach(_pat IN LISTS _ORT_OV_PATTERNS) + file(GLOB _tmp "${_dir}/${_pat}") + list(APPEND _result ${_tmp}) + endforeach() + set(${_out_var} "${_result}" PARENT_SCOPE) + endfunction() + + if(DEFINED ENV{INTEL_OPENVINO_DIR}) + file(TO_CMAKE_PATH "$ENV{INTEL_OPENVINO_DIR}" _ORT_OV_ROOT) + set(_ov_bin "${_ORT_OV_ROOT}/runtime/bin/intel64") + set(_tbb_bin "${_ORT_OV_ROOT}/runtime/3rdparty/tbb/bin") + + # TBB: split into non-debug (Release/RelWithDebInfo) and debug subsets + file(GLOB _all_tbb "${_tbb_bin}/tbb*.dll") + set(_tbb_release "") + set(_tbb_debug "") + foreach(_f IN LISTS _all_tbb) + get_filename_component(_lname "${_f}" NAME) + string(TOLOWER "${_lname}" _lname_lower) + if(_lname_lower MATCHES "debug") + list(APPEND _tbb_debug "${_f}") + else() + list(APPEND _tbb_release "${_f}") + endif() + endforeach() + + if(EXISTS "${_ov_bin}/Release") + _ort_glob_ov(_ov_release "${_ov_bin}/Release") + set(ORT_OV_TBB_INSTALL_FILES_Release ${_ov_release} ${_tbb_release}) + endif() + if(EXISTS "${_ov_bin}/Debug") + _ort_glob_ov(_ov_debug "${_ov_bin}/Debug") + set(ORT_OV_TBB_INSTALL_FILES_Debug ${_ov_debug} ${_tbb_debug}) + endif() + if(EXISTS "${_ov_bin}/RelWithDebInfo") + _ort_glob_ov(_ov_rwdi "${_ov_bin}/RelWithDebInfo") + set(ORT_OV_TBB_INSTALL_FILES_RelWithDebInfo ${_ov_rwdi} ${_tbb_release}) + elseif(DEFINED ORT_OV_TBB_INSTALL_FILES_Release) + set(ORT_OV_TBB_INSTALL_FILES_RelWithDebInfo ${ORT_OV_TBB_INSTALL_FILES_Release}) + endif() + else() + # Python wheel layout — all binaries flat in /openvino/libs/ + set(Python3_FIND_VIRTUALENV FIRST) + find_package(Python3 QUIET COMPONENTS Interpreter) + if(Python3_FOUND) + execute_process( + COMMAND "${Python3_EXECUTABLE}" -c + "import openvino, pathlib; print(pathlib.Path(openvino.__file__).parent)" + OUTPUT_VARIABLE _ORT_OV_WHEEL_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _ORT_OV_WHEEL_RESULT) + endif() + if(Python3_FOUND AND _ORT_OV_WHEEL_RESULT EQUAL 0 AND _ORT_OV_WHEEL_DIR) + file(TO_CMAKE_PATH "${_ORT_OV_WHEEL_DIR}" _ORT_OV_ROOT) + set(_libs "${_ORT_OV_ROOT}/libs") + if(EXISTS "${_libs}") + _ort_glob_ov(_ov_wheel "${_libs}") + file(GLOB _tbb_wheel "${_libs}/tbb*.dll") + set(ORT_OV_TBB_INSTALL_FILES_wheel ${_ov_wheel} ${_tbb_wheel}) + endif() + endif() + endif() + + # Collect unique DLL filenames for SxS dep manifest embedding + set(_all_files + ${ORT_OV_TBB_INSTALL_FILES_Release} + ${ORT_OV_TBB_INSTALL_FILES_Debug} + ${ORT_OV_TBB_INSTALL_FILES_RelWithDebInfo} + ${ORT_OV_TBB_INSTALL_FILES_wheel}) + set(ORT_OV_TBB_DLL_NAMES "") + foreach(_f IN LISTS _all_files) + get_filename_component(_ext "${_f}" EXT) + get_filename_component(_name "${_f}" NAME) + if(_ext STREQUAL ".dll") + list(APPEND ORT_OV_TBB_DLL_NAMES "${_name}") + endif() + endforeach() + list(REMOVE_DUPLICATES ORT_OV_TBB_DLL_NAMES) + + if(ORT_OV_TBB_DLL_NAMES) + message(STATUS "OpenVINO SxS: DLLs for manifest: ${ORT_OV_TBB_DLL_NAMES}") + + # --- Pre-generate SxS manifests at configure time --- + set(_sxs_manifest_dir "${CMAKE_BINARY_DIR}/sxs_manifests") + file(MAKE_DIRECTORY "${_sxs_manifest_dir}") + set(_ep_file_version "${ORT_VERSION}.0") + + # Per-DLL dep manifests (dep.manifest.in uses ${DLL_BASE_NAME} and ${EP_FILE_VERSION}) + foreach(_dll_name IN LISTS ORT_OV_TBB_DLL_NAMES) + get_filename_component(_dll_we "${_dll_name}" NAME_WE) + set(DLL_BASE_NAME "${_dll_we}") + set(EP_FILE_VERSION "${_ep_file_version}") + configure_file( + "${CMAKE_CURRENT_LIST_DIR}/sxs/dep.manifest.in" + "${_sxs_manifest_dir}/${_dll_we}.dep.manifest" + ) + endforeach() + + # Per-config assembly manifests (only list DLLs that belong to each config) + set(ORT_SXS_VERSION "${ORT_VERSION}") + set(_asm_configs Release Debug RelWithDebInfo) + if(DEFINED ORT_OV_TBB_INSTALL_FILES_wheel) + set(_asm_configs wheel) + endif() + + foreach(_cfg IN LISTS _asm_configs) + set(ORT_SXS_ASSEMBLY_FILE_ENTRIES "") + foreach(_f IN LISTS ORT_OV_TBB_INSTALL_FILES_${_cfg}) + get_filename_component(_ext "${_f}" EXT) + get_filename_component(_name "${_f}" NAME) + if(_ext STREQUAL ".dll") + string(APPEND ORT_SXS_ASSEMBLY_FILE_ENTRIES " \n") + endif() + endforeach() + configure_file( + "${CMAKE_CURRENT_LIST_DIR}/sxs/assembly.manifest.in" + "${_sxs_manifest_dir}/openvino_runtime_${_cfg}.manifest" + @ONLY) + set(ORT_SXS_ASSEMBLY_MANIFEST_${_cfg} "${_sxs_manifest_dir}/openvino_runtime_${_cfg}.manifest") + endforeach() + + # --- Build-time staging: copy, strip signatures, embed dep manifests --- + set(_sxs_staging_root "${CMAKE_BINARY_DIR}/sxs_staging") + + function(_ort_sxs_stage_file _out_dst _cfg _src) + get_filename_component(_name "${_src}" NAME) + get_filename_component(_ext "${_src}" EXT) + set(_dst "${_sxs_staging_root}/${_cfg}/${_name}") + + if(_ext STREQUAL ".dll") + get_filename_component(_name_we "${_src}" NAME_WE) + add_custom_command( + OUTPUT "${_dst}" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${_sxs_staging_root}/${_cfg}" + COMMAND "${CMAKE_COMMAND}" -E copy "${_src}" "${_dst}" + COMMAND "${CMAKE_COMMAND}" + "-DSIGNTOOL=${ORT_SIGNTOOL_EXE}" + "-DDLL_PATH=${_dst}" + -P "${CMAKE_CURRENT_LIST_DIR}/sxs/remove_signature.cmake" + COMMAND "${CMAKE_COMMAND}" + "-DCMAKE_MT=${CMAKE_MT}" + "-DDLL_PATH=${_dst}" + "-DMANIFEST_PATH=${_sxs_manifest_dir}/${_name_we}.dep.manifest" + -P "${CMAKE_CURRENT_LIST_DIR}/sxs/embed_manifest.cmake" + DEPENDS "${_src}" "${_sxs_manifest_dir}/${_name_we}.dep.manifest" + COMMENT "SxS ${_cfg}/${_name}: remove signature + embed manifest" + VERBATIM + ) + else() + add_custom_command( + OUTPUT "${_dst}" + COMMAND "${CMAKE_COMMAND}" -E make_directory "${_sxs_staging_root}/${_cfg}" + COMMAND "${CMAKE_COMMAND}" -E copy "${_src}" "${_dst}" + DEPENDS "${_src}" + COMMENT "SxS ${_cfg}/${_name}: copy" + VERBATIM + ) + endif() + set(${_out_dst} "${_dst}" PARENT_SCOPE) + endfunction() + + if(DEFINED ORT_OV_TBB_INSTALL_FILES_wheel) + set(_staged_wheel "") + foreach(_src IN LISTS ORT_OV_TBB_INSTALL_FILES_wheel) + _ort_sxs_stage_file(_dst "$" "${_src}") + list(APPEND _staged_wheel "${_dst}") + endforeach() + set(ORT_OV_TBB_STAGED_FILES_wheel "${_staged_wheel}") + add_custom_target(ort_embed_ov_tbb_manifests ALL DEPENDS ${ORT_OV_TBB_STAGED_FILES_wheel}) + else() + set(_all_staged "") + foreach(_cfg IN ITEMS Release Debug RelWithDebInfo) + foreach(_src IN LISTS ORT_OV_TBB_INSTALL_FILES_${_cfg}) + _ort_sxs_stage_file(_dst "${_cfg}" "${_src}") + list(APPEND _all_staged "${_dst}") + list(APPEND ORT_OV_TBB_STAGED_FILES_${_cfg} "${_dst}") + endforeach() + endforeach() + add_custom_target(ort_embed_ov_tbb_manifests ALL DEPENDS ${_all_staged}) + endif() + + # --- Post-build: embed dep manifest into onnxruntime_providers_openvino.dll --- + set(DLL_BASE_NAME "onnxruntime_providers_openvino") + set(EP_FILE_VERSION "${_ep_file_version}") + configure_file( + "${CMAKE_CURRENT_LIST_DIR}/sxs/dep.manifest.in" + "${_sxs_manifest_dir}/onnxruntime_providers_openvino.dep.manifest" + ) + add_custom_command(TARGET onnxruntime_providers_openvino POST_BUILD + COMMAND "${CMAKE_COMMAND}" + "-DCMAKE_MT=${CMAKE_MT}" + "-DDLL_PATH=$" + "-DMANIFEST_PATH=${_sxs_manifest_dir}/onnxruntime_providers_openvino.dep.manifest" + -P "${CMAKE_CURRENT_LIST_DIR}/sxs/embed_manifest.cmake" + COMMENT "SxS: embedding dep manifest into onnxruntime_providers_openvino.dll" + VERBATIM + ) + + # --- Install staged OV+TBB binaries and assembly manifest --- + if(DEFINED ORT_OV_TBB_STAGED_FILES_wheel) + install(FILES ${ORT_OV_TBB_STAGED_FILES_wheel} DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(FILES "${ORT_SXS_ASSEMBLY_MANIFEST_wheel}" + RENAME "openvino_runtime.manifest" + DESTINATION ${CMAKE_INSTALL_BINDIR}) + else() + foreach(_config IN ITEMS Release Debug RelWithDebInfo) + if(ORT_OV_TBB_STAGED_FILES_${_config}) + install(FILES ${ORT_OV_TBB_STAGED_FILES_${_config}} + DESTINATION ${CMAKE_INSTALL_BINDIR} + CONFIGURATIONS ${_config}) + endif() + if(ORT_SXS_ASSEMBLY_MANIFEST_${_config}) + install(FILES "${ORT_SXS_ASSEMBLY_MANIFEST_${_config}}" + RENAME "openvino_runtime.manifest" + DESTINATION ${CMAKE_INSTALL_BINDIR} + CONFIGURATIONS ${_config}) + endif() + endforeach() + endif() + else() + message(WARNING "OpenVINO SxS: No OV/TBB DLLs found — SxS manifest embedding skipped.\n" + "Ensure INTEL_OPENVINO_DIR is set (setupvars.bat) or the openvino wheel is installed.") + endif() +endif() diff --git a/cmake/onnxruntime_unittests.cmake b/cmake/onnxruntime_unittests.cmake index 23eccb22476df..c6acda1a83973 100644 --- a/cmake/onnxruntime_unittests.cmake +++ b/cmake/onnxruntime_unittests.cmake @@ -1187,6 +1187,9 @@ endif() if (onnxruntime_ENABLE_TRAINING_TORCH_INTEROP) target_link_libraries(onnxruntime_test_all PRIVATE Python::Python) endif() +if (WIN32 AND onnxruntime_USE_OPENVINO) + target_link_libraries(onnxruntime_test_all PRIVATE psapi) +endif() onnxruntime_apply_emscripten_test_link_settings(onnxruntime_test_all) if (onnxruntime_ENABLE_ATEN) diff --git a/cmake/sxs/assembly.manifest.in b/cmake/sxs/assembly.manifest.in new file mode 100644 index 0000000000000..bf67fac66ab14 --- /dev/null +++ b/cmake/sxs/assembly.manifest.in @@ -0,0 +1,12 @@ + + + + +@ORT_SXS_ASSEMBLY_FILE_ENTRIES@ diff --git a/cmake/sxs/dep.manifest.in b/cmake/sxs/dep.manifest.in new file mode 100644 index 0000000000000..8d6c180fd37cc --- /dev/null +++ b/cmake/sxs/dep.manifest.in @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/cmake/sxs/embed_manifest.cmake b/cmake/sxs/embed_manifest.cmake new file mode 100644 index 0000000000000..32bd767c5250b --- /dev/null +++ b/cmake/sxs/embed_manifest.cmake @@ -0,0 +1,24 @@ +# Copyright (C) Intel Corporation +# Licensed under the MIT License +# +# cmake -P script: embed a manifest file into a PE binary as RT_MANIFEST resource ID 2. +# +# Variables (passed via -D): +# CMAKE_MT — absolute path to mt.exe +# DLL_PATH — absolute path to the PE file to modify +# MANIFEST_PATH — absolute path to the .manifest file to embed +cmake_minimum_required(VERSION 3.28) + +execute_process( + COMMAND "${CMAKE_MT}" -nologo + "-manifest" "${MANIFEST_PATH}" + "-outputresource:${DLL_PATH};2" + RESULT_VARIABLE _rc + OUTPUT_VARIABLE _out + ERROR_VARIABLE _err +) + +if(NOT _rc EQUAL 0) + message(FATAL_ERROR + "embed_manifest: mt.exe failed for '${DLL_PATH}' (exit ${_rc}):\n${_out}${_err}") +endif() diff --git a/cmake/sxs/remove_signature.cmake b/cmake/sxs/remove_signature.cmake new file mode 100644 index 0000000000000..92b2dd0fc165b --- /dev/null +++ b/cmake/sxs/remove_signature.cmake @@ -0,0 +1,35 @@ +# Copyright (C) Intel Corporation +# Licensed under the MIT License +# +# cmake -P script: remove the Authenticode digital signature from a PE file. +# +# Variables (passed via -D): +# SIGNTOOL — absolute path to signtool.exe +# DLL_PATH — absolute path to the PE file +# +# Uses signtool verify to detect whether the file is signed before attempting +# removal, so unsigned files are skipped cleanly without parsing error messages. +cmake_minimum_required(VERSION 3.28) + +execute_process( + COMMAND "${SIGNTOOL}" verify /pa "${DLL_PATH}" + RESULT_VARIABLE _signed + OUTPUT_QUIET ERROR_QUIET +) + +if(NOT _signed EQUAL 0) + message(STATUS "remove_signature: '${DLL_PATH}' is not signed — skipped") + return() +endif() + +execute_process( + COMMAND "${SIGNTOOL}" remove /s "${DLL_PATH}" + RESULT_VARIABLE _rc + OUTPUT_VARIABLE _out + ERROR_VARIABLE _err +) + +if(NOT _rc EQUAL 0) + message(FATAL_ERROR + "remove_signature: signtool remove failed for '${DLL_PATH}' (exit ${_rc}):\n${_out}${_err}") +endif() diff --git a/onnxruntime/test/providers/openvino/openvino_ep_sxs_isolation_test.cc b/onnxruntime/test/providers/openvino/openvino_ep_sxs_isolation_test.cc new file mode 100644 index 0000000000000..83a4d6a1c8691 --- /dev/null +++ b/onnxruntime/test/providers/openvino/openvino_ep_sxs_isolation_test.cc @@ -0,0 +1,154 @@ +// Copyright (C) Intel Corporation +// Licensed under the MIT License +// +// Windows-only test for SxS (Side-by-Side) DLL isolation. +// Verifies that OpenVINO EP loads its own private copy of openvino.dll even +// when another copy is already loaded in the process. + +#ifdef _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "core/session/onnxruntime_cxx_api.h" +#include "gtest/gtest.h" + +namespace fs = std::filesystem; + +extern std::unique_ptr ort_env; + +namespace { + +static fs::path GetExeDir() { + wchar_t exe_path[MAX_PATH] = {}; + GetModuleFileNameW(nullptr, exe_path, MAX_PATH); + return fs::path(exe_path).parent_path(); +} + +// Returns full paths of all loaded DLLs whose name contains the given fragment. +static std::vector FindLoadedModules(std::wstring_view name_fragment_lower) { + std::vector result; + std::vector modules(1024); + DWORD cbNeeded = 0; + + // Retry with a larger buffer if the initial allocation is too small. + for (int attempt = 0; attempt < 3; ++attempt) { + if (!EnumProcessModulesEx(GetCurrentProcess(), modules.data(), + static_cast(modules.size() * sizeof(HMODULE)), + &cbNeeded, LIST_MODULES_ALL)) { + return result; + } + if (cbNeeded <= modules.size() * sizeof(HMODULE)) break; + modules.resize(cbNeeded / sizeof(HMODULE) + 64); + } + + DWORD count = std::min(cbNeeded / sizeof(HMODULE), + static_cast(modules.size())); + for (DWORD i = 0; i < count; ++i) { + wchar_t path[MAX_PATH] = {}; + if (!GetModuleFileNameExW(GetCurrentProcess(), modules[i], path, MAX_PATH)) { + continue; + } + std::wstring wpath(path); + std::wstring lower = wpath; + for (auto& c : lower) c = static_cast(std::towlower(c)); + if (lower.find(name_fragment_lower) != std::wstring::npos) { + result.push_back(wpath); + } + } + return result; +} + +} // namespace + +// --------------------------------------------------------------------------- +struct SxsIsolationFixture : public ::testing::Test { + fs::path bin_dir_; + fs::path temp_dir_; + fs::path preloaded_ov_; + HMODULE h_preloaded_ov_ = nullptr; + + void SetUp() override { + bin_dir_ = GetExeDir(); + + if (!fs::exists(bin_dir_ / L"openvino.dll")) { + GTEST_SKIP() << "openvino.dll not found — run from installed directory"; + } + + // Skip if manifests haven't been embedded (cmake --install) + if (!fs::exists(bin_dir_ / L"openvino_runtime.manifest")) { + GTEST_SKIP() << "openvino_runtime.manifest not found — run cmake --install first"; + } + + // Copy openvino.dll to a temp folder and pre-load it. + // This simulates another app having already loaded + // its own openvino.dll into this process. + temp_dir_ = fs::temp_directory_path() / + (std::wstring(L"ort_openvino_sxs_test_") + std::to_wstring(GetCurrentProcessId())); + fs::create_directories(temp_dir_); + + preloaded_ov_ = temp_dir_ / L"openvino.dll"; + fs::copy_file(bin_dir_ / L"openvino.dll", preloaded_ov_, + fs::copy_options::overwrite_existing); + + // Load into module list without actually initializing the DLL + h_preloaded_ov_ = LoadLibraryExW(preloaded_ov_.wstring().c_str(), nullptr, + DONT_RESOLVE_DLL_REFERENCES); + ASSERT_NE(h_preloaded_ov_, nullptr) + << "Pre-load of openvino.dll failed (error " << GetLastError() << ")"; + } + + void TearDown() override { + if (h_preloaded_ov_) { + FreeLibrary(h_preloaded_ov_); + h_preloaded_ov_ = nullptr; + } + std::error_code ec; + fs::remove_all(temp_dir_, ec); + } +}; + +// --------------------------------------------------------------------------- +TEST_F(SxsIsolationFixture, SxsLoadsPrivateAssemblyCopy) { + // At this point we have 1 openvino.dll loaded (the fake pre-loaded one) + auto ov_mods_before = FindLoadedModules(L"openvino.dll"); + ASSERT_GE(ov_mods_before.size(), 1u); + + // Trigger OpenVINO EP load — SxS should cause a SECOND openvino.dll to + // load from the bin directory, ignoring the already-loaded copy. + Ort::SessionOptions session_opts; + std::unordered_map ov_options; + ov_options["device_type"] = "CPU"; + session_opts.AppendExecutionProvider_OpenVINO_V2(ov_options); + + // If SxS works, we should now have 2 distinct openvino.dll modules + auto ov_mods_after = FindLoadedModules(L"openvino.dll"); + + ASSERT_GE(ov_mods_after.size(), 2u) + << "SxS isolation failed: expected >=2 openvino.dll instances, got " + << ov_mods_after.size(); + + // Confirm both paths are present + auto iequals = [](const std::wstring& a, const std::wstring& b) { + return _wcsicmp(a.c_str(), b.c_str()) == 0; + }; + auto contains = [&](const std::wstring& path) { + return std::any_of(ov_mods_after.begin(), ov_mods_after.end(), + [&](const auto& p) { return iequals(p, path); }); + }; + + EXPECT_TRUE(contains(preloaded_ov_.wstring())) + << "Pre-loaded openvino.dll not found in module list"; + EXPECT_TRUE(contains((bin_dir_ / L"openvino.dll").wstring())) + << "SxS bin-dir openvino.dll not found in module list"; +} + +#endif // _WIN32