Skip to content

Commit e46c0d8

Browse files
authored
[QNN EP] Use absolute path of libcdsprpc.dll on Windows so it doesn't need to be copied anywhere. (microsoft#23791)
### Description Look up and use absolute path of libcdsprpc.dll on Windows. ### Motivation and Context The QNN EP's HTP shared memory allocator requires use of the libcdsprpc shared library. On Windows, this previously required copying libcdsprpc.dll from some driver-specific path to somewhere the running code could find it. After this change, libcdsprpc.dll no longer needs to be copied.
1 parent 7864192 commit e46c0d8

File tree

4 files changed

+146
-50
lines changed

4 files changed

+146
-50
lines changed

onnxruntime/core/providers/qnn/rpcmem_library.cc

Lines changed: 136 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,157 @@
22
// Licensed under the MIT License
33

44
#include "core/providers/qnn/rpcmem_library.h"
5+
6+
#if defined(_WIN32)
7+
#include <filesystem>
8+
9+
#include <sysinfoapi.h>
10+
#include <winsvc.h>
11+
#endif // defined(_WIN32)
12+
513
#include "core/providers/qnn/ort_api.h"
614

715
namespace onnxruntime::qnn {
816

17+
// Unload the dynamic library referenced by `library_handle`.
18+
// Avoid throwing because this may run from a dtor.
19+
void DynamicLibraryHandleDeleter::operator()(void* library_handle) noexcept {
20+
if (library_handle == nullptr) {
21+
return;
22+
}
23+
24+
const auto& env = GetDefaultEnv();
25+
const auto unload_status = env.UnloadDynamicLibrary(library_handle);
26+
27+
if (!unload_status.IsOK()) {
28+
LOGS_DEFAULT(WARNING) << "Failed to unload dynamic library. Error: " << unload_status.ErrorMessage();
29+
}
30+
}
31+
932
namespace {
1033

11-
const PathChar* GetRpcMemSharedLibraryPath() {
1234
#if defined(_WIN32)
13-
return ORT_TSTR("libcdsprpc.dll");
14-
#else
15-
return ORT_TSTR("libcdsprpc.so");
16-
#endif
35+
36+
struct ServiceHandleDeleter {
37+
void operator()(SC_HANDLE handle) { ::CloseServiceHandle(handle); }
38+
};
39+
40+
using UniqueServiceHandle = std::unique_ptr<std::remove_pointer_t<SC_HANDLE>, ServiceHandleDeleter>;
41+
42+
Status ReadEnvironmentVariable(const wchar_t* name, std::wstring& value_out) {
43+
const DWORD value_size = ::GetEnvironmentVariableW(name, nullptr, 0);
44+
ORT_RETURN_IF(value_size == 0,
45+
"Failed to get environment variable length. GetEnvironmentVariableW error: ", ::GetLastError());
46+
47+
std::vector<wchar_t> value(value_size);
48+
49+
ORT_RETURN_IF(::GetEnvironmentVariableW(name, value.data(), value_size) == 0,
50+
"Failed to get environment variable value. GetEnvironmentVariableW error: ", ::GetLastError());
51+
52+
value_out = std::wstring{value.data()};
53+
return Status::OK();
1754
}
1855

19-
DynamicLibraryHandle LoadDynamicLibrary(const PathString& path, bool global_symbols) {
20-
// Custom deleter to unload the shared library. Avoid throwing from it because it may run in dtor.
21-
const auto unload_library = [](void* library_handle) {
22-
if (library_handle == nullptr) {
23-
return;
24-
}
56+
Status GetServiceBinaryDirectoryPath(const wchar_t* service_name,
57+
std::filesystem::path& service_binary_directory_path_out) {
58+
SC_HANDLE scm_handle_raw = ::OpenSCManagerW(nullptr, // local computer
59+
nullptr, // SERVICES_ACTIVE_DATABASE
60+
STANDARD_RIGHTS_READ);
61+
ORT_RETURN_IF(scm_handle_raw == nullptr,
62+
"Failed to open handle to service control manager. OpenSCManagerW error: ", ::GetLastError());
63+
64+
auto scm_handle = UniqueServiceHandle{scm_handle_raw};
65+
66+
SC_HANDLE service_handle_raw = ::OpenServiceW(scm_handle.get(),
67+
service_name,
68+
SERVICE_QUERY_CONFIG);
69+
ORT_RETURN_IF(service_handle_raw == nullptr,
70+
"Failed to open service handle. OpenServiceW error: ", ::GetLastError());
71+
72+
auto service_handle = UniqueServiceHandle{service_handle_raw};
73+
74+
// get service config required buffer size
75+
DWORD service_config_buffer_size{};
76+
ORT_RETURN_IF(!::QueryServiceConfigW(service_handle.get(), nullptr, 0, &service_config_buffer_size) &&
77+
::GetLastError() != ERROR_INSUFFICIENT_BUFFER,
78+
"Failed to query service configuration buffer size. QueryServiceConfigW error: ", ::GetLastError());
2579

26-
const auto& env = GetDefaultEnv();
27-
const auto unload_status = env.UnloadDynamicLibrary(library_handle);
80+
// get the service config
81+
std::vector<std::byte> service_config_buffer(service_config_buffer_size);
82+
QUERY_SERVICE_CONFIGW* service_config = reinterpret_cast<QUERY_SERVICE_CONFIGW*>(service_config_buffer.data());
83+
ORT_RETURN_IF(!::QueryServiceConfigW(service_handle.get(), service_config, service_config_buffer_size,
84+
&service_config_buffer_size),
85+
"Failed to query service configuration. QueryServiceConfigW error: ", ::GetLastError());
2886

29-
if (!unload_status.IsOK()) {
30-
LOGS_DEFAULT(WARNING) << "Failed to unload shared library. Error: " << unload_status.ErrorMessage();
31-
}
32-
};
87+
std::wstring service_binary_path_name = service_config->lpBinaryPathName;
3388

89+
// replace system root placeholder with the value of the SYSTEMROOT environment variable
90+
const std::wstring system_root_placeholder = L"\\SystemRoot";
91+
92+
ORT_RETURN_IF(service_binary_path_name.find(system_root_placeholder, 0) != 0,
93+
"Service binary path '", ToUTF8String(service_binary_path_name),
94+
"' does not start with expected system root placeholder value '",
95+
ToUTF8String(system_root_placeholder), "'.");
96+
97+
std::wstring system_root{};
98+
ORT_RETURN_IF_ERROR(ReadEnvironmentVariable(L"SYSTEMROOT", system_root));
99+
service_binary_path_name.replace(0, system_root_placeholder.size(), system_root);
100+
101+
const auto service_binary_path = std::filesystem::path{service_binary_path_name};
102+
auto service_binary_directory_path = service_binary_path.parent_path();
103+
104+
ORT_RETURN_IF(!std::filesystem::exists(service_binary_directory_path),
105+
"Service binary directory path does not exist: ", service_binary_directory_path.string());
106+
107+
service_binary_directory_path_out = std::move(service_binary_directory_path);
108+
return Status::OK();
109+
}
110+
111+
#endif // defined(_WIN32)
112+
113+
Status GetRpcMemDynamicLibraryPath(PathString& path_out) {
114+
#if defined(_WIN32)
115+
116+
std::filesystem::path qcnspmcdm_dir_path{};
117+
ORT_RETURN_IF_ERROR(GetServiceBinaryDirectoryPath(L"qcnspmcdm", qcnspmcdm_dir_path));
118+
const auto libcdsprpc_path = qcnspmcdm_dir_path / L"libcdsprpc.dll";
119+
path_out = libcdsprpc_path.wstring();
120+
return Status::OK();
121+
122+
#else // ^^^ defined(_WIN32) / vvv !defined(_WIN32)
123+
124+
path_out = ORT_TSTR("libcdsprpc.so");
125+
return Status::OK();
126+
127+
#endif // !defined(_WIN32)
128+
}
129+
130+
Status LoadDynamicLibrary(const PathString& path, bool global_symbols,
131+
UniqueDynamicLibraryHandle& library_handle_out) {
34132
const auto& env = GetDefaultEnv();
35-
void* library_handle = nullptr;
133+
void* library_handle_raw = nullptr;
134+
ORT_RETURN_IF_ERROR(env.LoadDynamicLibrary(path, global_symbols, &library_handle_raw));
135+
136+
library_handle_out = UniqueDynamicLibraryHandle{library_handle_raw};
137+
return Status::OK();
138+
}
139+
140+
UniqueDynamicLibraryHandle GetRpcMemDynamicLibraryHandle() {
141+
std::string_view error_message_prefix = "Failed to initialize RPCMEM dynamic library handle: ";
142+
143+
PathString rpcmem_library_path{};
144+
auto status = GetRpcMemDynamicLibraryPath(rpcmem_library_path);
145+
if (!status.IsOK()) {
146+
ORT_THROW(error_message_prefix, status.ErrorMessage());
147+
}
36148

37-
const auto load_status = env.LoadDynamicLibrary(path, global_symbols, &library_handle);
38-
if (!load_status.IsOK()) {
39-
ORT_THROW("Failed to load ", ToUTF8String(path), ": ", load_status.ErrorMessage());
149+
UniqueDynamicLibraryHandle library_handle{};
150+
status = LoadDynamicLibrary(rpcmem_library_path, /* global_symbols */ false, library_handle);
151+
if (!status.IsOK()) {
152+
ORT_THROW(error_message_prefix, status.ErrorMessage());
40153
}
41154

42-
return DynamicLibraryHandle{library_handle, unload_library};
155+
return library_handle;
43156
}
44157

45158
RpcMemApi CreateApi(void* library_handle) {
@@ -58,7 +171,7 @@ RpcMemApi CreateApi(void* library_handle) {
58171
} // namespace
59172

60173
RpcMemLibrary::RpcMemLibrary()
61-
: library_handle_(LoadDynamicLibrary(GetRpcMemSharedLibraryPath(), /* global_symbols */ false)),
174+
: library_handle_(GetRpcMemDynamicLibraryHandle()),
62175
api_{CreateApi(library_handle_.get())} {
63176
}
64177

onnxruntime/core/providers/qnn/rpcmem_library.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010

1111
namespace onnxruntime::qnn {
1212

13-
using DynamicLibraryHandle = std::unique_ptr<void, void (*)(void*)>;
13+
struct DynamicLibraryHandleDeleter {
14+
void operator()(void* library_handle) noexcept;
15+
};
16+
17+
using UniqueDynamicLibraryHandle = std::unique_ptr<void, DynamicLibraryHandleDeleter>;
1418

1519
// This namespace contains constants and typedefs corresponding to functions from rpcmem.h.
1620
// https://github.com/quic/fastrpc/blob/v0.1.1/inc/rpcmem.h
@@ -61,7 +65,7 @@ class RpcMemLibrary {
6165
const RpcMemApi& Api() const { return api_; }
6266

6367
private:
64-
DynamicLibraryHandle library_handle_;
68+
UniqueDynamicLibraryHandle library_handle_;
6569
RpcMemApi api_;
6670
};
6771

onnxruntime/test/providers/qnn/qnn_basic_test.cc

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,18 +1166,8 @@ TEST_F(QnnHTPBackendTests, UseHtpSharedMemoryAllocatorForInputs) {
11661166
try {
11671167
qnn_ep = QnnExecutionProviderWithOptions(provider_options);
11681168
} catch (const OnnxRuntimeException& e) {
1169-
// handle particular exception that indicates that the libcdsprpc.so / dll can't be loaded
1170-
// NOTE: To run this on a local Windows ARM64 device, you need to copy libcdsprpc.dll to the build directory:
1171-
// - Open File Explorer
1172-
// - Go to C:/Windows/System32/DriverStore/FileRepository/
1173-
// - Search for a folder that begins with qcnspmcdm8380.inf_arm64_ and open it
1174-
// - Copy the libcdsprpc.dll into the build/[PATH CONTAINING onnxruntime.dll] directory of the application.
1175-
// TODO(adrianlizarraga): Update CMake build for unittests to automatically copy libcdsprpc.dll into build directory
1176-
#if defined(_WIN32)
1177-
constexpr const char* expected_error_message = "Failed to load libcdsprpc.dll";
1178-
#else
1179-
constexpr const char* expected_error_message = "Failed to load libcdsprpc.so";
1180-
#endif
1169+
// handle exception that indicates that the libcdsprpc.so / dll can't be loaded
1170+
constexpr const char* expected_error_message = "Failed to initialize RPCMEM dynamic library handle";
11811171
ASSERT_THAT(e.what(), testing::HasSubstr(expected_error_message));
11821172
GTEST_SKIP() << "HTP shared memory allocator is unavailable.";
11831173
}

onnxruntime/test/shared_lib/test_inference.cc

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1960,20 +1960,9 @@ static bool CreateSessionWithQnnEpAndQnnHtpSharedMemoryAllocator(PATH_TYPE model
19601960
session = Ort::Session{*ort_env, model_path, session_options};
19611961
return true;
19621962
} catch (const Ort::Exception& e) {
1963-
// handle particular exception that indicates that the libcdsprpc.so / dll can't be loaded
1964-
// NOTE: To run this on a local Windows ARM64 device, you need to copy libcdsprpc.dll to the build directory:
1965-
// - Open File Explorer
1966-
// - Go to C:/Windows/System32/DriverStore/FileRepository/
1967-
// - Search for a folder that begins with qcnspmcdm8380.inf_arm64_ and open it
1968-
// - Copy the libcdsprpc.dll into the build/[PATH CONTAINING onnxruntime.dll] directory of the application.
1969-
// TODO(adrianlizarraga): Update CMake build for unittests to automatically copy libcdsprpc.dll into build directory
1963+
// handle exception that indicates that the libcdsprpc.so / dll can't be loaded
19701964
std::string_view error_message = e.what();
1971-
1972-
#if defined(_WIN32)
1973-
std::string_view expected_error_message = "Failed to load libcdsprpc.dll";
1974-
#else
1975-
std::string_view expected_error_message = "Failed to load libcdsprpc.so";
1976-
#endif
1965+
std::string_view expected_error_message = "Failed to initialize RPCMEM dynamic library handle";
19771966

19781967
if (e.GetOrtErrorCode() == ORT_FAIL &&
19791968
error_message.find(expected_error_message) != std::string_view::npos) {

0 commit comments

Comments
 (0)