1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use windows_core::Interface as _;
use crate::opc_da::{
client::GuidIterator,
com_utils::{IntoBridge, ToNative, TryToNative as _},
errors::{OpcError, OpcResult},
typedefs::{ClassContext, ServerInfo},
};
/// Trait defining client functionality for OPC Data Access servers.
pub trait ClientTrait<Server: TryFrom<windows::core::IUnknown, Error = windows::core::Error>> {
/// GUID of the catalog used to enumerate servers.
const CATALOG_ID: windows::core::GUID;
/// Retrieves an iterator over available server GUIDs.
///
/// # Returns
///
/// A `Result` containing a `GuidIterator` over server GUIDs, or an error if the operation fails.
fn get_servers(&self) -> OpcResult<GuidIterator> {
tracing::debug!("Enumerating OPC DA Server classes via COM Component Categories Manager");
// SAFETY: Calling COM function CLSIDFromProgID with valid ProgID string.
let id = unsafe {
windows::Win32::System::Com::CLSIDFromProgID(windows::core::w!("OPC.ServerList.1"))?
};
// SAFETY: Calling COM function CoCreateInstance with valid CLSID to instantiate IOPCServerList.
let servers: crate::bindings::comn::IOPCServerList = unsafe {
// TODO: Use CoCreateInstanceEx
windows::Win32::System::Com::CoCreateInstance(
&id,
None,
// TODO: Convert from filters
windows::Win32::System::Com::CLSCTX_ALL,
)?
};
let versions = [Self::CATALOG_ID];
// SAFETY: Calling COM method EnumClassesOfCategories with valid catalog category GUID slices.
let iter = unsafe {
servers
.EnumClassesOfCategories(&versions, &versions)
.map_err(|e| {
windows::core::Error::new(e.code(), "Failed to enumerate server classes")
})?
};
Ok(GuidIterator::new(iter))
}
/// Creates a server instance from the specified class ID.
///
/// # Parameters
///
/// - `class_id`: The GUID of the server class to instantiate.
///
/// # Returns
///
/// A `Result` containing the server instance, or an error if creation fails.
fn create_server(
&self,
class_id: windows::core::GUID,
class_context: ClassContext,
) -> OpcResult<Server> {
tracing::debug!(
?class_id,
?class_context,
"Creating OPC server instance via COM CoCreateInstance"
);
// SAFETY: Calling COM function CoCreateInstance with valid class_id to instantiate IOPCServer.
let server: crate::bindings::da::IOPCServer = unsafe {
windows::Win32::System::Com::CoCreateInstance(
&class_id,
None,
class_context.to_native(),
)?
};
server
.cast::<windows::core::IUnknown>()?
.try_into()
.map_err(|source| OpcError::Com { source })
}
fn create_server2(
&self,
class_id: windows::core::GUID,
class_context: ClassContext,
server_info: Option<ServerInfo>,
) -> OpcResult<Server> {
let mut results = [windows::Win32::System::Com::MULTI_QI {
pIID: &windows::core::IUnknown::IID,
pItf: core::mem::ManuallyDrop::new(None),
hr: windows::core::HRESULT(0),
}];
// SAFETY: Calling COM function CoCreateInstanceEx with valid class_id and MULTI_QI array.
unsafe {
windows::Win32::System::Com::CoCreateInstanceEx(
&class_id,
None,
class_context.to_native(),
match server_info {
Some(info) => Some(&info.into_bridge().try_to_native()?),
None => None,
},
&mut results,
)?
};
if results[0].hr.is_err() {
return Err(OpcError::Com {
source: results[0].hr.into(),
});
}
match results[0].pItf.as_ref() {
Some(itf) => itf
.cast::<windows::core::IUnknown>()?
.try_into()
.map_err(|source| OpcError::Com { source }),
None => Err(OpcError::Com {
source: windows::core::Error::from(windows::Win32::Foundation::E_POINTER),
}),
}
}
}