From 59b691a32b4b81ac35f6c57e52aed40b227ad56a Mon Sep 17 00:00:00 2001 From: Ronbb Date: Fri, 18 Jul 2025 16:25:06 +0800 Subject: [PATCH 01/10] Remove opc_da --- Cargo.lock | 524 +----------- opc_da/Cargo.toml | 8 - opc_da/src/client/iterator.rs | 250 ------ opc_da/src/client/mod.rs | 22 - opc_da/src/client/tests.rs | 134 --- opc_da/src/client/traits/async_io.rs | 135 ---- opc_da/src/client/traits/async_io2.rs | 125 --- opc_da/src/client/traits/async_io3.rs | 103 --- opc_da/src/client/traits/browse.rs | 126 --- .../traits/browse_server_address_space.rs | 104 --- opc_da/src/client/traits/client.rs | 108 --- opc_da/src/client/traits/common.rs | 71 -- .../traits/connection_point_container.rs | 62 -- opc_da/src/client/traits/data_callback.rs | 131 --- opc_da/src/client/traits/data_object.rs | 131 --- opc_da/src/client/traits/group_state_mgt.rs | 112 --- opc_da/src/client/traits/group_state_mgt2.rs | 33 - opc_da/src/client/traits/item_deadband_mgt.rs | 113 --- opc_da/src/client/traits/item_io.rs | 110 --- opc_da/src/client/traits/item_mgt.rs | 274 ------- opc_da/src/client/traits/item_properties.rs | 159 ---- opc_da/src/client/traits/item_sampling_mgt.rs | 182 ----- opc_da/src/client/traits/mod.rs | 71 -- .../client/traits/public_group_state_mgt.rs | 28 - opc_da/src/client/traits/server.rs | 134 --- .../src/client/traits/server_public_groups.rs | 39 - opc_da/src/client/traits/sync_io.rs | 102 --- opc_da/src/client/traits/sync_io2.rs | 118 --- opc_da/src/client/unified/actor/client.rs | 65 -- opc_da/src/client/unified/actor/mod.rs | 23 - opc_da/src/client/unified/actor/runtime.rs | 13 - opc_da/src/client/unified/actor/server.rs | 7 - opc_da/src/client/unified/actor/tests.rs | 13 - opc_da/src/client/unified/client.rs | 67 -- opc_da/src/client/unified/group.rs | 659 --------------- opc_da/src/client/unified/guard.rs | 96 --- opc_da/src/client/unified/mod.rs | 14 - opc_da/src/client/unified/server.rs | 127 --- opc_da/src/client/unified/tests.rs | 20 - opc_da/src/client/v1/mod.rs | 151 ---- opc_da/src/client/v2/mod.rs | 209 ----- opc_da/src/client/v3/mod.rs | 196 ----- opc_da/src/def.rs | 752 ----------------- opc_da/src/server/com/base/basic.rs | 17 - opc_da/src/server/com/base/mod.rs | 5 - opc_da/src/server/com/base/variant.rs | 17 - opc_da/src/server/com/builder.rs | 5 - opc_da/src/server/com/connection_point.rs | 73 -- opc_da/src/server/com/enumeration.rs | 341 -------- opc_da/src/server/com/group.rs | 765 ------------------ opc_da/src/server/com/item.rs | 1 - opc_da/src/server/com/memory.rs | 220 ----- opc_da/src/server/com/mod.rs | 16 - opc_da/src/server/com/server.rs | 558 ------------- opc_da/src/server/com/utils.rs | 328 -------- opc_da/src/server/com/variant.rs | 94 --- opc_da/src/server/mod.rs | 2 - opc_da/src/server/traits/def.rs | 330 -------- opc_da/src/server/traits/group.rs | 318 -------- opc_da/src/server/traits/item.rs | 1 - opc_da/src/server/traits/mod.rs | 9 - opc_da/src/server/traits/server.rs | 252 ------ opc_da/src/utils/memory.rs | 485 ----------- opc_da/src/utils/mod.rs | 7 - opc_da/src/utils/native.rs | 164 ---- opc_da/src/utils/try_iterator.rs | 98 --- 66 files changed, 12 insertions(+), 10015 deletions(-) delete mode 100644 opc_da/src/client/iterator.rs delete mode 100644 opc_da/src/client/mod.rs delete mode 100644 opc_da/src/client/tests.rs delete mode 100644 opc_da/src/client/traits/async_io.rs delete mode 100644 opc_da/src/client/traits/async_io2.rs delete mode 100644 opc_da/src/client/traits/async_io3.rs delete mode 100644 opc_da/src/client/traits/browse.rs delete mode 100644 opc_da/src/client/traits/browse_server_address_space.rs delete mode 100644 opc_da/src/client/traits/client.rs delete mode 100644 opc_da/src/client/traits/common.rs delete mode 100644 opc_da/src/client/traits/connection_point_container.rs delete mode 100644 opc_da/src/client/traits/data_callback.rs delete mode 100644 opc_da/src/client/traits/data_object.rs delete mode 100644 opc_da/src/client/traits/group_state_mgt.rs delete mode 100644 opc_da/src/client/traits/group_state_mgt2.rs delete mode 100644 opc_da/src/client/traits/item_deadband_mgt.rs delete mode 100644 opc_da/src/client/traits/item_io.rs delete mode 100644 opc_da/src/client/traits/item_mgt.rs delete mode 100644 opc_da/src/client/traits/item_properties.rs delete mode 100644 opc_da/src/client/traits/item_sampling_mgt.rs delete mode 100644 opc_da/src/client/traits/mod.rs delete mode 100644 opc_da/src/client/traits/public_group_state_mgt.rs delete mode 100644 opc_da/src/client/traits/server.rs delete mode 100644 opc_da/src/client/traits/server_public_groups.rs delete mode 100644 opc_da/src/client/traits/sync_io.rs delete mode 100644 opc_da/src/client/traits/sync_io2.rs delete mode 100644 opc_da/src/client/unified/actor/client.rs delete mode 100644 opc_da/src/client/unified/actor/mod.rs delete mode 100644 opc_da/src/client/unified/actor/runtime.rs delete mode 100644 opc_da/src/client/unified/actor/server.rs delete mode 100644 opc_da/src/client/unified/actor/tests.rs delete mode 100644 opc_da/src/client/unified/client.rs delete mode 100644 opc_da/src/client/unified/group.rs delete mode 100644 opc_da/src/client/unified/guard.rs delete mode 100644 opc_da/src/client/unified/mod.rs delete mode 100644 opc_da/src/client/unified/server.rs delete mode 100644 opc_da/src/client/unified/tests.rs delete mode 100644 opc_da/src/client/v1/mod.rs delete mode 100644 opc_da/src/client/v2/mod.rs delete mode 100644 opc_da/src/client/v3/mod.rs delete mode 100644 opc_da/src/def.rs delete mode 100644 opc_da/src/server/com/base/basic.rs delete mode 100644 opc_da/src/server/com/base/mod.rs delete mode 100644 opc_da/src/server/com/base/variant.rs delete mode 100644 opc_da/src/server/com/builder.rs delete mode 100644 opc_da/src/server/com/connection_point.rs delete mode 100644 opc_da/src/server/com/enumeration.rs delete mode 100644 opc_da/src/server/com/group.rs delete mode 100644 opc_da/src/server/com/item.rs delete mode 100644 opc_da/src/server/com/memory.rs delete mode 100644 opc_da/src/server/com/mod.rs delete mode 100644 opc_da/src/server/com/server.rs delete mode 100644 opc_da/src/server/com/utils.rs delete mode 100644 opc_da/src/server/com/variant.rs delete mode 100644 opc_da/src/server/mod.rs delete mode 100644 opc_da/src/server/traits/def.rs delete mode 100644 opc_da/src/server/traits/group.rs delete mode 100644 opc_da/src/server/traits/item.rs delete mode 100644 opc_da/src/server/traits/mod.rs delete mode 100644 opc_da/src/server/traits/server.rs delete mode 100644 opc_da/src/utils/memory.rs delete mode 100644 opc_da/src/utils/mod.rs delete mode 100644 opc_da/src/utils/native.rs delete mode 100644 opc_da/src/utils/try_iterator.rs diff --git a/Cargo.lock b/Cargo.lock index c492fdd..f094752 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,279 +2,18 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "actix" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" -dependencies = [ - "actix-macros", - "actix-rt", - "actix_derive", - "bitflags", - "bytes", - "crossbeam-channel", - "futures-core", - "futures-sink", - "futures-task", - "futures-util", - "log", - "once_cell", - "parking_lot", - "pin-project-lite", - "smallvec", - "tokio", - "tokio-util", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-rt" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix_derive" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "bitflags" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" - -[[package]] -name = "bstr" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cfg-if" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "globset" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "libc" -version = "0.2.174" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.59.0", -] - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - [[package]] name = "opc_ae_bindings" version = "0.3.0" @@ -282,7 +21,7 @@ dependencies = [ "windows", "windows-bindgen", "windows-core", - "windows-targets 0.53.2", + "windows-targets", ] [[package]] @@ -300,19 +39,16 @@ dependencies = [ "windows", "windows-bindgen", "windows-core", - "windows-targets 0.53.2", + "windows-targets", ] [[package]] name = "opc_da" version = "0.3.1" dependencies = [ - "actix", - "globset", "opc_classic_utils", "opc_comn_bindings", "opc_da_bindings", - "tokio", "windows", "windows-core", ] @@ -324,7 +60,7 @@ dependencies = [ "windows", "windows-bindgen", "windows-core", - "windows-targets 0.53.2", + "windows-targets", ] [[package]] @@ -334,44 +70,9 @@ dependencies = [ "windows", "windows-bindgen", "windows-core", - "windows-targets 0.53.2", -] - -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", + "windows-targets", ] -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "proc-macro2" version = "1.0.95" @@ -390,50 +91,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "serde" version = "1.0.219" @@ -466,37 +129,6 @@ dependencies = [ "serde", ] -[[package]] -name = "signal-hook-registry" -version = "1.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "syn" version = "2.0.104" @@ -508,62 +140,12 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tokio" -version = "1.46.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" -dependencies = [ - "backtrace", - "bytes", - "io-uring", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "slab", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-util" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "windows" version = "0.61.3" @@ -677,54 +259,20 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - [[package]] name = "windows-targets" version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -736,96 +284,48 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "windows_x86_64_msvc" version = "0.53.0" diff --git a/opc_da/Cargo.toml b/opc_da/Cargo.toml index c2de177..766689d 100644 --- a/opc_da/Cargo.toml +++ b/opc_da/Cargo.toml @@ -12,16 +12,8 @@ default-target = "x86_64-pc-windows-msvc" targets = [] [dependencies] -actix = { workspace = true } -globset = { workspace = true } opc_classic_utils = { workspace = true } opc_comn_bindings = { workspace = true } opc_da_bindings = { workspace = true } -tokio = { workspace = true } windows = { workspace = true } windows-core = { workspace = true } - -[features] -default = ["unstable_client", "unstable_server"] -unstable_client = [] -unstable_server = [] diff --git a/opc_da/src/client/iterator.rs b/opc_da/src/client/iterator.rs deleted file mode 100644 index e57a2ec..0000000 --- a/opc_da/src/client/iterator.rs +++ /dev/null @@ -1,250 +0,0 @@ -use crate::{ - def::ItemAttributes, - utils::{RemoteArray, RemotePointer, TryToLocal as _}, -}; - -const MAX_CACHE_SIZE: usize = 16; - -/// Iterator over COM GUIDs from IEnumGUID. -/// -/// # Safety -/// This struct wraps a COM interface and must be used according to COM rules. -pub struct GuidIterator { - inner: windows::Win32::System::Com::IEnumGUID, - cache: Box<[windows::core::GUID; MAX_CACHE_SIZE]>, - index: u32, - count: u32, - done: bool, -} - -impl GuidIterator { - /// Creates a new iterator from a COM interface. - pub fn new(inner: windows::Win32::System::Com::IEnumGUID) -> Self { - Self { - inner, - cache: Box::from([windows::core::GUID::zeroed(); MAX_CACHE_SIZE]), - index: 0, - count: 0, - done: false, - } - } -} - -impl Iterator for GuidIterator { - type Item = windows::core::Result; - - fn next(&mut self) -> Option { - if self.done { - return None; - } - - if self.index == self.cache.len() as u32 { - let code = unsafe { - self.inner - .Next(self.cache.as_mut_slice(), Some(&mut self.count)) - }; - - if code.is_ok() { - if self.count == 0 { - self.done = true; - return None; - } - - self.index = 0; - } else { - self.done = true; - return Some(Err(windows::core::Error::new( - code, - "Failed to get next GUID", - ))); - } - } - - let current = self.cache[self.index as usize]; - self.index += 1; - Some(Ok(current)) - } -} - -pub struct StringIterator { - inner: windows::Win32::System::Com::IEnumString, - cache: Box<[windows::core::PWSTR; MAX_CACHE_SIZE]>, - index: u32, - count: u32, - done: bool, -} - -impl StringIterator { - pub fn new(inner: windows::Win32::System::Com::IEnumString) -> Self { - Self { - inner, - cache: Box::new([windows::core::PWSTR::null(); MAX_CACHE_SIZE]), - index: 0, - count: 0, - done: false, - } - } -} - -impl Iterator for StringIterator { - type Item = windows::core::Result; - - fn next(&mut self) -> Option { - if self.done { - return None; - } - - if self.index == self.cache.len() as u32 { - let code = unsafe { - self.inner - .Next(self.cache.as_mut_slice(), Some(&mut self.count)) - }; - - if code.is_ok() { - if self.count == 0 { - self.done = true; - return None; - } - - self.index = 0; - } else { - self.done = true; - return Some(Err(windows::core::Error::new( - code, - "Failed to get next string", - ))); - } - } - - let current = RemotePointer::from(self.cache[self.index as usize]); - self.index += 1; - Some(current.try_into()) - } -} - -pub struct GroupIterator> { - inner: windows::Win32::System::Com::IEnumUnknown, - cache: Box<[Option; MAX_CACHE_SIZE]>, - index: u32, - count: u32, - done: bool, - _mark: std::marker::PhantomData, -} - -impl> GroupIterator { - pub fn new(inner: windows::Win32::System::Com::IEnumUnknown) -> Self { - Self { - inner, - cache: Box::from([const { None }; MAX_CACHE_SIZE]), - index: 0, - count: 0, - done: false, - _mark: std::marker::PhantomData, - } - } -} - -impl> Iterator - for GroupIterator -{ - type Item = windows::core::Result; - - fn next(&mut self) -> Option { - if self.done { - return None; - } - - if self.index == self.cache.len() as u32 { - let code = unsafe { - self.inner - .Next(self.cache.as_mut_slice(), Some(&mut self.count)) - }; - - if code.is_ok() { - if self.count == 0 { - self.done = true; - return None; - } - - self.index = 0; - } else { - self.done = true; - return Some(Err(windows::core::Error::new( - code, - "Failed to get next group", - ))); - } - } - - let current = self.cache[self.index as usize].take(); - self.index += 1; - Some(match current { - Some(group) => group.try_into(), - None => Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Failed to get group, returned null", - )), - }) - } -} - -// for opc_da_bindings::IEnumOPCItemAttributes -pub struct ItemAttributeIterator { - inner: opc_da_bindings::IEnumOPCItemAttributes, - cache: RemoteArray, - index: u32, - done: bool, -} - -impl ItemAttributeIterator { - pub fn new(inner: opc_da_bindings::IEnumOPCItemAttributes) -> Self { - Self { - inner, - cache: RemoteArray::empty(), - index: 0, - done: false, - } - } -} - -impl Iterator for ItemAttributeIterator { - type Item = windows::core::Result; - - fn next(&mut self) -> Option { - if self.done { - return None; - } - - if self.index == self.cache.len() { - let mut attrs = RemoteArray::new(MAX_CACHE_SIZE as u32); - - let result = unsafe { - self.inner.Next( - MAX_CACHE_SIZE as u32, - attrs.as_mut_ptr(), - attrs.as_mut_len_ptr(), - ) - }; - - match result { - Ok(_) => { - if attrs.is_empty() { - self.done = true; - return None; - } - - self.cache = attrs; - self.index = 0; - } - Err(err) => { - self.done = true; - return Some(Err(err)); - } - } - } - - let current = self.cache.as_slice()[self.index as usize].try_to_local(); - self.index += 1; - Some(current) - } -} diff --git a/opc_da/src/client/mod.rs b/opc_da/src/client/mod.rs deleted file mode 100644 index 976b32a..0000000 --- a/opc_da/src/client/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! OPC DA client implementation. -//! -//! This module provides implementations for OPC DA client functionality across -//! different versions of the specification (1.0, 2.0, and 3.0). It includes: -//! -//! - Version-specific implementations in `v1`, `v2`, and `v3` modules -//! - A unified client interface in the `unified` module -//! - Common traits and memory management utilities - -mod iterator; -mod traits; - -pub mod unified; -pub mod v1; -pub mod v2; -pub mod v3; - -pub use iterator::*; -pub use traits::*; - -#[cfg(test)] -mod tests; diff --git a/opc_da/src/client/tests.rs b/opc_da/src/client/tests.rs deleted file mode 100644 index db4c482..0000000 --- a/opc_da/src/client/tests.rs +++ /dev/null @@ -1,134 +0,0 @@ -use unified::{Guard, Server}; - -use crate::utils::LocalPointer; - -use super::*; - -#[test] -fn test_client() { - let client = Guard::new(unified::Client::v2()).expect("Failed to create guard"); - - let servers = client - .get_servers() - .expect("Failed to get servers") - .collect::>(); - - if servers.is_empty() { - panic!("No servers found"); - } - - let server_id = servers - .first() - .cloned() - .expect("No server found") - .expect("Failed to get server id"); - - println!("Server ID: {:?}", server_id); - - let server = client - .create_server(server_id) - .expect("Failed to create server"); - - let server = match server { - Server::V2(server) => server, - _ => panic!("Expected V2 server"), - }; - - let branch = StringIterator::new( - server - .browse_opc_item_ids(opc_da_bindings::OPC_BRANCH, Some(""), 0, 0) - .expect("Failed to browse items"), - ) - .take(1) - .collect::, _>>() - .expect("No names found") - .pop() - .expect("No branch found"); - - println!("Branch: {:?}", branch); - - server - .change_browse_position(opc_da_bindings::OPC_BROWSE_TO, &branch) - .expect("Failed to change browse position"); - - let leaf = StringIterator::new( - server - .browse_opc_item_ids(opc_da_bindings::OPC_FLAT, Some(""), 0, 0) - .expect("Failed to browse items"), - ) - .take(1) - .collect::, _>>() - .expect("No names found") - .pop() - .expect("No leaf found"); - - println!("Leaf: {:?}", leaf); - - let name = server.get_item_id(&leaf).expect("Failed to get item id"); - - println!("Item name: {:?}", name); - - let mut group_server_handle = 0u32; - let mut revised_percent_deadband = 0u32; - let group = server - .add_group( - "test", - true, - 0, - 0, - 0, - 0, - 0.0, - &mut revised_percent_deadband, - &mut group_server_handle, - ) - .expect("Failed to add group"); - - let name = LocalPointer::from(&name); - let (results, errors) = group - .add_items(&[opc_da_bindings::tagOPCITEMDEF { - szAccessPath: windows::core::PWSTR::null(), - szItemID: name.as_pwstr(), - bActive: true.into(), - hClient: 0, - dwBlobSize: 0, - pBlob: std::ptr::null_mut(), - vtRequestedDataType: 0, - wReserved: 0, - }]) - .expect("Failed to add items"); - - assert_eq!(errors.len(), 1, "Expected exactly one error result"); - let error = errors.as_slice().first().expect("Error array is empty"); - assert!(error.is_ok(), "Unexpected error: {:?}", error); - - assert_eq!(results.len(), 1, "Expected exactly one result"); - let server_handle = results - .as_slice() - .first() - .unwrap_or_else(|| panic!("Expected 1 result, got {}", results.len())) - .hServer; - - let (states, errors) = - SyncIoTrait::read(&group, opc_da_bindings::OPC_DS_CACHE, &[server_handle]) - .expect("Failed to read"); - - if errors.len() != 1 { - panic!("Expected 1 error, got {}", errors.len()); - } - - let error = errors.as_slice().first().unwrap(); - if error.is_err() { - panic!("Error, got {:?}", error); - } - - if states.len() != 1 { - panic!("Expected 1 state, got {}", states.len()); - } - - let state = states.as_slice().first().unwrap(); - println!("State: {:?}", state.vDataValue); - - let cloned_value = state.vDataValue.clone(); - SyncIoTrait::write(&group, &[server_handle], &[cloned_value]).expect("Failed to write"); -} diff --git a/opc_da/src/client/traits/async_io.rs b/opc_da/src/client/traits/async_io.rs deleted file mode 100644 index bec975f..0000000 --- a/opc_da/src/client/traits/async_io.rs +++ /dev/null @@ -1,135 +0,0 @@ -use windows::Win32::System::Variant::VARIANT; - -use crate::utils::RemoteArray; - -/// Asynchronous I/O functionality (OPC DA 1.0). -/// -/// Provides basic asynchronous read/write operations using connection point callbacks. -/// This is the original asynchronous interface defined in OPC DA 1.0. -pub trait AsyncIoTrait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCAsyncIO>; - - /// Reads values asynchronously from the server. - /// - /// # Arguments - /// * `connection` - Connection point cookie for receiving callbacks - /// * `source` - Specifies whether to read from cache or device - /// * `server_handles` - Array of server item handles to read - /// - /// # Returns - /// * `transaction_id` - Identifies this operation in callbacks - /// * `errors` - Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if server_handles is empty - fn read( - &self, - connection: u32, - source: opc_da_bindings::tagOPCDATASOURCE, - server_handles: &[u32], - ) -> windows::core::Result<(u32, RemoteArray)> { - if server_handles.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - - let mut transaction_id = 0; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.Read( - connection, - source, - len, - server_handles.as_ptr(), - &mut transaction_id, - errors.as_mut_ptr(), - )?; - } - - Ok((transaction_id, errors)) - } - - /// Writes values asynchronously to the server. - /// - /// # Arguments - /// * `connection` - Connection point cookie for receiving callbacks - /// * `server_handles` - Array of server item handles to write - /// * `values` - Array of values to write - /// - /// # Returns - /// * `transaction_id` - Identifies this operation in callbacks - /// * `errors` - Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays are empty or have different lengths - fn write( - &self, - connection: u32, - server_handles: &[u32], - values: &[VARIANT], - ) -> windows::core::Result<(u32, RemoteArray)> { - if server_handles.len() != values.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and values must have the same length", - )); - } - - if server_handles.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - - let mut transaction_id = 0; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.Write( - connection, - len, - server_handles.as_ptr(), - values.as_ptr(), - &mut transaction_id, - errors.as_mut_ptr(), - )?; - } - - Ok((transaction_id, errors)) - } - - /// Refreshes all active items asynchronously. - /// - /// # Arguments - /// * `connection` - Connection point cookie for receiving callbacks - /// * `source` - Specifies whether to refresh from cache or device - /// - /// # Returns - /// Transaction ID for identifying the operation in callbacks - fn refresh( - &self, - connection: u32, - source: opc_da_bindings::tagOPCDATASOURCE, - ) -> windows::core::Result { - unsafe { self.interface()?.Refresh(connection, source) } - } - - /// Cancels an outstanding asynchronous operation. - /// - /// # Arguments - /// * `transaction_id` - ID of the operation to cancel - /// - /// # Returns - /// Result indicating success or failure of cancel request - fn cancel(&self, transaction_id: u32) -> windows::core::Result<()> { - unsafe { self.interface()?.Cancel(transaction_id) } - } -} diff --git a/opc_da/src/client/traits/async_io2.rs b/opc_da/src/client/traits/async_io2.rs deleted file mode 100644 index 1909a4b..0000000 --- a/opc_da/src/client/traits/async_io2.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::utils::RemoteArray; - -/// Asynchronous I/O functionality (OPC DA 2.0). -/// -/// Provides enhanced asynchronous read/write operations without requiring -/// connection point callbacks. This trait extends the functionality of -/// AsyncIoTrait with improved error handling and control mechanisms. -pub trait AsyncIo2Trait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCAsyncIO2>; - - /// Initiates an asynchronous read operation. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles to read - /// * `transaction_id` - Client-provided transaction identifier - /// - /// # Returns - /// Tuple containing (cancel_id, error_array) where: - /// - cancel_id: Identifier used to cancel the operation - /// - error_array: Array of HRESULT values indicating per-item status - fn read( - &self, - server_handles: &[u32], - transaction_id: u32, - ) -> windows::core::Result<(u32, RemoteArray)> { - let len = server_handles.len().try_into()?; - - let mut cancel_id = 0; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.Read( - len, - server_handles.as_ptr(), - transaction_id, - &mut cancel_id, - errors.as_mut_ptr(), - )?; - } - - Ok((cancel_id, errors)) - } - - /// Initiates an asynchronous write operation. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles to write - /// * `values` - Array of VARIANT values to write - /// * `transaction_id` - Client-provided transaction identifier - /// - /// # Returns - /// Tuple containing (cancel_id, error_array) where: - /// - cancel_id: Identifier used to cancel the operation - /// - error_array: Array of HRESULT values indicating per-item status - fn write( - &self, - server_handles: &[u32], - values: &[windows::Win32::System::Variant::VARIANT], - transaction_id: u32, - ) -> windows::core::Result<(u32, RemoteArray)> { - let len = server_handles.len().try_into()?; - - let mut cancel_id = 0; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.Write( - len, - server_handles.as_ptr(), - values.as_ptr(), - transaction_id, - &mut cancel_id, - errors.as_mut_ptr(), - )?; - } - - Ok((cancel_id, errors)) - } - - /// Refreshes all active items from the specified source. - /// - /// # Arguments - /// * `source` - Data source (cache or device) - /// * `transaction_id` - Client-provided transaction identifier - /// - /// # Returns - /// Cancel ID that can be used to cancel the operation - fn refresh2( - &self, - source: opc_da_bindings::tagOPCDATASOURCE, - transaction_id: u32, - ) -> windows::core::Result { - unsafe { self.interface()?.Refresh2(source, transaction_id) } - } - - /// Cancels a pending asynchronous operation. - /// - /// # Arguments - /// * `cancel_id` - Cancel ID returned from read/write operations - /// - /// # Returns - /// `Ok(())` if the operation was successfully canceled - fn cancel2(&self, cancel_id: u32) -> windows::core::Result<()> { - unsafe { self.interface()?.Cancel2(cancel_id) } - } - - /// Enables or disables asynchronous I/O operations. - /// - /// # Arguments - /// * `enable` - `true` to enable async operations, `false` to disable - /// - /// # Returns - /// `Ok(())` if the enable state was successfully changed - fn set_enable(&self, enable: bool) -> windows::core::Result<()> { - unsafe { self.interface()?.SetEnable(enable) } - } - - /// Gets the current enable state of asynchronous I/O operations. - /// - /// # Returns - /// `true` if async operations are enabled, `false` otherwise - fn get_enable(&self) -> windows::core::Result { - unsafe { self.interface()?.GetEnable().map(|v| v.as_bool()) } - } -} diff --git a/opc_da/src/client/traits/async_io3.rs b/opc_da/src/client/traits/async_io3.rs deleted file mode 100644 index fce76c3..0000000 --- a/opc_da/src/client/traits/async_io3.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::utils::RemoteArray; - -/// Asynchronous I/O functionality (OPC DA 3.0). -/// -/// Provides methods for enhanced asynchronous read/write operations with -/// quality and timestamp information. -pub trait AsyncIo3Trait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCAsyncIO3>; - - /// Reads values with maximum age constraint. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// * `max_age` - Maximum age constraints for each item (milliseconds) - /// * `transaction_id` - Client-provided transaction identifier - /// - /// # Returns - /// Tuple containing cancel ID and array of per-item error codes - fn read_max_age( - &self, - server_handles: &[u32], - max_age: &[u32], - transaction_id: u32, - ) -> windows::core::Result<(u32, RemoteArray)> { - if server_handles.len() != max_age.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and max_age must have the same length", - )); - } - - let len = server_handles.len().try_into()?; - - let mut cancel_id = 0; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.ReadMaxAge( - len, - server_handles.as_ptr(), - max_age.as_ptr(), - transaction_id, - &mut cancel_id, - errors.as_mut_ptr(), - )?; - } - - Ok((cancel_id, errors)) - } - - /// Writes values with quality and timestamp information. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// * `values` - Array of values with quality and timestamp (VQT) - /// * `transaction_id` - Client-provided transaction identifier - /// - /// # Returns - /// Tuple containing cancel ID and array of per-item error codes - fn write_vqt( - &self, - server_handles: &[u32], - values: &[opc_da_bindings::tagOPCITEMVQT], - transaction_id: u32, - ) -> windows::core::Result<(u32, RemoteArray)> { - if server_handles.len() != values.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and values must have the same length", - )); - } - - let len = server_handles.len().try_into()?; - - let mut cancel_id = 0; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.WriteVQT( - len, - server_handles.as_ptr(), - values.as_ptr(), - transaction_id, - &mut cancel_id, - errors.as_mut_ptr(), - )?; - } - - Ok((cancel_id, errors)) - } - - /// Refreshes all active items with maximum age constraint. - /// - /// # Arguments - /// * `max_age` - Maximum age constraint in milliseconds - /// * `transaction_id` - Client-provided transaction identifier - /// - /// # Returns - /// Cancel ID for the refresh operation - fn refresh_max_age(&self, max_age: u32, transaction_id: u32) -> windows::core::Result { - unsafe { self.interface()?.RefreshMaxAge(max_age, transaction_id) } - } -} diff --git a/opc_da/src/client/traits/browse.rs b/opc_da/src/client/traits/browse.rs deleted file mode 100644 index a5c5326..0000000 --- a/opc_da/src/client/traits/browse.rs +++ /dev/null @@ -1,126 +0,0 @@ -use opc_da_bindings::{tagOPCBROWSEELEMENT, tagOPCBROWSEFILTER, tagOPCITEMPROPERTIES, IOPCBrowse}; - -use crate::utils::{LocalPointer, RemoteArray, RemotePointer}; - -/// Server address space browsing functionality (OPC DA 3.0). -/// -/// Provides methods to browse the hierarchical namespace of an OPC server -/// and retrieve item properties. -pub trait BrowseTrait { - fn interface(&self) -> windows::core::Result<&IOPCBrowse>; - - /// Gets properties for specified items from the server. - /// - /// # Arguments - /// * `item_ids` - Array of item identifiers to get properties for - /// * `return_property_values` - If true, return actual property values; if false, only property metadata - /// * `property_ids` - Specific property IDs to retrieve; empty array means all properties - /// - /// # Returns - /// Array of item properties containing property values and/or metadata - /// - /// # Errors - /// Returns E_INVALIDARG if item_ids is empty - fn get_properties( - &self, - item_ids: &[String], - return_property_values: bool, - property_ids: &[u32], - ) -> windows::core::Result> { - if item_ids.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "item_ids is empty", - )); - } - - let item_ptrs: LocalPointer>> = LocalPointer::from(item_ids); - let item_ptrs = item_ptrs.as_pcwstr_array(); - - let mut results = RemoteArray::new(item_ids.len().try_into()?); - - unsafe { - self.interface()?.GetProperties( - item_ids.len() as u32, - item_ptrs.as_ptr(), - return_property_values, - property_ids, - results.as_mut_ptr(), - )?; - } - - Ok(results) - } - - /// Browses a single branch or leaf in the server's address space. - /// - /// # Arguments - /// * `item_id` - Starting point for browsing (empty string for root) - /// * `max_elements` - Maximum number of elements to return - /// * `browse_filter` - Filter specifying what types of elements to return - /// * `element_name_filter` - Filter string for element names (can contain wildcards) - /// * `vendor_filter` - Vendor-specific filter string - /// * `return_all_properties` - If true, return all available properties - /// * `return_property_values` - If true, return property values; if false, only property metadata - /// * `property_ids` - Specific property IDs to retrieve when return_all_properties is false - /// - /// # Returns - /// Tuple containing: - /// - Boolean indicating if more elements are available - /// - Array of browse elements containing names and properties - #[allow(clippy::too_many_arguments)] - fn browse( - &self, - item_id: Option, - continuation_point: Option, - max_elements: u32, - browse_filter: tagOPCBROWSEFILTER, - element_name_filter: Option, - vendor_filter: Option, - return_all_properties: bool, - return_property_values: bool, - property_ids: &[u32], - ) -> windows::core::Result<(bool, Option, RemoteArray)> - where - S0: AsRef, - S1: AsRef, - S2: AsRef, - S3: AsRef, - { - let item_id = LocalPointer::from_option(item_id); - let element_name_filter = LocalPointer::from_option(element_name_filter); - let vendor_filter = LocalPointer::from_option(vendor_filter); - let mut continuation_point = - RemotePointer::from_option(continuation_point.as_ref().map(|v| v.as_ref())); - let mut more_elements = false.into(); - let mut count = 0; - let mut elements = RemoteArray::empty(); - - unsafe { - self.interface()?.Browse( - item_id.as_pcwstr(), - continuation_point.as_mut_pwstr_ptr(), - max_elements, - browse_filter, - element_name_filter.as_pcwstr(), - vendor_filter.as_pcwstr(), - return_all_properties, - return_property_values, - property_ids, - &mut more_elements, - &mut count, - elements.as_mut_ptr(), - )?; - } - - if count > 0 { - unsafe { elements.set_len(count) }; - } - - Ok(( - more_elements.into(), - continuation_point.try_into()?, - elements, - )) - } -} diff --git a/opc_da/src/client/traits/browse_server_address_space.rs b/opc_da/src/client/traits/browse_server_address_space.rs deleted file mode 100644 index 37ab93f..0000000 --- a/opc_da/src/client/traits/browse_server_address_space.rs +++ /dev/null @@ -1,104 +0,0 @@ -use opc_da_bindings::{ - tagOPCBROWSEDIRECTION, tagOPCBROWSETYPE, tagOPCNAMESPACETYPE, IOPCBrowseServerAddressSpace, -}; - -use crate::utils::{LocalPointer, RemotePointer}; - -/// Server address space browsing functionality. -/// -/// Provides methods to navigate and discover items in the OPC server's address space. -/// Supports hierarchical and flat address spaces with filtering capabilities. -pub trait BrowseServerAddressSpaceTrait { - fn interface(&self) -> windows::core::Result<&IOPCBrowseServerAddressSpace>; - - /// Queries the organization type of the server's address space. - /// - /// # Returns - /// The namespace type (hierarchical or flat) - fn query_organization(&self) -> windows::core::Result { - unsafe { self.interface()?.QueryOrganization() } - } - - /// Changes the current position in the server's address space. - /// - /// # Arguments - /// * `browse_direction` - Direction to move (up, down, or to) - /// * `position` - Target position string (branch name for down/to) - /// - /// # Returns - /// Result indicating success or failure of position change - fn change_browse_position( - &self, - browse_direction: tagOPCBROWSEDIRECTION, - position: &str, - ) -> windows::core::Result<()> { - let position = LocalPointer::from(position); - - unsafe { - self.interface()? - .ChangeBrowsePosition(browse_direction, position.as_pwstr()) - } - } - - /// Browses available item IDs at the current position. - /// - /// # Arguments - /// * `browse_type` - Type of items to browse (leaf, branch, or flat) - /// * `filter_criteria` - Pattern for filtering items - /// * `datatype_filter` - VT_* type to filter by (0 for all) - /// * `access_rights_filter` - Required access rights - /// - /// # Returns - /// Enumerator for matching item IDs - fn browse_opc_item_ids( - &self, - browse_type: tagOPCBROWSETYPE, - filter_criteria: Option, - data_type_filter: u16, - access_rights_filter: u32, - ) -> windows::core::Result - where - S0: AsRef, - { - let filter_criteria = LocalPointer::from_option(filter_criteria); - - unsafe { - self.interface()?.BrowseOPCItemIDs( - browse_type, - filter_criteria.as_pwstr(), - data_type_filter, - access_rights_filter, - ) - } - } - - /// Gets fully qualified item ID from a leaf item. - /// - /// # Arguments - /// * `item_data_id` - Item name at current position - /// - /// # Returns - /// Fully qualified item ID string - fn get_item_id(&self, item_data_id: &str) -> windows::core::Result { - let item_data_id = LocalPointer::from(item_data_id); - - let output = unsafe { self.interface()?.GetItemID(item_data_id.as_pwstr())? }; - - RemotePointer::from(output).try_into() - } - - /// Browses available access paths for an item. - /// - /// # Arguments - /// * `item_id` - Fully qualified item ID - /// - /// # Returns - /// Enumerator for available access paths - fn browse_access_paths( - &self, - item_id: &str, - ) -> windows::core::Result { - let item_id = LocalPointer::from(item_id); - unsafe { self.interface()?.BrowseAccessPaths(item_id.as_pwstr()) } - } -} diff --git a/opc_da/src/client/traits/client.rs b/opc_da/src/client/traits/client.rs deleted file mode 100644 index ec7495f..0000000 --- a/opc_da/src/client/traits/client.rs +++ /dev/null @@ -1,108 +0,0 @@ -use windows_core::Interface as _; - -use crate::{ - client::GuidIterator, - def::{ClassContext, ServerInfo}, - utils::{IntoBridge, ToNative, TryToNative as _}, -}; - -/// Trait defining client functionality for OPC Data Access servers. -pub trait ClientTrait> { - /// 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) -> windows::core::Result { - let id = unsafe { - windows::Win32::System::Com::CLSIDFromProgID(windows::core::w!("OPC.ServerList.1"))? - }; - - let servers: opc_comn_bindings::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]; - - 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, - ) -> windows::core::Result { - let server: opc_da_bindings::IOPCServer = unsafe { - windows::Win32::System::Com::CoCreateInstance( - &class_id, - None, - class_context.to_native(), - )? - }; - - server.cast::()?.try_into() - } - - fn create_server2( - &self, - class_id: windows::core::GUID, - class_context: ClassContext, - server_info: Option, - ) -> windows::core::Result { - 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), - }]; - - 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(results[0].hr.into()); - } - - match results[0].pItf.as_ref() { - Some(itf) => itf.cast::()?.try_into(), - None => Err(windows::core::Error::from( - windows::Win32::Foundation::E_POINTER, - )), - } - } -} diff --git a/opc_da/src/client/traits/common.rs b/opc_da/src/client/traits/common.rs deleted file mode 100644 index fac1bd5..0000000 --- a/opc_da/src/client/traits/common.rs +++ /dev/null @@ -1,71 +0,0 @@ -use opc_comn_bindings::IOPCCommon; - -use crate::utils::{LocalPointer, RemoteArray, RemotePointer}; - -/// Common OPC server functionality trait. -/// -/// Provides methods for locale management and error string retrieval. -/// This trait is implemented by all OPC DA servers to support basic -/// configuration and error handling capabilities. -pub trait CommonTrait { - fn interface(&self) -> windows::core::Result<&IOPCCommon>; - - /// Sets the locale ID for server string localization. - /// - /// # Arguments - /// * `locale_id` - Windows LCID (Locale ID) value for the desired language - /// - /// # Returns - /// Result indicating if the locale was successfully set - fn set_locale_id(&self, locale_id: u32) -> windows::core::Result<()> { - unsafe { self.interface()?.SetLocaleID(locale_id) } - } - - /// Gets the current locale ID used by the server. - /// - /// # Returns - /// Windows LCID value representing the current locale - fn get_locale_id(&self) -> windows::core::Result { - unsafe { self.interface()?.GetLocaleID() } - } - - /// Gets a list of locale IDs supported by the server. - /// - /// # Returns - /// Array of Windows LCID values for supported locales - fn query_available_locale_ids(&self) -> windows::core::Result> { - let mut locale_ids = RemoteArray::empty(); - - unsafe { - self.interface()? - .QueryAvailableLocaleIDs(locale_ids.as_mut_len_ptr(), locale_ids.as_mut_ptr())?; - } - - Ok(locale_ids) - } - - /// Gets a localized error description string. - /// - /// # Arguments - /// * `error` - HRESULT error code to get description for - /// - /// # Returns - /// Localized error message string in current locale - fn get_error_string(&self, error: windows::core::HRESULT) -> windows::core::Result { - let output = unsafe { self.interface()?.GetErrorString(error)? }; - - RemotePointer::from(output).try_into() - } - - /// Sets a client name for server identification. - /// - /// # Arguments - /// * `name` - Client application name or description - /// - /// # Returns - /// Result indicating if the client name was successfully set - fn set_client_name(&self, name: &str) -> windows::core::Result<()> { - let name = LocalPointer::from(name); - unsafe { self.interface()?.SetClientName(name.as_pcwstr()) } - } -} diff --git a/opc_da/src/client/traits/connection_point_container.rs b/opc_da/src/client/traits/connection_point_container.rs deleted file mode 100644 index 541f40f..0000000 --- a/opc_da/src/client/traits/connection_point_container.rs +++ /dev/null @@ -1,62 +0,0 @@ -use windows::core::{Interface as _, GUID}; - -/// COM connection point container functionality. -/// -/// Provides methods to establish connections between event sources -/// and event sinks in the OPC COM architecture. Used primarily for -/// handling asynchronous callbacks. -pub trait ConnectionPointContainerTrait { - fn interface( - &self, - ) -> windows::core::Result<&windows::Win32::System::Com::IConnectionPointContainer>; - - /// Finds a connection point for a specific interface. - /// - /// # Arguments - /// * `id` - GUID of the connection point interface to find - /// - /// # Returns - /// Connection point interface for the specified GUID - /// - /// # Safety - /// Caller must ensure: - /// - COM is properly initialized - /// - The underlying COM object is valid - /// - /// # Errors - /// Returns an error if: - /// - The COM operation fails - /// - The connection point is not found - fn find_connection_point( - &self, - id: &GUID, - ) -> windows::core::Result { - unsafe { self.interface()?.FindConnectionPoint(id) } - } - - fn data_callback_connection_point( - &self, - ) -> windows::core::Result { - self.find_connection_point(&opc_da_bindings::IOPCDataCallback::IID) - } - - /// Enumerates all available connection points. - /// - /// # Returns - /// Enumerator for iterating through available connection points - /// - /// # Safety - /// Caller must ensure: - /// - COM is properly initialized - /// - The underlying COM object is valid - /// - /// # Errors - /// Returns an error if: - /// - The COM operation fails - /// - No connection points are available - fn enum_connection_points( - &self, - ) -> windows::core::Result { - unsafe { self.interface()?.EnumConnectionPoints() } - } -} diff --git a/opc_da/src/client/traits/data_callback.rs b/opc_da/src/client/traits/data_callback.rs deleted file mode 100644 index dcd52fb..0000000 --- a/opc_da/src/client/traits/data_callback.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::{ - def::{CancelCompleteEvent, DataChangeEvent, ReadCompleteEvent, WriteCompleteEvent}, - utils::RemoteArray, -}; - -#[windows::core::implement( - // implicit implement IUnknown - opc_da_bindings::IOPCDataCallback, -)] -pub struct DataCallback<'a, T>(pub &'a T) -where - T: DataCallbackTrait; - -impl std::ops::Deref for DataCallback<'_, T> -where - T: DataCallbackTrait, -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -pub trait DataCallbackTrait { - #[allow(clippy::too_many_arguments)] - fn on_data_change(&self, event: DataChangeEvent) -> windows_core::Result<()>; - - #[allow(clippy::too_many_arguments)] - fn on_read_complete(&self, event: ReadCompleteEvent) -> windows_core::Result<()>; - - fn on_write_complete(&self, event: WriteCompleteEvent) -> windows_core::Result<()>; - - fn on_cancel_complete(&self, event: CancelCompleteEvent) -> windows_core::Result<()>; -} - -impl<'a, T: DataCallbackTrait + 'a> opc_da_bindings::IOPCDataCallback_Impl - for DataCallback_Impl<'a, T> -{ - fn OnDataChange( - &self, - transaction_id: u32, - group_handle: u32, - master_quality: windows_core::HRESULT, - master_error: windows_core::HRESULT, - count: u32, - client_items: *const u32, - values: *const windows::Win32::System::Variant::VARIANT, - qualities: *const u16, - timestamps: *const windows::Win32::Foundation::FILETIME, - errors: *const windows_core::HRESULT, - ) -> windows_core::Result<()> { - let client_items = RemoteArray::from_ptr(client_items, count); - let values = RemoteArray::from_ptr(values, count); - let qualities = RemoteArray::from_ptr(qualities, count); - let timestamps = RemoteArray::from_ptr(timestamps, count); - let errors = RemoteArray::from_ptr(errors, count); - - self.on_data_change(DataChangeEvent { - transaction_id, - group_handle, - master_quality, - master_error, - client_items, - values, - qualities, - timestamps, - errors, - }) - } - - fn OnReadComplete( - &self, - transaction_id: u32, - group_handle: u32, - master_quality: windows_core::HRESULT, - master_error: windows_core::HRESULT, - count: u32, - client_items: *const u32, - values: *const windows::Win32::System::Variant::VARIANT, - qualities: *const u16, - timestamps: *const windows::Win32::Foundation::FILETIME, - errors: *const windows_core::HRESULT, - ) -> windows_core::Result<()> { - let client_items = RemoteArray::from_ptr(client_items, count); - let values = RemoteArray::from_ptr(values, count); - let qualities = RemoteArray::from_ptr(qualities, count); - let timestamps = RemoteArray::from_ptr(timestamps, count); - let errors = RemoteArray::from_ptr(errors, count); - - self.on_read_complete(ReadCompleteEvent { - transaction_id, - group_handle, - master_quality, - master_error, - client_items, - values, - qualities, - timestamps, - errors, - }) - } - - fn OnWriteComplete( - &self, - transaction_id: u32, - group_handle: u32, - master_error: windows_core::HRESULT, - count: u32, - client_handles: *const u32, - errors: *const windows_core::HRESULT, - ) -> windows_core::Result<()> { - let client_handles = RemoteArray::from_ptr(client_handles, count); - let errors = RemoteArray::from_ptr(errors, count); - - self.on_write_complete(WriteCompleteEvent { - transaction_id, - group_handle, - master_error, - client_handles, - errors, - }) - } - - fn OnCancelComplete(&self, transaction_id: u32, group_handle: u32) -> windows_core::Result<()> { - self.on_cancel_complete(CancelCompleteEvent { - transaction_id, - group_handle, - }) - } -} diff --git a/opc_da/src/client/traits/data_object.rs b/opc_da/src/client/traits/data_object.rs deleted file mode 100644 index 0344977..0000000 --- a/opc_da/src/client/traits/data_object.rs +++ /dev/null @@ -1,131 +0,0 @@ -use windows::Win32::System::Com::{FORMATETC, STGMEDIUM}; - -/// Data transfer functionality using COM's structured storage. -/// -/// Provides methods to transfer data between client and server using -/// structured storage formats and advisory connections. -pub trait DataObjectTrait { - fn interface(&self) -> windows::core::Result<&windows::Win32::System::Com::IDataObject>; - - /// Gets data from the object in the specified format. - /// - /// # Arguments - /// * `format` - Format specification including clipboard format and storage medium - /// - /// # Returns - /// Storage medium containing the requested data - fn get_data(&self, format: &FORMATETC) -> windows::core::Result { - unsafe { self.interface()?.GetData(format) } - } - - /// Gets data in place using the specified format. - /// - /// # Arguments - /// * `format` - Format specification including clipboard format and storage medium - /// - /// # Returns - /// Storage medium updated with the requested data - fn get_data_here(&self, format: &FORMATETC) -> windows::core::Result { - let mut output = STGMEDIUM::default(); - unsafe { self.interface()?.GetDataHere(format, &mut output)? }; - Ok(output) - } - - /// Tests if data is available in the specified format. - /// - /// # Arguments - /// * `format` - Format specification to test for availability - /// - /// # Returns - /// Ok(()) if the format is supported, error otherwise - fn query_get_data(&self, format: &FORMATETC) -> windows::core::Result<()> { - unsafe { self.interface()?.QueryGetData(format) }.ok() - } - - /// Gets the canonical format equivalent to the specified format. - /// - /// # Arguments - /// * `format_in` - Format specification to convert - /// - /// # Returns - /// Canonical format specification - fn get_canonical_format(&self, format_in: &FORMATETC) -> windows::core::Result { - let mut output = FORMATETC::default(); - unsafe { - self.interface()? - .GetCanonicalFormatEtc(format_in, &mut output) - } - .ok()?; - - Ok(output) - } - - /// Sets data in the specified format. - /// - /// # Arguments - /// * `format` - Format specification for the data - /// * `medium` - Storage medium containing the data - /// * `release` - If true, the object takes ownership of medium - /// - /// # Returns - /// Ok(()) if data was set successfully - fn set_data( - &self, - format: &FORMATETC, - medium: &STGMEDIUM, - release: bool, - ) -> windows::core::Result<()> { - unsafe { self.interface()?.SetData(format, medium, release) } - } - - /// Enumerates available data formats. - /// - /// # Arguments - /// * `direction` - Direction of data flow (DATADIR_GET = 1, DATADIR_SET = 2) - /// - /// # Returns - /// Enumerator for available format specifications - fn enumerate_formats( - &self, - direction: u32, - ) -> windows::core::Result { - unsafe { self.interface()?.EnumFormatEtc(direction) } - } - - /// Establishes an advisory connection for data change notifications. - /// - /// # Arguments - /// * `format` - Format specification to monitor - /// * `advf` - Advisory flags controlling notification behavior - /// * `sink` - Sink interface to receive notifications - /// - /// # Returns - /// Connection token for the advisory connection - fn dadvise( - &self, - format: &FORMATETC, - advf: u32, - sink: &windows::Win32::System::Com::IAdviseSink, - ) -> windows::core::Result { - unsafe { self.interface()?.DAdvise(format, advf, sink) } - } - - /// Terminates an advisory connection. - /// - /// # Arguments - /// * `connection` - Connection token from dadvise - /// - /// # Returns - /// Ok(()) if connection was terminated successfully - fn dunadvise(&self, connection: u32) -> windows::core::Result<()> { - unsafe { self.interface()?.DUnadvise(connection) } - } - - /// Enumerates active advisory connections. - /// - /// # Returns - /// Enumerator for active advisory connections - fn enum_dadvise(&self) -> windows::core::Result { - unsafe { self.interface()?.EnumDAdvise() } - } -} diff --git a/opc_da/src/client/traits/group_state_mgt.rs b/opc_da/src/client/traits/group_state_mgt.rs deleted file mode 100644 index 9b4a7f4..0000000 --- a/opc_da/src/client/traits/group_state_mgt.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::{ - def::GroupState, - utils::{LocalPointer, RemotePointer}, -}; - -/// Group state management functionality. -/// -/// Provides methods to get and set various group state parameters including: -/// - Update rate -/// - Active state -/// - Time bias -/// - Deadband -/// - Locale ID -/// - Group handles -pub trait GroupStateMgtTrait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCGroupStateMgt>; - - /// Gets the current state of the group. - /// - /// Returns a GroupState structure containing all group parameters. - fn get_state(&self) -> windows::core::Result { - let mut state = GroupState::default(); - let mut active = windows_core::BOOL::default(); - let name = { - let mut name = RemotePointer::null(); - unsafe { - self.interface()?.GetState( - &mut state.update_rate, - &mut active, - name.as_mut_pwstr_ptr(), - &mut state.time_bias, - &mut state.percent_deadband, - &mut state.locale_id, - &mut state.client_handle, - &mut state.server_handle, - )?; - } - name - }; - - state.active = active.as_bool(); - state.name = name.try_into()?; - - Ok(state) - } - - /// Sets one or more group state parameters. - /// - /// # Arguments - /// * `update_rate` - Requested group update rate in milliseconds - /// * `active` - Group active state - /// * `time_bias` - Time bias from UTC in minutes - /// * `percent_deadband` - Percent deadband for analog items - /// * `locale_id` - Locale ID for status/error strings - /// * `client_handle` - Client-provided handle - /// - /// # Returns - /// The actual update rate set by the server - fn set_state( - &self, - update_rate: Option, - active: Option, - time_bias: Option, - percent_deadband: Option, - locale_id: Option, - client_handle: Option, - ) -> windows::core::Result { - let requested_update_rate = LocalPointer::new(update_rate); - let mut revised_update_rate = LocalPointer::new(Some(0)); - let active = LocalPointer::new(active.map(windows_core::BOOL::from)); - let time_bias = LocalPointer::new(time_bias); - let percent_deadband = LocalPointer::new(percent_deadband); - let locale_id = LocalPointer::new(locale_id); - let client_handle = LocalPointer::new(client_handle); - - unsafe { - self.interface()?.SetState( - requested_update_rate.as_ptr(), - revised_update_rate.as_mut_ptr(), - active.as_ptr(), - time_bias.as_ptr(), - percent_deadband.as_ptr(), - locale_id.as_ptr(), - client_handle.as_ptr(), - ) - }?; - - Ok(revised_update_rate.into_inner().unwrap_or_default()) - } - - /// Sets the name of the group. - fn set_name(&self, name: &str) -> windows::core::Result<()> { - let name = LocalPointer::from(name); - - unsafe { self.interface()?.SetName(name.as_pwstr()) } - } - - /// Creates a copy of the group with a new name. - /// - /// # Arguments - /// * `name` - Name for the new group - /// * `id` - Client-provided GUID for the new group - fn clone_group( - &self, - name: &str, - id: &windows::core::GUID, - ) -> windows::core::Result { - let name = LocalPointer::from(name); - - unsafe { self.interface()?.CloneGroup(name.as_pwstr(), id) } - } -} diff --git a/opc_da/src/client/traits/group_state_mgt2.rs b/opc_da/src/client/traits/group_state_mgt2.rs deleted file mode 100644 index c1f9ba1..0000000 --- a/opc_da/src/client/traits/group_state_mgt2.rs +++ /dev/null @@ -1,33 +0,0 @@ -/// Extended group state management trait (OPC DA 3.0). -/// -/// Provides methods to manage the keep-alive time for OPC groups. The keep-alive time -/// determines how often the server sends keep-alive notifications to maintain the -/// connection state, even when no data has changed. -pub trait GroupStateMgt2Trait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCGroupStateMgt2>; - - /// Sets the keep-alive time for the group in milliseconds. - /// - /// # Arguments - /// * `keep_alive_time` - The requested keep-alive time in milliseconds - /// - /// # Returns - /// The actual keep-alive time set by the server, which may differ from - /// the requested time based on server capabilities - /// - /// # Notes - /// The server may not support the exact requested time and will return - /// the closest supported value. A value of 0 typically disables keep-alive. - fn set_keep_alive(&self, keep_alive_time: u32) -> windows::core::Result { - unsafe { self.interface()?.SetKeepAlive(keep_alive_time) } - } - - /// Gets the current keep-alive time for the group. - /// - /// # Returns - /// The current keep-alive time in milliseconds. A value of 0 indicates - /// that keep-alive is disabled. - fn get_keep_alive(&self) -> windows::core::Result { - unsafe { self.interface()?.GetKeepAlive() } - } -} diff --git a/opc_da/src/client/traits/item_deadband_mgt.rs b/opc_da/src/client/traits/item_deadband_mgt.rs deleted file mode 100644 index d0850d4..0000000 --- a/opc_da/src/client/traits/item_deadband_mgt.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::utils::RemoteArray; - -/// Item deadband management functionality (OPC DA 3.0). -/// -/// Provides methods to manage per-item deadband values. Deadband settings -/// control the minimum value change required before the server reports -/// a data change to the client. -pub trait ItemDeadbandMgtTrait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemDeadbandMgt>; - - /// Sets deadband values for specified items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// * `deadbands` - Array of deadband percentages (0.0 to 100.0) - /// - /// # Returns - /// Array of HRESULT values indicating per-item status - /// - /// # Errors - /// Returns E_INVALIDARG if arrays have different lengths - fn set_item_deadband( - &self, - server_handles: &[u32], - deadbands: &[f32], - ) -> windows::core::Result> { - if server_handles.len() != deadbands.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and deadbands must have the same length", - )); - } - - // Validate deadband values (0.0 to 100.0) - if deadbands.iter().any(|&v| !(0.0..=100.0).contains(&v)) { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "deadband values must be between 0.0 and 100.0", - )); - } - - let len = server_handles.len().try_into()?; - - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.SetItemDeadband( - len, - server_handles.as_ptr(), - deadbands.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } - - /// Gets current deadband values for specified items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// - /// # Returns - /// Tuple containing: - /// - Array of deadband percentages (0.0 to 100.0) - /// - Array of HRESULT values indicating per-item status - fn get_item_deadband( - &self, - server_handles: &[u32], - ) -> windows::core::Result<(RemoteArray, RemoteArray)> { - let len = server_handles.len().try_into()?; - - let mut errors = RemoteArray::new(len); - let mut deadbands = RemoteArray::new(len); - - unsafe { - self.interface()?.GetItemDeadband( - len, - server_handles.as_ptr(), - deadbands.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((deadbands, errors)) - } - - /// Removes deadband settings for specified items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// - /// # Returns - /// Array of HRESULT values indicating per-item status - fn clear_item_deadband( - &self, - server_handles: &[u32], - ) -> windows::core::Result> { - let len = server_handles.len().try_into()?; - - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.ClearItemDeadband( - len, - server_handles.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } -} diff --git a/opc_da/src/client/traits/item_io.rs b/opc_da/src/client/traits/item_io.rs deleted file mode 100644 index 1c673a3..0000000 --- a/opc_da/src/client/traits/item_io.rs +++ /dev/null @@ -1,110 +0,0 @@ -use crate::utils::{LocalPointer, RemoteArray}; -use opc_da_bindings::{tagOPCITEMVQT, IOPCItemIO}; - -/// Direct item I/O functionality (OPC DA 3.0). -/// -/// Provides methods for direct read/write operations on items without requiring -/// group creation. This trait offers a simplified interface for basic data access. -pub trait ItemIoTrait { - fn interface(&self) -> windows::core::Result<&IOPCItemIO>; - - /// Reads values directly from items with age constraints. - /// - /// # Arguments - /// * `item_ids` - Array of fully qualified item IDs to read - /// * `max_age` - Maximum age constraints for each item in milliseconds - /// - /// # Returns - /// Tuple containing: - /// - Array of item values (VARIANT) - /// - Array of quality values - /// - Array of timestamps - /// - Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays are empty or have different lengths - #[allow(clippy::type_complexity)] - fn read( - &self, - item_ids: &[String], - max_age: &[u32], - ) -> windows::core::Result<( - RemoteArray, - RemoteArray, - RemoteArray, - RemoteArray, - )> { - if item_ids.is_empty() || max_age.is_empty() || item_ids.len() != max_age.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Invalid arguments - arrays must be non-empty and have same length", - )); - } - - let item_ptrs: LocalPointer>> = LocalPointer::from(item_ids); - let item_ptrs = item_ptrs.as_pcwstr_array(); - - let len = item_ids.len().try_into()?; - - let mut values = RemoteArray::new(len); - let mut qualities = RemoteArray::new(len); - let mut timestamps = RemoteArray::new(len); - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.Read( - item_ids.len() as u32, - item_ptrs.as_ptr(), - max_age.as_ptr(), - values.as_mut_ptr(), - qualities.as_mut_ptr(), - timestamps.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((values, qualities, timestamps, errors)) - } - - /// Writes values with quality and timestamp information. - /// - /// # Arguments - /// * `item_ids` - Array of fully qualified item IDs to write - /// * `item_vqts` - Array of value-quality-timestamp structures - /// - /// # Returns - /// Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays are empty or have different lengths - fn write_vqt( - &self, - item_ids: &[String], - item_vqts: &[tagOPCITEMVQT], - ) -> windows::core::Result> { - if item_ids.is_empty() || item_vqts.is_empty() || item_ids.len() != item_vqts.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Invalid arguments - arrays must be non-empty and have same length", - )); - } - - let len = item_ids.len().try_into()?; - - let item_ptrs = LocalPointer::from(item_ids); - let item_ptrs = item_ptrs.as_pcwstr_array(); - - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.WriteVQT( - len, - item_ptrs.as_ptr(), - item_vqts.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } -} diff --git a/opc_da/src/client/traits/item_mgt.rs b/opc_da/src/client/traits/item_mgt.rs deleted file mode 100644 index 030e95f..0000000 --- a/opc_da/src/client/traits/item_mgt.rs +++ /dev/null @@ -1,274 +0,0 @@ -use windows::core::Interface as _; - -use crate::{client::ItemAttributeIterator, utils::RemoteArray}; - -/// Item management functionality. -/// -/// Provides methods to manage OPC items within a group, including adding, -/// removing, and modifying item properties such as active state, client -/// handles, and data types. -pub trait ItemMgtTrait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemMgt>; - - /// Adds items to the group. - /// - /// # Arguments - /// * `items` - Array of item definitions containing item IDs and requested properties - /// - /// # Returns - /// Tuple containing: - /// - Array of item results with server handles and canonical data type - /// - Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if items array is empty - fn add_items( - &self, - items: &[opc_da_bindings::tagOPCITEMDEF], - ) -> windows::core::Result<( - RemoteArray, - RemoteArray, - )> { - if items.is_empty() { - return Err(windows_core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "items cannot be empty", - )); - } - - let len = items.len().try_into()?; - let mut results = RemoteArray::new(len); - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.AddItems( - len, - items.as_ptr(), - results.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((results, errors)) - } - - /// Validates item definitions without adding them to the group. - /// - /// # Arguments - /// * `items` - Array of item definitions to validate - /// * `blob_update` - Whether to validate blob update capability - /// - /// # Returns - /// Tuple containing: - /// - Array of item results with access rights and canonical data type - /// - Array of per-item error codes - fn validate_items( - &self, - items: &[opc_da_bindings::tagOPCITEMDEF], - blob_update: bool, - ) -> windows::core::Result<( - RemoteArray, - RemoteArray, - )> { - if items.is_empty() { - return Err(windows_core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "items cannot be empty", - )); - } - - let len = items.len().try_into()?; - let mut results = RemoteArray::new(len); - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.ValidateItems( - len, - items.as_ptr(), - blob_update, - results.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((results, errors)) - } - - /// Removes items from the group. - /// - /// # Arguments - /// * `server_handles` - Array of server handles for items to remove - /// - /// # Returns - /// Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if server_handles is empty - fn remove_items( - &self, - server_handles: &[u32], - ) -> windows::core::Result> { - if server_handles.is_empty() { - return Err(windows_core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()? - .RemoveItems(len, server_handles.as_ptr(), errors.as_mut_ptr())?; - } - - Ok(errors) - } - - /// Sets the active state of items. - /// - /// # Arguments - /// * `server_handles` - Array of server handles - /// * `active` - True to activate items, false to deactivate - /// - /// # Returns - /// Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if server_handles is empty - fn set_active_state( - &self, - server_handles: &[u32], - active: bool, - ) -> windows::core::Result> { - if server_handles.is_empty() { - return Err(windows_core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.SetActiveState( - len, - server_handles.as_ptr(), - active, - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } - - /// Sets client handles for items. - /// - /// # Arguments - /// * `server_handles` - Array of server handles - /// * `client_handles` - Array of new client handles - /// - /// # Returns - /// Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays are empty or have different lengths - fn set_client_handles( - &self, - server_handles: &[u32], - client_handles: &[u32], - ) -> windows::core::Result> { - if server_handles.len() != client_handles.len() { - return Err(windows_core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and client_handles must have the same length", - )); - } - - if server_handles.is_empty() { - return Err(windows_core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.SetClientHandles( - len, - server_handles.as_ptr(), - client_handles.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } - - /// Sets requested data types for items. - /// - /// # Arguments - /// * `server_handles` - Array of server handles - /// * `requested_datatypes` - Array of VT_* data types - /// - /// # Returns - /// Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays are empty or have different lengths - fn set_datatypes( - &self, - server_handles: &[u32], - requested_datatypes: &[u16], - ) -> windows::core::Result> { - if server_handles.len() != requested_datatypes.len() { - return Err(windows_core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and requested_datatypes must have the same length", - )); - } - - if server_handles.is_empty() { - return Err(windows_core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.SetDatatypes( - len, - server_handles.as_ptr(), - requested_datatypes.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } - - /// Creates an enumerator for item management. - /// - /// # Arguments - /// * `id` - Interface ID specifying the type of enumerator - /// - /// # Returns - /// Enumerator interface for iterating through items - fn create_enumerator(&self) -> windows::core::Result { - let enumerator = unsafe { - self.interface()? - .CreateEnumerator(&opc_da_bindings::IEnumOPCItemAttributes::IID)? - }; - - Ok(ItemAttributeIterator::new(enumerator.cast()?)) - } -} - -// ...existing code... diff --git a/opc_da/src/client/traits/item_properties.rs b/opc_da/src/client/traits/item_properties.rs deleted file mode 100644 index d1383fd..0000000 --- a/opc_da/src/client/traits/item_properties.rs +++ /dev/null @@ -1,159 +0,0 @@ -use crate::utils::{LocalPointer, RemoteArray}; -use opc_da_bindings::IOPCItemProperties; - -/// Item properties management functionality. -/// -/// Provides methods to query and retrieve item property information from -/// the OPC server. Properties include metadata such as engineering units, -/// descriptions, and other vendor-specific attributes. -pub trait ItemPropertiesTrait { - fn interface(&self) -> windows::core::Result<&IOPCItemProperties>; - - /// Queries available properties for a specific item. - /// - /// # Arguments - /// * `item_id` - Fully qualified item ID - /// - /// # Returns - /// Tuple containing: - /// - Array of property IDs - /// - Array of property descriptions - /// - Array of property data types (VT_*) - /// - /// # Errors - /// Returns E_INVALIDARG if item_id is empty - fn query_available_properties( - &self, - item_id: &str, - ) -> windows::core::Result<( - RemoteArray, // property IDs - RemoteArray, // descriptions - RemoteArray, // datatypes - )> { - if item_id.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "item_id is empty", - )); - } - - let item_id = LocalPointer::from(item_id); - - let mut count = 0; - let mut property_ids = RemoteArray::new(0); - let mut descriptions = RemoteArray::new(0); - let mut datatypes = RemoteArray::new(0); - - unsafe { - self.interface()?.QueryAvailableProperties( - item_id.as_pcwstr(), - &mut count, - property_ids.as_mut_ptr(), - descriptions.as_mut_ptr(), - datatypes.as_mut_ptr(), - )?; - } - - if count > 0 { - unsafe { - property_ids.set_len(count); - descriptions.set_len(count); - datatypes.set_len(count); - } - } - - Ok((property_ids, descriptions, datatypes)) - } - - /// Gets property values for a specific item. - /// - /// # Arguments - /// * `item_id` - Fully qualified item ID - /// * `property_ids` - Array of property IDs to retrieve - /// - /// # Returns - /// Tuple containing: - /// - Array of property values as VARIANTs - /// - Array of per-property error codes - /// - /// # Errors - /// Returns E_INVALIDARG if property_ids is empty - fn get_item_properties( - &self, - item_id: &str, - property_ids: &[u32], - ) -> windows::core::Result<( - RemoteArray, - RemoteArray, - )> { - if property_ids.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "property_ids is empty", - )); - } - - let item_id = LocalPointer::from(item_id); - - let mut values = RemoteArray::new(property_ids.len().try_into()?); - let mut errors = RemoteArray::new(property_ids.len().try_into()?); - - unsafe { - self.interface()?.GetItemProperties( - item_id.as_pcwstr(), - property_ids.len() as u32, - property_ids.as_ptr(), - values.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((values, errors)) - } - - /// Looks up item IDs for properties that are themselves OPC items. - /// - /// # Arguments - /// * `item_id` - Base item ID to look up properties for - /// * `property_ids` - Array of property IDs to look up - /// - /// # Returns - /// Tuple containing: - /// - Array of property-specific item IDs - /// - Array of per-property error codes - /// - /// # Errors - /// Returns E_INVALIDARG if property_ids is empty - fn lookup_item_ids( - &self, - item_id: &str, - property_ids: &[u32], - ) -> windows::core::Result<( - RemoteArray, - RemoteArray, - )> { - if property_ids.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "property_ids is empty", - )); - } - - let item_id = LocalPointer::from(item_id); - - let mut new_item_ids = RemoteArray::new(property_ids.len().try_into()?); - let mut errors = RemoteArray::new(property_ids.len().try_into()?); - - unsafe { - self.interface()?.LookupItemIDs( - item_id.as_pcwstr(), - property_ids.len().try_into()?, - property_ids.as_ptr(), - new_item_ids.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((new_item_ids, errors)) - } -} diff --git a/opc_da/src/client/traits/item_sampling_mgt.rs b/opc_da/src/client/traits/item_sampling_mgt.rs deleted file mode 100644 index f4c5421..0000000 --- a/opc_da/src/client/traits/item_sampling_mgt.rs +++ /dev/null @@ -1,182 +0,0 @@ -use crate::utils::RemoteArray; -use windows_core::BOOL; - -/// Item sampling management functionality (OPC DA 3.0). -/// -/// Provides methods to control sampling rates and buffering behavior -/// for individual items in an OPC group. -pub trait ItemSamplingMgtTrait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemSamplingMgt>; - - /// Sets sampling rates for specified items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// * `sampling_rates` - Array of requested sampling rates in milliseconds - /// - /// # Returns - /// Tuple containing: - /// - Array of actual sampling rates set by server - /// - Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays have different lengths - fn set_item_sampling_rate( - &self, - server_handles: &[u32], - sampling_rates: &[u32], - ) -> windows::core::Result<(RemoteArray, RemoteArray)> { - if server_handles.len() != sampling_rates.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and sampling_rates must have the same length", - )); - } - - let len = server_handles.len().try_into()?; - - let mut revised_rates = RemoteArray::new(len); - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.SetItemSamplingRate( - len, - server_handles.as_ptr(), - sampling_rates.as_ptr(), - revised_rates.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((revised_rates, errors)) - } - - /// Gets current sampling rates for specified items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// - /// # Returns - /// Tuple containing: - /// - Array of current sampling rates in milliseconds - /// - Array of per-item error codes - fn get_item_sampling_rate( - &self, - server_handles: &[u32], - ) -> windows::core::Result<(RemoteArray, RemoteArray)> { - let len = server_handles.len().try_into()?; - - let mut sampling_rates = RemoteArray::new(len); - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.GetItemSamplingRate( - len, - server_handles.as_ptr(), - sampling_rates.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((sampling_rates, errors)) - } - - /// Removes custom sampling rates for specified items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// - /// # Returns - /// Array of per-item error codes - fn clear_item_sampling_rate( - &self, - server_handles: &[u32], - ) -> windows::core::Result> { - let len = server_handles.len().try_into()?; - - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.ClearItemSamplingRate( - len, - server_handles.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } - - /// Enables or disables data buffering for specified items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// * `enable` - Array of boolean values to enable/disable buffering - /// - /// # Returns - /// Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays have different lengths - fn set_item_buffer_enable( - &self, - server_handles: &[u32], - enable: &[bool], - ) -> windows::core::Result> { - if server_handles.len() != enable.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and enable must have the same length", - )); - } - - let len = server_handles.len().try_into()?; - - let mut errors = RemoteArray::new(len); - let enable_bool: Vec = enable.iter().map(|&v| BOOL::from(v)).collect(); - - unsafe { - self.interface()?.SetItemBufferEnable( - len, - server_handles.as_ptr(), - enable_bool.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } - - /// Gets current buffer enable states for specified items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// - /// # Returns - /// Tuple containing: - /// - Array of current buffer enable states - /// - Array of per-item error codes - fn get_item_buffer_enable( - &self, - server_handles: &[u32], - ) -> windows::core::Result<( - RemoteArray, - RemoteArray, - )> { - let len = server_handles.len().try_into()?; - - let mut enable = RemoteArray::new(len); - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.GetItemBufferEnable( - len, - server_handles.as_ptr(), - enable.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((enable, errors)) - } -} diff --git a/opc_da/src/client/traits/mod.rs b/opc_da/src/client/traits/mod.rs deleted file mode 100644 index 9df27d5..0000000 --- a/opc_da/src/client/traits/mod.rs +++ /dev/null @@ -1,71 +0,0 @@ -/// OPC DA client trait definitions. -/// -/// This module contains all trait definitions for interacting with OPC DA servers. -/// The traits are organized by functionality and OPC DA version compatibility: -/// -/// Version independent traits: -/// - CommonTrait: Basic server configuration and error handling -/// - ConnectionPointContainerTrait: Event connection management -/// - DataObjectTrait: COM data transfer functionality -/// -/// OPC DA 1.0 traits: -/// - AsyncIoTrait: Basic asynchronous operations -/// - SyncIoTrait: Basic synchronous operations -/// -/// OPC DA 2.0 traits: -/// - AsyncIo2Trait: Enhanced asynchronous operations -/// - SyncIo2Trait: Enhanced synchronous operations -/// - BrowseServerAddressSpaceTrait: Address space navigation -/// -/// OPC DA 3.0 traits: -/// - AsyncIo3Trait: Advanced asynchronous operations -/// - BrowseTrait: Enhanced browsing capabilities -/// - ItemDeadbandMgtTrait: Item deadband management -/// - ItemIoTrait: Direct item access -/// - ItemSamplingMgtTrait: Sampling rate control -/// - GroupStateMgt2Trait: Extended group management -mod async_io; -mod async_io2; -mod async_io3; -mod browse; -mod browse_server_address_space; -mod client; -mod common; -mod connection_point_container; -mod data_callback; -mod data_object; -mod group_state_mgt; -mod group_state_mgt2; -mod item_deadband_mgt; -mod item_io; -mod item_mgt; -mod item_properties; -mod item_sampling_mgt; -mod public_group_state_mgt; -mod server; -mod server_public_groups; -mod sync_io; -mod sync_io2; - -pub use async_io::*; -pub use async_io2::*; -pub use async_io3::*; -pub use browse::*; -pub use browse_server_address_space::*; -pub use client::*; -pub use common::*; -pub use connection_point_container::*; -pub use data_callback::*; -pub use data_object::*; -pub use group_state_mgt::*; -pub use group_state_mgt2::*; -pub use item_deadband_mgt::*; -pub use item_io::*; -pub use item_mgt::*; -pub use item_properties::*; -pub use item_sampling_mgt::*; -pub use public_group_state_mgt::*; -pub use server::*; -pub use server_public_groups::*; -pub use sync_io::*; -pub use sync_io2::*; diff --git a/opc_da/src/client/traits/public_group_state_mgt.rs b/opc_da/src/client/traits/public_group_state_mgt.rs deleted file mode 100644 index 11e6c89..0000000 --- a/opc_da/src/client/traits/public_group_state_mgt.rs +++ /dev/null @@ -1,28 +0,0 @@ -/// Public group state management functionality. -/// -/// Provides methods to manage public groups in OPC servers. Public groups -/// can be shared between multiple clients, allowing for more efficient -/// server resource usage. -pub trait PublicGroupStateMgtTrait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCPublicGroupStateMgt>; - - /// Gets the public state of the group. - /// - /// # Returns - /// `true` if the group is public, `false` if it is private - fn get_state(&self) -> windows::core::Result { - unsafe { self.interface()?.GetState() }.map(|v| v.as_bool()) - } - - /// Converts a private group to a public group. - /// - /// # Returns - /// Ok(()) if the group was successfully converted to public - /// - /// # Notes - /// Once a group becomes public, it remains public until the server - /// is shut down or the group is deleted. - fn move_to_public(&self) -> windows::core::Result<()> { - unsafe { self.interface()?.MoveToPublic() } - } -} diff --git a/opc_da/src/client/traits/server.rs b/opc_da/src/client/traits/server.rs deleted file mode 100644 index ddac630..0000000 --- a/opc_da/src/client/traits/server.rs +++ /dev/null @@ -1,134 +0,0 @@ -use windows::core::Interface as _; - -use crate::{ - client::{GroupIterator, StringIterator}, - utils::{LocalPointer, RemotePointer}, -}; - -/// OPC Server management functionality. -/// -/// Provides methods to create and manage groups within an OPC server, -/// as well as monitor server status and enumerate existing groups. -pub trait ServerTrait> { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCServer>; - - /// Adds a new group to the OPC server. - /// - /// # Arguments - /// * `name` - Group name for identification - /// * `active` - Whether the group should initially be active - /// * `client_handle` - Client-assigned handle for the group - /// * `update_rate` - Requested update rate in milliseconds - /// * `locale_id` - Locale ID for text strings - /// * `time_bias` - Time zone bias in minutes from UTC - /// * `percent_deadband` - Percent change required to trigger updates - /// - /// # Returns - /// The newly created group object - /// - /// # Errors - /// Returns E_POINTER if group creation fails - #[allow(clippy::too_many_arguments)] - fn add_group( - &self, - name: &str, - active: bool, - client_handle: u32, - update_rate: u32, - locale_id: u32, - time_bias: i32, - percent_deadband: f32, - revised_percent_deadband: &mut u32, - server_handle: &mut u32, - ) -> windows::core::Result { - let mut group = None; - let group_name = LocalPointer::from(name); - let group_name = group_name.as_pcwstr(); - - unsafe { - self.interface()?.AddGroup( - group_name, - active, - update_rate, - client_handle, - &time_bias, - &percent_deadband, - locale_id, - server_handle, - revised_percent_deadband, - &opc_da_bindings::IOPCItemMgt::IID, - &mut group, - )?; - } - - match group { - None => Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Failed to add group, returned null", - )), - Some(group) => group.cast::()?.try_into(), - } - } - - /// Gets the current server status. - /// - /// # Returns - /// Server status structure containing vendor info, time, state, - /// and group counts - fn get_status( - &self, - ) -> windows::core::Result> { - let status = unsafe { self.interface()?.GetStatus()? }; - Ok(RemotePointer::from_raw(status)) - } - - /// Removes a group from the server. - /// - /// # Arguments - /// * `server_handle` - Server's handle for the group - /// * `force` - If true, remove even if clients are connected - fn remove_group(&self, server_handle: u32, force: bool) -> windows::core::Result<()> { - unsafe { - self.interface()?.RemoveGroup(server_handle, force)?; - } - Ok(()) - } - - /// Creates an enumerator for groups. - /// - /// # Arguments - /// * `scope` - Scope of groups to enumerate (public, private, or all) - /// - /// # Returns - /// Enumerator interface for iterating through groups - fn create_group_enumerator( - &self, - scope: opc_da_bindings::tagOPCENUMSCOPE, - ) -> windows::core::Result> { - let enumerator = unsafe { - self.interface()? - .CreateGroupEnumerator(scope, &windows::Win32::System::Com::IEnumUnknown::IID)? - }; - - Ok(GroupIterator::new(enumerator.cast()?)) - } - - /// Creates an enumerator for group names. - /// - /// # Arguments - /// * `scope` - Scope of group names to enumerate (public, private, or all) - /// - /// # Returns - /// Enumerator interface for iterating through group names - fn create_group_name_enumerator( - &self, - scope: opc_da_bindings::tagOPCENUMSCOPE, - ) -> windows::core::Result { - let enumerator = unsafe { - self.interface()? - .CreateGroupEnumerator(scope, &windows::Win32::System::Com::IEnumString::IID)? - }; - - Ok(StringIterator::new(enumerator.cast()?)) - } -} diff --git a/opc_da/src/client/traits/server_public_groups.rs b/opc_da/src/client/traits/server_public_groups.rs deleted file mode 100644 index 5260630..0000000 --- a/opc_da/src/client/traits/server_public_groups.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::utils::LocalPointer; - -/// Server public groups management functionality. -/// -/// Provides methods to access and manage public groups that can be shared -/// between multiple OPC clients for more efficient server resource usage. -pub trait ServerPublicGroupsTrait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCServerPublicGroups>; - - /// Gets a public group by its name. - /// - /// # Arguments - /// * `name` - Name of the public group to retrieve - /// * `id` - Interface ID for the requested group interface - /// - /// # Returns - /// The requested interface pointer for the public group - fn get_public_group_by_name( - &self, - name: &str, - id: &windows::core::GUID, - ) -> windows::core::Result { - let name = LocalPointer::from(name); - - unsafe { self.interface()?.GetPublicGroupByName(name.as_pcwstr(), id) } - } - - /// Removes a public group from the server. - /// - /// # Arguments - /// * `server_group` - Server handle of the group to remove - /// * `force` - If true, removes group even if clients are connected - /// - /// # Returns - /// Ok(()) if the group was successfully removed - fn remove_public_group(&self, server_group: u32, force: bool) -> windows::core::Result<()> { - unsafe { self.interface()?.RemovePublicGroup(server_group, force) } - } -} diff --git a/opc_da/src/client/traits/sync_io.rs b/opc_da/src/client/traits/sync_io.rs deleted file mode 100644 index 7c4e762..0000000 --- a/opc_da/src/client/traits/sync_io.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::utils::RemoteArray; -use windows::Win32::System::Variant::VARIANT; - -/// Synchronous I/O functionality (OPC DA 1.0). -/// -/// Provides methods for basic synchronous read/write operations -/// with direct server communication. -pub trait SyncIoTrait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCSyncIO>; - - /// Reads values synchronously from items. - /// - /// # Arguments - /// * `source` - Whether to read from cache or device - /// * `server_handles` - Array of server item handles - /// - /// # Returns - /// Tuple containing: - /// - Array of item states (value, quality, timestamp) - /// - Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if server_handles is empty - fn read( - &self, - source: opc_da_bindings::tagOPCDATASOURCE, - server_handles: &[u32], - ) -> windows::core::Result<( - RemoteArray, - RemoteArray, - )> { - if server_handles.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - - let mut item_values = RemoteArray::new(len); - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.Read( - source, - len, - server_handles.as_ptr(), - item_values.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((item_values, errors)) - } - - /// Writes values synchronously to items. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// * `values` - Array of values to write - /// - /// # Returns - /// Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays are empty or have different lengths - fn write( - &self, - server_handles: &[u32], - values: &[VARIANT], - ) -> windows::core::Result> { - if server_handles.len() != values.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and values must have the same length", - )); - } - - if server_handles.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.Write( - len, - server_handles.as_ptr(), - values.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } -} diff --git a/opc_da/src/client/traits/sync_io2.rs b/opc_da/src/client/traits/sync_io2.rs deleted file mode 100644 index d96e02f..0000000 --- a/opc_da/src/client/traits/sync_io2.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::utils::RemoteArray; -use windows::Win32::System::Variant::VARIANT; - -/// Synchronous I/O functionality (OPC DA 3.0). -/// -/// Provides enhanced synchronous read/write operations with support for -/// quality, timestamp, and maximum age constraints. -pub trait SyncIo2Trait { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCSyncIO2>; - - /// Reads values with maximum age constraint. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// * `max_age` - Maximum age constraints for each item in milliseconds - /// - /// # Returns - /// Tuple containing: - /// - Array of item values - /// - Array of quality values - /// - Array of timestamps - /// - Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays are empty or have different lengths - #[allow(clippy::type_complexity)] - fn read_max_age( - &self, - server_handles: &[u32], - max_age: &[u32], - ) -> windows::core::Result<( - RemoteArray, - RemoteArray, - RemoteArray, - RemoteArray, - )> { - if server_handles.len() != max_age.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and max_age must have the same length", - )); - } - - if server_handles.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - - let mut values = RemoteArray::new(len); - let mut qualities = RemoteArray::new(len); - let mut timestamps = RemoteArray::new(len); - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.ReadMaxAge( - len, - server_handles.as_ptr(), - max_age.as_ptr(), - values.as_mut_ptr(), - qualities.as_mut_ptr(), - timestamps.as_mut_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok((values, qualities, timestamps, errors)) - } - - /// Writes values with quality and timestamp information. - /// - /// # Arguments - /// * `server_handles` - Array of server item handles - /// * `values` - Array of value-quality-timestamp structures - /// - /// # Returns - /// Array of per-item error codes - /// - /// # Errors - /// Returns E_INVALIDARG if arrays are empty or have different lengths - fn write_vqt( - &self, - server_handles: &[u32], - values: &[opc_da_bindings::tagOPCITEMVQT], - ) -> windows::core::Result> { - if server_handles.len() != values.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles and values must have the same length", - )); - } - - if server_handles.is_empty() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "server_handles cannot be empty", - )); - } - - let len = server_handles.len().try_into()?; - - let mut errors = RemoteArray::new(len); - - unsafe { - self.interface()?.WriteVQT( - len, - server_handles.as_ptr(), - values.as_ptr(), - errors.as_mut_ptr(), - )?; - } - - Ok(errors) - } -} diff --git a/opc_da/src/client/unified/actor/client.rs b/opc_da/src/client/unified/actor/client.rs deleted file mode 100644 index d8f6f37..0000000 --- a/opc_da/src/client/unified/actor/client.rs +++ /dev/null @@ -1,65 +0,0 @@ -use actix::prelude::*; - -use crate::{ - client::{unified::Client, GuidIterator}, - mb_error, - utils::RemotePointer, -}; - -impl Actor for Client { - type Context = Context; -} - -impl Actor for GuidIterator { - type Context = Context; -} - -pub struct ClientActor(Addr); - -impl ClientActor { - pub fn new(client: Client) -> windows::core::Result { - Ok(Self(client.start())) - } -} - -// deref to the inner Addr -impl std::ops::Deref for ClientActor { - type Target = Addr; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[derive(Message)] -#[rtype(result = "windows::core::Result>")] -struct GetServerGuids; - -impl ClientActor { - pub async fn get_servers(&self) -> windows::core::Result> { - mb_error!(self.send(GetServerGuids).await) - } -} - -impl Handler for Client { - type Result = windows::core::Result>; - - fn handle(&mut self, _: GetServerGuids, _: &mut Self::Context) -> Self::Result { - self.get_servers()? - .map(|r| match r { - Ok(guid) => { - let name = unsafe { - windows::Win32::System::Com::ProgIDFromCLSID(&guid).map_err(|e| { - windows::core::Error::new(e.code(), "Failed to get ProgID") - }) - }?; - - let name = RemotePointer::from(name); - - Ok((guid, name.try_into()?)) - } - Err(e) => Err(e), - }) - .collect() - } -} diff --git a/opc_da/src/client/unified/actor/mod.rs b/opc_da/src/client/unified/actor/mod.rs deleted file mode 100644 index fcd7ec0..0000000 --- a/opc_da/src/client/unified/actor/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -mod client; -mod runtime; -mod server; - -#[cfg(test)] -mod tests; - -pub use client::*; -pub use runtime::*; - -fn mb_error(err: actix::MailboxError) -> windows::core::Error { - windows::core::Error::new( - windows::Win32::Foundation::E_FAIL, - format!("Failed to send message to client actor: {err:?}"), - ) -} - -#[macro_export] -macro_rules! mb_error { - ($err:expr) => { - $err.map_err($crate::client::unified::actor::mb_error)? - }; -} diff --git a/opc_da/src/client/unified/actor/runtime.rs b/opc_da/src/client/unified/actor/runtime.rs deleted file mode 100644 index 8045fbe..0000000 --- a/opc_da/src/client/unified/actor/runtime.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::client::unified::Guard; - -pub fn try_create_runtime() -> std::io::Result { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .on_thread_start(Guard::<()>::initialize) - .on_thread_stop(Guard::<()>::uninitialize) - .build() -} - -pub fn create_runtime() -> tokio::runtime::Runtime { - try_create_runtime().expect("Failed to create runtime") -} diff --git a/opc_da/src/client/unified/actor/server.rs b/opc_da/src/client/unified/actor/server.rs deleted file mode 100644 index 72fc8df..0000000 --- a/opc_da/src/client/unified/actor/server.rs +++ /dev/null @@ -1,7 +0,0 @@ -use actix::prelude::*; - -use crate::client::unified::Server; - -impl Actor for Server { - type Context = SyncContext; -} diff --git a/opc_da/src/client/unified/actor/tests.rs b/opc_da/src/client/unified/actor/tests.rs deleted file mode 100644 index 09e0c0f..0000000 --- a/opc_da/src/client/unified/actor/tests.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::client::unified::{create_runtime, Client}; - -use super::ClientActor; - -#[test] -fn test_actor() { - actix::System::with_tokio_rt(create_runtime).block_on(async { - let client = ClientActor::new(Client::v2()).expect("Failed to create client actor"); - let servers = client.get_servers().await.expect("Failed to get servers"); - - assert!(!servers.is_empty()); - }); -} diff --git a/opc_da/src/client/unified/client.rs b/opc_da/src/client/unified/client.rs deleted file mode 100644 index 334c665..0000000 --- a/opc_da/src/client/unified/client.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::{ - client::{v1, v2, v3, ClientTrait as _, GuidIterator}, - def::ClassContext, -}; - -use super::Server; - -#[derive(Debug)] -pub enum Client { - V1(v1::Client), - V2(v2::Client), - V3(v3::Client), -} - -impl Client { - pub fn v1() -> Self { - Self::V1(v1::Client) - } - - pub fn v2() -> Self { - Self::V2(v2::Client) - } - - pub fn v3() -> Self { - Self::V3(v3::Client) - } - - pub fn get_servers(&self) -> windows::core::Result { - match self { - Client::V1(client) => client.get_servers(), - Client::V2(client) => client.get_servers(), - Client::V3(client) => client.get_servers(), - } - } - - pub fn create_server(&self, class_id: windows::core::GUID) -> windows::core::Result { - match self { - Client::V1(client) => Ok(Server::V1( - client.create_server(class_id, ClassContext::All)?, - )), - Client::V2(client) => Ok(Server::V2( - client.create_server(class_id, ClassContext::All)?, - )), - Client::V3(client) => Ok(Server::V3( - client.create_server(class_id, ClassContext::All)?, - )), - } - } -} - -impl From for Client { - fn from(client: v1::Client) -> Self { - Self::V1(client) - } -} - -impl From for Client { - fn from(client: v2::Client) -> Self { - Self::V2(client) - } -} - -impl From for Client { - fn from(client: v3::Client) -> Self { - Self::V3(client) - } -} diff --git a/opc_da/src/client/unified/group.rs b/opc_da/src/client/unified/group.rs deleted file mode 100644 index 7ab42a1..0000000 --- a/opc_da/src/client/unified/group.rs +++ /dev/null @@ -1,659 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use windows_core::{ComObjectInner as _, IUnknown, Interface}; - -use crate::{ - client::{ - v1, v2, v3, AsyncIo2Trait, AsyncIo3Trait, ConnectionPointContainerTrait, DataCallback, - DataCallbackTrait, ItemMgtTrait, SyncIo2Trait, SyncIoTrait, - }, - def::{ - CancelCompleteEvent, DataChangeEvent, DataSourceTarget, ItemDef, ItemPartialValue, - ItemResult, ItemState, ItemValue, ReadCompleteEvent, WriteCompleteEvent, - }, - utils::{IntoBridge as _, TryToLocal as _, TryToNative as _}, -}; - -pub struct Group { - inner: GroupInner, - items: HashMap, - next_transaction_id: std::sync::atomic::AtomicU32, - initialized: bool, - data_callback_cookie: Option, - data_change_broadcaster: tokio::sync::broadcast::Sender, - data_change_awaiters: - std::sync::Mutex>>, - read_complete_awaiters: - std::sync::Mutex>>, - write_complete_awaiters: - std::sync::Mutex>>, - cancel_complete_awaiters: - std::sync::Mutex>>, -} - -pub enum GroupInner { - V1(v1::Group), - V2(v2::Group), - V3(v3::Group), -} - -pub struct Item { - pub name: String, - pub server_handle: u32, - pub client_handle: u32, -} - -impl Group { - fn new(inner: GroupInner) -> Self { - let data_change_broadcaster = tokio::sync::broadcast::Sender::new(32); - - Self { - inner, - items: HashMap::new(), - next_transaction_id: std::sync::atomic::AtomicU32::new(1), - initialized: false, - data_callback_cookie: None, - data_change_broadcaster, - data_change_awaiters: std::sync::Mutex::new(BTreeMap::new()), - read_complete_awaiters: std::sync::Mutex::new(BTreeMap::new()), - write_complete_awaiters: std::sync::Mutex::new(BTreeMap::new()), - cancel_complete_awaiters: std::sync::Mutex::new(BTreeMap::new()), - } - } - - pub fn initialize(&mut self) -> windows::core::Result<()> { - if self.initialized { - return Ok(()); - } - - let connection_point = match &self.inner { - GroupInner::V1(_) => return Ok(()), - GroupInner::V2(group) => group.data_callback_connection_point()?, - GroupInner::V3(group) => group.data_callback_connection_point()?, - }; - - if self.data_callback_cookie.is_none() { - let callback = DataCallback(self); - self.data_callback_cookie = Some(unsafe { - connection_point.Advise( - &callback - .into_object() - .into_interface::() - .cast::()?, - ) - }?); - } - - self.initialized = true; - - Ok(()) - } - - pub fn data_change_receiver(&self) -> tokio::sync::broadcast::Receiver { - self.data_change_broadcaster.subscribe() - } - - fn handle_callback( - &self, - awaiters: &std::sync::Mutex>>, - transaction_id: u32, - event: T, - ) -> windows::core::Result<()> { - let mut awaiters = awaiters.lock().map_err(|_| { - windows_core::Error::new(windows::Win32::Foundation::E_FAIL, "lock poisoned") - })?; - - let awaiter = awaiters.remove(&transaction_id).ok_or_else(|| { - windows_core::Error::new(windows::Win32::Foundation::E_FAIL, "no awaiter found") - })?; - - awaiter.send(event).map_err(|_| { - windows_core::Error::new(windows::Win32::Foundation::E_FAIL, "event awaiter dropped") - }) - } - - fn next_receiver( - &self, - awaiters: &std::sync::Mutex>>, - ) -> windows::core::Result<(u32, tokio::sync::oneshot::Receiver)> { - let transaction_id = self - .next_transaction_id - .fetch_add(1, std::sync::atomic::Ordering::SeqCst); - - let (sender, receiver) = tokio::sync::oneshot::channel(); - - let mut awaiters = awaiters.lock().map_err(|_| { - windows_core::Error::new(windows::Win32::Foundation::E_FAIL, "lock poisoned") - })?; - - awaiters.insert(transaction_id, sender); - - Ok((transaction_id, receiver)) - } -} - -impl DataCallbackTrait for Group { - fn on_data_change(&self, event: DataChangeEvent) -> windows_core::Result<()> { - self.data_change_broadcaster - .send(event.clone()) - .map_err(|_| { - windows_core::Error::new( - windows::Win32::Foundation::E_FAIL, - "data change event receiver dropped", - ) - })?; - - self.handle_callback(&self.data_change_awaiters, event.transaction_id, event) - } - - fn on_read_complete(&self, event: ReadCompleteEvent) -> windows_core::Result<()> { - self.handle_callback(&self.read_complete_awaiters, event.transaction_id, event) - } - - fn on_write_complete(&self, event: WriteCompleteEvent) -> windows_core::Result<()> { - self.handle_callback(&self.write_complete_awaiters, event.transaction_id, event) - } - - fn on_cancel_complete(&self, event: CancelCompleteEvent) -> windows_core::Result<()> { - self.handle_callback(&self.cancel_complete_awaiters, event.transaction_id, event) - } -} - -impl Group { - #[inline(always)] - fn item_mgt(&self) -> &dyn ItemMgtTrait { - match &self.inner { - GroupInner::V1(group) => group, - GroupInner::V2(group) => group, - GroupInner::V3(group) => group, - } - } - - pub fn add( - &self, - items: Vec, - ) -> windows::core::Result>> { - let bridge = items.into_bridge(); - self.item_mgt() - .add_items(&bridge.try_to_native()?)? - .try_to_local() - } - - pub fn validate( - &self, - items: Vec, - blob_update: bool, - ) -> windows::core::Result>> { - let bridge = items.into_bridge(); - self.item_mgt() - .validate_items(&bridge.try_to_native()?, blob_update)? - .try_to_local() - } - - pub fn remove( - &self, - server_handles: Vec, - ) -> windows::core::Result>> { - self.item_mgt() - .remove_items(&server_handles)? - .try_to_local() - } - - // TODO set_active_state - // TODO set_client_handle - // TODO set_datatypes - // TODO create_enumerator - - fn read_sync1( - &self, - sync_io1: &T, - data_source: DataSourceTarget, - server_handles: &[u32], - ) -> windows::core::Result>> { - let results: Vec> = sync_io1 - .read(data_source.try_to_native()?, server_handles)? - .try_to_local()?; - - Ok(results - .into_iter() - .map(|r| { - r.map(|r| ItemValue { - value: r.data_value, - quality: r.quality, - timestamp: r.timestamp, - }) - }) - .collect()) - } - - fn read_sync2( - &self, - sync_io2: &T, - server_handles: &[u32], - max_ages: &[u32], - ) -> windows::core::Result>> { - sync_io2 - .read_max_age(server_handles, max_ages)? - .try_to_local() - } - - pub fn read_sync( - &self, - items_names: &[S], - data_source: DataSourceTarget, - ) -> windows::core::Result>> - where - S: AsRef, - { - let server_handles: Vec = items_names - .iter() - .map(|name| { - self.items - .get(name.as_ref()) - .map(|item| item.server_handle) - .ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "item name not found", - ) - }) - }) - .collect::>()?; - - match &self.inner { - GroupInner::V1(group) => self.read_sync1(group, data_source, &server_handles), - GroupInner::V2(group) => self.read_sync1(group, data_source, &server_handles), - GroupInner::V3(group) => self.read_sync2( - group, - &server_handles, - &vec![data_source.max_age(); server_handles.len()], - ), - } - } - - fn read_async2( - &self, - async_io2: &T, - server_handles: &[u32], - ) -> windows::core::Result<( - DataCallbackFuture, - Vec>, - )> { - let (transaction_id, receiver) = self.next_receiver(&self.read_complete_awaiters)?; - - let (cancel_id, results) = async_io2.read(server_handles, transaction_id)?; - - Ok(( - DataCallbackFuture { - receiver: Box::pin(receiver), - transaction_id, - cancel_id, - }, - results.try_to_local()?, - )) - } - - fn read_async3( - &self, - async_io3: &T, - server_handles: &[u32], - max_ages: &[u32], - ) -> windows::core::Result<( - DataCallbackFuture, - Vec>, - )> { - let (transaction_id, receiver) = self.next_receiver(&self.read_complete_awaiters)?; - - let (cancel_id, results) = - async_io3.read_max_age(server_handles, max_ages, transaction_id)?; - - Ok(( - DataCallbackFuture { - receiver: Box::pin(receiver), - transaction_id, - cancel_id, - }, - results.try_to_local()?, - )) - } - - pub fn read_async>( - &self, - items_names: &[S], - data_source: DataSourceTarget, - ) -> windows::core::Result<( - DataCallbackFuture, - Vec>, - )> { - let server_handles: Vec = items_names - .iter() - .map(|name| { - self.items - .get(name.as_ref()) - .map(|item| item.server_handle) - .ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "item name not found", - ) - }) - }) - .collect::>()?; - - match &self.inner { - GroupInner::V1(_) => Err(windows_core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "read_async not implemented for v1", - )), - GroupInner::V2(group) => self.read_async2(group, &server_handles), - GroupInner::V3(group) => self.read_async3( - group, - &server_handles, - &vec![data_source.max_age(); server_handles.len()], - ), - } - } - - fn write_sync1( - &self, - sync_io1: &T, - server_handles: &[u32], - item_values: &[windows::Win32::System::Variant::VARIANT], - ) -> windows::core::Result>> { - let results: Vec> = sync_io1 - .write(server_handles, item_values)? - .try_to_local()?; - - Ok(results) - } - - fn write_sync2( - &self, - sync_io2: &T, - server_handles: &[u32], - item_values: &[opc_da_bindings::tagOPCITEMVQT], - ) -> windows::core::Result>> { - sync_io2 - .write_vqt(server_handles, item_values)? - .try_to_local() - } - - pub fn write_sync( - &self, - item_entities: &[(S, ItemPartialValue)], - ) -> windows::core::Result>> - where - S: AsRef, - { - let server_handles: Vec = item_entities - .iter() - .map(|(name, _)| { - self.items - .get(name.as_ref()) - .map(|item| item.server_handle) - .ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "item name not found", - ) - }) - }) - .collect::>()?; - - let variants = item_entities.iter().map(|(_, value)| value.value.clone()); - - let item_values = item_entities.iter().map(|(_, value)| value.try_to_native()); - - match &self.inner { - GroupInner::V1(group) => { - self.write_sync1(group, &server_handles, &variants.collect::>()) - } - GroupInner::V2(group) => { - self.write_sync1(group, &server_handles, &variants.collect::>()) - } - GroupInner::V3(group) => self.write_sync2( - group, - &server_handles, - &item_values.collect::>>()?, - ), - } - } - - fn write_async2( - &self, - async_io2: &T, - server_handles: &[u32], - item_values: &[windows::Win32::System::Variant::VARIANT], - ) -> windows::core::Result<( - DataCallbackFuture, - Vec>, - )> { - let transaction_id = self - .next_transaction_id - .fetch_add(1, std::sync::atomic::Ordering::SeqCst); - - let (sender, receive) = tokio::sync::oneshot::channel(); - - let mut awaiters = self.write_complete_awaiters.lock().map_err(|_| { - windows_core::Error::new(windows::Win32::Foundation::E_FAIL, "lock poisoned") - })?; - - awaiters.insert(transaction_id, sender); - - let (cancel_id, results) = async_io2.write(server_handles, item_values, transaction_id)?; - - Ok(( - DataCallbackFuture { - receiver: Box::pin(receive), - transaction_id, - cancel_id, - }, - results.try_to_local()?, - )) - } - - fn write_async3( - &self, - async_io3: &T, - server_handles: &[u32], - item_values: &[opc_da_bindings::tagOPCITEMVQT], - ) -> windows::core::Result<( - DataCallbackFuture, - Vec>, - )> { - let (transaction_id, receiver) = self.next_receiver(&self.write_complete_awaiters)?; - - let (cancel_id, results) = - async_io3.write_vqt(server_handles, item_values, transaction_id)?; - - Ok(( - DataCallbackFuture { - receiver: Box::pin(receiver), - transaction_id, - cancel_id, - }, - results.try_to_local()?, - )) - } - - pub fn write_async( - &self, - item_entities: &[(S, ItemPartialValue)], - ) -> windows::core::Result<( - DataCallbackFuture, - Vec>, - )> - where - S: AsRef, - { - let server_handles: Vec = item_entities - .iter() - .map(|(name, _)| { - self.items - .get(name.as_ref()) - .map(|item| item.server_handle) - .ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "item name not found", - ) - }) - }) - .collect::>()?; - - let variants = item_entities.iter().map(|(_, value)| value.value.clone()); - - let item_values = item_entities.iter().map(|(_, value)| value.try_to_native()); - - match &self.inner { - GroupInner::V1(_) => Err(windows_core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "write_async not implemented for v1", - )), - GroupInner::V2(group) => { - self.write_async2(group, &server_handles, &variants.collect::>()) - } - GroupInner::V3(group) => self.write_async3( - group, - &server_handles, - &item_values.collect::>>()?, - ), - } - } - - fn cancel_async2( - &self, - async_io2: &T, - cancel_id: u32, - ) -> windows::core::Result> { - let (sender, receiver) = tokio::sync::oneshot::channel(); - - let mut awaiters = self.cancel_complete_awaiters.lock().map_err(|_| { - windows_core::Error::new(windows::Win32::Foundation::E_FAIL, "lock poisoned") - })?; - - awaiters.insert(cancel_id, sender); - - async_io2.cancel2(cancel_id)?; - - Ok(DataCallbackFuture { - receiver: Box::pin(receiver), - transaction_id: cancel_id, - cancel_id, - }) - } - - pub fn cancel_async( - &self, - cancel_id: u32, - ) -> windows::core::Result> { - match &self.inner { - GroupInner::V1(_) => Err(windows_core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "cancel_async not implemented for v1", - )), - GroupInner::V2(group) => self.cancel_async2(group, cancel_id), - GroupInner::V3(group) => self.cancel_async2(group, cancel_id), - } - } - - fn refresh2_async( - &self, - async_io2: &T, - data_source: DataSourceTarget, - ) -> windows::core::Result> { - let (transaction_id, receiver) = self.next_receiver(&self.data_change_awaiters)?; - - let cancel_id = async_io2.refresh2(data_source.try_to_native()?, transaction_id)?; - - Ok(DataCallbackFuture { - receiver: Box::pin(receiver), - transaction_id, - cancel_id, - }) - } - - fn refresh3_async( - &self, - async_io3: &T, - data_source: DataSourceTarget, - ) -> windows::core::Result> { - let (transaction_id, receiver) = self.next_receiver(&self.data_change_awaiters)?; - - let cancel_id = async_io3.refresh_max_age(data_source.max_age(), transaction_id)?; - - Ok(DataCallbackFuture { - receiver: Box::pin(receiver), - transaction_id, - cancel_id, - }) - } - - pub fn refresh_async( - &self, - data_source: DataSourceTarget, - ) -> windows::core::Result> { - match &self.inner { - GroupInner::V1(_) => Err(windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "refresh not implemented for v1", - )), - GroupInner::V2(group) => self.refresh2_async(group, data_source), - GroupInner::V3(group) => self.refresh3_async(group, data_source), - } - } -} - -impl From for Group { - fn from(group: v1::Group) -> Self { - Self::new(GroupInner::V1(group)) - } -} - -impl From for Group { - fn from(group: v2::Group) -> Self { - Self::new(GroupInner::V2(group)) - } -} - -impl From for Group { - fn from(group: v3::Group) -> Self { - Self::new(GroupInner::V3(group)) - } -} - -pub struct DataCallbackFuture { - receiver: std::pin::Pin>>, - transaction_id: u32, - cancel_id: u32, -} - -impl DataCallbackFuture { - pub fn cancel_id(&self) -> u32 { - self.cancel_id - } - - pub fn transaction_id(&self) -> u32 { - self.transaction_id - } -} - -impl std::future::Future for DataCallbackFuture { - type Output = windows::core::Result; - - fn poll( - mut self: std::pin::Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - match self.receiver.as_mut().poll(cx) { - std::task::Poll::Ready(Ok(event)) => std::task::Poll::Ready(Ok(event)), - std::task::Poll::Ready(Err(_)) => { - std::task::Poll::Ready(Err(windows_core::Error::new( - windows::Win32::Foundation::E_FAIL, - "data change event receiver dropped", - ))) - } - std::task::Poll::Pending => std::task::Poll::Pending, - } - } -} diff --git a/opc_da/src/client/unified/guard.rs b/opc_da/src/client/unified/guard.rs deleted file mode 100644 index 8802aff..0000000 --- a/opc_da/src/client/unified/guard.rs +++ /dev/null @@ -1,96 +0,0 @@ -/// A RAII guard that manages COM initialization and uninitialization for a thread. -/// -/// This type ensures that COM is properly initialized when the guard is created and -/// properly uninitialized when the guard is dropped. It wraps an inner value of type `T` -/// and provides access to it through the `Deref` trait. -/// -/// # Thread Safety -/// The guard is intentionally not `Send` and not `Sync` to ensure COM operations -/// remain on the thread where they were initialized. -#[derive(Debug)] -pub struct Guard { - inner: T, - /// Marker to ensure `Client` is not `Send` and not `Sync`. - _marker: std::marker::PhantomData<*const ()>, -} - -impl Guard { - /// Creates a new guard that initializes COM and wraps the provided value. - /// - /// # Arguments - /// * `value` - The value to wrap in the guard - /// - /// # Returns - /// Returns a `Result` containing the guard if COM initialization succeeds. - /// - /// # Errors - /// Returns an error if COM initialization fails. - pub fn new(value: T) -> windows::core::Result { - let guard = Self { - inner: value, - _marker: std::marker::PhantomData, - }; - - Self::try_initialize()?; - - Ok(guard) - } -} - -/// Provides direct access to the wrapped value through reference. -impl std::ops::Deref for Guard { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -/// Ensures COM is uninitialized when the guard is dropped. -impl Drop for Guard { - fn drop(&mut self) { - Self::uninitialize(); - } -} - -impl Guard { - /// Ensures COM is initialized for the current thread. - /// - /// # Returns - /// Returns the HRESULT of the COM initialization. - /// - /// # Thread Safety - /// COM initialization is performed with COINIT_MULTITHREADED flag. - /// - /// # Note - /// Callers should check the returned HRESULT for initialization failures. - pub(crate) fn try_initialize() -> windows::core::Result<()> { - unsafe { - windows::Win32::System::Com::CoInitializeEx( - None, - windows::Win32::System::Com::COINIT_MULTITHREADED, - ) - } - .ok() - } - - /// Initializes COM for the current thread, panicking on failure. - /// - /// # Panics - /// Panics if COM initialization fails. - /// - /// # Thread Safety - /// COM initialization is performed with COINIT_MULTITHREADED flag. - pub(crate) fn initialize() { - Self::try_initialize().expect("Failed to initialize COM"); - } - - /// Uninitializes COM for the current thread. - /// - /// # Safety - /// This method should be called when the thread is shutting down - /// and no more COM calls will be made. - pub(crate) fn uninitialize() { - unsafe { windows::Win32::System::Com::CoUninitialize() }; - } -} diff --git a/opc_da/src/client/unified/mod.rs b/opc_da/src/client/unified/mod.rs deleted file mode 100644 index 3d5e476..0000000 --- a/opc_da/src/client/unified/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod actor; -pub mod client; -pub mod group; -pub mod guard; -pub mod server; - -pub use actor::*; -pub use client::*; -pub use group::*; -pub use guard::*; -pub use server::*; - -#[cfg(test)] -mod tests; diff --git a/opc_da/src/client/unified/server.rs b/opc_da/src/client/unified/server.rs deleted file mode 100644 index 23aa2b6..0000000 --- a/opc_da/src/client/unified/server.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::{ - client::{v1, v2, v3, ServerTrait}, - def::{BrowseFilter, BrowseType, EnumScope, GroupState, ServerStatus}, - utils::{ToNative as _, TryToLocal}, -}; - -use super::Group; - -pub enum Server { - V1(v1::Server), - V2(v2::Server), - V3(v3::Server), -} - -impl Server { - fn add_group_with_server< - G: TryFrom, - T: ServerTrait, - >( - server: &T, - mut state: GroupState, - ) -> windows::core::Result { - server.add_group( - &state.name, - state.active, - state.client_handle, - state.update_rate, - state.locale_id, - state.time_bias, - state.percent_deadband, - &mut state.update_rate, - &mut state.server_handle, - ) - } - - pub fn add_group(&self, state: GroupState) -> windows::core::Result { - match self { - Self::V1(server) => Ok(Self::add_group_with_server(server, state)?.into()), - Self::V2(server) => Ok(Self::add_group_with_server(server, state)?.into()), - Self::V3(server) => Ok(Self::add_group_with_server(server, state)?.into()), - } - } - - pub fn get_status(&self) -> windows::core::Result { - let status = match self { - Self::V1(server) => server.get_status(), - Self::V2(server) => server.get_status(), - Self::V3(server) => server.get_status(), - }?; - - status.ok()?.try_to_local() - } - - pub fn remove_group(&self, server_handle: u32, force: bool) -> windows::core::Result<()> { - match self { - Self::V1(server) => server.remove_group(server_handle, force), - Self::V2(server) => server.remove_group(server_handle, force), - Self::V3(server) => server.remove_group(server_handle, force), - } - } - - pub fn create_group_enumerator( - &self, - scope: EnumScope, - ) -> windows::core::Result { - let scope = scope.to_native(); - - let iterator = match self { - Self::V1(server) => GroupIterator::V1(server.create_group_enumerator(scope)?), - Self::V2(server) => GroupIterator::V2(server.create_group_enumerator(scope)?), - Self::V3(server) => GroupIterator::V3(server.create_group_enumerator(scope)?), - }; - - Ok(iterator) - } -} - -impl From for Server { - fn from(server: v1::Server) -> Self { - Self::V1(server) - } -} - -impl From for Server { - fn from(server: v2::Server) -> Self { - Self::V2(server) - } -} - -impl From for Server { - fn from(server: v3::Server) -> Self { - Self::V3(server) - } -} - -pub enum GroupIterator { - V1(v1::GroupIterator), - V2(v2::GroupIterator), - V3(v3::GroupIterator), -} - -impl Iterator for GroupIterator { - type Item = windows::core::Result; - - fn next(&mut self) -> Option { - match self { - Self::V1(iterator) => iterator.next().map(|group| group.map(Group::from)), - Self::V2(iterator) => iterator.next().map(|group| group.map(Group::from)), - Self::V3(iterator) => iterator.next().map(|group| group.map(Group::from)), - } - } -} - -pub struct BrowseItemsOptions { - pub browse_type: BrowseType, - pub browse_filter: BrowseFilter, - pub item_id: Option, - pub continuation_point: Option, - pub data_type_filter: u16, - pub access_rights_filter: u32, - pub max_elements: u32, - pub element_name_filter: Option, - pub vendor_filter: Option, - pub return_all_properties: bool, - pub return_property_values: bool, - pub property_ids: Vec, -} diff --git a/opc_da/src/client/unified/tests.rs b/opc_da/src/client/unified/tests.rs deleted file mode 100644 index 35ef1a8..0000000 --- a/opc_da/src/client/unified/tests.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::def::*; - -use super::*; - -#[test] -fn test_unified() { - let client = Guard::new(Client::v2()).expect("Failed to create client guard"); - let mut servers = client.get_servers().expect("Failed to get servers"); - let server_id = servers - .next() - .expect("No servers found") - .expect("Failed to get server id"); - - let server = client - .create_server(server_id) - .expect("Failed to create server"); - - let group_state = GroupState::default(); - let _ = server.add_group(group_state).expect("Failed to add group"); -} diff --git a/opc_da/src/client/v1/mod.rs b/opc_da/src/client/v1/mod.rs deleted file mode 100644 index 177268d..0000000 --- a/opc_da/src/client/v1/mod.rs +++ /dev/null @@ -1,151 +0,0 @@ -//! OPC DA 1.0 server and group implementations. -//! -//! This module implements the original OPC DA 1.0 interfaces for servers and groups, -//! providing basic functionality for legacy systems. - -use windows::core::Interface as _; - -use super::{ - traits::{ - AsyncIoTrait, BrowseServerAddressSpaceTrait, DataObjectTrait, GroupStateMgtTrait, - ItemMgtTrait, PublicGroupStateMgtTrait, ServerPublicGroupsTrait, ServerTrait, SyncIoTrait, - }, - ClientTrait, -}; - -/// Client for OPC DA 1.0 servers. -#[derive(Debug)] -pub struct Client; - -impl ClientTrait for Client { - const CATALOG_ID: windows::core::GUID = opc_da_bindings::CATID_OPCDAServer10::IID; -} - -/// An OPC DA 1.0 server implementation. -/// -/// Provides access to OPC DA 1.0 server interfaces including: -/// - `IOPCServer` for basic server operations -/// - `IOPCServerPublicGroups` for public group management -/// - `IOPCBrowseServerAddressSpace` for browsing the address space -pub struct Server { - pub(crate) server: opc_da_bindings::IOPCServer, - pub(crate) server_public_groups: Option, - pub(crate) browse_server_address_space: Option, -} - -impl TryFrom for Server { - type Error = windows::core::Error; - - fn try_from(value: windows::core::IUnknown) -> windows::core::Result { - Ok(Self { - server: value.cast()?, - server_public_groups: value.cast().ok(), - browse_server_address_space: value.cast().ok(), - }) - } -} - -impl ServerTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCServer> { - Ok(&self.server) - } -} - -impl ServerPublicGroupsTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCServerPublicGroups> { - self.server_public_groups.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IOPCServerPublicGroups not supported", - ) - }) - } -} - -impl BrowseServerAddressSpaceTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCBrowseServerAddressSpace> { - self.browse_server_address_space.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IOPCBrowseServerAddressSpace not supported", - ) - }) - } -} - -/// Iterator over OPC DA 1.0 groups. -pub type GroupIterator = super::GroupIterator; - -/// An OPC DA 1.0 group implementation. -/// -/// Provides access to OPC DA 1.0 group interfaces including: -/// - `IOPCItemMgt` for item management -/// - `IOPCGroupStateMgt` for group state management -/// - `IOPCPublicGroupStateMgt` for public group operations -/// - `IOPCSyncIO` for synchronous operations -/// - `IOPCAsyncIO` for asynchronous operations -/// - `IDataObject` for data transfer -pub struct Group { - pub(crate) item_mgt: opc_da_bindings::IOPCItemMgt, - pub(crate) group_state_mgt: opc_da_bindings::IOPCGroupStateMgt, - pub(crate) public_group_state_mgt: Option, - pub(crate) sync_io: opc_da_bindings::IOPCSyncIO, - pub(crate) async_io: opc_da_bindings::IOPCAsyncIO, - pub(crate) data_object: windows::Win32::System::Com::IDataObject, -} - -impl TryFrom for Group { - type Error = windows::core::Error; - - fn try_from(value: windows::core::IUnknown) -> windows::core::Result { - Ok(Self { - item_mgt: value.cast()?, - group_state_mgt: value.cast()?, - public_group_state_mgt: value.cast().ok(), - sync_io: value.cast()?, - async_io: value.cast()?, - data_object: value.cast()?, - }) - } -} - -impl ItemMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemMgt> { - Ok(&self.item_mgt) - } -} - -impl GroupStateMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCGroupStateMgt> { - Ok(&self.group_state_mgt) - } -} - -impl PublicGroupStateMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCPublicGroupStateMgt> { - self.public_group_state_mgt.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IOPCPublicGroupStateMgt not supported", - ) - }) - } -} - -impl SyncIoTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCSyncIO> { - Ok(&self.sync_io) - } -} - -impl AsyncIoTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCAsyncIO> { - Ok(&self.async_io) - } -} - -impl DataObjectTrait for Group { - fn interface(&self) -> windows::core::Result<&windows::Win32::System::Com::IDataObject> { - Ok(&self.data_object) - } -} diff --git a/opc_da/src/client/v2/mod.rs b/opc_da/src/client/v2/mod.rs deleted file mode 100644 index 46411fe..0000000 --- a/opc_da/src/client/v2/mod.rs +++ /dev/null @@ -1,209 +0,0 @@ -//! OPC DA 2.0 server and group implementations. -//! -//! This module implements the OPC DA 2.0 interfaces for servers and groups, -//! providing compatibility with the 2.0 version of the specification. - -use windows::core::Interface as _; - -use super::{ - ClientTrait, - traits::{ - AsyncIo2Trait, AsyncIoTrait, BrowseServerAddressSpaceTrait, CommonTrait, - ConnectionPointContainerTrait, DataObjectTrait, GroupStateMgtTrait, ItemMgtTrait, - ItemPropertiesTrait, PublicGroupStateMgtTrait, ServerPublicGroupsTrait, ServerTrait, - SyncIoTrait, - }, -}; - -/// Client for OPC DA 2.0 servers. -#[derive(Debug)] -pub struct Client; - -impl ClientTrait for Client { - const CATALOG_ID: windows::core::GUID = opc_da_bindings::CATID_OPCDAServer20::IID; -} - -/// An OPC DA 2.0 server implementation. -/// -/// Provides access to OPC DA 2.0 server interfaces including: -/// - `IOPCServer` for basic server operations -/// - `IOPCCommon` for server status and locale management -/// - `IOPCItemProperties` for browsing item properties -/// - `IOPCServerPublicGroups` for public group management -/// - `IOPCBrowseServerAddressSpace` for browsing the address space -pub struct Server { - pub(crate) server: opc_da_bindings::IOPCServer, - pub(crate) common: opc_comn_bindings::IOPCCommon, - pub(crate) connection_point_container: windows::Win32::System::Com::IConnectionPointContainer, - pub(crate) item_properties: opc_da_bindings::IOPCItemProperties, - pub(crate) server_public_groups: Option, - pub(crate) browse_server_address_space: Option, -} - -impl TryFrom for Server { - type Error = windows::core::Error; - - fn try_from(value: windows::core::IUnknown) -> windows::core::Result { - Ok(Self { - server: value.cast()?, - common: value.cast()?, - connection_point_container: value.cast()?, - item_properties: value.cast()?, - server_public_groups: value.cast().ok(), - browse_server_address_space: value.cast().ok(), - }) - } -} - -impl ServerTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCServer> { - Ok(&self.server) - } -} - -impl CommonTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_comn_bindings::IOPCCommon> { - Ok(&self.common) - } -} - -impl ConnectionPointContainerTrait for Server { - fn interface( - &self, - ) -> windows::core::Result<&windows::Win32::System::Com::IConnectionPointContainer> { - Ok(&self.connection_point_container) - } -} - -impl ItemPropertiesTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemProperties> { - Ok(&self.item_properties) - } -} - -impl ServerPublicGroupsTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCServerPublicGroups> { - self.server_public_groups.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IOPCServerPublicGroups not supported", - ) - }) - } -} - -impl BrowseServerAddressSpaceTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCBrowseServerAddressSpace> { - self.browse_server_address_space.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IOPCBrowseServerAddressSpace not supported", - ) - }) - } -} - -/// Iterator over OPC DA 2.0 groups. -pub type GroupIterator = super::GroupIterator; - -/// An OPC DA 2.0 group implementation. -/// -/// Provides access to OPC DA 2.0 group interfaces including: -/// - `IOPCItemMgt` for item management -/// - `IOPCGroupStateMgt` for group state management -/// - `IOPCPublicGroupStateMgt` for public group operations -/// - `IOPCSyncIO` for synchronous operations -/// - `IOPCAsyncIO` and `IOPCAsyncIO2` for asynchronous operations -/// - `IDataObject` for data transfer -pub struct Group { - pub(crate) item_mgt: opc_da_bindings::IOPCItemMgt, - pub(crate) group_state_mgt: opc_da_bindings::IOPCGroupStateMgt, - pub(crate) public_group_state_mgt: Option, - pub(crate) sync_io: opc_da_bindings::IOPCSyncIO, - pub(crate) async_io: Option, - pub(crate) async_io2: opc_da_bindings::IOPCAsyncIO2, - pub(crate) connection_point_container: windows::Win32::System::Com::IConnectionPointContainer, - pub(crate) data_object: Option, -} - -impl TryFrom for Group { - type Error = windows::core::Error; - - fn try_from(value: windows::core::IUnknown) -> windows::core::Result { - Ok(Self { - item_mgt: value.cast()?, - group_state_mgt: value.cast()?, - public_group_state_mgt: value.cast().ok(), - sync_io: value.cast()?, - async_io: value.cast().ok(), - async_io2: value.cast()?, - connection_point_container: value.cast()?, - data_object: value.cast().ok(), - }) - } -} - -impl ItemMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemMgt> { - Ok(&self.item_mgt) - } -} - -impl GroupStateMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCGroupStateMgt> { - Ok(&self.group_state_mgt) - } -} - -impl PublicGroupStateMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCPublicGroupStateMgt> { - self.public_group_state_mgt.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IOPCPublicGroupStateMgt not supported", - ) - }) - } -} - -impl SyncIoTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCSyncIO> { - Ok(&self.sync_io) - } -} - -impl AsyncIoTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCAsyncIO> { - self.async_io.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IOPCAsyncIO not supported", - ) - }) - } -} - -impl AsyncIo2Trait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCAsyncIO2> { - Ok(&self.async_io2) - } -} - -impl ConnectionPointContainerTrait for Group { - fn interface( - &self, - ) -> windows::core::Result<&windows::Win32::System::Com::IConnectionPointContainer> { - Ok(&self.connection_point_container) - } -} - -impl DataObjectTrait for Group { - fn interface(&self) -> windows::core::Result<&windows::Win32::System::Com::IDataObject> { - self.data_object.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IDataObject not supported", - ) - }) - } -} diff --git a/opc_da/src/client/v3/mod.rs b/opc_da/src/client/v3/mod.rs deleted file mode 100644 index fa485a5..0000000 --- a/opc_da/src/client/v3/mod.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! OPC DA 3.0 server and group implementations. -//! -//! This module implements the OPC DA 3.0 interfaces for servers and groups, -//! providing access to the latest features of the OPC DA specification. - -use windows::core::Interface as _; - -use super::{ - ClientTrait, - traits::{ - AsyncIo2Trait, AsyncIo3Trait, BrowseTrait, CommonTrait, ConnectionPointContainerTrait, - GroupStateMgt2Trait, GroupStateMgtTrait, ItemDeadbandMgtTrait, ItemIoTrait, ItemMgtTrait, - ItemSamplingMgtTrait, ServerTrait, SyncIo2Trait, SyncIoTrait, - }, -}; - -/// Client for OPC DA 3.0 servers. -#[derive(Debug)] -pub struct Client; - -impl ClientTrait for Client { - const CATALOG_ID: windows::core::GUID = opc_da_bindings::CATID_OPCDAServer30::IID; -} - -/// An OPC DA 3.0 server implementation. -/// -/// Provides access to OPC DA 3.0 server interfaces including: -/// - `IOPCServer` for basic server operations -/// - `IOPCCommon` for server status and locale management -/// - `IOPCBrowse` for browsing the server address space -/// - `IOPCItemIO` for direct item read/write operations -pub struct Server { - pub(crate) server: opc_da_bindings::IOPCServer, - pub(crate) common: opc_comn_bindings::IOPCCommon, - pub(crate) connection_point_container: windows::Win32::System::Com::IConnectionPointContainer, - pub(crate) browse: opc_da_bindings::IOPCBrowse, - pub(crate) item_io: opc_da_bindings::IOPCItemIO, -} - -impl TryFrom for Server { - type Error = windows::core::Error; - - fn try_from(value: windows::core::IUnknown) -> windows::core::Result { - Ok(Self { - server: value.cast()?, - common: value.cast()?, - connection_point_container: value.cast()?, - browse: value.cast()?, - item_io: value.cast()?, - }) - } -} - -impl ServerTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCServer> { - Ok(&self.server) - } -} - -impl CommonTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_comn_bindings::IOPCCommon> { - Ok(&self.common) - } -} - -impl ConnectionPointContainerTrait for Server { - fn interface( - &self, - ) -> windows::core::Result<&windows::Win32::System::Com::IConnectionPointContainer> { - Ok(&self.connection_point_container) - } -} - -impl BrowseTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCBrowse> { - Ok(&self.browse) - } -} - -impl ItemIoTrait for Server { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemIO> { - Ok(&self.item_io) - } -} - -/// Iterator over OPC DA 3.0 groups. -pub type GroupIterator = super::GroupIterator; - -/// An OPC DA 3.0 group implementation. -/// -/// Provides access to OPC DA 3.0 group interfaces including: -/// - `IOPCItemMgt` for item management -/// - `IOPCGroupStateMgt` and `IOPCGroupStateMgt2` for group state management -/// - `IOPCSyncIO` and `IOPCSyncIO2` for synchronous operations -/// - `IOPCAsyncIO2` and `IOPCAsyncIO3` for asynchronous operations -/// - `IOPCItemSamplingMgt` for item sampling control -/// - `IOPCItemDeadbandMgt` for deadband management -#[derive(Debug)] -pub struct Group { - pub(crate) item_mgt: opc_da_bindings::IOPCItemMgt, - pub(crate) group_state_mgt: opc_da_bindings::IOPCGroupStateMgt, - pub(crate) group_state_mgt2: opc_da_bindings::IOPCGroupStateMgt2, - pub(crate) sync_io: opc_da_bindings::IOPCSyncIO, - pub(crate) sync_io2: opc_da_bindings::IOPCSyncIO2, - pub(crate) async_io2: opc_da_bindings::IOPCAsyncIO2, - pub(crate) async_io3: opc_da_bindings::IOPCAsyncIO3, - pub(crate) item_sampling_mgt: Option, - pub(crate) item_deadband_mgt: opc_da_bindings::IOPCItemDeadbandMgt, - pub(crate) connection_point_container: windows::Win32::System::Com::IConnectionPointContainer, -} - -impl TryFrom for Group { - type Error = windows::core::Error; - - fn try_from(value: windows::core::IUnknown) -> windows::core::Result { - Ok(Self { - item_mgt: value.cast()?, - group_state_mgt: value.cast()?, - group_state_mgt2: value.cast()?, - sync_io: value.cast()?, - sync_io2: value.cast()?, - async_io2: value.cast()?, - async_io3: value.cast()?, - item_deadband_mgt: value.cast()?, - item_sampling_mgt: value.cast().ok(), - connection_point_container: value.cast()?, - }) - } -} - -impl ItemMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemMgt> { - Ok(&self.item_mgt) - } -} - -impl GroupStateMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCGroupStateMgt> { - Ok(&self.group_state_mgt) - } -} - -impl GroupStateMgt2Trait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCGroupStateMgt2> { - Ok(&self.group_state_mgt2) - } -} - -impl SyncIoTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCSyncIO> { - Ok(&self.sync_io) - } -} - -impl SyncIo2Trait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCSyncIO2> { - Ok(&self.sync_io2) - } -} - -impl AsyncIo2Trait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCAsyncIO2> { - Ok(&self.async_io2) - } -} - -impl AsyncIo3Trait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCAsyncIO3> { - Ok(&self.async_io3) - } -} - -impl ItemDeadbandMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemDeadbandMgt> { - Ok(&self.item_deadband_mgt) - } -} - -impl ItemSamplingMgtTrait for Group { - fn interface(&self) -> windows::core::Result<&opc_da_bindings::IOPCItemSamplingMgt> { - self.item_sampling_mgt.as_ref().ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_NOTIMPL, - "IOPCItemSamplingMgt not supported", - ) - }) - } -} - -impl ConnectionPointContainerTrait for Group { - fn interface( - &self, - ) -> windows::core::Result<&windows::Win32::System::Com::IConnectionPointContainer> { - Ok(&self.connection_point_container) - } -} diff --git a/opc_da/src/def.rs b/opc_da/src/def.rs deleted file mode 100644 index fcc8716..0000000 --- a/opc_da/src/def.rs +++ /dev/null @@ -1,752 +0,0 @@ -use crate::{ - try_from_native, - utils::{IntoBridge, LocalPointer, RemoteArray, ToNative, TryFromNative, TryToNative}, -}; - -#[derive(Debug, Clone, PartialEq)] -pub enum Version { - V1, - V2, - V3, -} - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct GroupState { - pub update_rate: u32, - pub active: bool, - pub name: String, - pub time_bias: i32, - pub percent_deadband: f32, - pub locale_id: u32, - pub client_handle: u32, - pub server_handle: u32, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ServerStatus { - pub start_time: std::time::SystemTime, - pub current_time: std::time::SystemTime, - pub last_update_time: std::time::SystemTime, - pub server_state: ServerState, - pub group_count: u32, - pub band_width: u32, - pub major_version: u16, - pub minor_version: u16, - pub build_number: u16, - pub vendor_info: String, -} - -impl TryFromNative for ServerStatus { - fn try_from_native( - native: &opc_da_bindings::tagOPCSERVERSTATUS, - ) -> windows::core::Result { - Ok(Self { - start_time: try_from_native!(&native.ftStartTime), - current_time: try_from_native!(&native.ftCurrentTime), - last_update_time: try_from_native!(&native.ftLastUpdateTime), - server_state: try_from_native!(&native.dwServerState), - group_count: native.dwGroupCount, - band_width: native.dwBandWidth, - major_version: native.wMajorVersion, - minor_version: native.wMinorVersion, - build_number: native.wBuildNumber, - vendor_info: try_from_native!(&native.szVendorInfo), - }) - } -} - -#[derive(Debug, Clone, Default, PartialEq)] -pub struct ItemDef { - pub access_path: String, - pub item_id: String, - pub active: bool, - pub client_handle: u32, - pub data_type: u16, - pub blob: Vec, -} - -pub struct ItemDefBridge { - pub access_path: LocalPointer>, - pub item_id: LocalPointer>, - pub active: bool, - pub item_client_handle: u32, - pub requested_data_type: u16, - pub blob: LocalPointer>, -} - -impl IntoBridge for ItemDef { - fn into_bridge(self) -> ItemDefBridge { - ItemDefBridge { - access_path: LocalPointer::from(&self.access_path), - item_id: LocalPointer::from(&self.item_id), - active: self.active, - item_client_handle: self.client_handle, - requested_data_type: self.data_type, - blob: LocalPointer::new(Some(self.blob)), - } - } -} - -impl TryToNative for ItemDefBridge { - fn try_to_native(&self) -> windows::core::Result { - Ok(opc_da_bindings::tagOPCITEMDEF { - szAccessPath: self.access_path.as_pwstr(), - szItemID: self.item_id.as_pwstr(), - bActive: self.active.into(), - hClient: self.item_client_handle, - vtRequestedDataType: self.requested_data_type, - dwBlobSize: self.blob.len().try_into().map_err(|_| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Blob size exceeds u32 maximum value", - ) - })?, - pBlob: self.blob.as_array_ptr() as *mut _, - wReserved: 0, - }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ItemResult { - pub server_handle: u32, - pub data_type: u16, - pub access_rights: u32, - pub blob: Vec, -} - -impl TryFromNative for ItemResult { - fn try_from_native(native: &opc_da_bindings::tagOPCITEMRESULT) -> windows::core::Result { - Ok(Self { - server_handle: native.hServer, - data_type: native.vtCanonicalDataType, - access_rights: native.dwAccessRights, - blob: RemoteArray::from_mut_ptr(native.pBlob, native.dwBlobSize) - .as_slice() - .to_vec(), - }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum ServerState { - Running, - Failed, - NoConfig, - Suspended, - Test, - CommunicationFault, -} - -impl TryFromNative for ServerState { - fn try_from_native(native: &opc_da_bindings::tagOPCSERVERSTATE) -> windows::core::Result { - match *native { - opc_da_bindings::OPC_STATUS_RUNNING => Ok(ServerState::Running), - opc_da_bindings::OPC_STATUS_FAILED => Ok(ServerState::Failed), - opc_da_bindings::OPC_STATUS_NOCONFIG => Ok(ServerState::NoConfig), - opc_da_bindings::OPC_STATUS_SUSPENDED => Ok(ServerState::Suspended), - opc_da_bindings::OPC_STATUS_TEST => Ok(ServerState::Test), - opc_da_bindings::OPC_STATUS_COMM_FAULT => Ok(ServerState::CommunicationFault), - unknown => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - format!("Unknown server state: {unknown:?}"), - )), - } - } -} - -impl ToNative for ServerState { - fn to_native(&self) -> opc_da_bindings::tagOPCSERVERSTATE { - match self { - ServerState::Running => opc_da_bindings::OPC_STATUS_RUNNING, - ServerState::Failed => opc_da_bindings::OPC_STATUS_FAILED, - ServerState::NoConfig => opc_da_bindings::OPC_STATUS_NOCONFIG, - ServerState::Suspended => opc_da_bindings::OPC_STATUS_SUSPENDED, - ServerState::Test => opc_da_bindings::OPC_STATUS_TEST, - ServerState::CommunicationFault => opc_da_bindings::OPC_STATUS_COMM_FAULT, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum EnumScope { - PrivateConnections, - PublicConnections, - AllConnections, - Public, - Private, - All, -} - -impl TryFromNative for EnumScope { - fn try_from_native(native: &opc_da_bindings::tagOPCENUMSCOPE) -> windows::core::Result { - match *native { - opc_da_bindings::OPC_ENUM_PRIVATE_CONNECTIONS => Ok(EnumScope::PrivateConnections), - opc_da_bindings::OPC_ENUM_PUBLIC_CONNECTIONS => Ok(EnumScope::PublicConnections), - opc_da_bindings::OPC_ENUM_ALL_CONNECTIONS => Ok(EnumScope::AllConnections), - opc_da_bindings::OPC_ENUM_PUBLIC => Ok(EnumScope::Public), - opc_da_bindings::OPC_ENUM_PRIVATE => Ok(EnumScope::Private), - opc_da_bindings::OPC_ENUM_ALL => Ok(EnumScope::All), - unknown => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - format!("Unknown enum scope: {unknown:?}"), - )), - } - } -} - -impl ToNative for EnumScope { - fn to_native(&self) -> opc_da_bindings::tagOPCENUMSCOPE { - match self { - EnumScope::PrivateConnections => opc_da_bindings::OPC_ENUM_PRIVATE_CONNECTIONS, - EnumScope::PublicConnections => opc_da_bindings::OPC_ENUM_PUBLIC_CONNECTIONS, - EnumScope::AllConnections => opc_da_bindings::OPC_ENUM_ALL_CONNECTIONS, - EnumScope::Public => opc_da_bindings::OPC_ENUM_PUBLIC, - EnumScope::Private => opc_da_bindings::OPC_ENUM_PRIVATE, - EnumScope::All => opc_da_bindings::OPC_ENUM_ALL, - } - } -} - -pub struct ItemAttributes { - pub access_path: String, - pub item_id: String, - pub active: bool, - pub client_handle: u32, - pub server_handle: u32, - pub access_rights: u32, - pub blob: Vec, - pub requested_data_type: u16, - pub canonical_data_type: u16, - pub eu_type: EuType, - pub eu_info: windows::Win32::System::Variant::VARIANT, -} - -impl TryFromNative for ItemAttributes { - fn try_from_native( - native: &opc_da_bindings::tagOPCITEMATTRIBUTES, - ) -> windows::core::Result { - Ok(Self { - access_path: try_from_native!(&native.szAccessPath), - item_id: try_from_native!(&native.szItemID), - active: native.bActive.into(), - client_handle: native.hClient, - server_handle: native.hServer, - access_rights: native.dwAccessRights, - blob: RemoteArray::from_mut_ptr(native.pBlob, native.dwBlobSize) - .as_slice() - .to_vec(), - requested_data_type: native.vtRequestedDataType, - canonical_data_type: native.vtCanonicalDataType, - eu_type: try_from_native!(&native.dwEUType), - eu_info: native.vEUInfo.clone(), - }) - } -} - -pub enum EuType { - NoEnum, - Analog, - Enumerated, -} - -impl TryFromNative for EuType { - fn try_from_native(native: &opc_da_bindings::tagOPCEUTYPE) -> windows::core::Result { - match *native { - opc_da_bindings::OPC_NOENUM => Ok(EuType::NoEnum), - opc_da_bindings::OPC_ANALOG => Ok(EuType::Analog), - opc_da_bindings::OPC_ENUMERATED => Ok(EuType::Enumerated), - unknown => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - format!("Unknown EU type: {unknown:?}"), - )), - } - } -} - -pub struct ItemState { - pub client_handle: u32, - pub timestamp: std::time::SystemTime, - pub quality: u16, - pub data_value: windows::Win32::System::Variant::VARIANT, -} - -impl TryFromNative for ItemState { - fn try_from_native(native: &opc_da_bindings::tagOPCITEMSTATE) -> windows::core::Result { - Ok(Self { - client_handle: native.hClient, - timestamp: try_from_native!(&native.ftTimeStamp), - quality: native.wQuality, - data_value: native.vDataValue.clone(), - }) - } -} - -pub enum DataSourceTarget { - ForceCache, - ForceDevice, - WithMaxAge(u32), -} - -impl DataSourceTarget { - pub fn max_age(&self) -> u32 { - match self { - DataSourceTarget::WithMaxAge(max_age) => *max_age, - DataSourceTarget::ForceCache => u32::MAX, - DataSourceTarget::ForceDevice => 0, - } - } -} - -impl TryFromNative for DataSourceTarget { - fn try_from_native(native: &opc_da_bindings::tagOPCDATASOURCE) -> windows::core::Result { - match *native { - opc_da_bindings::OPC_DS_CACHE => Ok(DataSourceTarget::ForceCache), - opc_da_bindings::OPC_DS_DEVICE => Ok(DataSourceTarget::ForceDevice), - unknown => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - format!("Unknown data source: {unknown:?}"), - )), - } - } -} - -impl TryToNative for DataSourceTarget { - fn try_to_native(&self) -> windows::core::Result { - match self { - DataSourceTarget::ForceCache => Ok(opc_da_bindings::OPC_DS_CACHE), - DataSourceTarget::ForceDevice => Ok(opc_da_bindings::OPC_DS_DEVICE), - DataSourceTarget::WithMaxAge(_) => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "MaxAge data source requires a value", - )), - } - } -} - -pub struct ItemValue { - pub value: windows::Win32::System::Variant::VARIANT, - pub quality: u16, - pub timestamp: std::time::SystemTime, -} - -impl - TryFromNative<( - RemoteArray, - RemoteArray, - RemoteArray, - RemoteArray, - )> for Vec> -{ - fn try_from_native( - native: &( - RemoteArray, - RemoteArray, - RemoteArray, - RemoteArray, - ), - ) -> windows::core::Result { - let (values, qualities, timestamps, errors) = native; - - if values.len() != qualities.len() - || values.len() != timestamps.len() - || values.len() != errors.len() - { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Arrays have different lengths", - )); - } - - Ok(values - .as_slice() - .iter() - .zip(qualities.as_slice()) - .zip(timestamps.as_slice()) - .zip(errors.as_slice()) - .map(|(((value, quality), timestamp), error)| { - if error.is_ok() { - Ok(ItemValue { - value: value.clone(), - quality: *quality, - timestamp: try_from_native!(timestamp), - }) - } else { - Err((*error).into()) - } - }) - .collect()) - } -} - -pub struct ItemPartialValue { - pub value: windows::Win32::System::Variant::VARIANT, - pub quality: Option, - pub timestamp: Option, -} - -// try to native -impl TryToNative for ItemPartialValue { - fn try_to_native(&self) -> windows::core::Result { - Ok(opc_da_bindings::tagOPCITEMVQT { - vDataValue: self.value.clone(), - bQualitySpecified: self.quality.is_some().into(), - wQuality: self.quality.unwrap_or_default(), - bTimeStampSpecified: self.timestamp.is_some().into(), - ftTimeStamp: self - .timestamp - .map(|t| t.try_to_native()) - .transpose()? - .unwrap_or_default(), - wReserved: 0, - dwReserved: 0, - }) - } -} - -pub enum BrowseType { - Branch, - Leaf, - Flat, -} - -impl TryFromNative for BrowseType { - fn try_from_native(native: &opc_da_bindings::tagOPCBROWSETYPE) -> windows::core::Result { - match *native { - opc_da_bindings::OPC_BRANCH => Ok(BrowseType::Branch), - opc_da_bindings::OPC_LEAF => Ok(BrowseType::Leaf), - opc_da_bindings::OPC_FLAT => Ok(BrowseType::Flat), - unknown => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - format!("Unknown browse type: {unknown:?}"), - )), - } - } -} - -impl ToNative for BrowseType { - fn to_native(&self) -> opc_da_bindings::tagOPCBROWSETYPE { - match self { - BrowseType::Branch => opc_da_bindings::OPC_BRANCH, - BrowseType::Leaf => opc_da_bindings::OPC_LEAF, - BrowseType::Flat => opc_da_bindings::OPC_FLAT, - } - } -} - -pub enum BrowseFilter { - All, - Branches, - Items, -} - -impl TryFromNative for BrowseFilter { - fn try_from_native( - native: &opc_da_bindings::tagOPCBROWSEFILTER, - ) -> windows::core::Result { - match *native { - opc_da_bindings::OPC_BROWSE_FILTER_ALL => Ok(BrowseFilter::All), - opc_da_bindings::OPC_BROWSE_FILTER_BRANCHES => Ok(BrowseFilter::Branches), - opc_da_bindings::OPC_BROWSE_FILTER_ITEMS => Ok(BrowseFilter::Items), - unknown => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - format!("Unknown browse filter: {unknown:?}"), - )), - } - } -} - -impl ToNative for BrowseFilter { - fn to_native(&self) -> opc_da_bindings::tagOPCBROWSEFILTER { - match self { - BrowseFilter::All => opc_da_bindings::OPC_BROWSE_FILTER_ALL, - BrowseFilter::Branches => opc_da_bindings::OPC_BROWSE_FILTER_BRANCHES, - BrowseFilter::Items => opc_da_bindings::OPC_BROWSE_FILTER_ITEMS, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum DataCallbackEvent { - DataChange(DataChangeEvent), - ReadComplete(ReadCompleteEvent), - WriteComplete(WriteCompleteEvent), - CancelComplete(CancelCompleteEvent), -} - -#[derive(Debug, Clone, PartialEq)] -pub struct DataChangeEvent { - pub transaction_id: u32, - pub group_handle: u32, - pub master_quality: windows_core::HRESULT, - pub master_error: windows_core::HRESULT, - pub client_items: RemoteArray, - pub values: RemoteArray, - pub qualities: RemoteArray, - pub timestamps: RemoteArray, - pub errors: RemoteArray, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct ReadCompleteEvent { - pub transaction_id: u32, - pub group_handle: u32, - pub master_quality: windows_core::HRESULT, - pub master_error: windows_core::HRESULT, - pub client_items: RemoteArray, - pub values: RemoteArray, - pub qualities: RemoteArray, - pub timestamps: RemoteArray, - pub errors: RemoteArray, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct WriteCompleteEvent { - pub transaction_id: u32, - pub group_handle: u32, - pub master_error: windows_core::HRESULT, - pub client_handles: RemoteArray, - pub errors: RemoteArray, -} - -#[derive(Debug, Clone, PartialEq)] -pub struct CancelCompleteEvent { - pub transaction_id: u32, - pub group_handle: u32, -} - -pub enum NamespaceType { - Flat, - Hierarchy, -} - -impl TryFromNative for NamespaceType { - fn try_from_native( - native: &opc_da_bindings::tagOPCNAMESPACETYPE, - ) -> windows::core::Result { - match *native { - opc_da_bindings::OPC_NS_HIERARCHIAL => Ok(NamespaceType::Hierarchy), - opc_da_bindings::OPC_NS_FLAT => Ok(NamespaceType::Flat), - unknown => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - format!("Unknown namespace type: {unknown:?}"), - )), - } - } -} - -impl ToNative for NamespaceType { - fn to_native(&self) -> opc_da_bindings::tagOPCNAMESPACETYPE { - match self { - NamespaceType::Hierarchy => opc_da_bindings::OPC_NS_HIERARCHIAL, - NamespaceType::Flat => opc_da_bindings::OPC_NS_FLAT, - } - } -} - -// COSERVERINFO -#[derive(Debug, Clone, PartialEq)] -pub struct ServerInfo { - pub name: String, - pub auth_info: AuthInfo, -} - -pub struct ServerInfoBridge { - pub name: LocalPointer>, - pub auth_info: AuthInfoBridge, -} - -impl IntoBridge for ServerInfo { - fn into_bridge(self) -> ServerInfoBridge { - ServerInfoBridge { - name: LocalPointer::from(&self.name), - auth_info: self.auth_info.into_bridge(), - } - } -} - -impl TryToNative for ServerInfoBridge { - fn try_to_native(&self) -> windows::core::Result { - Ok(windows::Win32::System::Com::COSERVERINFO { - dwReserved1: 0, - dwReserved2: 0, - pwszName: self.name.as_pwstr(), - pAuthInfo: &self.auth_info.try_to_native()? as *const _ as *mut _, - }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct AuthInfo { - pub authn_svc: u32, - pub authz_svc: u32, - pub server_principal_name: String, - pub authn_level: u32, - pub impersonation_level: u32, - pub auth_identity_data: AuthIdentity, - pub capabilities: u32, -} - -pub struct AuthInfoBridge { - pub authn_svc: u32, - pub authz_svc: u32, - pub server_principal_name: LocalPointer>, - pub authn_level: u32, - pub impersonation_level: u32, - pub auth_identity_data: AuthIdentityBridge, - pub capabilities: u32, -} - -impl IntoBridge for AuthInfo { - fn into_bridge(self) -> AuthInfoBridge { - AuthInfoBridge { - authn_svc: self.authn_svc, - authz_svc: self.authz_svc, - server_principal_name: LocalPointer::from(&self.server_principal_name), - authn_level: self.authn_level, - impersonation_level: self.impersonation_level, - auth_identity_data: self.auth_identity_data.into_bridge(), - capabilities: self.capabilities, - } - } -} - -impl TryToNative for AuthInfoBridge { - fn try_to_native(&self) -> windows::core::Result { - Ok(windows::Win32::System::Com::COAUTHINFO { - dwAuthnSvc: self.authn_svc, - dwAuthzSvc: self.authz_svc, - pwszServerPrincName: self.server_principal_name.as_pwstr(), - dwAuthnLevel: self.authn_level, - dwImpersonationLevel: self.impersonation_level, - pAuthIdentityData: &self.auth_identity_data.try_to_native()? as *const _ as *mut _, - dwCapabilities: self.capabilities, - }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct AuthIdentity { - pub user: String, - pub domain: String, - pub password: String, - pub flags: u32, -} - -pub struct AuthIdentityBridge { - pub user: LocalPointer>, - pub domain: LocalPointer>, - pub password: LocalPointer>, - pub flags: u32, -} - -impl IntoBridge for AuthIdentity { - fn into_bridge(self) -> AuthIdentityBridge { - AuthIdentityBridge { - user: LocalPointer::from(&self.user), - domain: LocalPointer::from(&self.domain), - password: LocalPointer::from(&self.password), - flags: self.flags, - } - } -} - -impl TryToNative for AuthIdentityBridge { - fn try_to_native(&self) -> windows::core::Result { - Ok(windows::Win32::System::Com::COAUTHIDENTITY { - User: self.user.as_pwstr().0, - UserLength: self.user.len().try_into().map_err(|_| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "User name exceeds u32 maximum length", - ) - })?, - Domain: self.domain.as_pwstr().0, - DomainLength: self.domain.len().try_into().map_err(|_| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Domain name exceeds u32 maximum length", - ) - })?, - Password: self.password.as_pwstr().0, - PasswordLength: self.password.len().try_into().map_err(|_| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Password exceeds u32 maximum length", - ) - })?, - Flags: self.flags, - }) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum ClassContext { - All, - InProcServer, - InProcHandler, - LocalServer, - InProcServer16, - RemoteServer, - InProcHandler16, - NoCodeDownload, - NoCustomMarshal, - EnableCodeDownload, - NoFailureLog, - DisableAAA, - EnableAAA, - FromDefaultContext, - ActivateX86Server, - Activate32BitServer, - Activate64BitServer, - EnableCloaking, - AppContainer, - ActivateAAAAsIU, - ActivateARM32Server, - AllowLowerTrustRegistration, - PsDll, -} - -impl ToNative for ClassContext { - fn to_native(&self) -> windows::Win32::System::Com::CLSCTX { - match self { - ClassContext::All => windows::Win32::System::Com::CLSCTX_ALL, - ClassContext::InProcServer => windows::Win32::System::Com::CLSCTX_INPROC_SERVER, - ClassContext::InProcHandler => windows::Win32::System::Com::CLSCTX_INPROC_HANDLER, - ClassContext::LocalServer => windows::Win32::System::Com::CLSCTX_LOCAL_SERVER, - ClassContext::InProcServer16 => windows::Win32::System::Com::CLSCTX_INPROC_SERVER16, - ClassContext::RemoteServer => windows::Win32::System::Com::CLSCTX_REMOTE_SERVER, - ClassContext::InProcHandler16 => windows::Win32::System::Com::CLSCTX_INPROC_HANDLER16, - ClassContext::NoCodeDownload => windows::Win32::System::Com::CLSCTX_NO_CODE_DOWNLOAD, - ClassContext::NoCustomMarshal => windows::Win32::System::Com::CLSCTX_NO_CUSTOM_MARSHAL, - ClassContext::EnableCodeDownload => { - windows::Win32::System::Com::CLSCTX_ENABLE_CODE_DOWNLOAD - } - ClassContext::NoFailureLog => windows::Win32::System::Com::CLSCTX_NO_FAILURE_LOG, - ClassContext::DisableAAA => windows::Win32::System::Com::CLSCTX_DISABLE_AAA, - ClassContext::EnableAAA => windows::Win32::System::Com::CLSCTX_ENABLE_AAA, - ClassContext::FromDefaultContext => { - windows::Win32::System::Com::CLSCTX_FROM_DEFAULT_CONTEXT - } - ClassContext::ActivateX86Server => { - windows::Win32::System::Com::CLSCTX_ACTIVATE_X86_SERVER - } - ClassContext::Activate32BitServer => { - windows::Win32::System::Com::CLSCTX_ACTIVATE_32_BIT_SERVER - } - ClassContext::Activate64BitServer => { - windows::Win32::System::Com::CLSCTX_ACTIVATE_64_BIT_SERVER - } - ClassContext::EnableCloaking => windows::Win32::System::Com::CLSCTX_ENABLE_CLOAKING, - ClassContext::AppContainer => windows::Win32::System::Com::CLSCTX_APPCONTAINER, - ClassContext::ActivateAAAAsIU => windows::Win32::System::Com::CLSCTX_ACTIVATE_AAA_AS_IU, - ClassContext::ActivateARM32Server => { - windows::Win32::System::Com::CLSCTX_ACTIVATE_ARM32_SERVER - } - ClassContext::AllowLowerTrustRegistration => { - windows::Win32::System::Com::CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION - } - ClassContext::PsDll => windows::Win32::System::Com::CLSCTX_PS_DLL, - } - } -} diff --git a/opc_da/src/server/com/base/basic.rs b/opc_da/src/server/com/base/basic.rs deleted file mode 100644 index 279d94b..0000000 --- a/opc_da/src/server/com/base/basic.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::variant::Variant; - -#[derive(Clone, Default)] -pub struct Quality(pub u16); - -#[derive(Default)] -pub struct Value { - pub variant: Variant, - pub quality: Quality, - pub timestamp: Option, -} - -#[derive(Default)] -pub struct AccessRight { - pub readable: bool, - pub writable: bool, -} diff --git a/opc_da/src/server/com/base/mod.rs b/opc_da/src/server/com/base/mod.rs deleted file mode 100644 index 65b26b5..0000000 --- a/opc_da/src/server/com/base/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod basic; -pub mod variant; - -pub use basic::*; -pub use variant::*; diff --git a/opc_da/src/server/com/base/variant.rs b/opc_da/src/server/com/base/variant.rs deleted file mode 100644 index 63d4228..0000000 --- a/opc_da/src/server/com/base/variant.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[derive(Clone, Default)] -pub enum Variant { - #[default] - Empty, - Bool(bool), - String(String), - I8(i8), - I16(i16), - I32(i32), - I64(i64), - F32(f32), - F64(f64), - U8(u8), - U16(u16), - U32(u32), - U64(u64), -} diff --git a/opc_da/src/server/com/builder.rs b/opc_da/src/server/com/builder.rs deleted file mode 100644 index 2fe0923..0000000 --- a/opc_da/src/server/com/builder.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub struct Builder {} - -impl Builder { - pub fn register() {} -} diff --git a/opc_da/src/server/com/connection_point.rs b/opc_da/src/server/com/connection_point.rs deleted file mode 100644 index 2a2b448..0000000 --- a/opc_da/src/server/com/connection_point.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::{collections::BTreeMap, mem::ManuallyDrop}; - -use windows::Win32::System::Com::{ - IConnectionPoint, IConnectionPointContainer, IConnectionPoint_Impl, IEnumConnections, -}; - -use super::enumeration::ConnectionsEnumerator; - -#[windows::core::implement(IConnectionPoint)] -pub struct ConnectionPoint { - container: IConnectionPointContainer, - interface_id: windows::core::GUID, - next_cookie: core::sync::atomic::AtomicU32, - connections: tokio::sync::RwLock>, -} - -impl ConnectionPoint { - pub fn new( - container: IConnectionPointContainer, - interface_id: windows::core::GUID, - ) -> ConnectionPoint { - ConnectionPoint { - container, - interface_id, - next_cookie: core::sync::atomic::AtomicU32::new(0), - connections: tokio::sync::RwLock::new(BTreeMap::new()), - } - } -} - -impl IConnectionPoint_Impl for ConnectionPoint_Impl { - fn GetConnectionInterface(&self) -> windows::core::Result { - Ok(self.interface_id) - } - - fn GetConnectionPointContainer(&self) -> windows::core::Result { - Ok(self.container.clone()) - } - - fn Advise( - &self, - sink: windows::core::Ref<'_, windows::core::IUnknown>, - ) -> windows::core::Result { - let cookie = self - .next_cookie - .fetch_add(1, core::sync::atomic::Ordering::SeqCst); - self.connections - .blocking_write() - .insert(cookie, sink.unwrap().clone()); - Ok(cookie) - } - - fn Unadvise(&self, cookie: u32) -> windows::core::Result<()> { - self.connections.blocking_write().remove(&cookie); - Ok(()) - } - - fn EnumConnections(&self) -> windows::core::Result { - Ok( - windows::core::ComObjectInner::into_object(ConnectionsEnumerator::new( - self.connections - .blocking_read() - .iter() - .map(|(k, v)| windows::Win32::System::Com::CONNECTDATA { - pUnk: ManuallyDrop::new(Some(v.clone())), - dwCookie: *k, - }) - .collect(), - )) - .into_interface(), - ) - } -} diff --git a/opc_da/src/server/com/enumeration.rs b/opc_da/src/server/com/enumeration.rs deleted file mode 100644 index 9827bb4..0000000 --- a/opc_da/src/server/com/enumeration.rs +++ /dev/null @@ -1,341 +0,0 @@ -use windows::{ - Win32::{ - Foundation::{S_FALSE, S_OK}, - System::Com::{ - CONNECTDATA, IConnectionPoint, IEnumConnectionPoints, IEnumConnectionPoints_Impl, - IEnumConnections, IEnumConnections_Impl, IEnumString, IEnumString_Impl, IEnumUnknown, - IEnumUnknown_Impl, - }, - }, - core::PWSTR, -}; - -use crate::safe_call; - -use super::memory::{FreeRaw as _, IntoArrayRef, IntoComArrayRef, IntoRef as _}; - -struct Enumerator { - items: Vec, - index: core::sync::atomic::AtomicUsize, -} - -impl Enumerator { - fn new(items: Vec) -> Self { - Self { - items, - index: core::sync::atomic::AtomicUsize::default(), - } - } - - pub fn next( - &self, - count: u32, - fetched: &mut u32, - elements: &mut [T], - ) -> windows::core::HRESULT { - let current_index = self - .index - .fetch_add(count as _, core::sync::atomic::Ordering::SeqCst); - if current_index >= self.items.len() { - return S_FALSE; - } - - let end_index = (current_index + count as usize).min(self.items.len()); - let slice = &self.items[current_index..end_index]; - *fetched = slice.len() as u32; - - for (i, element) in slice.iter().enumerate() { - elements[i] = element.clone(); - } - - S_OK - } - - pub fn skip(&self, count: u32) -> windows::core::HRESULT { - let current_index = self.index.load(core::sync::atomic::Ordering::SeqCst); - let new_index = current_index.saturating_add(count as usize); - let max_index = self.items.len(); - - if new_index >= max_index { - self.index - .store(max_index, core::sync::atomic::Ordering::SeqCst); - } else { - self.index - .store(new_index, core::sync::atomic::Ordering::SeqCst); - } - - S_OK - } - - fn reset(&self) -> windows::core::HRESULT { - self.index.store(0, core::sync::atomic::Ordering::SeqCst); - S_OK - } -} - -impl Clone for Enumerator { - fn clone(&self) -> Self { - Self { - items: self.items.clone(), - index: core::sync::atomic::AtomicUsize::new( - self.index.load(core::sync::atomic::Ordering::SeqCst), - ), - } - } -} - -#[windows::core::implement(IEnumString)] -#[repr(transparent)] -pub struct StringEnumerator(Enumerator>); - -#[windows::core::implement(IEnumUnknown)] -#[repr(transparent)] -pub struct UnknownEnumerator(Enumerator); - -#[windows::core::implement(IEnumConnectionPoints)] -#[repr(transparent)] -pub struct ConnectionPointsEnumerator(Enumerator); - -#[windows::core::implement(IEnumConnections)] -#[repr(transparent)] -pub struct ConnectionsEnumerator(Enumerator); - -#[windows::core::implement(opc_da_bindings::IEnumOPCItemAttributes)] -#[repr(transparent)] -pub struct ItemAttributesEnumerator(Enumerator); - -impl StringEnumerator { - pub fn new(strings: Vec) -> Self { - Self(Enumerator::new( - strings - .into_iter() - .map(|s| s.encode_utf16().chain(Some(0)).collect()) - .collect(), - )) - } -} - -impl UnknownEnumerator { - pub fn new(items: Vec) -> Self { - Self(Enumerator::new(items)) - } -} - -impl ConnectionPointsEnumerator { - pub fn new(connection_points: Vec) -> Self { - Self(Enumerator::new(connection_points)) - } -} - -impl ConnectionsEnumerator { - pub fn new(connections: Vec) -> Self { - Self(Enumerator::new(connections)) - } -} - -impl ItemAttributesEnumerator { - pub fn new(items: Vec) -> Self { - Self(Enumerator::new(items)) - } -} - -impl IEnumString_Impl for StringEnumerator_Impl { - fn Next( - &self, - count: u32, - range_elements: *mut windows::core::PWSTR, - count_fetched: *mut u32, - ) -> windows::core::HRESULT { - let fetched = match count_fetched.into_ref() { - Ok(fetched) => fetched, - Err(e) => return e.code(), - }; - - let elements = match range_elements.into_array_ref(count) { - Ok(elements) => elements, - Err(e) => return e.code(), - }; - - let mut strings = Vec::with_capacity(count as usize); - - let code = self.0.next(count, fetched, &mut strings); - if code != S_OK { - return code; - } - - for (i, string) in strings.iter_mut().enumerate() { - let pwstr = PWSTR(string.as_mut_ptr()); - elements[i] = pwstr; - } - - S_OK - } - - fn Skip(&self, count: u32) -> windows::core::HRESULT { - self.0.skip(count) - } - - fn Reset(&self) -> windows::core::Result<()> { - self.0.reset().ok() - } - - fn Clone(&self) -> windows::core::Result { - Ok(IEnumString::from(StringEnumerator(self.0.clone()))) - } -} - -impl IEnumUnknown_Impl for UnknownEnumerator_Impl { - fn Next( - &self, - count: u32, - range_elements: *mut Option, - fetched_count: *mut u32, - ) -> windows::core::HRESULT { - let fetched = match fetched_count.into_ref() { - Ok(fetched) => fetched, - Err(e) => return e.code(), - }; - - let elements = match range_elements.into_array_ref(count) { - Ok(elements) => elements, - Err(e) => return e.code(), - }; - - let mut items = Vec::with_capacity(count as usize); - - let code = self.0.next(count, fetched, &mut items); - if code != S_OK { - return code; - } - - for (i, unk) in items.into_iter().enumerate() { - elements[i] = Some(unk); - } - - S_OK - } - - fn Skip(&self, count: u32) -> windows::core::Result<()> { - self.0.skip(count).ok() - } - - fn Reset(&self) -> windows::core::Result<()> { - self.0.reset().ok() - } - - fn Clone(&self) -> windows::core::Result { - Ok(IEnumUnknown::from(UnknownEnumerator(self.0.clone()))) - } -} - -impl IEnumConnectionPoints_Impl for ConnectionPointsEnumerator_Impl { - fn Next( - &self, - count: u32, - range_connection_points: *mut Option, - count_fetched: *mut u32, - ) -> windows::core::HRESULT { - let fetched = match count_fetched.into_ref() { - Ok(fetched) => fetched, - Err(e) => return e.code(), - }; - - let elements = match range_connection_points.into_array_ref(count) { - Ok(elements) => elements, - Err(e) => return e.code(), - }; - - let mut items = Vec::with_capacity(count as usize); - - let code = self.0.next(count, fetched, &mut items); - if code != S_OK { - return code; - } - - for (i, cp) in items.into_iter().enumerate() { - elements[i] = Some(cp); - } - - S_OK - } - - fn Skip(&self, count: u32) -> windows::core::Result<()> { - self.0.skip(count).ok() - } - - fn Reset(&self) -> windows::core::Result<()> { - self.0.reset().ok() - } - - fn Clone(&self) -> windows::core::Result { - Ok(IEnumConnectionPoints::from(ConnectionPointsEnumerator( - self.0.clone(), - ))) - } -} - -impl IEnumConnections_Impl for ConnectionsEnumerator_Impl { - fn Next( - &self, - count: u32, - range_connect_data: *mut CONNECTDATA, - count_fetched: *mut u32, - ) -> windows::core::HRESULT { - let fetched = match count_fetched.into_ref() { - Ok(fetched) => fetched, - Err(e) => return e.code(), - }; - - let elements = match range_connect_data.into_array_ref(count) { - Ok(elements) => elements, - Err(e) => return e.code(), - }; - - self.0.next(count, fetched, elements) - } - - fn Skip(&self, count: u32) -> windows::core::Result<()> { - self.0.skip(count).ok() - } - - fn Reset(&self) -> windows::core::Result<()> { - self.0.reset().ok() - } - - fn Clone(&self) -> windows::core::Result { - Ok(IEnumConnections::from(ConnectionsEnumerator( - self.0.clone(), - ))) - } -} - -impl opc_da_bindings::IEnumOPCItemAttributes_Impl for ItemAttributesEnumerator_Impl { - fn Next( - &self, - count: u32, - items: *mut *mut opc_da_bindings::tagOPCITEMATTRIBUTES, - fetched_count: *mut u32, - ) -> windows::core::Result<()> { - let fetched = fetched_count.into_ref()?; - let elements = items.into_com_array_ref(count)?; - - safe_call! { - self.0.next(count, fetched, elements).ok(), - items - } - } - - fn Skip(&self, count: u32) -> windows::core::Result<()> { - self.0.skip(count).ok() - } - - fn Reset(&self) -> windows::core::Result<()> { - self.0.reset().ok() - } - - fn Clone(&self) -> windows::core::Result { - Ok(opc_da_bindings::IEnumOPCItemAttributes::from( - ItemAttributesEnumerator(self.0.clone()), - )) - } -} diff --git a/opc_da/src/server/com/group.rs b/opc_da/src/server/com/group.rs deleted file mode 100644 index 3034716..0000000 --- a/opc_da/src/server/com/group.rs +++ /dev/null @@ -1,765 +0,0 @@ -use crate::{ - safe_call, - server::{ - com::memory::{FreeRaw as _, IntoRef as _}, - traits::GroupTrait, - }, -}; - -use super::memory::IntoComArrayRef; - -#[windows::core::implement( - // implicit implement IUnknown - opc_da_bindings::IOPCItemMgt, - opc_da_bindings::IOPCGroupStateMgt, - opc_da_bindings::IOPCGroupStateMgt2, - opc_da_bindings::IOPCPublicGroupStateMgt, - opc_da_bindings::IOPCSyncIO, - opc_da_bindings::IOPCSyncIO2, - opc_da_bindings::IOPCAsyncIO2, - opc_da_bindings::IOPCAsyncIO3, - opc_da_bindings::IOPCItemDeadbandMgt, - opc_da_bindings::IOPCItemSamplingMgt, - windows::Win32::System::Com::IConnectionPointContainer, - opc_da_bindings::IOPCAsyncIO, - windows::Win32::System::Com::IDataObject -)] -pub struct Group(pub T) -where - T: GroupTrait + 'static; - -impl core::ops::Deref for Group { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// 1.0 required -// 2.0 required -// 3.0 required -impl opc_da_bindings::IOPCItemMgt_Impl for Group_Impl { - fn AddItems( - &self, - count: u32, - items: *const opc_da_bindings::tagOPCITEMDEF, - results: *mut *mut opc_da_bindings::tagOPCITEMRESULT, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.add_items( - items.into_com_array_ref(count)?, - results.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - results, - errors - } - } - - fn ValidateItems( - &self, - count: u32, - items: *const opc_da_bindings::tagOPCITEMDEF, - blob_update: windows_core::BOOL, - validation_results: *mut *mut opc_da_bindings::tagOPCITEMRESULT, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.validate_items( - items.into_com_array_ref(count)?, - blob_update, - validation_results.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - validation_results, - errors - } - } - - fn RemoveItems( - &self, - count: u32, - item_server_handles: *const u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.remove_items( - item_server_handles.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn SetActiveState( - &self, - count: u32, - item_server_handles: *const u32, - active: windows_core::BOOL, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.set_active_state( - item_server_handles.into_com_array_ref(count)?, - active, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn SetClientHandles( - &self, - count: u32, - item_server_handles: *const u32, - handle_client: *const u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.set_client_handles( - item_server_handles.into_com_array_ref(count)?, - handle_client.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn SetDatatypes( - &self, - count: u32, - item_server_handles: *const u32, - requested_data_types: *const u16, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.set_data_types( - item_server_handles.into_com_array_ref(count)?, - requested_data_types.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn CreateEnumerator( - &self, - reference_interface_id: *const windows::core::GUID, - ) -> windows::core::Result { - safe_call! { - self.create_enumerator(reference_interface_id.into_ref()?), - } - } -} - -// 1.0 required -// 2.0 required -// 3.0 required -impl opc_da_bindings::IOPCGroupStateMgt_Impl for Group_Impl { - fn GetState( - &self, - update_rate: *mut u32, - active: *mut windows_core::BOOL, - name: *mut windows::core::PWSTR, - time_bias: *mut i32, - percent_deadband: *mut f32, - locale_id: *mut u32, - group_client_handle: *mut u32, - item_server_handle_group: *mut u32, - ) -> windows::core::Result<()> { - self.get_state( - update_rate.into_ref()?, - active.into_ref()?, - name.into_ref()?, - time_bias.into_ref()?, - percent_deadband.into_ref()?, - locale_id.into_ref()?, - group_client_handle.into_ref()?, - item_server_handle_group.into_ref()?, - ) - } - - fn SetState( - &self, - requested_update_rate: *const u32, - revised_update_rate: *mut u32, - active: *const windows_core::BOOL, - time_bias: *const i32, - percent_deadband: *const f32, - locale_id: *const u32, - group_client_handle: *const u32, - ) -> windows::core::Result<()> { - self.set_state( - requested_update_rate.into_ref()?, - revised_update_rate.into_ref()?, - active.into_ref()?, - time_bias.into_ref()?, - percent_deadband.into_ref()?, - locale_id.into_ref()?, - group_client_handle.into_ref()?, - ) - } - - fn SetName(&self, name: &windows::core::PCWSTR) -> windows::core::Result<()> { - self.set_name(name) - } - - fn CloneGroup( - &self, - name: &windows::core::PCWSTR, - reference_interface_id: *const windows::core::GUID, - ) -> windows::core::Result { - self.clone_group(name, reference_interface_id.into_ref()?) - } -} - -// 1.0 N/A -// 2.0 N/A -// 3.0 required -impl opc_da_bindings::IOPCGroupStateMgt2_Impl for Group_Impl { - fn SetKeepAlive(&self, keep_alive_time: u32) -> windows::core::Result { - self.set_keep_alive(keep_alive_time) - } - - fn GetKeepAlive(&self) -> windows::core::Result { - self.get_keep_alive() - } -} - -// 1.0 optional -// 2.0 optional -// 3.0 N/A -impl opc_da_bindings::IOPCPublicGroupStateMgt_Impl for Group_Impl { - fn GetState(&self) -> windows::core::Result { - self.get_public_group_state() - } - - fn MoveToPublic(&self) -> windows::core::Result<()> { - self.move_to_public() - } -} - -// 1.0 required -// 2.0 required -// 3.0 required -impl opc_da_bindings::IOPCSyncIO_Impl for Group_Impl { - fn Read( - &self, - source: opc_da_bindings::tagOPCDATASOURCE, - count: u32, - item_server_handles: *const u32, - item_values: *mut *mut opc_da_bindings::tagOPCITEMSTATE, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.read( - source, - item_server_handles.into_com_array_ref(count)?, - item_values.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - item_values, - errors - } - } - - fn Write( - &self, - count: u32, - item_server_handles: *const u32, - item_values: *const windows::Win32::System::Variant::VARIANT, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.write( - item_server_handles.into_com_array_ref(count)?, - item_values.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - errors - } - } -} - -// 1.0 N/A -// 2.0 N/A -// 3.0 required -impl opc_da_bindings::IOPCSyncIO2_Impl for Group_Impl { - fn ReadMaxAge( - &self, - count: u32, - item_server_handles: *const u32, - max_age: *const u32, - values: *mut *mut windows::Win32::System::Variant::VARIANT, - qualities: *mut *mut u16, - timestamps: *mut *mut windows::Win32::Foundation::FILETIME, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.read_max_age( - item_server_handles.into_com_array_ref(count)?, - max_age.into_com_array_ref(count)?, - values.into_com_array_ref(count)?, - qualities.into_com_array_ref(count)?, - timestamps.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - values, - qualities, - timestamps, - errors - } - } - - fn WriteVQT( - &self, - count: u32, - item_server_handles: *const u32, - item_vqt: *const opc_da_bindings::tagOPCITEMVQT, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.write_vqt( - count, - item_server_handles.into_com_array_ref(count)?, - item_vqt.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - errors - } - } -} - -// 1.0 N/A -// 2.0 required -// 3.0 required -impl opc_da_bindings::IOPCAsyncIO2_Impl for Group_Impl { - fn Read( - &self, - count: u32, - item_server_handles: *const u32, - transaction_id: u32, - cancel_id: *mut u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.read2( - item_server_handles.into_com_array_ref(count)?, - transaction_id, - cancel_id.into_ref()?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn Write( - &self, - count: u32, - item_server_handles: *const u32, - item_values: *const windows::Win32::System::Variant::VARIANT, - transaction_id: u32, - cancel_id: *mut u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.write2( - count, - item_server_handles.into_com_array_ref(count)?, - item_values.into_com_array_ref(count)?, - transaction_id, - cancel_id.into_ref()?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn Refresh2( - &self, - source: opc_da_bindings::tagOPCDATASOURCE, - transaction_id: u32, - ) -> windows::core::Result { - self.refresh2(source, transaction_id) - } - - fn Cancel2(&self, cancel_id: u32) -> windows::core::Result<()> { - self.cancel2(cancel_id) - } - - fn SetEnable(&self, enable: windows_core::BOOL) -> windows::core::Result<()> { - self.set_enable(enable) - } - - fn GetEnable(&self) -> windows::core::Result { - self.get_enable() - } -} - -// 1.0 N/A -// 2.0 N/A -// 3.0 required -impl opc_da_bindings::IOPCAsyncIO3_Impl for Group_Impl { - fn ReadMaxAge( - &self, - count: u32, - item_server_handles: *const u32, - max_age: *const u32, - transaction_id: u32, - cancel_id: *mut u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.read_max_age2( - item_server_handles.into_com_array_ref(count)?, - max_age.into_com_array_ref(count)?, - transaction_id, - cancel_id.into_ref()?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn WriteVQT( - &self, - count: u32, - item_server_handles: *const u32, - item_vqt: *const opc_da_bindings::tagOPCITEMVQT, - transaction_id: u32, - cancel_id: *mut u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.write_vqt2( - item_server_handles.into_com_array_ref(count)?, - item_vqt.into_com_array_ref(count)?, - transaction_id, - cancel_id.into_ref()?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn RefreshMaxAge(&self, max_age: u32, transaction_id: u32) -> windows::core::Result { - self.refresh_max_age(max_age, transaction_id) - } -} - -// 1.0 N/A -// 2.0 N/A -// 3.0 required -impl opc_da_bindings::IOPCItemDeadbandMgt_Impl for Group_Impl { - fn SetItemDeadband( - &self, - count: u32, - item_server_handles: *const u32, - percent_deadband: *const f32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.set_item_deadband( - item_server_handles.into_com_array_ref(count)?, - percent_deadband.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn GetItemDeadband( - &self, - count: u32, - item_server_handles: *const u32, - percent_deadband: *mut *mut f32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.get_item_deadband( - item_server_handles.into_com_array_ref(count)?, - percent_deadband.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - percent_deadband, - errors - } - } - - fn ClearItemDeadband( - &self, - count: u32, - item_server_handles: *const u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.clear_item_deadband( - item_server_handles.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - errors - } - } -} - -// 1.0 N/A -// 2.0 N/A -// 3.0 optional -impl opc_da_bindings::IOPCItemSamplingMgt_Impl for Group_Impl { - fn SetItemSamplingRate( - &self, - count: u32, - item_server_handles: *const u32, - requested_sampling_rate: *const u32, - revised_sampling_rate: *mut *mut u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.set_item_sampling_rate( - count, - item_server_handles.into_com_array_ref(count)?, - requested_sampling_rate.into_com_array_ref(count)?, - revised_sampling_rate.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - revised_sampling_rate, - errors - } - } - - fn GetItemSamplingRate( - &self, - count: u32, - item_server_handles: *const u32, - sampling_rate: *mut *mut u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.get_item_sampling_rate( - item_server_handles.into_com_array_ref(count)?, - sampling_rate.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - sampling_rate, - errors - } - } - - fn ClearItemSamplingRate( - &self, - count: u32, - item_server_handles: *const u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.clear_item_sampling_rate( - item_server_handles.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn SetItemBufferEnable( - &self, - count: u32, - item_server_handles: *const u32, - penable: *const windows_core::BOOL, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.set_item_buffer_enable( - item_server_handles.into_com_array_ref(count)?, - penable.into_ref()?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn GetItemBufferEnable( - &self, - count: u32, - item_server_handles: *const u32, - enable: *mut *mut windows_core::BOOL, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.get_item_buffer_enable( - item_server_handles.into_com_array_ref(count)?, - enable.into_com_array_ref(count)?, - errors.into_com_array_ref(count)?, - ), - enable, - errors - } - } -} - -// 1.0 N/A -// 2.0 required -// 3.0 required -impl windows::Win32::System::Com::IConnectionPointContainer_Impl - for Group_Impl -{ - fn EnumConnectionPoints( - &self, - ) -> windows::core::Result { - self.enum_connection_points() - } - - fn FindConnectionPoint( - &self, - reference_interface_id: *const windows::core::GUID, - ) -> windows::core::Result { - self.find_connection_point(reference_interface_id.into_ref()?) - } -} - -// 1.0 required -// 2.0 optional -// 3.0 N/A -impl opc_da_bindings::IOPCAsyncIO_Impl for Group_Impl { - fn Read( - &self, - connection: u32, - source: opc_da_bindings::tagOPCDATASOURCE, - count: u32, - item_server_handles: *const u32, - transaction_id: *mut u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.read3( - connection, - source, - item_server_handles.into_com_array_ref(count)?, - transaction_id.into_ref()?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn Write( - &self, - connection: u32, - count: u32, - item_server_handles: *const u32, - item_values: *const windows::Win32::System::Variant::VARIANT, - transaction_id: *mut u32, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - safe_call! { - self.write3( - connection, - item_server_handles.into_com_array_ref(count)?, - item_values.into_com_array_ref(count)?, - transaction_id.into_ref()?, - errors.into_com_array_ref(count)?, - ), - errors - } - } - - fn Refresh( - &self, - connection: u32, - source: opc_da_bindings::tagOPCDATASOURCE, - ) -> windows::core::Result { - self.refresh(connection, source) - } - - fn Cancel(&self, transaction_id: u32) -> windows::core::Result<()> { - self.cancel(transaction_id) - } -} - -// 1.0 required -// 2.0 optional -// 3.0 N/A -impl windows::Win32::System::Com::IDataObject_Impl for Group_Impl { - fn GetData( - &self, - format_etc_in: *const windows::Win32::System::Com::FORMATETC, - ) -> windows::core::Result { - self.get_data(format_etc_in.into_ref()?) - } - - fn GetDataHere( - &self, - format_etc_in: *const windows::Win32::System::Com::FORMATETC, - storage_medium: *mut windows::Win32::System::Com::STGMEDIUM, - ) -> windows::core::Result<()> { - self.get_data_here(format_etc_in.into_ref()?, storage_medium.into_ref()?) - } - - fn QueryGetData( - &self, - format_etc_in: *const windows::Win32::System::Com::FORMATETC, - ) -> windows::core::HRESULT { - let format_etc_in = match format_etc_in.into_ref() { - Ok(format_etc_in) => format_etc_in, - Err(err) => return err.code(), - }; - - self.query_get_data(format_etc_in) - } - - fn GetCanonicalFormatEtc( - &self, - format_etc_in: *const windows::Win32::System::Com::FORMATETC, - format_etc_inout: *mut windows::Win32::System::Com::FORMATETC, - ) -> windows::core::HRESULT { - let format_etc_in = match format_etc_in.into_ref() { - Ok(format_etc_in) => format_etc_in, - Err(err) => return err.code(), - }; - - let format_etc_inout = match format_etc_inout.into_ref() { - Ok(format_etc_inout) => format_etc_inout, - Err(err) => return err.code(), - }; - - self.get_canonical_format_etc(format_etc_in, format_etc_inout) - } - - fn SetData( - &self, - format_etc_in: *const windows::Win32::System::Com::FORMATETC, - storage_medium: *const windows::Win32::System::Com::STGMEDIUM, - release: windows_core::BOOL, - ) -> windows::core::Result<()> { - self.set_data( - format_etc_in.into_ref()?, - storage_medium.into_ref()?, - release, - ) - } - - fn EnumFormatEtc( - &self, - direction: u32, - ) -> windows::core::Result { - self.enum_format_etc(direction) - } - - fn DAdvise( - &self, - format_etc_in: *const windows::Win32::System::Com::FORMATETC, - adv: u32, - sink: windows::core::Ref<'_, windows::Win32::System::Com::IAdviseSink>, - ) -> windows::core::Result { - self.data_advise(format_etc_in.into_ref()?, adv, sink) - } - - fn DUnadvise(&self, connection: u32) -> windows::core::Result<()> { - self.data_unadvise(connection) - } - - fn EnumDAdvise(&self) -> windows::core::Result { - self.enum_data_advise() - } -} diff --git a/opc_da/src/server/com/item.rs b/opc_da/src/server/com/item.rs deleted file mode 100644 index 8b13789..0000000 --- a/opc_da/src/server/com/item.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/opc_da/src/server/com/memory.rs b/opc_da/src/server/com/memory.rs deleted file mode 100644 index ffedb57..0000000 --- a/opc_da/src/server/com/memory.rs +++ /dev/null @@ -1,220 +0,0 @@ -use windows::Win32::{Foundation::E_POINTER, System::Com::CoTaskMemAlloc}; - -pub trait IntoRef { - fn into_ref(self) -> windows::core::Result; -} - -pub trait IntoArrayRef { - fn into_array_ref(self, count: u32) -> windows::core::Result; -} - -pub trait IntoComArrayRef { - fn into_com_array_ref(self, count: u32) -> windows::core::Result; -} - -pub trait FreeRaw { - fn free_raw(self); -} - -#[macro_export] -macro_rules! safe_call { - ($call:expr, $($p:ident),*) => { - let result = $call; - - if result.is_err() { - $($p.free_raw();)* - } - - result - }; -} - -impl<'a, P> IntoRef<&'a P> for *const P { - #[inline(always)] - fn into_ref(self) -> windows::core::Result<&'a P> { - if self.is_null() { - Err(windows::core::Error::from_hresult(E_POINTER)) - } else { - Ok(unsafe { &*self }) - } - } -} - -impl<'a, P> IntoRef<&'a mut P> for *mut P { - #[inline(always)] - fn into_ref(self) -> windows::core::Result<&'a mut P> { - if self.is_null() { - Err(windows::core::Error::from_hresult(E_POINTER)) - } else { - Ok(unsafe { &mut *self }) - } - } -} - -impl<'a, P> IntoArrayRef<&'a mut [P]> for *mut P { - #[inline(always)] - fn into_array_ref(self, count: u32) -> windows::core::Result<&'a mut [P]> { - if self.is_null() { - Err(windows::core::Error::from_hresult(E_POINTER)) - } else { - unsafe { Ok(std::slice::from_raw_parts_mut(self, count as usize)) } - } - } -} - -impl<'a, P: windows::core::Interface> IntoArrayRef<&'a mut [Option

]> - for windows::core::OutRef<'a, P> -{ - #[inline(always)] - fn into_array_ref(self, count: u32) -> windows::core::Result<&'a mut [Option

]> { - if self.is_null() { - Err(windows::core::Error::from_hresult(E_POINTER)) - } else { - unsafe { - Ok(std::slice::from_raw_parts_mut( - *(&self as *const _ as *mut *mut Option

), - count as usize, - )) - } - } - } -} - -impl

FreeRaw for *mut *mut P { - #[inline(always)] - fn free_raw(self) { - unsafe { - windows::Win32::System::Com::CoTaskMemFree(if self.is_null() { - None - } else { - Some(*self as _) - }); - } - } -} - -impl<'a, P> IntoComArrayRef<&'a [P]> for *const P { - #[inline(always)] - fn into_com_array_ref(self, count: u32) -> windows::core::Result<&'a [P]> { - if self.is_null() { - Err(windows::core::Error::from_hresult(E_POINTER)) - } else { - Ok(unsafe { core::slice::from_raw_parts(self, count as usize) }) - } - } -} - -impl<'a, P> IntoComArrayRef<&'a mut [P]> for *mut *mut P { - #[inline(always)] - fn into_com_array_ref(self, count: u32) -> windows::core::Result<&'a mut [P]> { - if self.is_null() { - Err(windows::core::Error::from_hresult(E_POINTER)) - } else { - unsafe { - let new_pointer = - CoTaskMemAlloc(std::mem::size_of::

() * count as usize) as *mut P; - - if new_pointer.is_null() { - return Err(windows::core::Error::from_hresult(E_POINTER)); - } else { - *self = new_pointer; - } - - Ok(std::slice::from_raw_parts_mut(new_pointer, count as usize)) - } - } - } -} - -impl<'a, P1, P2> IntoComArrayRef> for (*const P1, *const P2) { - #[inline(always)] - fn into_com_array_ref(self, count: u32) -> windows::core::Result> { - let (p0, p1) = self; - - Ok(p0 - .into_com_array_ref(count)? - .iter() - .zip(p1.into_com_array_ref(count)?.iter()) - .collect()) - } -} - -impl<'a, P1, P2> IntoComArrayRef> for (*const P1, *mut *mut P2) { - #[inline(always)] - fn into_com_array_ref(self, count: u32) -> windows::core::Result> { - let (p0, p1) = self; - - Ok(p0 - .into_com_array_ref(count)? - .iter() - .zip(p1.into_com_array_ref(count)?.iter_mut()) - .collect()) - } -} - -impl<'a, P1, P2> IntoComArrayRef> for (*mut *mut P1, *mut *mut P2) { - #[inline(always)] - fn into_com_array_ref( - self, - count: u32, - ) -> windows::core::Result> { - let (p0, p1) = self; - - Ok(p0 - .into_com_array_ref(count)? - .iter_mut() - .zip(p1.into_com_array_ref(count)?.iter_mut()) - .collect()) - } -} - -impl<'a, C1, M1, M2> IntoComArrayRef> - for (*const C1, (*mut *mut M1, *mut *mut M2)) -{ - #[inline(always)] - fn into_com_array_ref( - self, - count: u32, - ) -> windows::core::Result> { - let (c, m) = self; - - Ok(c.into_com_array_ref(count)? - .iter() - .zip(m.into_com_array_ref(count)?) - .collect()) - } -} - -impl<'a, C1, C2, M1> IntoComArrayRef> - for ((*const C1, *const C2), *mut *mut M1) -{ - #[inline(always)] - fn into_com_array_ref( - self, - count: u32, - ) -> windows::core::Result> { - let (c, m) = self; - - Ok(c.into_com_array_ref(count)? - .into_iter() - .zip(m.into_com_array_ref(count)?) - .collect()) - } -} - -impl<'a, C1, C2, M1, M2> IntoComArrayRef> - for ((*const C1, *const C2), (*mut *mut M1, *mut *mut M2)) -{ - #[inline(always)] - fn into_com_array_ref( - self, - count: u32, - ) -> windows::core::Result> { - let (c, m) = self; - - Ok(c.into_com_array_ref(count)? - .into_iter() - .zip(m.into_com_array_ref(count)?) - .collect()) - } -} diff --git a/opc_da/src/server/com/mod.rs b/opc_da/src/server/com/mod.rs deleted file mode 100644 index 4ba0618..0000000 --- a/opc_da/src/server/com/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -pub mod base; -pub mod builder; -pub mod connection_point; -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub mod enumeration; -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub mod group; -pub mod item; -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub mod memory; -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub mod server; -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub mod utils; -#[allow(clippy::not_unsafe_ptr_arg_deref)] -pub mod variant; diff --git a/opc_da/src/server/com/server.rs b/opc_da/src/server/com/server.rs deleted file mode 100644 index 6cf251a..0000000 --- a/opc_da/src/server/com/server.rs +++ /dev/null @@ -1,558 +0,0 @@ -use crate::{ - server::traits::{ItemOptionalVqt, ItemWithMaxAge, ServerTrait}, - utils::{TryToLocal as _, TryToNative as _}, -}; - -use super::{ - enumeration::{ConnectionPointsEnumerator, StringEnumerator}, - utils::{ - PointerReader, PointerWriter, TryReadArray, TryWriteArrayPointer, TryWriteInto, - TryWritePointer, TryWriteTo, - }, -}; - -#[windows::core::implement( - // implicit implement IUnknown - opc_da_bindings::IOPCServer, - opc_comn_bindings::IOPCCommon, - windows::Win32::System::Com::IConnectionPointContainer, - opc_da_bindings::IOPCItemProperties, - opc_da_bindings::IOPCBrowse, - opc_da_bindings::IOPCServerPublicGroups, - opc_da_bindings::IOPCBrowseServerAddressSpace, - opc_da_bindings::IOPCItemIO -)] -pub struct Server(pub T) -where - T: ServerTrait + 'static; - -impl core::ops::Deref for Server { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// 1.0 required -// 2.0 required -// 3.0 required -impl opc_da_bindings::IOPCServer_Impl for Server_Impl { - fn AddGroup( - &self, - name: &windows::core::PCWSTR, - active: windows_core::BOOL, - requested_update_rate: u32, - client_group: u32, - time_bias: *const i32, - percent_deadband: *const f32, - locale_id: u32, - server_group: *mut u32, - revised_update_rate: *mut u32, - reference_interface_id: *const windows::core::GUID, - unknown: windows::core::OutRef<'_, windows::core::IUnknown>, - ) -> windows::core::Result<()> { - let info = self.add_group( - unsafe { name.to_string() }?, - active.as_bool(), - requested_update_rate, - client_group, - unsafe { time_bias.as_ref() }.copied(), - unsafe { percent_deadband.as_ref() }.copied(), - locale_id, - unsafe { reference_interface_id.as_ref() }.map(|id| id.to_u128()), - )?; - - PointerWriter::try_write(info.server_group, server_group)?; - PointerWriter::try_write(info.revised_update_rate, revised_update_rate)?; - PointerWriter::try_write_into(info.unknown, unknown)?; - - Ok(()) - } - - fn GetErrorString( - &self, - error: windows::core::HRESULT, - locale: u32, - ) -> windows::core::Result { - let s = self.get_error_string_locale(error.0, locale)?; - PointerWriter::try_write_to(&s) - } - - fn GetGroupByName( - &self, - name: &windows::core::PCWSTR, - reference_interface_id: *const windows::core::GUID, - ) -> windows::core::Result { - self.get_group_by_name( - unsafe { name.to_string() }?, - if reference_interface_id.is_null() { - None - } else { - Some(unsafe { *reference_interface_id }.to_u128()) - }, - ) - } - - fn GetStatus(&self) -> windows::core::Result<*mut opc_da_bindings::tagOPCSERVERSTATUS> { - let status: opc_da_bindings::tagOPCSERVERSTATUS = self.get_status()?.try_into()?; - PointerWriter::try_write_to(status) - } - - fn RemoveGroup( - &self, - server_group: u32, - force: windows_core::BOOL, - ) -> windows::core::Result<()> { - self.remove_group(server_group, force.as_bool()) - } - - fn CreateGroupEnumerator( - &self, - scope: opc_da_bindings::tagOPCENUMSCOPE, - reference_interface_id: *const windows::core::GUID, - ) -> windows::core::Result { - self.create_group_enumerator( - scope.try_to_local()?, - unsafe { reference_interface_id.as_ref() }.map(|id| id.to_u128()), - ) - } -} - -// 1.0 N/A -// 2.0 required -// 3.0 required -impl opc_comn_bindings::IOPCCommon_Impl for Server_Impl { - fn SetLocaleID(&self, locale_id: u32) -> windows::core::Result<()> { - self.set_locale_id(locale_id) - } - - fn GetLocaleID(&self) -> windows::core::Result { - self.get_locale_id() - } - - fn QueryAvailableLocaleIDs( - &self, - count: *mut u32, - locale_ids: *mut *mut u32, - ) -> windows::core::Result<()> { - let available_locale_ids = self.query_available_locale_ids()?; - PointerWriter::try_write(available_locale_ids.len() as _, count)?; - PointerWriter::try_write_array_pointer(&available_locale_ids, locale_ids)?; - Ok(()) - } - - fn GetErrorString( - &self, - error: windows::core::HRESULT, - ) -> windows::core::Result { - let s = self.get_error_string(error.0).map_err(|e| { - // Map internal errors to appropriate COM errors - windows::core::Error::new(windows::Win32::Foundation::E_FAIL, e.to_string()) - })?; - let mut out = windows::core::PWSTR::null(); - PointerWriter::try_write_into(&s, &mut out).map_err(|e| { - // Handle allocation failures - windows::core::Error::new(windows::Win32::Foundation::E_OUTOFMEMORY, e.to_string()) - })?; - Ok(out) - } - - fn SetClientName(&self, name: &windows::core::PCWSTR) -> windows::core::Result<()> { - self.set_client_name(unsafe { name.to_string() }?) - } -} - -// 1.0 N/A -// 2.0 required -// 3.0 required -impl windows::Win32::System::Com::IConnectionPointContainer_Impl - for Server_Impl -{ - fn EnumConnectionPoints( - &self, - ) -> windows::core::Result { - let connection_points = self.enum_connection_points()?; - - Ok( - windows::core::ComObjectInner::into_object(ConnectionPointsEnumerator::new( - connection_points, - )) - .into_interface(), - ) - } - - fn FindConnectionPoint( - &self, - reference_interface_id: *const windows::core::GUID, - ) -> windows::core::Result { - self.find_connection_point(reference_interface_id) - } -} - -// 1.0 N/A -// 2.0 required -// 3.0 N/A -impl opc_da_bindings::IOPCItemProperties_Impl for Server_Impl { - fn QueryAvailableProperties( - &self, - item_id: &windows::core::PCWSTR, - count: *mut u32, - property_ids: *mut *mut u32, - descriptions: *mut *mut windows::core::PWSTR, - data_types: *mut *mut u16, - ) -> windows::core::Result<()> { - let vec = self.query_available_properties(unsafe { item_id.to_string() }?)?; - - PointerWriter::try_write(vec.len() as _, count)?; - - PointerWriter::try_write_array_pointer( - &vec.iter().map(|p| p.property_id).collect::>(), - property_ids, - )?; - - PointerWriter::try_write_into( - &vec.iter() - .map(|p| p.description.as_str()) - .collect::>(), - descriptions, - )?; - - PointerWriter::try_write_array_pointer( - &vec.iter().map(|p| p.data_type).collect::>(), - data_types, - )?; - - Ok(()) - } - - fn GetItemProperties( - &self, - item_id: &windows::core::PCWSTR, - count: u32, - property_ids: *const u32, - data: *mut *mut windows::Win32::System::Variant::VARIANT, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - let property_ids = PointerReader::try_read_array(count, property_ids)?; - - let vec = self.get_item_properties(unsafe { item_id.to_string() }?, property_ids)?; - - PointerWriter::try_write_array_pointer( - &vec.iter().map(|p| p.error).collect::>(), - errors, - )?; - - PointerWriter::try_write_array_pointer( - &vec.into_iter().map(|p| p.data.into()).collect::>(), - data, - )?; - - Ok(()) - } - - fn LookupItemIDs( - &self, - item_id: &windows::core::PCWSTR, - count: u32, - property_ids: *const u32, - new_item_ids: *mut *mut windows::core::PWSTR, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - let property_ids = PointerReader::try_read_array(count, property_ids)?; - - let vec = self.lookup_item_ids(unsafe { item_id.to_string() }?, property_ids)?; - - PointerWriter::try_write_into( - &vec.iter() - .map(|p| p.new_item_id.as_str()) - .collect::>(), - new_item_ids, - )?; - - PointerWriter::try_write_array_pointer( - &vec.iter().map(|p| p.error).collect::>(), - errors, - )?; - - Ok(()) - } -} - -// 1.0 N/A -// 2.0 N/A -// 3.0 required -impl opc_da_bindings::IOPCBrowse_Impl for Server_Impl { - fn GetProperties( - &self, - item_count: u32, - item_ids: *const windows::core::PCWSTR, - return_property_values: windows_core::BOOL, - property_count: u32, - property_ids: *const u32, - item_properties: *mut *mut opc_da_bindings::tagOPCITEMPROPERTIES, - ) -> windows::core::Result<()> { - let item_ids = PointerReader::try_read_array(item_count, item_ids)?; - let property_ids = PointerReader::try_read_array(property_count, property_ids)?; - - let properties = - self.get_properties(item_ids, return_property_values.as_bool(), property_ids)?; - - PointerWriter::try_write_array_pointer( - &properties - .into_iter() - .map(|item| match item.try_into() { - Ok(item) => item, - Err(error) => opc_da_bindings::tagOPCITEMPROPERTIES { - hrErrorID: (error as windows::core::Error).code(), - ..Default::default() - }, - }) - .collect::>(), - item_properties, - )?; - - Ok(()) - } - - fn Browse( - &self, - item_id: &windows::core::PCWSTR, - continuation_point: *mut windows::core::PWSTR, - max_elements_returned: u32, - browse_filter: opc_da_bindings::tagOPCBROWSEFILTER, - element_name_filter: &windows::core::PCWSTR, - vendor_filter: &windows::core::PCWSTR, - return_all_properties: windows_core::BOOL, - return_property_values: windows_core::BOOL, - property_count: u32, - property_ids: *const u32, - more_elements: *mut windows_core::BOOL, - count: *mut u32, - browse_elements: *mut *mut opc_da_bindings::tagOPCBROWSEELEMENT, - ) -> windows::core::Result<()> { - let item_id = unsafe { item_id.to_string()? }; - let element_name_filter = unsafe { element_name_filter.to_string()? }; - let vendor_filter = unsafe { vendor_filter.to_string()? }; - let property_ids = PointerReader::try_read_array(property_count, property_ids)?; - - let result = self.browse( - item_id, - unsafe { - continuation_point - .as_ref() - .map(|s| s.to_string()) - .transpose()? - }, - max_elements_returned, - browse_filter.try_into()?, - element_name_filter, - vendor_filter, - return_all_properties.as_bool(), - return_property_values.as_bool(), - property_ids, - )?; - - PointerWriter::try_write(result.elements.len() as _, count)?; - - PointerWriter::try_write_array_pointer( - &result - .elements - .into_iter() - .map(|element| match element.try_into() { - Ok(element) => element, - Err(error) => { - let mut element = opc_da_bindings::tagOPCBROWSEELEMENT::default(); - element.ItemProperties.hrErrorID = (error as windows::core::Error).code(); - element - } - }) - .collect::>(), - browse_elements, - )?; - - PointerWriter::try_write(result.more_elements.into(), more_elements)?; - - match result.continuation_point { - Some(new_continuation_point) => { - PointerWriter::try_write_into(&new_continuation_point, continuation_point)? - } - None => unsafe { - *continuation_point = windows::core::PWSTR::null(); - }, - } - - Ok(()) - } -} - -// 1.0 optional -// 2.0 optional -// 3.0 N/A -impl opc_da_bindings::IOPCServerPublicGroups_Impl for Server_Impl { - fn GetPublicGroupByName( - &self, - name: &windows::core::PCWSTR, - reference_interface_id: *const windows::core::GUID, - ) -> windows::core::Result { - if reference_interface_id.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Null reference_interface_id", - )); - } - - self.get_public_group_by_name( - unsafe { name.to_string() }?, - unsafe { *reference_interface_id }.to_u128(), - ) - } - - fn RemovePublicGroup( - &self, - server_group: u32, - force: windows_core::BOOL, - ) -> windows::core::Result<()> { - self.remove_public_group(server_group, force.as_bool()) - } -} - -// 1.0 optional -// 2.0 optional -// 3.0 N/A -impl opc_da_bindings::IOPCBrowseServerAddressSpace_Impl - for Server_Impl -{ - fn QueryOrganization(&self) -> windows::core::Result { - self.query_organization().map(Into::into) - } - - fn ChangeBrowsePosition( - &self, - browse_direction: opc_da_bindings::tagOPCBROWSEDIRECTION, - string: &windows::core::PCWSTR, - ) -> windows::core::Result<()> { - self.change_browse_position((browse_direction, unsafe { string.to_string() }?).try_into()?) - } - - fn BrowseOPCItemIDs( - &self, - browse_filter_type: opc_da_bindings::tagOPCBROWSETYPE, - filter_criteria: &windows::core::PCWSTR, - variant_data_type_filter: u16, - access_rights_filter: u32, - ) -> windows::core::Result { - self.browse_opc_item_ids( - browse_filter_type.try_into()?, - unsafe { filter_criteria.to_string() }?, - variant_data_type_filter, - access_rights_filter, - ) - } - - fn GetItemID( - &self, - item_data_id: &windows::core::PCWSTR, - ) -> windows::core::Result { - let item_id = self.get_item_id(unsafe { item_data_id.to_string() }?)?; - PointerWriter::try_write_to(&item_id) - } - - fn BrowseAccessPaths( - &self, - item_id: &windows::core::PCWSTR, - ) -> windows::core::Result { - let access_paths = self.browse_access_paths(unsafe { item_id.to_string() }?)?; - - Ok( - windows::core::ComObjectInner::into_object(StringEnumerator::new(access_paths)) - .into_interface(), - ) - } -} - -// 1.0 N/A -// 2.0 N/A -// 3.0 required -impl opc_da_bindings::IOPCItemIO_Impl for Server_Impl { - fn Read( - &self, - count: u32, - item_ids: *const windows::core::PCWSTR, - max_ages: *const u32, - values: *mut *mut windows::Win32::System::Variant::VARIANT, - qualities: *mut *mut u16, - timestamps: *mut *mut windows::Win32::Foundation::FILETIME, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - let item_ids = PointerReader::try_read_array(count, item_ids)?; - let max_ages = PointerReader::try_read_array(count, max_ages)?; - - let result = self.read( - item_ids - .into_iter() - .zip(max_ages) - .map(|(item_id, max_age)| ItemWithMaxAge { item_id, max_age }) - .collect(), - )?; - - PointerWriter::try_write_array_pointer( - &result - .iter() - .map(|vqt| vqt.value.clone().into()) - .collect::>(), - values, - )?; - - PointerWriter::try_write_array_pointer( - &result.iter().map(|vqt| vqt.quality).collect::>(), - qualities, - )?; - - PointerWriter::try_write_array_pointer( - &result - .iter() - .map(|vqt| vqt.timestamp.try_to_native()) - .collect::>>()?, - timestamps, - )?; - - PointerWriter::try_write_array_pointer( - &result.iter().map(|vqt| vqt.error).collect::>(), - errors, - )?; - - Ok(()) - } - - fn WriteVQT( - &self, - count: u32, - item_ids: *const windows::core::PCWSTR, - item_vqt: *const opc_da_bindings::tagOPCITEMVQT, - errors: *mut *mut windows::core::HRESULT, - ) -> windows::core::Result<()> { - let item_ids = PointerReader::try_read_array(count, item_ids)?; - let item_vqt = PointerReader::try_read_array(count, item_vqt)? - .into_iter() - .try_fold(vec![], |mut acc, item| { - acc.push(item.try_into()?); - windows::core::Result::Ok(acc) - })?; - - let result = self.write_vqt( - item_ids - .into_iter() - .zip(item_vqt) - .map(|(item_id, optional_vqt)| ItemOptionalVqt { - item_id, - optional_vqt, - }) - .collect(), - )?; - - PointerWriter::try_write_array_pointer(&result, errors)?; - - Ok(()) - } -} diff --git a/opc_da/src/server/com/utils.rs b/opc_da/src/server/com/utils.rs deleted file mode 100644 index b56c093..0000000 --- a/opc_da/src/server/com/utils.rs +++ /dev/null @@ -1,328 +0,0 @@ -pub struct PointerReader; - -pub struct PointerWriter; - -pub trait TryWritePointer { - type Error; - - fn try_write(value: T, pointer: *mut T) -> Result<(), Self::Error>; -} - -pub trait TryWriteInto { - type Error; - - fn try_write_into(value: T, pointer: R) -> Result<(), Self::Error>; -} - -pub trait TryWriteTo { - type Error; - - fn try_write_to(value: T) -> Result; -} - -pub trait TryWriteArray { - type Error; - - fn try_write_array(values: &[T], pointer: *mut R) -> Result<(), Self::Error>; -} - -pub trait TryWriteArrayPointer { - type Error; - - fn try_write_array_pointer(values: &[T], pointer: *mut *mut R) -> Result<(), Self::Error>; -} - -pub trait TryRead { - type Error; - - fn try_read(pointer: *const T) -> Result; -} - -pub trait TryReadArray { - type Error; - - fn try_read_array(count: u32, pointer: *const T) -> Result, Self::Error>; -} - -impl TryRead for PointerReader { - type Error = windows::core::Error; - - fn try_read(pointer: *const T) -> Result { - if pointer.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Null pointer passed for 'pointer'", - )); - } - - Ok(unsafe { pointer.read() }) - } -} - -impl TryWritePointer for PointerWriter { - type Error = windows::core::Error; - - fn try_write(value: T, pointer: *mut T) -> Result<(), Self::Error> { - if pointer.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Null pointer passed for 'pointer'", - )); - } - - unsafe { - pointer.write(value); - } - - Ok(()) - } -} - -impl TryWriteInto> - for PointerWriter -{ - type Error = windows::core::Error; - - fn try_write_into(value: T, pointer: windows::core::OutRef<'_, T>) -> Result<(), Self::Error> { - if pointer.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Null pointer passed for 'pointer'", - )); - } - - pointer.write(Some(value))?; - - Ok(()) - } -} - -/// Allocates memory for a string and writes it to the provided pointer. -/// -/// # Safety -/// The caller is responsible for freeing the allocated memory using `CoTaskMemFree`. -impl> TryWriteInto for PointerWriter { - type Error = windows::core::Error; - - fn try_write_into(value: T, pointer: *mut windows::core::PWSTR) -> Result<(), Self::Error> { - let p = value - .as_ref() - .encode_utf16() - .chain(core::iter::once(0)) - .collect::>(); - - let ptr = unsafe { - windows::Win32::System::Com::CoTaskMemAlloc(p.len() * core::mem::size_of::()) - }; - - if ptr.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_OUTOFMEMORY, - "Failed to allocate memory for the string", - )); - } - - unsafe { - core::ptr::copy_nonoverlapping(p.as_ptr(), ptr as *mut u16, p.len()); - *pointer = windows::core::PWSTR(ptr as *mut u16); - } - - Ok(()) - } -} - -impl<'a, T: AsRef<[&'a str]>> TryWriteInto for PointerWriter { - type Error = windows::core::Error; - - fn try_write_into( - value: T, - pointer: *mut *mut windows::core::PWSTR, - ) -> Result<(), Self::Error> { - let mut strings = Vec::with_capacity(value.as_ref().len()); - for s in value.as_ref() { - let p = s - .encode_utf16() - .chain(core::iter::once(0)) - .collect::>(); - let ptr = unsafe { - windows::Win32::System::Com::CoTaskMemAlloc(p.len() * core::mem::size_of::()) - }; - - if ptr.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_OUTOFMEMORY, - "Failed to allocate memory for the string", - )); - } - - unsafe { - core::ptr::copy_nonoverlapping(p.as_ptr(), ptr as *mut u16, p.len()); - strings.push(windows::core::PWSTR(ptr as *mut u16)); - } - } - - let ptr = unsafe { - windows::Win32::System::Com::CoTaskMemAlloc( - strings.len() * core::mem::size_of::(), - ) - }; - - if ptr.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_OUTOFMEMORY, - "Failed to allocate memory for the array of strings", - )); - } - - unsafe { - core::ptr::copy_nonoverlapping( - strings.as_ptr(), - ptr as *mut windows::core::PWSTR, - strings.len(), - ); - *pointer = ptr as _; - } - - Ok(()) - } -} - -impl> TryWriteTo for W { - type Error = windows::core::Error; - - fn try_write_to(value: T) -> windows::core::Result<*mut T> { - let ptr: *mut T = core::ptr::null_mut(); - Self::try_write(value, ptr)?; - Ok(ptr) - } -} - -impl> TryWriteTo for PointerWriter { - type Error = windows::core::Error; - - fn try_write_to(value: T) -> windows::core::Result { - let ptr: *mut windows::core::PWSTR = core::ptr::null_mut(); - Self::try_write_into(value, ptr)?; - Ok(unsafe { *ptr }) - } -} - -impl TryReadArray for PointerReader { - type Error = windows::core::Error; - - fn try_read_array(count: u32, pointer: *const T) -> Result, Self::Error> { - if pointer.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Null pointer passed for 'pointer'", - )); - } - - let mut result = Vec::with_capacity(count as usize); - unsafe { - for i in 0..count { - result.push(pointer.add(i as usize).read()); - } - } - Ok(result) - } -} - -impl TryWriteArray for PointerWriter { - type Error = windows::core::Error; - - fn try_write_array(values: &[T], pointer: *mut T) -> Result<(), Self::Error> { - if pointer.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Null pointer passed for 'pointer'", - )); - } - - unsafe { - core::ptr::copy_nonoverlapping(values.as_ptr(), pointer, values.len()); - } - - Ok(()) - } -} - -impl TryWriteArrayPointer for PointerWriter { - type Error = windows::core::Error; - - fn try_write_array_pointer(values: &[T], pointer: *mut *mut T) -> Result<(), Self::Error> { - if pointer.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Null pointer passed for 'pointer'", - )); - } - - let size = core::mem::size_of_val(values); - let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; - - if ptr.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_OUTOFMEMORY, - "Failed to allocate memory for the array", - )); - } - - unsafe { - core::ptr::copy_nonoverlapping(values.as_ptr(), ptr as *mut T, values.len()); - *pointer = ptr as *mut T; - } - - Ok(()) - } -} - -impl TryReadArray for PointerReader { - type Error = windows::core::Error; - - fn try_read_array( - count: u32, - pointer: *const windows::core::PWSTR, - ) -> Result, Self::Error> { - let mut result = Vec::with_capacity(count as usize); - unsafe { - for i in 0..count { - let pwstr = pointer.add(i as usize).read(); - if pwstr.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Null pointer encountered while reading string", - )); - } - result.push(pwstr.to_string()?); - } - } - - Ok(result) - } -} - -impl TryReadArray for PointerReader { - type Error = windows::core::Error; - - fn try_read_array( - count: u32, - pointer: *const windows::core::PCWSTR, - ) -> Result, Self::Error> { - let mut result = Vec::with_capacity(count as usize); - unsafe { - for i in 0..count { - let pwstr = pointer.add(i as usize).read(); - if pwstr.is_null() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_POINTER, - "Null pointer encountered while reading string", - )); - } - result.push(pwstr.to_string()?); - } - } - - Ok(result) - } -} diff --git a/opc_da/src/server/com/variant.rs b/opc_da/src/server/com/variant.rs deleted file mode 100644 index 46b7d6c..0000000 --- a/opc_da/src/server/com/variant.rs +++ /dev/null @@ -1,94 +0,0 @@ -use windows::{Win32::System::Variant::VARIANT, core::BSTR}; - -use super::base::{AccessRight, Quality, Variant}; - -use opc_da_bindings; - -impl Variant { - // get type id - pub fn get_data_type(&self) -> u16 { - match self { - Variant::Empty => windows::Win32::System::Variant::VT_EMPTY, - Variant::Bool(_) => windows::Win32::System::Variant::VT_BOOL, - Variant::String(_) => windows::Win32::System::Variant::VT_BSTR, - Variant::I8(_) => windows::Win32::System::Variant::VT_I1, - Variant::I16(_) => windows::Win32::System::Variant::VT_I2, - Variant::I32(_) => windows::Win32::System::Variant::VT_I4, - Variant::I64(_) => windows::Win32::System::Variant::VT_I8, - Variant::F32(_) => windows::Win32::System::Variant::VT_R4, - Variant::F64(_) => windows::Win32::System::Variant::VT_R8, - Variant::U8(_) => windows::Win32::System::Variant::VT_UI1, - Variant::U16(_) => windows::Win32::System::Variant::VT_UI2, - Variant::U32(_) => windows::Win32::System::Variant::VT_UI4, - Variant::U64(_) => windows::Win32::System::Variant::VT_UI8, - } - .0 - } -} - -impl Quality { - pub fn to_u16(&self) -> u16 { - self.0 - } -} - -impl AccessRight { - pub fn to_u32(&self) -> u32 { - let mut value = 0; - if self.readable { - value |= opc_da_bindings::OPC_READABLE; - } - if self.writable { - value |= opc_da_bindings::OPC_WRITEABLE; - } - value - } -} - -impl From for VARIANT { - fn from(val: Variant) -> Self { - match val { - Variant::Empty => VARIANT::default(), - Variant::Bool(value) => VARIANT::from(value), - Variant::String(value) => VARIANT::from(BSTR::from(value)), - Variant::I8(value) => VARIANT::from(value), - Variant::I16(value) => VARIANT::from(value), - Variant::I32(value) => VARIANT::from(value), - Variant::I64(value) => VARIANT::from(value), - Variant::F32(value) => VARIANT::from(value), - Variant::F64(value) => VARIANT::from(value), - Variant::U8(value) => VARIANT::from(value), - Variant::U16(value) => VARIANT::from(value), - Variant::U32(value) => VARIANT::from(value), - Variant::U64(value) => VARIANT::from(value), - } - } -} - -impl From for Variant { - fn from(value: VARIANT) -> Self { - unsafe { - let value = &value.Anonymous.Anonymous; - match value.vt { - windows::Win32::System::Variant::VT_EMPTY => Variant::Empty, - windows::Win32::System::Variant::VT_BOOL => { - Variant::Bool(value.Anonymous.boolVal.as_bool()) - } - windows::Win32::System::Variant::VT_BSTR => { - Variant::String(value.Anonymous.bstrVal.to_string()) - } - windows::Win32::System::Variant::VT_I1 => Variant::I8(value.Anonymous.cVal), - windows::Win32::System::Variant::VT_I2 => Variant::I16(value.Anonymous.iVal), - windows::Win32::System::Variant::VT_I4 => Variant::I32(value.Anonymous.lVal), - windows::Win32::System::Variant::VT_I8 => Variant::I64(value.Anonymous.llVal), - windows::Win32::System::Variant::VT_R4 => Variant::F32(value.Anonymous.fltVal), - windows::Win32::System::Variant::VT_R8 => Variant::F64(value.Anonymous.dblVal), - windows::Win32::System::Variant::VT_UI1 => Variant::U8(value.Anonymous.bVal), - windows::Win32::System::Variant::VT_UI2 => Variant::U16(value.Anonymous.uiVal), - windows::Win32::System::Variant::VT_UI4 => Variant::U32(value.Anonymous.ulVal), - windows::Win32::System::Variant::VT_UI8 => Variant::U64(value.Anonymous.ullVal), - _ => Variant::Empty, - } - } - } -} diff --git a/opc_da/src/server/mod.rs b/opc_da/src/server/mod.rs deleted file mode 100644 index 8e7f1f4..0000000 --- a/opc_da/src/server/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod com; -pub mod traits; diff --git a/opc_da/src/server/traits/def.rs b/opc_da/src/server/traits/def.rs deleted file mode 100644 index 5960096..0000000 --- a/opc_da/src/server/traits/def.rs +++ /dev/null @@ -1,330 +0,0 @@ -use crate::{ - def::ServerStatus, - server::com::{ - base::Variant, - utils::{PointerWriter, TryWriteArray, TryWriteTo}, - }, - utils::{ToNative as _, TryToLocal as _, TryToNative as _}, -}; - -pub struct AvailableProperty { - pub property_id: u32, - pub description: String, - pub data_type: u16, -} - -pub struct ItemPropertyData { - pub property_id: u32, - pub data: Variant, - pub error: windows::core::HRESULT, -} - -pub struct NewItem { - pub new_item_id: String, - pub error: windows::core::HRESULT, -} - -pub struct ItemProperties { - pub error_id: windows::core::HRESULT, - pub item_properties: Vec, -} - -pub struct ItemProperty { - pub data_type: u16, - pub property_id: u32, - pub item_id: String, - pub description: String, - pub value: Variant, - pub error_id: windows::core::HRESULT, -} - -pub enum BrowseFilter { - All, - Branches, - Items, -} - -pub struct BrowseResult { - pub more_elements: bool, - pub continuation_point: Option, - pub elements: Vec, -} - -pub struct BrowseElement { - pub name: String, - pub item_id: String, - pub flag_value: u32, - pub item_properties: ItemProperties, -} - -pub enum NamespaceType { - Flat, - Hierarchical, -} - -pub enum BrowseDirection { - Up, - Down, - To(String), -} - -pub enum BrowseType { - Branch, - Leaf, - Flat, -} - -pub struct ItemWithMaxAge { - pub item_id: String, - pub max_age: u32, -} - -pub struct Vqt { - pub value: Variant, - pub quality: u16, - pub timestamp: std::time::SystemTime, -} - -pub struct ItemVqt { - pub value: Variant, - pub quality: Option, - pub timestamp: Option, -} - -pub struct VqtWithError { - pub value: Variant, - pub quality: u16, - pub timestamp: std::time::SystemTime, - pub error: windows::core::HRESULT, -} - -pub struct ItemOptionalVqt { - pub item_id: String, - pub optional_vqt: ItemVqt, -} - -pub struct GroupInfo { - pub server_group: u32, - pub revised_update_rate: u32, - pub unknown: windows::core::IUnknown, -} - -pub struct FormatEtc {} - -pub struct StorageMedium {} - -pub enum DataSource { - Cache, - Device, -} - -pub struct ItemDef {} - -pub struct ItemResult {} - -pub struct ItemState {} - -impl TryFrom for opc_da_bindings::tagOPCITEMPROPERTIES { - type Error = windows::core::Error; - - fn try_from(value: ItemProperties) -> Result { - let result = opc_da_bindings::tagOPCITEMPROPERTIES { - hrErrorID: value.error_id, - dwNumProperties: value.item_properties.len() as u32, - pItemProperties: core::ptr::null_mut(), - dwReserved: 0, - }; - - PointerWriter::try_write_array( - &value - .item_properties - .into_iter() - .map(|item_property| match item_property.try_into() { - Ok(item_property) => item_property, - Err(error) => opc_da_bindings::tagOPCITEMPROPERTY { - hrErrorID: (error as windows::core::Error).code(), - ..Default::default() - }, - }) - .collect::>(), - result.pItemProperties, - )?; - - Ok(result) - } -} - -impl TryFrom for opc_da_bindings::tagOPCITEMPROPERTY { - type Error = windows::core::Error; - - fn try_from(value: ItemProperty) -> Result { - Ok(opc_da_bindings::tagOPCITEMPROPERTY { - vtDataType: value.data_type, - wReserved: 0, - dwPropertyID: value.property_id, - szItemID: PointerWriter::try_write_to(&value.item_id)?, - szDescription: PointerWriter::try_write_to(&value.description)?, - vValue: value.value.into(), - hrErrorID: value.error_id, - dwReserved: 0, - }) - } -} - -impl From for opc_da_bindings::tagOPCBROWSEFILTER { - fn from(value: BrowseFilter) -> Self { - match value { - BrowseFilter::All => opc_da_bindings::OPC_BROWSE_FILTER_ALL, - BrowseFilter::Branches => opc_da_bindings::OPC_BROWSE_FILTER_BRANCHES, - BrowseFilter::Items => opc_da_bindings::OPC_BROWSE_FILTER_ITEMS, - } - } -} - -impl TryFrom for BrowseFilter { - type Error = windows::core::Error; - - fn try_from(value: opc_da_bindings::tagOPCBROWSEFILTER) -> Result { - match value { - opc_da_bindings::OPC_BROWSE_FILTER_ALL => Ok(BrowseFilter::All), - opc_da_bindings::OPC_BROWSE_FILTER_BRANCHES => Ok(BrowseFilter::Branches), - opc_da_bindings::OPC_BROWSE_FILTER_ITEMS => Ok(BrowseFilter::Items), - _ => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Invalid BrowseFilter", - )), - } - } -} - -impl TryFrom for opc_da_bindings::tagOPCBROWSEELEMENT { - type Error = windows::core::Error; - - fn try_from(value: BrowseElement) -> Result { - Ok(opc_da_bindings::tagOPCBROWSEELEMENT { - szName: PointerWriter::try_write_to(&value.name)?, - szItemID: PointerWriter::try_write_to(&value.item_id)?, - dwFlagValue: value.flag_value, - dwReserved: 0, - ItemProperties: value.item_properties.try_into()?, - }) - } -} - -impl From for opc_da_bindings::tagOPCNAMESPACETYPE { - fn from(value: NamespaceType) -> Self { - match value { - NamespaceType::Flat => opc_da_bindings::OPC_NS_FLAT, - NamespaceType::Hierarchical => opc_da_bindings::OPC_NS_HIERARCHIAL, - } - } -} - -impl TryFrom for NamespaceType { - type Error = windows::core::Error; - - fn try_from(value: opc_da_bindings::tagOPCNAMESPACETYPE) -> Result { - match value { - opc_da_bindings::OPC_NS_FLAT => Ok(NamespaceType::Flat), - opc_da_bindings::OPC_NS_HIERARCHIAL => Ok(NamespaceType::Hierarchical), - _ => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Invalid NamespaceType", - )), - } - } -} - -impl TryFrom<(opc_da_bindings::tagOPCBROWSEDIRECTION, String)> for BrowseDirection { - type Error = windows::core::Error; - - fn try_from( - value: (opc_da_bindings::tagOPCBROWSEDIRECTION, String), - ) -> Result { - match value { - (opc_da_bindings::OPC_BROWSE_UP, _) => Ok(BrowseDirection::Up), - (opc_da_bindings::OPC_BROWSE_DOWN, _) => Ok(BrowseDirection::Down), - (opc_da_bindings::OPC_BROWSE_TO, name) => Ok(BrowseDirection::To(name)), - _ => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Invalid BrowseDirection", - )), - } - } -} - -impl From for (opc_da_bindings::tagOPCBROWSEDIRECTION, String) { - fn from(value: BrowseDirection) -> Self { - match value { - BrowseDirection::Up => (opc_da_bindings::OPC_BROWSE_UP, String::new()), - BrowseDirection::Down => (opc_da_bindings::OPC_BROWSE_DOWN, String::new()), - BrowseDirection::To(name) => (opc_da_bindings::OPC_BROWSE_TO, name), - } - } -} - -impl TryFrom for BrowseType { - type Error = windows::core::Error; - - fn try_from(value: opc_da_bindings::tagOPCBROWSETYPE) -> Result { - match value { - opc_da_bindings::OPC_BRANCH => Ok(BrowseType::Branch), - opc_da_bindings::OPC_LEAF => Ok(BrowseType::Leaf), - opc_da_bindings::OPC_FLAT => Ok(BrowseType::Flat), - _ => Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Invalid BrowseFilter", - )), - } - } -} - -impl From for opc_da_bindings::tagOPCBROWSETYPE { - fn from(value: BrowseType) -> Self { - match value { - BrowseType::Branch => opc_da_bindings::OPC_BRANCH, - BrowseType::Leaf => opc_da_bindings::OPC_LEAF, - BrowseType::Flat => opc_da_bindings::OPC_FLAT, - } - } -} - -impl TryFrom for ItemVqt { - type Error = windows::core::Error; - - fn try_from(value: opc_da_bindings::tagOPCITEMVQT) -> Result { - Ok(ItemVqt { - value: value.vDataValue.into(), - quality: if value.bQualitySpecified.as_bool() { - Some(value.wQuality) - } else { - None - }, - timestamp: if value.bTimeStampSpecified.as_bool() { - Some(value.ftTimeStamp.try_to_local()?) - } else { - None - }, - }) - } -} - -impl TryFrom for opc_da_bindings::tagOPCSERVERSTATUS { - type Error = windows::core::Error; - - fn try_from(value: ServerStatus) -> Result { - Ok(Self { - ftStartTime: value.start_time.try_to_native()?, - ftCurrentTime: value.current_time.try_to_native()?, - ftLastUpdateTime: value.last_update_time.try_to_native()?, - dwServerState: value.server_state.to_native(), - dwGroupCount: value.group_count, - dwBandWidth: value.band_width, - wMajorVersion: value.major_version, - wMinorVersion: value.minor_version, - wBuildNumber: value.build_number, - szVendorInfo: PointerWriter::try_write_to(&value.vendor_info)?, - wReserved: 0, - }) - } -} diff --git a/opc_da/src/server/traits/group.rs b/opc_da/src/server/traits/group.rs deleted file mode 100644 index 57a9c55..0000000 --- a/opc_da/src/server/traits/group.rs +++ /dev/null @@ -1,318 +0,0 @@ -pub trait GroupTrait { - fn add_items( - &self, - items: &[opc_da_bindings::tagOPCITEMDEF], - results: &mut [opc_da_bindings::tagOPCITEMRESULT], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn validate_items( - &self, - items: &[opc_da_bindings::tagOPCITEMDEF], - blob_update: windows_core::BOOL, - validation_results: &mut [opc_da_bindings::tagOPCITEMRESULT], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn remove_items( - &self, - item_server_handles: &[u32], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn set_active_state( - &self, - item_server_handles: &[u32], - active: windows_core::BOOL, - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn set_client_handles( - &self, - item_server_handles: &[u32], - handle_client: &[u32], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn set_data_types( - &self, - item_server_handles: &[u32], - requested_data_types: &[u16], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn create_enumerator( - &self, - reference_interface_id: &windows::core::GUID, - ) -> windows::core::Result; - - #[allow(clippy::too_many_arguments)] - fn get_state( - &self, - update_rate: &mut u32, - active: &mut windows_core::BOOL, - name: &mut windows::core::PWSTR, - time_bias: &mut i32, - percent_deadband: &mut f32, - locale_id: &mut u32, - group_client_handle: &mut u32, - item_server_handles_group: &mut u32, - ) -> windows::core::Result<()>; - - #[allow(clippy::too_many_arguments)] - fn set_state( - &self, - requested_update_rate: &u32, - revised_update_rate: &mut u32, - active: &windows_core::BOOL, - time_bias: &i32, - percent_deadband: &f32, - locale_id: &u32, - group_client_handle: &u32, - ) -> windows::core::Result<()>; - - fn set_name(&self, name: &windows::core::PCWSTR) -> windows::core::Result<()>; - - fn clone_group( - &self, - name: &windows::core::PCWSTR, - reference_interface_id: &windows::core::GUID, - ) -> windows::core::Result; - - fn set_keep_alive(&self, keep_alive_time: u32) -> windows::core::Result; - - fn get_keep_alive(&self) -> windows::core::Result; - - fn get_public_group_state(&self) -> windows::core::Result; - - fn move_to_public(&self) -> windows::core::Result<()>; - - fn read( - &self, - source: opc_da_bindings::tagOPCDATASOURCE, - item_server_handles: &[u32], - item_values: &mut [opc_da_bindings::tagOPCITEMSTATE], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn write( - &self, - item_server_handles: &[u32], - item_values: &[windows::Win32::System::Variant::VARIANT], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn read_max_age( - &self, - item_server_handles: &[u32], - max_age: &[u32], - values: &mut [windows::Win32::System::Variant::VARIANT], - qualities: &mut [u16], - timestamps: &mut [windows::Win32::Foundation::FILETIME], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn write_vqt( - &self, - count: u32, - item_server_handles: &[u32], - item_vqt: &[opc_da_bindings::tagOPCITEMVQT], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn read2( - &self, - item_server_handles: &[u32], - transaction_id: u32, - cancel_id: &mut u32, - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn write2( - &self, - count: u32, - item_server_handles: &[u32], - item_values: &[windows::Win32::System::Variant::VARIANT], - transaction_id: u32, - cancel_id: &mut u32, - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn refresh2( - &self, - source: opc_da_bindings::tagOPCDATASOURCE, - transaction_id: u32, - ) -> windows::core::Result; - - fn cancel2(&self, cancel_id: u32) -> windows::core::Result<()>; - - fn set_enable(&self, enable: windows_core::BOOL) -> windows::core::Result<()>; - - fn get_enable(&self) -> windows::core::Result; - - fn read_max_age2( - &self, - item_server_handles: &[u32], - max_age: &[u32], - transaction_id: u32, - cancel_id: &mut u32, - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn write_vqt2( - &self, - item_server_handles: &[u32], - item_vqt: &[opc_da_bindings::tagOPCITEMVQT], - transaction_id: u32, - cancel_id: &mut u32, - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn refresh_max_age(&self, max_age: u32, transaction_id: u32) -> windows::core::Result; - - fn set_item_deadband( - &self, - item_server_handles: &[u32], - percent_deadband: &[f32], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn get_item_deadband( - &self, - item_server_handles: &[u32], - percent_deadband: &mut [f32], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn clear_item_deadband( - &self, - item_server_handles: &[u32], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn set_item_sampling_rate( - &self, - count: u32, - item_server_handles: &[u32], - requested_sampling_rate: &[u32], - revised_sampling_rate: &mut [u32], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn get_item_sampling_rate( - &self, - item_server_handles: &[u32], - sampling_rate: &mut [u32], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn clear_item_sampling_rate( - &self, - item_server_handles: &[u32], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn set_item_buffer_enable( - &self, - item_server_handles: &[u32], - penable: &windows_core::BOOL, - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn get_item_buffer_enable( - &self, - item_server_handles: &[u32], - enable: &mut [windows_core::BOOL], - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn enum_connection_points( - &self, - ) -> windows::core::Result; - - fn find_connection_point( - &self, - reference_interface_id: &windows::core::GUID, - ) -> windows::core::Result; - - fn read3( - &self, - connection: u32, - source: opc_da_bindings::tagOPCDATASOURCE, - item_server_handles: &[u32], - transaction_id: &mut u32, - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn write3( - &self, - connection: u32, - item_server_handles: &[u32], - item_values: &[windows::Win32::System::Variant::VARIANT], - transaction_id: &mut u32, - errors: &mut [windows::core::HRESULT], - ) -> windows::core::Result<()>; - - fn refresh( - &self, - connection: u32, - source: opc_da_bindings::tagOPCDATASOURCE, - ) -> windows::core::Result; - - fn cancel(&self, transaction_id: u32) -> windows::core::Result<()>; - - fn get_data( - &self, - format_etc_in: &windows::Win32::System::Com::FORMATETC, - ) -> windows::core::Result; - - fn get_data_here( - &self, - format_etc_in: &windows::Win32::System::Com::FORMATETC, - storage_medium: &mut windows::Win32::System::Com::STGMEDIUM, - ) -> windows::core::Result<()>; - - fn query_get_data( - &self, - format_etc_in: &windows::Win32::System::Com::FORMATETC, - ) -> windows::core::HRESULT; - - fn get_canonical_format_etc( - &self, - format_etc_in: &windows::Win32::System::Com::FORMATETC, - format_etc_out: &mut windows::Win32::System::Com::FORMATETC, - ) -> windows::core::HRESULT; - - fn set_data( - &self, - format_etc_in: &windows::Win32::System::Com::FORMATETC, - medium: &windows::Win32::System::Com::STGMEDIUM, - release: windows_core::BOOL, - ) -> windows::core::Result<()>; - - fn enum_format_etc( - &self, - direction: u32, - ) -> windows::core::Result; - - /// Establishes an advisory connection. - /// - /// # Arguments - /// * `sink` - The sink interface. If None, any existing connection will be removed. - /// - /// # Returns - /// * The connection token if sink is Some and connection is established - /// * Ok(0) if sink is None (indicating no connection) - /// * An error if the operation fails - fn data_advise( - &self, - format_etc_in: &windows::Win32::System::Com::FORMATETC, - adv: u32, - sink: windows::core::Ref<'_, windows::Win32::System::Com::IAdviseSink>, - ) -> windows::core::Result; - - fn data_unadvise(&self, connection: u32) -> windows::core::Result<()>; - - fn enum_data_advise(&self) - -> windows::core::Result; -} diff --git a/opc_da/src/server/traits/item.rs b/opc_da/src/server/traits/item.rs deleted file mode 100644 index 0ec23fb..0000000 --- a/opc_da/src/server/traits/item.rs +++ /dev/null @@ -1 +0,0 @@ -pub trait ItemTrait {} diff --git a/opc_da/src/server/traits/mod.rs b/opc_da/src/server/traits/mod.rs deleted file mode 100644 index f53bc47..0000000 --- a/opc_da/src/server/traits/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod def; -mod group; -mod item; -mod server; - -pub use def::*; -pub use group::*; -pub use item::*; -pub use server::*; diff --git a/opc_da/src/server/traits/server.rs b/opc_da/src/server/traits/server.rs deleted file mode 100644 index 5e4604e..0000000 --- a/opc_da/src/server/traits/server.rs +++ /dev/null @@ -1,252 +0,0 @@ -use crate::def::{EnumScope, ServerStatus}; - -use super::def::*; - -/// Server trait -/// -/// This trait defines the methods that a opc da server must implement to be used by the opc da client. -pub trait ServerTrait { - /// Sets the locale ID to be used for string conversions. - /// - /// Implements the `IOPCCommon::SetLocaleID` method. - /// - /// # Arguments - /// * `locale_id` - The locale ID to set - /// - /// # Returns - /// An error if the locale ID is not supported - fn set_locale_id(&self, locale_id: u32) -> windows::core::Result<()>; - - /// Returns the current locale ID. - /// - /// Implements the `IOPCCommon::GetLocaleID` method. - /// - /// # Returns - /// The current locale ID - fn get_locale_id(&self) -> windows::core::Result; - - /// Returns the list of available locale IDs. - /// - /// Implements the `IOPCCommon::QueryAvailableLocaleIDs` method. - /// - /// # Returns - /// The list of available locale IDs - fn query_available_locale_ids(&self) -> windows::core::Result>; - - /// Returns the error string for the given error code. - /// - /// Implements the `IOPCCommon::GetErrorString` method. - /// - /// # Arguments - /// * `error` - The error code - /// - /// # Returns - /// The error string for the given error code - fn get_error_string(&self, error: i32) -> windows::core::Result; - - /// Sets the name of the client application. - /// - /// Implements the `IOPCCommon::SetClientName` method. - /// - /// # Arguments - /// * `name` - The client application name - /// - /// # Returns - /// An error if the operation fails - fn set_client_name(&self, name: String) -> windows::core::Result<()>; - - /// Returns the name of the client application. - /// - /// Implements the `IConnectionPointContainer::EnumConnectionPoints` method. - /// - /// # Returns - /// - /// A result containing a vector of connection points - fn enum_connection_points( - &self, - ) -> windows::core::Result>; - - /// Returns the connection point for the given reference interface ID. - /// - /// Implements the `IConnectionPointContainer::FindConnectionPoint` method. - /// - /// # Arguments - /// * `reference_interface_id` - The reference interface ID - /// - /// # Returns - /// The connection point - fn find_connection_point( - &self, - reference_interface_id: *const windows::core::GUID, - ) -> windows::core::Result; - - /// Returns the list of available properties for the given item ID. - /// - /// Implements the `IOPCBrowse::QueryAvailableProperties` method. - /// - /// # Arguments - /// * `item_id` - The item ID - /// - /// # Returns - /// The list of available properties - fn query_available_properties( - &self, - item_id: String, - ) -> windows::core::Result>; - - /// Returns the properties for the given item ID. - /// - /// Implements the `IOPCItemProperties::GetItemProperties` method. - /// - /// # Arguments - /// * `item_id` - The item ID - /// * `property_ids` - The list of property IDs - /// - /// # Returns - /// The properties for the given item ID - fn get_item_properties( - &self, - item_id: String, - property_ids: Vec, - ) -> windows::core::Result>; - - /// Lookup the item IDs for the given item ID and property IDs. - /// - /// Implements the `IOPCBrowse::LookupItemIDs` method. - /// - /// # Arguments - /// * `item_id` - The item ID - /// * `property_ids` - The list of property IDs - /// - /// # Returns - /// The item IDs for the given item ID and property IDs - fn lookup_item_ids( - &self, - item_id: String, - property_ids: Vec, - ) -> windows::core::Result>; - - /// Returns the properties for the given item IDs. - /// - /// Implements the `IOPCItemProperties::GetProperties` method. - /// - /// # Arguments - /// * `item_ids` - The list of item IDs - /// * `return_property_values` - Whether to return property values - /// * `property_ids` - The list of property IDs - /// - /// # Returns - /// The properties for the given item IDs - fn get_properties( - &self, - item_ids: Vec, - return_property_values: bool, - property_ids: Vec, - ) -> windows::core::Result>; - - /// Browse the server for items. - /// - /// Implements the `IOPCBrowse::Browse` method. - /// - /// # Arguments - /// * `item_id` - The item ID - /// * `continuation_point` - The continuation point - /// * `max_elements_returned` - The maximum number of elements to return - /// * `browse_filter` - The browse filter - /// * `element_name_filter` - The element name filter - /// * `vendor_filter` - The vendor filter - /// * `return_all_properties` - Whether to return all properties - /// * `return_property_values` - Whether to return property values - /// * `property_ids` - The list of property IDs - /// - /// # Returns - /// The browse result - #[allow(clippy::too_many_arguments)] - fn browse( - &self, - item_id: String, - continuation_point: Option, - max_elements_returned: u32, - browse_filter: BrowseFilter, - element_name_filter: String, - vendor_filter: String, - return_all_properties: bool, - return_property_values: bool, - property_ids: Vec, - ) -> windows::core::Result; - - /// Get the public group by name. - /// - /// Implements the `IOPCServerPublicGroups::GetPublicGroupByName` method. - /// - /// # Arguments - /// * `name` - The name of the public group - /// * `reference_interface_id` - The reference interface ID - /// - /// # Returns - /// The public group - fn get_public_group_by_name( - &self, - name: String, - reference_interface_id: u128, - ) -> windows::core::Result; - - fn remove_public_group(&self, server_group: u32, force: bool) -> windows::core::Result<()>; - - fn query_organization(&self) -> windows::core::Result; - - fn change_browse_position( - &self, - browse_direction: BrowseDirection, - ) -> windows::core::Result<()>; - - fn browse_opc_item_ids( - &self, - browse_filter_type: BrowseType, - filter_criteria: String, - variant_data_type_filter: u16, - access_rights_filter: u32, - ) -> windows::core::Result; - - fn get_item_id(&self, item_data_id: String) -> windows::core::Result; - - fn browse_access_paths(&self, item_id: String) -> windows::core::Result>; - - fn read(&self, items: Vec) -> windows::core::Result>; - - fn write_vqt( - &self, - items: Vec, - ) -> windows::core::Result>; - - #[allow(clippy::too_many_arguments)] - fn add_group( - &self, - name: String, - active: bool, - requested_update_rate: u32, - client_group: u32, - time_bias: Option, - percent_deadband: Option, - locale_id: u32, - reference_interface_id: Option, - ) -> windows::core::Result; - - fn get_error_string_locale(&self, error: i32, locale: u32) -> windows::core::Result; - - fn get_group_by_name( - &self, - name: String, - reference_interface_id: Option, - ) -> windows::core::Result; - - fn get_status(&self) -> windows::core::Result; - - fn remove_group(&self, server_group: u32, force: bool) -> windows::core::Result<()>; - - fn create_group_enumerator( - &self, - scope: EnumScope, - reference_interface_id: Option, - ) -> windows::core::Result; -} diff --git a/opc_da/src/utils/memory.rs b/opc_da/src/utils/memory.rs deleted file mode 100644 index c29f4bf..0000000 --- a/opc_da/src/utils/memory.rs +++ /dev/null @@ -1,485 +0,0 @@ -//! Memory management utilities for the OPC DA client. -//! -//! This module provides safe wrappers around COM memory allocations and arrays. -//! -//! It includes three main types: -//! - `RemoteArray` for managing arrays allocated by COM. -//! - `RemotePointer` for managing single values allocated by COM. -//! - `LocalPointer` for managing local memory that needs to be passed to COM functions. - -use windows::{ - Win32::System::Com::{CoTaskMemAlloc, CoTaskMemFree}, - core::PWSTR, -}; - -/// A safe wrapper around arrays allocated by COM. -/// -/// This struct ensures proper cleanup of COM-allocated memory when dropped. -/// It provides safe access to the underlying array through slices. -#[derive(Debug, Clone, PartialEq)] -pub struct RemoteArray { - pointer: RemotePointer, - len: u32, -} - -impl RemoteArray { - /// Creates a new `RemoteArray` with the specified length. - /// The underlying pointer is initialized to null. - #[inline(always)] - pub fn new(len: u32) -> Self { - Self { - pointer: RemotePointer::null(), - len, - } - } - - /// Creates a `RemoteArray` from a raw pointer and length. - /// - /// # Safety - /// The caller must ensure that the pointer is valid and points to a COM-allocated array. - #[inline(always)] - pub(crate) fn from_mut_ptr(pointer: *mut T, len: u32) -> Self { - Self { - pointer: RemotePointer::from_raw(pointer), - len, - } - } - - /// Creates a `RemoteArray` from a constant pointer and length. - /// - /// # Safety - /// The caller must ensure that the pointer is valid and points to a COM-allocated array. - #[inline(always)] - pub(crate) fn from_ptr(pointer: *const T, len: u32) -> Self { - Self { - pointer: RemotePointer::from_raw(pointer as *mut T), - len, - } - } - - /// Creates an empty `RemoteArray`. - #[inline(always)] - pub fn empty() -> Self { - Self { - pointer: RemotePointer::null(), - len: 0, - } - } - - /// Returns a mutable pointer to the array pointer. - /// - /// This is useful when calling COM functions that output an array via a pointer to a pointer. - #[inline(always)] - pub fn as_mut_ptr(&mut self) -> *mut *mut T { - self.pointer.as_mut_ptr() - } - - /// Returns a slice to the underlying array. - /// - /// # Safety - /// The caller must ensure that the `pointer` is valid for reads and points to an array of `len` elements. - #[inline(always)] - pub fn as_slice(&self) -> &[T] { - if self.pointer.inner.is_null() || self.len == 0 { - return &[]; - } - - let len = usize::try_from(self.len).unwrap_or(0); - - // Pointer and length are guaranteed to be valid - unsafe { core::slice::from_raw_parts(self.pointer.inner, len) } - } - - /// Returns a mutable slice to the underlying array. - /// - /// # Safety - /// The caller must ensure that the `pointer` is valid for reads and writes and points to an array of `len` elements. - #[inline(always)] - pub fn as_mut_slice(&mut self) -> &mut [T] { - if self.pointer.inner.is_null() || self.len == 0 { - return &mut []; - } - - let len = usize::try_from(self.len).unwrap_or(0); - - // Pointer and length are guaranteed to be valid - unsafe { core::slice::from_raw_parts_mut(self.pointer.inner, len) } - } - - /// Returns the length of the array. - #[inline(always)] - pub fn len(&self) -> u32 { - if self.pointer.inner.is_null() { - return 0; - } - - self.len - } - - /// Checks if the array is empty. - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.len == 0 || self.pointer.inner.is_null() - } - - /// Returns a mutable pointer to the length. - /// - /// This is useful when calling COM functions that output the length via a pointer. - #[inline(always)] - pub fn as_mut_len_ptr(&mut self) -> *mut u32 { - &mut self.len - } - - /// Sets the length of the array. - /// - /// # Safety - /// The caller must ensure that the new length is valid for the underlying array. - #[inline(always)] - pub(crate) unsafe fn set_len(&mut self, len: u32) { - self.len = len; - } - - pub fn into_vec(self) -> Vec> { - self.as_slice() - .iter() - .map(|v| RemotePointer::from_raw(v as *const T as *mut T)) - .collect() - } -} - -impl Default for RemoteArray { - /// Creates an empty `RemoteArray` by default. - #[inline(always)] - fn default() -> Self { - Self::empty() - } -} - -/// A safe wrapper around a pointer allocated by COM. -/// -/// This struct ensures proper cleanup of COM-allocated memory when dropped. -/// It provides methods to access the underlying pointer. -#[repr(transparent)] -#[derive(Debug, Clone, PartialEq)] -pub struct RemotePointer { - inner: *mut T, -} - -impl RemotePointer { - /// Creates a new `RemotePointer` initialized to null. - #[inline(always)] - pub fn null() -> Self { - Self { - inner: core::ptr::null_mut(), - } - } - - /// Returns a mutable pointer to the inner pointer. - /// - /// Useful for COM functions that output data via a pointer to a pointer. - #[inline(always)] - pub(crate) fn from_raw(pointer: *mut T) -> Self { - Self { inner: pointer } - } - - // pub(crate) fn copy(value: &T) -> Self { - // let pointer = unsafe { CoTaskMemAlloc(std::mem::size_of::()) }; - // unsafe { - // core::ptr::copy_nonoverlapping(value, pointer as _, 1); - // } - // Self { - // inner: pointer as _, - // } - // } - - pub(crate) fn copy_slice(value: &[T]) -> Self { - let pointer = unsafe { CoTaskMemAlloc(core::mem::size_of_val(value)) }; - unsafe { - core::ptr::copy_nonoverlapping(value.as_ptr(), pointer as _, value.len()); - } - Self { - inner: pointer as _, - } - } - - #[inline(always)] - pub fn as_mut_ptr(&mut self) -> *mut *mut T { - &mut self.inner - } - - /// Returns an `Option` referencing the inner value if it is not null. - /// - /// # Safety - /// The caller must ensure that the inner pointer is valid for reads. - #[inline(always)] - pub fn as_ref(&self) -> Option<&T> { - // Pointer is guaranteed to be valid - unsafe { self.inner.as_ref() } - } - - #[inline(always)] - pub fn ok(&self) -> windows::core::Result<&T> { - // Pointer is guaranteed to be valid - unsafe { self.inner.as_ref() }.ok_or_else(|| { - windows::core::Error::new(windows::Win32::Foundation::E_POINTER, "Pointer is null") - }) - } - - #[inline(always)] - pub fn from_option>>(value: Option) -> Self { - match value { - Some(value) => value.into(), - None => Self::null(), - } - } -} - -impl Default for RemotePointer { - /// Creates a new `RemotePointer` initialized to null by default. - #[inline(always)] - fn default() -> Self { - Self::null() - } -} - -impl From for RemotePointer { - /// Converts a `PWSTR` to a `RemotePointer`. - #[inline(always)] - fn from(value: PWSTR) -> Self { - Self { - inner: value.as_ptr(), - } - } -} - -impl From<&str> for RemotePointer { - /// Converts a string slice to a `RemotePointer`. - #[inline(always)] - fn from(value: &str) -> Self { - Self::copy_slice(&value.encode_utf16().chain(Some(0)).collect::>()) - } -} - -impl TryFrom> for String { - type Error = windows::core::Error; - - /// Attempts to convert a `RemotePointer` to a `String`. - /// - /// # Errors - /// Returns an error if the pointer is null or if the string conversion fails. - #[inline(always)] - fn try_from(value: RemotePointer) -> Result { - if value.inner.is_null() { - return Err(windows::Win32::Foundation::E_POINTER.into()); - } - - // Has checked for null pointer - Ok(unsafe { PWSTR(value.inner).to_string() }?) - } -} - -impl TryFrom> for Option { - type Error = windows::core::Error; - - /// Attempts to convert a `RemotePointer` to an `Option`. - /// - /// # Errors - /// Returns an error if the string conversion fails. - #[inline(always)] - fn try_from(value: RemotePointer) -> Result { - if value.inner.is_null() { - return Ok(None); - } - - // Has checked for null pointer - Ok(Some(unsafe { PWSTR(value.inner).to_string() }?)) - } -} - -impl RemotePointer { - /// Returns a mutable pointer to a `PWSTR`. - #[inline(always)] - pub fn as_mut_pwstr_ptr(&mut self) -> *mut PWSTR { - &mut self.inner as *mut *mut u16 as *mut PWSTR - } -} - -impl Drop for RemotePointer { - /// Drops the `RemotePointer`, freeing the COM-allocated memory. - #[inline(always)] - fn drop(&mut self) { - if !self.inner.is_null() { - unsafe { - CoTaskMemFree(Some(self.inner as _)); - } - } - } -} - -/// A safe wrapper around locally allocated memory needing to be passed to COM functions. -/// -/// This struct is useful for preparing data to be read by COM functions. -pub struct LocalPointer { - inner: Option>, -} - -impl LocalPointer { - /// Creates a new `LocalPointer` from an optional value. - #[inline(always)] - pub fn new(value: Option) -> Self { - Self { - inner: value.map(|v| Box::new(v)), - } - } - - /// Creates a `LocalPointer` from a boxed value. - #[inline(always)] - pub fn from_box(value: Box) -> Self { - Self { inner: Some(value) } - } - - #[inline(always)] - pub fn from_option>>(value: Option) -> Self { - match value { - Some(value) => value.into(), - None => Self::new(None), - } - } - - /// Returns a constant pointer to the inner value. - #[inline(always)] - pub fn as_ptr(&self) -> *const T { - match &self.inner { - Some(value) => value.as_ref() as *const T, - None => std::ptr::null_mut(), - } - } - - /// Returns a mutable pointer to the inner value. - #[inline(always)] - pub fn as_mut_ptr(&mut self) -> *mut T { - match &mut self.inner { - Some(value) => value.as_mut() as *mut T, - None => std::ptr::null_mut(), - } - } - - /// Consumes the `LocalPointer`, returning the inner value if it exists. - #[inline(always)] - pub fn into_inner(self) -> Option { - self.inner.map(|v| *v) - } - - /// Returns a reference to the inner value if it exists. - #[inline(always)] - pub fn inner(&self) -> Option<&T> { - self.inner.as_ref().map(|v| v.as_ref()) - } -} - -// Implementations for string handling - -impl> From for LocalPointer> { - /// Converts a string slice to a `LocalPointer` containing a UTF-16 encoded null-terminated string. - #[inline(always)] - fn from(s: S) -> Self { - Self::new(Some(s.as_ref().encode_utf16().chain(Some(0)).collect())) - } -} - -impl From<&[String]> for LocalPointer>> { - /// Converts a slice of `String`s to a `LocalPointer` containing vectors of UTF-16 encoded null-terminated strings. - #[inline(always)] - fn from(values: &[String]) -> Self { - Self::new(Some( - values - .iter() - .map(|s| s.encode_utf16().chain(Some(0)).collect()) - .collect(), - )) - } -} - -impl LocalPointer> { - /// Returns the length of the inner vector. - #[inline(always)] - pub fn len(&self) -> usize { - match &self.inner { - Some(values) => values.len(), - None => 0, - } - } - - /// Checks if the inner vector is empty. - #[inline(always)] - pub fn is_empty(&self) -> bool { - match &self.inner { - Some(values) => values.is_empty(), - None => true, - } - } - - /// Returns a constant pointer to the inner array. - #[inline(always)] - pub fn as_array_ptr(&self) -> *const T { - match &self.inner { - Some(values) => values.as_ptr(), - None => std::ptr::null(), - } - } - - /// Returns a mutable pointer to the inner array. - #[inline(always)] - pub fn as_mut_array_ptr(&mut self) -> *mut T { - match &mut self.inner { - Some(values) => values.as_mut_ptr(), - None => std::ptr::null_mut(), - } - } -} - -impl LocalPointer>> { - /// Converts the inner vector of UTF-16 strings to a vector of `PWSTR`. - #[inline(always)] - pub fn as_pwstr_array(&self) -> Vec { - match &self.inner { - Some(values) => values - .iter() - .map(|value| windows::core::PWSTR(value.as_ptr() as _)) - .collect(), - None => vec![windows::core::PWSTR::null()], - } - } - - /// Converts the inner vector of UTF-16 strings to a vector of `PCWSTR`. - #[inline(always)] - pub fn as_pcwstr_array(&self) -> Vec { - match &self.inner { - Some(values) => values - .iter() - .map(|value| windows::core::PCWSTR::from_raw(value.as_ptr() as _)) - .collect(), - None => vec![windows::core::PCWSTR::null()], - } - } -} - -impl LocalPointer> { - /// Converts the inner UTF-16 string to a `PWSTR`. - #[inline(always)] - pub fn as_pwstr(&self) -> windows::core::PWSTR { - match &self.inner { - Some(value) => windows::core::PWSTR(value.as_ptr() as _), - None => windows::core::PWSTR::null(), - } - } - - /// Converts the inner UTF-16 string to a `PCWSTR`. - #[inline(always)] - pub fn as_pcwstr(&self) -> windows::core::PCWSTR { - match &self.inner { - Some(value) => windows::core::PCWSTR::from_raw(value.as_ptr() as _), - None => windows::core::PCWSTR::null(), - } - } -} diff --git a/opc_da/src/utils/mod.rs b/opc_da/src/utils/mod.rs deleted file mode 100644 index ce795b4..0000000 --- a/opc_da/src/utils/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod memory; -mod native; -mod try_iterator; - -pub use memory::*; -pub(crate) use native::*; -pub use try_iterator::*; diff --git a/opc_da/src/utils/native.rs b/opc_da/src/utils/native.rs deleted file mode 100644 index 47c5d17..0000000 --- a/opc_da/src/utils/native.rs +++ /dev/null @@ -1,164 +0,0 @@ -use super::{RemoteArray, RemotePointer}; - -pub(crate) trait IntoBridge { - fn into_bridge(self) -> Bridge; -} - -pub(crate) trait ToNative { - fn to_native(&self) -> Native; -} - -pub(crate) trait FromNative { - fn from_native(native: &Native) -> Self - where - Self: Sized; -} - -pub(crate) trait TryToNative { - fn try_to_native(&self) -> windows::core::Result; -} - -pub(crate) trait TryFromNative { - fn try_from_native(native: &Native) -> windows::core::Result - where - Self: Sized; -} - -pub(crate) trait TryToLocal { - fn try_to_local(&self) -> windows::core::Result; -} - -impl> TryToLocal for Native { - fn try_to_local(&self) -> windows::core::Result { - T::try_from_native(self) - } -} - -impl> TryFromNative for T { - fn try_from_native(native: &Native) -> windows::core::Result { - Ok(Self::from_native(native)) - } -} - -impl> TryToNative for T { - fn try_to_native(&self) -> windows::core::Result { - Ok(self.to_native()) - } -} - -impl> IntoBridge> for Vec { - fn into_bridge(self) -> Vec { - self.into_iter().map(IntoBridge::into_bridge).collect() - } -} - -impl + Clone> IntoBridge> for &[B] { - fn into_bridge(self) -> Vec { - self.iter().cloned().map(IntoBridge::into_bridge).collect() - } -} - -impl> TryToNative> for Vec { - fn try_to_native(&self) -> windows::core::Result> { - self.iter().map(TryToNative::try_to_native).collect() - } -} - -impl TryFromNative> for Vec> { - fn try_from_native( - native: &RemoteArray, - ) -> windows::core::Result { - Ok(native.as_slice().iter().map(|v| (*v).ok()).collect()) - } -} - -impl> TryFromNative> for Vec { - fn try_from_native(native: &RemoteArray) -> windows::core::Result { - native.as_slice().iter().map(T::try_from_native).collect() - } -} -impl> - TryFromNative<(RemoteArray, RemoteArray)> - for Vec> -{ - fn try_from_native( - native: &(RemoteArray, RemoteArray), - ) -> windows::core::Result { - let (results, errors) = native; - if results.len() != errors.len() { - return Err(windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "Results and errors arrays have different lengths", - )); - } - - Ok(results - .as_slice() - .iter() - .zip(errors.as_slice()) - .map(|(result, error)| { - if error.is_ok() { - T::try_from_native(result) - } else { - Err((*error).into()) - } - }) - .collect()) - } -} - -impl TryFromNative for std::time::SystemTime { - fn try_from_native( - native: &windows::Win32::Foundation::FILETIME, - ) -> windows::core::Result { - let ft = ((native.dwHighDateTime as u64) << 32) | (native.dwLowDateTime as u64); - let duration_since_1601 = std::time::Duration::from_nanos(ft * 100); - - let windows_to_unix_epoch_diff = std::time::Duration::from_secs(11_644_473_600); - let duration_since_unix_epoch = duration_since_1601 - .checked_sub(windows_to_unix_epoch_diff) - .ok_or_else(|| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "FILETIME is before UNIX_EPOCH", - ) - })?; - - Ok(std::time::UNIX_EPOCH + duration_since_unix_epoch) - } -} - -#[macro_export] -macro_rules! try_from_native { - ($native:expr) => { - TryFromNative::try_from_native($native)? - }; -} - -impl TryToNative for std::time::SystemTime { - fn try_to_native(&self) -> windows::core::Result { - let duration_since_unix_epoch = - self.duration_since(std::time::UNIX_EPOCH).map_err(|_| { - windows::core::Error::new( - windows::Win32::Foundation::E_INVALIDARG, - "SystemTime is before UNIX_EPOCH", - ) - })?; - - let duration_since_windows_epoch = - duration_since_unix_epoch + std::time::Duration::from_secs(11_644_473_600); - - let ft = duration_since_windows_epoch.as_nanos() / 100; - - Ok(windows::Win32::Foundation::FILETIME { - dwLowDateTime: ft as u32, - dwHighDateTime: (ft >> 32) as u32, - }) - } -} - -impl TryFromNative for String { - fn try_from_native(native: &windows::core::PWSTR) -> windows::core::Result { - RemotePointer::from(*native).try_into() - } -} diff --git a/opc_da/src/utils/try_iterator.rs b/opc_da/src/utils/try_iterator.rs deleted file mode 100644 index cce11c0..0000000 --- a/opc_da/src/utils/try_iterator.rs +++ /dev/null @@ -1,98 +0,0 @@ -pub trait TryIterator { - type Item; - type Error; - - fn try_next(&mut self) -> Result, Self::Error>; -} - -pub struct TryIter { - inner: T, - done: bool, -} - -impl Iterator for TryIter { - type Item = Result; - - fn next(&mut self) -> Option { - if self.done { - return None; - } - - match self.inner.try_next() { - Ok(Some(item)) => Some(Ok(item)), - Ok(None) => { - self.done = true; - None - } - Err(e) => { - self.done = true; - Some(Err(e)) - } - } - } -} - -pub trait TryCacheIterator { - type Item; - type Error; - type Cache: IntoIterator; - - fn try_cache(&mut self) -> Result, Self::Error>; - - #[inline(always)] - fn into_iter(self) -> TryCacheIter - where - Self: Sized, - { - TryCacheIter::new(self) - } -} - -pub struct TryCacheIter { - inner: T, - cache: Option<<::Cache as std::iter::IntoIterator>::IntoIter>, - done: bool, -} - -impl TryCacheIter { - pub fn new(inner: T) -> Self { - Self { - inner, - cache: None, - done: false, - } - } -} - -impl Iterator for TryCacheIter { - type Item = Result; - - fn next(&mut self) -> Option { - if self.done { - return None; - } - - let next = match &mut self.cache { - Some(cache) => cache.next(), - None => None, - }; - - match next { - Some(item) => Some(Ok(item)), - None => match self.inner.try_cache() { - Ok(Some(cache)) => { - self.cache = Some(cache.into_iter()); - self.next() - } - Ok(None) => { - self.done = true; - None - } - Err(e) => { - self.done = true; - Some(Err(e)) - } - }, - } - } -} From e20d43cdd8f194e0048c98fcca6c863834cbc23b Mon Sep 17 00:00:00 2001 From: Ronbb Date: Mon, 21 Jul 2025 16:28:04 +0800 Subject: [PATCH 02/10] Add thiserror --- Cargo.lock | 21 +++++++++++++++++++++ Cargo.toml | 4 +++- opc_da/Cargo.toml | 1 + opc_da/src/lib.rs | 6 ------ 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f094752..1409e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,6 +49,7 @@ dependencies = [ "opc_classic_utils", "opc_comn_bindings", "opc_da_bindings", + "thiserror", "windows", "windows-core", ] @@ -140,6 +141,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index 9214aaa..d1eebab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,9 @@ globset = "0.4.16" opc_classic_utils = { version = "0.3.0", path = "opc_classic_utils" } opc_comn_bindings = { path = "opc_comn_bindings" } opc_da_bindings = { path = "opc_da_bindings" } -tokio = { version = "1.46.1", features = ["full"] } +opc_hda_bindings = { version = "0.3.0", path = "opc_hda_bindings" } +thiserror = "1.0" +tokio = { version = "1", features = ["full"] } windows = { version = "0.61.3", features = [ "Win32_Foundation", "Win32_Graphics_Gdi", diff --git a/opc_da/Cargo.toml b/opc_da/Cargo.toml index 766689d..b8b9a87 100644 --- a/opc_da/Cargo.toml +++ b/opc_da/Cargo.toml @@ -15,5 +15,6 @@ targets = [] opc_classic_utils = { workspace = true } opc_comn_bindings = { workspace = true } opc_da_bindings = { workspace = true } +thiserror = { workspace = true } windows = { workspace = true } windows-core = { workspace = true } diff --git a/opc_da/src/lib.rs b/opc_da/src/lib.rs index 930930e..8b13789 100644 --- a/opc_da/src/lib.rs +++ b/opc_da/src/lib.rs @@ -1,7 +1 @@ -pub mod def; -pub mod utils; -#[cfg(feature = "unstable_client")] -pub mod client; -#[cfg(feature = "unstable_server")] -pub mod server; From ab19a6af151a3699d27821b14cf8cd895f2bb18c Mon Sep 17 00:00:00 2001 From: Ronbb Date: Thu, 24 Jul 2025 14:16:54 +0800 Subject: [PATCH 03/10] Update build configuration and dependencies - Update Cargo.toml workspace dependencies - Clean up build.rs files in binding crates - Update Cargo.lock with latest dependency versions --- Cargo.lock | 12 ++++++------ Cargo.toml | 9 +++------ opc_ae_bindings/build.rs | 1 - opc_comn_bindings/build.rs | 1 - opc_da_bindings/build.rs | 1 - opc_hda_bindings/build.rs | 1 - 6 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1409e30..34d41fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,9 +120,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", @@ -143,18 +143,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index d1eebab..f76c38b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,14 +10,11 @@ members = [ ] [workspace.dependencies] -actix = "0.13.5" -globset = "0.4.16" -opc_classic_utils = { version = "0.3.0", path = "opc_classic_utils" } +opc_classic_utils = { path = "opc_classic_utils" } opc_comn_bindings = { path = "opc_comn_bindings" } opc_da_bindings = { path = "opc_da_bindings" } -opc_hda_bindings = { version = "0.3.0", path = "opc_hda_bindings" } -thiserror = "1.0" -tokio = { version = "1", features = ["full"] } +opc_hda_bindings = { path = "opc_hda_bindings" } +thiserror = "2.0" windows = { version = "0.61.3", features = [ "Win32_Foundation", "Win32_Graphics_Gdi", diff --git a/opc_ae_bindings/build.rs b/opc_ae_bindings/build.rs index 19d638c..c2a53ae 100644 --- a/opc_ae_bindings/build.rs +++ b/opc_ae_bindings/build.rs @@ -13,7 +13,6 @@ fn main() { "--filter", "OPCAE", "--flat", - "--implement", ]) .unwrap(); } diff --git a/opc_comn_bindings/build.rs b/opc_comn_bindings/build.rs index 2c9cbd4..47fff18 100644 --- a/opc_comn_bindings/build.rs +++ b/opc_comn_bindings/build.rs @@ -13,7 +13,6 @@ fn main() { "--filter", "OPCCOMN", "--flat", - "--implement", ]) .unwrap(); } diff --git a/opc_da_bindings/build.rs b/opc_da_bindings/build.rs index 81fcc3c..7acb62f 100644 --- a/opc_da_bindings/build.rs +++ b/opc_da_bindings/build.rs @@ -13,7 +13,6 @@ fn main() { "--filter", "OPCDA", "--flat", - "--implement", ]) .unwrap(); } diff --git a/opc_hda_bindings/build.rs b/opc_hda_bindings/build.rs index edfbc7d..97b203b 100644 --- a/opc_hda_bindings/build.rs +++ b/opc_hda_bindings/build.rs @@ -13,7 +13,6 @@ fn main() { "--filter", "OPCHDA", "--flat", - "--implement", ]) .unwrap(); } From 252304fe47ece8df107627e030d65f93fab40e38 Mon Sep 17 00:00:00 2001 From: Ronbb Date: Thu, 24 Jul 2025 18:07:37 +0800 Subject: [PATCH 04/10] feat: enhance OPC Classic Utils memory management and macro functionality Key improvements: 1. Fix Drop implementation for Caller* types - CallerAllocatedArray: preserve pointer integrity, only clear length - CallerAllocatedPtr: maintain pointer validity - CallerAllocatedWString: keep pointer intact - Align with COM memory management conventions 2. Add convenience macros - write_caller_allocated_ptr!: write to caller-allocated pointers - write_caller_allocated_array!: write to caller-allocated arrays - alloc_callee_wstring!: allocate callee-allocated wide strings - All macros include comprehensive documentation and examples 3. Add opc_comn module - Implement OPC Common interface - Provide basic COM functionality support 4. Enhance documentation and examples - Update README.md with macro usage instructions - Add macro_usage.rs example file - Add detailed documentation for all macros 5. Code quality improvements - Pass all clippy checks - Pass all unit tests - Fix memory management issues These improvements make OPC Classic API development safer and more convenient. --- Cargo.lock | 79 +++------------ Cargo.toml | 3 +- opc_ae_bindings/Cargo.toml | 1 - opc_classic_utils/README.md | 35 +++++++ opc_classic_utils/examples/macro_usage.rs | 114 ++++++++++++++++++++++ opc_classic_utils/src/memory/array.rs | 68 ++++++++++++- opc_classic_utils/src/memory/ptr.rs | 59 ++++++++++- opc_classic_utils/src/memory/wstring.rs | 99 ++++++++++++++++++- opc_comn/.gitignore | 1 + opc_comn/Cargo.toml | 19 ++++ opc_comn/README.md | 9 ++ opc_comn/src/lib.rs | 1 + opc_comn/src/opc_common.rs | 51 ++++++++++ opc_comn_bindings/Cargo.toml | 1 - opc_da/Cargo.toml | 2 +- opc_da_bindings/Cargo.toml | 1 - opc_hda_bindings/Cargo.toml | 1 - 17 files changed, 464 insertions(+), 80 deletions(-) create mode 100644 opc_classic_utils/examples/macro_usage.rs create mode 100644 opc_comn/.gitignore create mode 100644 opc_comn/Cargo.toml create mode 100644 opc_comn/README.md create mode 100644 opc_comn/src/lib.rs create mode 100644 opc_comn/src/opc_common.rs diff --git a/Cargo.lock b/Cargo.lock index 34d41fd..342951e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,7 +21,6 @@ dependencies = [ "windows", "windows-bindgen", "windows-core", - "windows-targets", ] [[package]] @@ -32,6 +31,17 @@ dependencies = [ "windows-core", ] +[[package]] +name = "opc_comn" +version = "0.3.1" +dependencies = [ + "opc_classic_utils", + "opc_comn_bindings", + "thiserror", + "windows", + "windows-core", +] + [[package]] name = "opc_comn_bindings" version = "0.3.0" @@ -39,7 +49,6 @@ dependencies = [ "windows", "windows-bindgen", "windows-core", - "windows-targets", ] [[package]] @@ -61,7 +70,6 @@ dependencies = [ "windows", "windows-bindgen", "windows-core", - "windows-targets", ] [[package]] @@ -71,7 +79,6 @@ dependencies = [ "windows", "windows-bindgen", "windows-core", - "windows-targets", ] [[package]] @@ -280,22 +287,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - [[package]] name = "windows-threading" version = "0.1.0" @@ -304,51 +295,3 @@ checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ "windows-link", ] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" diff --git a/Cargo.toml b/Cargo.toml index f76c38b..3c98cdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "opc_ae_bindings", "opc_classic_utils", + "opc_comn", "opc_comn_bindings", "opc_da", "opc_da_bindings", @@ -11,6 +12,7 @@ members = [ [workspace.dependencies] opc_classic_utils = { path = "opc_classic_utils" } +opc_comn = { path = "opc_comn" } opc_comn_bindings = { path = "opc_comn_bindings" } opc_da_bindings = { path = "opc_da_bindings" } opc_hda_bindings = { path = "opc_hda_bindings" } @@ -26,7 +28,6 @@ windows = { version = "0.61.3", features = [ ] } windows-bindgen = "0.62.1" windows-core = "0.61.2" -windows-targets = "0.53.2" [profile.dev.build-override] debug = true diff --git a/opc_ae_bindings/Cargo.toml b/opc_ae_bindings/Cargo.toml index c8dbda7..8c753eb 100644 --- a/opc_ae_bindings/Cargo.toml +++ b/opc_ae_bindings/Cargo.toml @@ -16,7 +16,6 @@ targets = [] [dependencies] windows = { workspace = true } windows-core = { workspace = true } -windows-targets = { workspace = true } [build-dependencies] windows-bindgen = { workspace = true } diff --git a/opc_classic_utils/README.md b/opc_classic_utils/README.md index ebaf3f9..2e13ef9 100644 --- a/opc_classic_utils/README.md +++ b/opc_classic_utils/README.md @@ -87,6 +87,41 @@ let server_output = server_method(&client_input); | `CallerAllocatedWString` | Input strings | Caller allocates, callee frees | | `CalleeAllocatedWString` | Output strings | Callee allocates, caller frees | +## Convenience Macros + +The library provides several convenience macros to simplify common operations: + +### `write_caller_allocated_ptr!` +Writes a value to a caller-allocated pointer with proper error handling: + +```rust +use opc_classic_utils::write_caller_allocated_ptr; + +let mut count: u32 = 0; +let count_ptr = &mut count as *mut u32; +write_caller_allocated_ptr!(count_ptr, 42u32)?; +``` + +### `write_caller_allocated_array!` +Writes an array to a caller-allocated pointer: + +```rust +use opc_classic_utils::write_caller_allocated_array; + +let mut array_ptr: *mut u32 = std::ptr::null_mut(); +let data = vec![1u32, 2u32, 3u32]; +write_caller_allocated_array!(&mut array_ptr, &data)?; +``` + +### `alloc_callee_wstring!` +Allocates a callee-allocated wide string from a Rust string: + +```rust +use opc_classic_utils::alloc_callee_wstring; + +let error_string_ptr = alloc_callee_wstring!("Error message")?; +``` + ## Benefits - **Prevents Memory Leaks**: Automatic cleanup for callee-allocated memory diff --git a/opc_classic_utils/examples/macro_usage.rs b/opc_classic_utils/examples/macro_usage.rs new file mode 100644 index 0000000..8a0faae --- /dev/null +++ b/opc_classic_utils/examples/macro_usage.rs @@ -0,0 +1,114 @@ +//! Example demonstrating the usage of convenience macros for OPC Classic memory management +//! +//! This example shows how to use the new macros: +//! - `write_caller_allocated_ptr!` - for writing to caller-allocated pointers +//! - `write_caller_allocated_array!` - for writing to caller-allocated arrays +//! - `alloc_callee_wstring!` - for allocating callee-allocated wide strings + +use opc_classic_utils::{ + alloc_callee_wstring, write_caller_allocated_array, write_caller_allocated_ptr, +}; + +fn main() -> windows::core::Result<()> { + println!("=== OPC Classic Utils Macro Usage Examples ===\n"); + + // Example 1: Using write_caller_allocated_ptr! macro + println!("1. Using write_caller_allocated_ptr! macro:"); + { + let mut count: u32 = 0; + let count_ptr = &mut count as *mut u32; + + // Using the macro to write to a caller-allocated pointer + write_caller_allocated_ptr!(count_ptr, 42u32)?; + println!(" Written value: {}", count); + } + + // Example 2: Using write_caller_allocated_array! macro + println!("\n2. Using write_caller_allocated_array! macro:"); + { + let mut array_ptr: *mut u32 = std::ptr::null_mut(); + + // Using the macro to write an array to a caller-allocated pointer + let data = vec![1u32, 2u32, 3u32, 4u32, 5u32]; + write_caller_allocated_array!(&mut array_ptr, &data)?; + + println!(" Array pointer: {:?}", array_ptr); + println!(" Array is not null: {}", !array_ptr.is_null()); + } + + // Example 3: Using alloc_callee_wstring! macro + println!("\n3. Using alloc_callee_wstring! macro:"); + { + let error_message = "This is an error message"; + + // Using the macro to allocate a callee-allocated wide string + let wide_string_ptr = alloc_callee_wstring!(error_message)?; + + println!(" Wide string pointer: {:?}", wide_string_ptr); + } + + // Example 4: Simulating OPC Common interface usage + println!("\n4. Simulating OPC Common interface usage:"); + { + // Simulate QueryAvailableLocaleIDs method + let mut count: u32 = 0; + let mut locale_ids_ptr: *mut u32 = std::ptr::null_mut(); + + // Simulate available locale IDs + let available_locale_ids = vec![0x0409u32, 0x0410u32, 0x0411u32]; // EN-US, IT, JA + + // Write count using macro + write_caller_allocated_ptr!(&mut count, available_locale_ids.len() as u32)?; + + // Write array using macro + write_caller_allocated_array!(&mut locale_ids_ptr, &available_locale_ids)?; + + println!(" Available locale count: {}", count); + println!(" Locale IDs pointer: {:?}", locale_ids_ptr); + } + + // Example 5: Error handling with macros + println!("\n5. Error handling with macros:"); + { + // Simulate getting an error string from an HRESULT + let hresult = windows::Win32::Foundation::E_POINTER; + let error_string_ptr = alloc_callee_wstring!("Pointer is invalid")?; + + println!(" HRESULT: {:?}", hresult); + println!(" Error string pointer: {:?}", error_string_ptr); + } + + println!("\n=== All examples completed successfully! ==="); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_write_caller_allocated_ptr_macro() { + let mut value: u32 = 0; + let ptr = &mut value as *mut u32; + + let result = write_caller_allocated_ptr!(ptr, 123u32); + assert!(result.is_ok()); + assert_eq!(value, 123); + } + + #[test] + fn test_write_caller_allocated_array_macro() { + let mut array_ptr: *mut u32 = std::ptr::null_mut(); + let data = vec![1u32, 2u32, 3u32]; + + let result = write_caller_allocated_array!(&mut array_ptr, &data); + assert!(result.is_ok()); + assert!(!array_ptr.is_null()); + } + + #[test] + fn test_alloc_callee_wstring_macro() { + let result = alloc_callee_wstring!("test string"); + assert!(result.is_ok()); + } +} diff --git a/opc_classic_utils/src/memory/array.rs b/opc_classic_utils/src/memory/array.rs index 985e961..7e9f729 100644 --- a/opc_classic_utils/src/memory/array.rs +++ b/opc_classic_utils/src/memory/array.rs @@ -162,9 +162,10 @@ impl CallerAllocatedArray { impl Drop for CallerAllocatedArray { fn drop(&mut self) { // Do NOT free the memory - the callee is responsible for this - // Just clear the pointer to prevent use-after-free - self.ptr = ptr::null_mut(); + // Just clear the length to prevent use-after-free + // Keep the pointer intact for the callee to use self.len = 0; + // Note: We don't clear self.ptr because the callee needs it } } @@ -345,3 +346,66 @@ impl Default for CalleeAllocatedArray { } } } + +/// Writes an array to a caller-allocated pointer using COM memory management +/// +/// This macro simplifies writing arrays to caller-allocated pointers by creating +/// a `CallerAllocatedArray` from a slice and writing its pointer to the target. +/// +/// # Arguments +/// +/// * `$ptr` - A raw pointer (`*mut *mut T`) that points to caller-allocated memory +/// * `$value` - A slice (`&[T]`) containing the array data to write +/// +/// # Returns +/// +/// Returns `Result<(), windows::core::Error>`: +/// * `Ok(())` - Array was successfully written +/// * `Err(...)` - Memory allocation failed or pointer is invalid +/// +/// # Safety +/// +/// The caller must ensure that: +/// * `$ptr` is a valid pointer to caller-allocated memory +/// * The memory pointed to by `$ptr` is properly initialized +/// * The callee (COM function) will be responsible for freeing the array memory +/// * The array elements are `Copy` types +/// +/// # Memory Management +/// +/// This macro: +/// 1. Allocates memory using `CoTaskMemAlloc` for the array data +/// 2. Copies the slice data into the allocated memory +/// 3. Writes the pointer to the allocated memory to `$ptr` +/// 4. The callee is responsible for freeing the memory using `CoTaskMemFree` +/// +/// # Example +/// +/// ```rust +/// use opc_classic_utils::write_caller_allocated_array; +/// +/// let mut array_ptr: *mut u32 = std::ptr::null_mut(); +/// let data = vec![1u32, 2u32, 3u32, 4u32, 5u32]; +/// +/// // Write the array to the caller-allocated pointer +/// write_caller_allocated_array!(&mut array_ptr, &data)?; +/// +/// // The array_ptr now points to COM-allocated memory containing the data +/// // The callee (COM function) will be responsible for freeing this memory +/// # Ok::<(), windows::core::Error>(()) +/// ``` +/// +/// # Typical Use Cases +/// +/// * Passing arrays to COM function calls +/// * OPC Classic API array parameter passing +/// * Setting up caller-allocated array output parameters +#[macro_export] +macro_rules! write_caller_allocated_array { + ($ptr:expr, $value:expr) => { + opc_classic_utils::write_caller_allocated_ptr!( + $ptr, + opc_classic_utils::CallerAllocatedArray::from_slice($value)?.as_ptr() + ) + }; +} diff --git a/opc_classic_utils/src/memory/ptr.rs b/opc_classic_utils/src/memory/ptr.rs index 07f2dee..f68b5a6 100644 --- a/opc_classic_utils/src/memory/ptr.rs +++ b/opc_classic_utils/src/memory/ptr.rs @@ -103,8 +103,8 @@ impl CallerAllocatedPtr { impl Drop for CallerAllocatedPtr { fn drop(&mut self) { // Do NOT free the memory - the callee is responsible for this - // Just clear the pointer to prevent use-after-free - self.ptr = ptr::null_mut(); + // Keep the pointer intact for the callee to use + // Note: We don't clear self.ptr because the callee needs it } } @@ -238,3 +238,58 @@ impl Default for CalleeAllocatedPtr { } } } + +/// Writes a value to a caller-allocated pointer using COM memory management +/// +/// This macro simplifies writing values to caller-allocated pointers by wrapping +/// the raw pointer in a `CallerAllocatedPtr` and safely writing the value. +/// +/// # Arguments +/// +/// * `$ptr` - A raw pointer (`*mut T`) that points to caller-allocated memory +/// * `$value` - The value to write to the pointer +/// +/// # Returns +/// +/// Returns `Result<(), windows::core::Error>`: +/// * `Ok(())` - Value was successfully written +/// * `Err(E_INVALIDARG)` - The pointer is null or invalid +/// +/// # Safety +/// +/// The caller must ensure that: +/// * `$ptr` is a valid pointer to caller-allocated memory +/// * The memory pointed to by `$ptr` is properly initialized +/// * The callee (COM function) will be responsible for freeing the memory +/// +/// # Example +/// +/// ```rust +/// use opc_classic_utils::write_caller_allocated_ptr; +/// +/// let mut count: u32 = 0; +/// let count_ptr = &mut count as *mut u32; +/// +/// // Write a value to the caller-allocated pointer +/// write_caller_allocated_ptr!(count_ptr, 42u32)?; +/// assert_eq!(count, 42); +/// # Ok::<(), windows::core::Error>(()) +/// ``` +/// +/// # Typical Use Cases +/// +/// * Writing input parameters to COM function calls +/// * Setting up caller-allocated output parameters +/// * OPC Classic API parameter passing +#[macro_export] +macro_rules! write_caller_allocated_ptr { + ($ptr:expr, $value:expr) => { + unsafe { + let mut ptr = opc_classic_utils::CallerAllocatedPtr::from_raw($ptr); + let value = $value; + ptr.as_mut() + .ok_or(windows::Win32::Foundation::E_INVALIDARG) + .map(|ptr| *ptr = value) + } + }; +} diff --git a/opc_classic_utils/src/memory/wstring.rs b/opc_classic_utils/src/memory/wstring.rs index 5d919b3..c450e2e 100644 --- a/opc_classic_utils/src/memory/wstring.rs +++ b/opc_classic_utils/src/memory/wstring.rs @@ -2,7 +2,7 @@ use std::ffi::{OsStr, OsString}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; use std::ptr; use windows::Win32::System::Com::{CoTaskMemAlloc, CoTaskMemFree}; -use windows::core::PCWSTR; +use windows::core::{PCWSTR, PWSTR}; /// A smart pointer for wide string pointers that the **caller allocates and callee frees** /// @@ -128,12 +128,18 @@ impl CallerAllocatedWString { pub fn as_pcwstr(&self) -> PCWSTR { PCWSTR(self.ptr) } + + /// Converts to a `PWSTR` for use with Windows APIs + pub fn as_pwstr(&self) -> PWSTR { + PWSTR(self.ptr) + } } impl Drop for CallerAllocatedWString { fn drop(&mut self) { // Do NOT free the memory - the callee is responsible for this - self.ptr = ptr::null_mut(); + // Keep the pointer intact for the callee to use + // Note: We don't clear self.ptr because the callee needs it } } @@ -208,6 +214,12 @@ impl CalleeAllocatedWString { } } + /// Creates a `CalleeAllocatedWString` from a Rust string + pub fn from_string(s: String) -> Result { + use std::str::FromStr; + Self::from_str(&s) + } + /// Converts the wide string to a Rust string slice /// /// # Safety @@ -268,6 +280,11 @@ impl CalleeAllocatedWString { pub fn as_pcwstr(&self) -> PCWSTR { PCWSTR(self.ptr) } + + /// Converts to a `PWSTR` for use with Windows APIs + pub fn as_pwstr(&self) -> PWSTR { + PWSTR(self.ptr) + } } impl Drop for CalleeAllocatedWString { @@ -300,3 +317,81 @@ impl Clone for CalleeAllocatedWString { Self { ptr: self.ptr } } } + +impl std::str::FromStr for CalleeAllocatedWString { + type Err = windows::core::Error; + + fn from_str(s: &str) -> Result { + let wide_string: Vec = OsStr::new(s) + .encode_wide() + .chain(std::iter::once(0)) + .collect(); + let len = wide_string.len() - 1; // Exclude null terminator for allocation + let ptr = unsafe { CoTaskMemAlloc(len * std::mem::size_of::()) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + unsafe { + std::ptr::copy_nonoverlapping(wide_string.as_ptr(), ptr.cast(), wide_string.len()); + } + Ok(unsafe { Self::new(ptr.cast()) }) + } +} + +/// Allocates a callee-allocated wide string from a Rust string +/// +/// This macro simplifies creating callee-allocated wide strings by converting +/// a Rust string to a `CalleeAllocatedWString` and returning its `PWSTR` representation. +/// +/// # Arguments +/// +/// * `$s` - A string expression (`String`, `&str`, or any type that can be converted to `String`) +/// +/// # Returns +/// +/// Returns `Result`: +/// * `Ok(PWSTR)` - Successfully allocated wide string pointer +/// * `Err(...)` - Memory allocation failed +/// +/// # Memory Management +/// +/// This macro: +/// 1. Converts the input string to a wide string (UTF-16) +/// 2. Allocates memory using `CoTaskMemAlloc` for the wide string +/// 3. Copies the wide string data into the allocated memory +/// 4. Returns a `PWSTR` pointing to the allocated memory +/// 5. The caller is responsible for freeing the memory using `CoTaskMemFree` +/// +/// # Example +/// +/// ```rust +/// use opc_classic_utils::alloc_callee_wstring; +/// use windows_core::PWSTR; +/// +/// // Allocate a wide string from a Rust string +/// let wide_string: PWSTR = alloc_callee_wstring!("Hello, World!")?; +/// +/// // Use the wide string with Windows APIs +/// // The caller is responsible for freeing the memory when done +/// unsafe { +/// // Use wide_string with Windows API calls +/// // ... +/// // Free the memory when done +/// windows::Win32::System::Com::CoTaskMemFree(Some(wide_string.0.cast())); +/// } +/// # Ok::<(), windows::core::Error>(()) +/// ``` +/// +/// # Typical Use Cases +/// +/// * Creating wide strings for Windows API calls +/// * OPC Classic API string parameter passing +/// * Converting Rust strings to COM-allocated wide strings +/// * Setting up callee-allocated string output parameters +#[macro_export] +macro_rules! alloc_callee_wstring { + ($s:expr) => {{ + use std::str::FromStr; + opc_classic_utils::CalleeAllocatedWString::from_str(&$s).map(|s| s.as_pwstr()) + }}; +} diff --git a/opc_comn/.gitignore b/opc_comn/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/opc_comn/.gitignore @@ -0,0 +1 @@ +/target diff --git a/opc_comn/Cargo.toml b/opc_comn/Cargo.toml new file mode 100644 index 0000000..c7bfbce --- /dev/null +++ b/opc_comn/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "opc_comn" +version = "0.3.1" +edition = "2024" +description = "OPC Common" +repository = "https://github.com/Ronbb/rust_opc" +license = "MIT" +keywords = ["comn", "opc", "opccomn"] + +[package.metadata.docs.rs] +default-target = "x86_64-pc-windows-msvc" +targets = [] + +[dependencies] +opc_classic_utils = { workspace = true } +opc_comn_bindings = { workspace = true } +thiserror = { workspace = true } +windows = { workspace = true } +windows-core = { workspace = true } diff --git a/opc_comn/README.md b/opc_comn/README.md new file mode 100644 index 0000000..f493a3c --- /dev/null +++ b/opc_comn/README.md @@ -0,0 +1,9 @@ +# OPC Comn + +Please see docs on [docs.rs](https://docs.rs/opc_comn/). + +## **UNSTABLE** + +**Warning: This library is still under development and may not be fully functional. Use at your own risk.** + +**Note: The API is unstable, tests are incomplete, and there may be unsafe memory operations.** diff --git a/opc_comn/src/lib.rs b/opc_comn/src/lib.rs new file mode 100644 index 0000000..20bf240 --- /dev/null +++ b/opc_comn/src/lib.rs @@ -0,0 +1 @@ +mod opc_common; diff --git a/opc_comn/src/opc_common.rs b/opc_comn/src/opc_common.rs new file mode 100644 index 0000000..3dfe6fa --- /dev/null +++ b/opc_comn/src/opc_common.rs @@ -0,0 +1,51 @@ +#[windows::core::implement(opc_comn_bindings::IOPCCommon)] +pub struct OPCCommon(T) +where + T: 'static + traits::OPCCommon; + +impl opc_comn_bindings::IOPCCommon_Impl for OPCCommon_Impl { + fn SetLocaleID(&self, dwlcid: u32) -> windows_core::Result<()> { + self.0.set_locale_id(dwlcid) + } + + fn GetLocaleID(&self) -> windows_core::Result { + self.0.get_locale_id() + } + + fn QueryAvailableLocaleIDs( + &self, + pdwcount: *mut u32, + pdwlcid: *mut *mut u32, + ) -> windows_core::Result<()> { + let locale_ids = self.0.query_available_locale_ids()?; + opc_classic_utils::write_caller_allocated_ptr!(pdwcount, locale_ids.len().try_into()?)?; + opc_classic_utils::write_caller_allocated_array!(pdwlcid, locale_ids.as_slice())?; + + Ok(()) + } + + fn GetErrorString( + &self, + dwerror: windows_core::HRESULT, + ) -> windows_core::Result { + opc_classic_utils::alloc_callee_wstring!(self.0.get_error_string(dwerror)?) + } + + fn SetClientName(&self, szname: &windows_core::PCWSTR) -> windows_core::Result<()> { + self.0.set_client_name(unsafe { + opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szname) + .to_string() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?) + } +} + +pub mod traits { + pub trait OPCCommon { + fn set_locale_id(&self, dwlcid: u32) -> windows_core::Result<()>; + fn get_locale_id(&self) -> windows_core::Result; + fn query_available_locale_ids(&self) -> windows_core::Result>; + fn get_error_string(&self, dwerror: windows_core::HRESULT) -> windows_core::Result; + fn set_client_name(&self, szname: String) -> windows_core::Result<()>; + } +} diff --git a/opc_comn_bindings/Cargo.toml b/opc_comn_bindings/Cargo.toml index 966673e..5b4206e 100644 --- a/opc_comn_bindings/Cargo.toml +++ b/opc_comn_bindings/Cargo.toml @@ -16,7 +16,6 @@ targets = [] [dependencies] windows = { workspace = true } windows-core = { workspace = true } -windows-targets = { workspace = true } [build-dependencies] windows-bindgen = { workspace = true } diff --git a/opc_da/Cargo.toml b/opc_da/Cargo.toml index b8b9a87..7263212 100644 --- a/opc_da/Cargo.toml +++ b/opc_da/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" description = "OPC Data Access" repository = "https://github.com/Ronbb/rust_opc" license = "MIT" -keywords = ["da", "opc"] +keywords = ["da", "opc", "opcda"] [package.metadata.docs.rs] default-target = "x86_64-pc-windows-msvc" diff --git a/opc_da_bindings/Cargo.toml b/opc_da_bindings/Cargo.toml index 9859ed7..13b2d07 100644 --- a/opc_da_bindings/Cargo.toml +++ b/opc_da_bindings/Cargo.toml @@ -16,7 +16,6 @@ targets = [] [dependencies] windows = { workspace = true } windows-core = { workspace = true } -windows-targets = { workspace = true } [build-dependencies] windows-bindgen = { workspace = true } diff --git a/opc_hda_bindings/Cargo.toml b/opc_hda_bindings/Cargo.toml index 03330bf..9aabf75 100644 --- a/opc_hda_bindings/Cargo.toml +++ b/opc_hda_bindings/Cargo.toml @@ -16,7 +16,6 @@ targets = [] [dependencies] windows = { workspace = true } windows-core = { workspace = true } -windows-targets = { workspace = true } [build-dependencies] windows-bindgen = { workspace = true } From 55b543b432073f005bb8358f685a75a2ba6ac5e6 Mon Sep 17 00:00:00 2001 From: Ronbb Date: Sun, 27 Jul 2025 22:10:39 +0800 Subject: [PATCH 05/10] OPC DA --- opc_classic_utils/src/memory/array.rs | 67 +++++ opc_classic_utils/src/memory/ptr.rs | 5 +- opc_classic_utils/src/memory/ptr_array.rs | 52 ++++ opc_classic_utils/src/memory/wstring.rs | 7 + opc_comn/src/lib.rs | 9 + opc_comn/src/opc_common.rs | 256 ++++++++++++++++ opc_da/src/lib.rs | 23 ++ opc_da/src/opc_async_io.rs | 337 ++++++++++++++++++++++ opc_da/src/opc_browse.rs | 129 +++++++++ opc_da/src/opc_data_callback.rs | 166 +++++++++++ opc_da/src/opc_group.rs | 203 +++++++++++++ opc_da/src/opc_item.rs | 247 ++++++++++++++++ opc_da/src/opc_server.rs | 143 +++++++++ opc_da/src/opc_sync_io.rs | 249 ++++++++++++++++ 14 files changed, 1889 insertions(+), 4 deletions(-) create mode 100644 opc_da/src/opc_async_io.rs create mode 100644 opc_da/src/opc_browse.rs create mode 100644 opc_da/src/opc_data_callback.rs create mode 100644 opc_da/src/opc_group.rs create mode 100644 opc_da/src/opc_item.rs create mode 100644 opc_da/src/opc_server.rs create mode 100644 opc_da/src/opc_sync_io.rs diff --git a/opc_classic_utils/src/memory/array.rs b/opc_classic_utils/src/memory/array.rs index 7e9f729..bdc60fa 100644 --- a/opc_classic_utils/src/memory/array.rs +++ b/opc_classic_utils/src/memory/array.rs @@ -409,3 +409,70 @@ macro_rules! write_caller_allocated_array { ) }; } + +/// Copies data directly to a caller-allocated array +/// +/// This macro simplifies copying data directly to a caller-allocated array +/// without allocating new memory. It's used when the caller has already +/// allocated the array and we just need to copy data into it. +/// +/// # Arguments +/// +/// * `$dst` - A raw pointer (`*mut T`) to the destination array +/// * `$src` - A slice (`&[T]`) containing the source data +/// +/// # Returns +/// +/// Returns `Result<(), windows::core::Error>`: +/// * `Ok(())` - Data was successfully copied +/// * `Err(E_INVALIDARG)` - The destination pointer is null +/// +/// # Safety +/// +/// The caller must ensure that: +/// * `$dst` is a valid pointer to caller-allocated memory +/// * The destination array has sufficient space for the source data +/// * The array elements are `Copy` types +/// +/// # Example +/// +/// ```rust +/// use opc_classic_utils::copy_to_caller_array; +/// +/// let mut array: [u32; 5] = [0; 5]; +/// let data = vec![1u32, 2u32, 3u32, 4u32, 5u32]; +/// +/// // Copy data directly to the caller-allocated array +/// copy_to_caller_array!(array.as_mut_ptr(), &data)?; +/// assert_eq!(array, [1, 2, 3, 4, 5]); +/// # Ok::<(), windows::core::Error>(()) +/// ``` +/// +/// # Typical Use Cases +/// +/// * Copying data to caller-allocated output parameters +/// * OPC Classic API array output parameters +/// * Direct memory copying without allocation +#[macro_export] +macro_rules! copy_to_caller_array { + ($dst:expr, $src:expr) => { + unsafe { + let mut dst_ptr = opc_classic_utils::CallerAllocatedArray::from_raw($dst, $src.len()); + if dst_ptr.is_null() { + return Err(windows::core::Error::new( + windows::Win32::Foundation::E_INVALIDARG, + "Destination pointer is null", + )); + } + if let Some(dst_slice) = dst_ptr.as_mut_slice() { + dst_slice.copy_from_slice($src); + Ok(()) + } else { + Err(windows::core::Error::new( + windows::Win32::Foundation::E_INVALIDARG, + "Failed to get mutable slice", + )) + } + } + }; +} diff --git a/opc_classic_utils/src/memory/ptr.rs b/opc_classic_utils/src/memory/ptr.rs index f68b5a6..cb748b0 100644 --- a/opc_classic_utils/src/memory/ptr.rs +++ b/opc_classic_utils/src/memory/ptr.rs @@ -159,10 +159,7 @@ impl CalleeAllocatedPtr { /// Creates a new `CalleeAllocatedPtr` from a value, allocating memory /// /// This allocates memory using `CoTaskMemAlloc` and copies the value into it. - pub fn from_value(value: &T) -> Result - where - T: Copy, - { + pub fn from_value(value: &T) -> Result { let size = std::mem::size_of::(); let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; if ptr.is_null() { diff --git a/opc_classic_utils/src/memory/ptr_array.rs b/opc_classic_utils/src/memory/ptr_array.rs index c16b00d..e7fa715 100644 --- a/opc_classic_utils/src/memory/ptr_array.rs +++ b/opc_classic_utils/src/memory/ptr_array.rs @@ -1,6 +1,8 @@ use std::ptr; use windows::Win32::System::Com::{CoTaskMemAlloc, CoTaskMemFree}; +use crate::CalleeAllocatedPtr; + /// A smart pointer for COM memory pointer arrays that the **caller allocates and callee frees** /// /// This is used for input pointer array parameters where the caller allocates memory @@ -249,6 +251,56 @@ impl CalleeAllocatedPtrArray { Self { ptr, len } } + /// Creates a new `CalleeAllocatedPtrArray` from a slice + pub fn from_slice(slice: &[T]) -> Result { + if slice.is_empty() { + return Ok(Self { + ptr: ptr::null_mut(), + len: 0, + }); + } + let array = Self::allocate(slice.len())?; + + // convert items to CalleeAllocatedPtr write to array + for (i, item) in slice.iter().enumerate() { + let item_ptr = CalleeAllocatedPtr::from_value(item)?; + if item_ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + unsafe { *array.ptr.add(i) = item_ptr.as_ptr() }; + } + + Ok(array) + } + + /// Allocates memory for a pointer array using `CoTaskMemAlloc` and creates a `CalleeAllocatedPtrArray` + /// + /// This allocates memory that will be freed by the caller (COM function). + /// The callee is responsible for ensuring the caller will free this memory. + pub fn allocate(len: usize) -> Result { + if len == 0 { + return Ok(Self { + ptr: ptr::null_mut(), + len: 0, + }); + } + + let size = std::mem::size_of::<*mut T>() + .checked_mul(len) + .ok_or_else(|| { + windows::core::Error::new( + windows::core::HRESULT::from_win32(0x80070057), // E_INVALIDARG + "Pointer array size overflow", + ) + })?; + + let ptr = unsafe { CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + Ok(unsafe { Self::new(ptr.cast(), len) }) + } + /// Returns the raw pointer without transferring ownership pub fn as_ptr(&self) -> *mut *mut T { self.ptr diff --git a/opc_classic_utils/src/memory/wstring.rs b/opc_classic_utils/src/memory/wstring.rs index c450e2e..ccec571 100644 --- a/opc_classic_utils/src/memory/wstring.rs +++ b/opc_classic_utils/src/memory/wstring.rs @@ -38,6 +38,13 @@ impl CallerAllocatedWString { } } + /// Creates a new `CallerAllocatedWString` from a `PWSTR` + pub fn from_pwstr(pwstr: PWSTR) -> Self { + Self { + ptr: pwstr.as_ptr(), + } + } + /// Allocates memory using `CoTaskMemAlloc` and creates a `CallerAllocatedWString` /// /// This allocates memory for a wide string that will be freed by the callee. diff --git a/opc_comn/src/lib.rs b/opc_comn/src/lib.rs index 20bf240..f038f08 100644 --- a/opc_comn/src/lib.rs +++ b/opc_comn/src/lib.rs @@ -1 +1,10 @@ mod opc_common; + +pub use opc_common::{ + OPCCommon, OPCEnumGUID, OPCServerList, OPCServerList2, OPCShutdown, + traits::{ + OPCCommon as OPCCommonTrait, OPCEnumGUID as OPCEnumGUIDTrait, + OPCServerList as OPCServerListTrait, OPCServerList2 as OPCServerList2Trait, + OPCShutdown as OPCShutdownTrait, + }, +}; diff --git a/opc_comn/src/opc_common.rs b/opc_comn/src/opc_common.rs index 3dfe6fa..3c4613d 100644 --- a/opc_comn/src/opc_common.rs +++ b/opc_comn/src/opc_common.rs @@ -40,6 +40,221 @@ impl opc_comn_bindings::IOPCCommon_Impl for OPCCommon_Impl } } +#[windows::core::implement(opc_comn_bindings::IOPCEnumGUID)] +pub struct OPCEnumGUID(T) +where + T: 'static + traits::OPCEnumGUID; + +impl opc_comn_bindings::IOPCEnumGUID_Impl for OPCEnumGUID_Impl { + fn Next( + &self, + celt: u32, + rgelt: *mut windows_core::GUID, + pceltfetched: *mut u32, + ) -> windows_core::Result<()> { + let guids = self.0.next(celt)?; + let fetched = guids.len() as u32; + + if !rgelt.is_null() { + opc_classic_utils::copy_to_caller_array!(rgelt, &guids)?; + } + + opc_classic_utils::write_caller_allocated_ptr!(pceltfetched, fetched)?; + + Ok(()) + } + + fn Skip(&self, celt: u32) -> windows_core::Result<()> { + self.0.skip(celt) + } + + fn Reset(&self) -> windows_core::Result<()> { + self.0.reset() + } + + fn Clone(&self) -> windows_core::Result { + self.0.clone_enum() + } +} + +#[windows::core::implement(opc_comn_bindings::IOPCServerList)] +pub struct OPCServerList(T) +where + T: 'static + traits::OPCServerList; + +impl opc_comn_bindings::IOPCServerList_Impl for OPCServerList_Impl { + fn EnumClassesOfCategories( + &self, + cimplemented: u32, + rgcatidimpl: *const windows_core::GUID, + crequired: u32, + rgcatidreq: *const windows_core::GUID, + ) -> windows_core::Result { + let implemented = if !rgcatidimpl.is_null() { + unsafe { + opc_classic_utils::CallerAllocatedArray::new( + rgcatidimpl as *mut windows_core::GUID, + cimplemented as usize, + ) + .as_slice() + .unwrap_or(&[]) + .to_vec() + } + } else { + Vec::new() + }; + + let required = if !rgcatidreq.is_null() { + unsafe { + opc_classic_utils::CallerAllocatedArray::new( + rgcatidreq as *mut windows_core::GUID, + crequired as usize, + ) + .as_slice() + .unwrap_or(&[]) + .to_vec() + } + } else { + Vec::new() + }; + + self.0.enum_classes_of_categories(implemented, required) + } + + fn GetClassDetails( + &self, + clsid: *const windows_core::GUID, + ppszprogid: *mut windows_core::PWSTR, + ppszusertype: *mut windows_core::PWSTR, + ) -> windows_core::Result<()> { + let (prog_id, user_type) = self.0.get_class_details(unsafe { *clsid })?; + + opc_classic_utils::write_caller_allocated_ptr!( + ppszprogid, + opc_classic_utils::alloc_callee_wstring!(prog_id)? + )?; + opc_classic_utils::write_caller_allocated_ptr!( + ppszusertype, + opc_classic_utils::alloc_callee_wstring!(user_type)? + )?; + + Ok(()) + } + + fn CLSIDFromProgID( + &self, + szprogid: &windows_core::PCWSTR, + ) -> windows_core::Result { + let prog_id = unsafe { + opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szprogid) + .to_string() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?; + + self.0.clsid_from_prog_id(prog_id) + } +} + +#[windows::core::implement(opc_comn_bindings::IOPCServerList2)] +pub struct OPCServerList2(T) +where + T: 'static + traits::OPCServerList2; + +impl opc_comn_bindings::IOPCServerList2_Impl for OPCServerList2_Impl { + fn EnumClassesOfCategories( + &self, + cimplemented: u32, + rgcatidimpl: *const windows_core::GUID, + crequired: u32, + rgcatidreq: *const windows_core::GUID, + ) -> windows_core::Result { + let implemented = if !rgcatidimpl.is_null() { + unsafe { + opc_classic_utils::CallerAllocatedArray::new( + rgcatidimpl as *mut windows_core::GUID, + cimplemented as usize, + ) + .as_slice() + .unwrap_or(&[]) + .to_vec() + } + } else { + Vec::new() + }; + + let required = if !rgcatidreq.is_null() { + unsafe { + opc_classic_utils::CallerAllocatedArray::new( + rgcatidreq as *mut windows_core::GUID, + crequired as usize, + ) + .as_slice() + .unwrap_or(&[]) + .to_vec() + } + } else { + Vec::new() + }; + + self.0.enum_classes_of_categories(implemented, required) + } + + fn GetClassDetails( + &self, + clsid: *const windows_core::GUID, + ppszprogid: *mut windows_core::PWSTR, + ppszusertype: *mut windows_core::PWSTR, + ppszverindprogid: *mut windows_core::PWSTR, + ) -> windows_core::Result<()> { + let (prog_id, user_type, ver_ind_prog_id) = self.0.get_class_details(unsafe { *clsid })?; + + opc_classic_utils::write_caller_allocated_ptr!( + ppszprogid, + opc_classic_utils::alloc_callee_wstring!(prog_id)? + )?; + opc_classic_utils::write_caller_allocated_ptr!( + ppszusertype, + opc_classic_utils::alloc_callee_wstring!(user_type)? + )?; + opc_classic_utils::write_caller_allocated_ptr!( + ppszverindprogid, + opc_classic_utils::alloc_callee_wstring!(ver_ind_prog_id)? + )?; + + Ok(()) + } + + fn CLSIDFromProgID( + &self, + szprogid: &windows_core::PCWSTR, + ) -> windows_core::Result { + let prog_id = unsafe { + opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szprogid) + .to_string() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?; + + self.0.clsid_from_prog_id(prog_id) + } +} + +#[windows::core::implement(opc_comn_bindings::IOPCShutdown)] +pub struct OPCShutdown(T) +where + T: 'static + traits::OPCShutdown; + +impl opc_comn_bindings::IOPCShutdown_Impl for OPCShutdown_Impl { + fn ShutdownRequest(&self, szreason: &windows_core::PCWSTR) -> windows_core::Result<()> { + let reason = unsafe { + opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szreason) + .to_string() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?; + + self.0.shutdown_request(reason) + } +} + pub mod traits { pub trait OPCCommon { fn set_locale_id(&self, dwlcid: u32) -> windows_core::Result<()>; @@ -48,4 +263,45 @@ pub mod traits { fn get_error_string(&self, dwerror: windows_core::HRESULT) -> windows_core::Result; fn set_client_name(&self, szname: String) -> windows_core::Result<()>; } + + pub trait OPCEnumGUID { + fn next(&self, celt: u32) -> windows_core::Result>; + fn skip(&self, celt: u32) -> windows_core::Result<()>; + fn reset(&self) -> windows_core::Result<()>; + fn clone_enum(&self) -> windows_core::Result; + } + + pub trait OPCServerList { + fn enum_classes_of_categories( + &self, + implemented: Vec, + required: Vec, + ) -> windows_core::Result; + + fn get_class_details( + &self, + clsid: windows_core::GUID, + ) -> windows_core::Result<(String, String)>; + + fn clsid_from_prog_id(&self, prog_id: String) -> windows_core::Result; + } + + pub trait OPCServerList2 { + fn enum_classes_of_categories( + &self, + implemented: Vec, + required: Vec, + ) -> windows_core::Result; + + fn get_class_details( + &self, + clsid: windows_core::GUID, + ) -> windows_core::Result<(String, String, String)>; + + fn clsid_from_prog_id(&self, prog_id: String) -> windows_core::Result; + } + + pub trait OPCShutdown { + fn shutdown_request(&self, reason: String) -> windows_core::Result<()>; + } } diff --git a/opc_da/src/lib.rs b/opc_da/src/lib.rs index 8b13789..bca356e 100644 --- a/opc_da/src/lib.rs +++ b/opc_da/src/lib.rs @@ -1 +1,24 @@ +mod opc_async_io; +mod opc_browse; +mod opc_data_callback; +mod opc_group; +mod opc_item; +mod opc_server; +mod opc_sync_io; +pub use opc_async_io::{ + OPCAsyncIO, + traits::{ + OPCAsyncIO as OPCAsyncIOTrait, OPCAsyncIO2 as OPCAsyncIO2Trait, + OPCAsyncIO3 as OPCAsyncIO3Trait, + }, +}; +pub use opc_browse::{OPCBrowse, traits::OPCBrowse as OPCBrowseTrait}; +pub use opc_data_callback::{OPCDataCallback, traits::OPCDataCallback as OPCDataCallbackTrait}; +pub use opc_group::{OPCGroup, traits::OPCGroup as OPCGroupTrait}; +pub use opc_item::{OPCItem, traits::OPCItem as OPCItemTrait}; +pub use opc_server::{OPCServer, traits::OPCServer as OPCServerTrait}; +pub use opc_sync_io::{ + OPCSyncIO, + traits::{OPCSyncIO as OPCSyncIOTrait, OPCSyncIO2 as OPCSyncIO2Trait}, +}; diff --git a/opc_da/src/opc_async_io.rs b/opc_da/src/opc_async_io.rs new file mode 100644 index 0000000..2310192 --- /dev/null +++ b/opc_da/src/opc_async_io.rs @@ -0,0 +1,337 @@ +use windows_core::*; +use opc_da_bindings::*; +use opc_classic_utils::*; + +#[windows::core::implement(IOPCAsyncIO)] +pub struct OPCAsyncIO(T) +where + T: 'static + traits::OPCAsyncIO; + +impl IOPCAsyncIO_Impl for OPCAsyncIO_Impl { + fn Read( + &self, + dwconnection: u32, + dwsource: tagOPCDATASOURCE, + dwcount: u32, + phserver: *const u32, + ptransactionid: *mut u32, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + + let (transaction_id, errors) = self.0.read(dwconnection, dwsource, server_handles.to_vec())?; + + write_caller_allocated_ptr!(ptransactionid, transaction_id)?; + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } + + fn Write( + &self, + dwconnection: u32, + dwcount: u32, + phserver: *const u32, + pitemvalues: *const windows::Win32::System::Variant::VARIANT, + ptransactionid: *mut u32, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + let values = unsafe { + std::slice::from_raw_parts(pitemvalues, dwcount as usize) + }; + + let (transaction_id, errors) = self.0.write(dwconnection, server_handles.to_vec(), values.to_vec())?; + + write_caller_allocated_ptr!(ptransactionid, transaction_id)?; + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } + + fn Refresh( + &self, + dwconnection: u32, + dwsource: tagOPCDATASOURCE, + ) -> Result { + self.0.refresh(dwconnection, dwsource) + } + + fn Cancel(&self, dwtransactionid: u32) -> Result<()> { + self.0.cancel(dwtransactionid) + } +} + + + +#[windows::core::implement(IOPCAsyncIO2, IOPCAsyncIO3)] +pub struct OPCAsyncIO3(T) +where + T: 'static + traits::OPCAsyncIO3; + +impl IOPCAsyncIO2_Impl for OPCAsyncIO3_Impl { + fn Read( + &self, + dwcount: u32, + phserver: *const u32, + dwtransactionid: u32, + pdwcancelid: *mut u32, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + + let (cancel_id, errors) = traits::OPCAsyncIO2::read(&self.0, dwcount, server_handles.to_vec(), dwtransactionid)?; + + write_caller_allocated_ptr!(pdwcancelid, cancel_id)?; + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } + + fn Write( + &self, + dwcount: u32, + phserver: *const u32, + pitemvalues: *const windows::Win32::System::Variant::VARIANT, + dwtransactionid: u32, + pdwcancelid: *mut u32, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + let values = unsafe { + std::slice::from_raw_parts(pitemvalues, dwcount as usize) + }; + + let (cancel_id, errors) = traits::OPCAsyncIO2::write(&self.0, dwcount, server_handles.to_vec(), values.to_vec(), dwtransactionid)?; + + write_caller_allocated_ptr!(pdwcancelid, cancel_id)?; + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } + + fn Refresh2( + &self, + dwsource: tagOPCDATASOURCE, + dwtransactionid: u32, + ) -> Result { + self.0.refresh2(dwsource, dwtransactionid) + } + + fn Cancel2(&self, dwcancelid: u32) -> Result<()> { + self.0.cancel2(dwcancelid) + } + + fn SetEnable(&self, benable: BOOL) -> Result<()> { + self.0.set_enable(benable.as_bool()) + } + + fn GetEnable(&self) -> Result { + let enabled = self.0.get_enable()?; + Ok(BOOL::from(enabled)) + } +} + +impl IOPCAsyncIO3_Impl for OPCAsyncIO3_Impl { + fn ReadMaxAge( + &self, + dwcount: u32, + phserver: *const u32, + pdwmaxage: *const u32, + dwtransactionid: u32, + pdwcancelid: *mut u32, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + let max_ages = unsafe { + std::slice::from_raw_parts(pdwmaxage, dwcount as usize) + }; + + let (cancel_id, errors) = self.0.read_max_age(dwcount, server_handles.to_vec(), max_ages.to_vec(), dwtransactionid)?; + + write_caller_allocated_ptr!(pdwcancelid, cancel_id)?; + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } + + fn WriteVQT( + &self, + dwcount: u32, + phserver: *const u32, + pitemvqt: *const tagOPCITEMVQT, + dwtransactionid: u32, + pdwcancelid: *mut u32, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + let item_vqt = unsafe { + std::slice::from_raw_parts(pitemvqt, dwcount as usize) + }; + + let (cancel_id, errors) = self.0.write_vqt(dwcount, server_handles.to_vec(), item_vqt.to_vec(), dwtransactionid)?; + + write_caller_allocated_ptr!(pdwcancelid, cancel_id)?; + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } + + fn RefreshMaxAge( + &self, + dwmaxage: u32, + dwtransactionid: u32, + ) -> Result { + self.0.refresh_max_age(dwmaxage, dwtransactionid) + } +} + +pub mod traits { + use super::*; + + pub trait OPCAsyncIO { + fn read(&self, connection: u32, source: tagOPCDATASOURCE, server_handles: Vec) -> Result<(u32, Vec)>; + + fn write(&self, connection: u32, server_handles: Vec, values: Vec) -> Result<(u32, Vec)>; + + fn refresh(&self, connection: u32, source: tagOPCDATASOURCE) -> Result; + + fn cancel(&self, transaction_id: u32) -> Result<()>; + } + + pub trait OPCAsyncIO2: OPCAsyncIO { + fn read(&self, count: u32, server_handles: Vec, transaction_id: u32) -> Result<(u32, Vec)>; + + fn write(&self, count: u32, server_handles: Vec, values: Vec, transaction_id: u32) -> Result<(u32, Vec)>; + + fn refresh2(&self, source: tagOPCDATASOURCE, transaction_id: u32) -> Result; + + fn cancel2(&self, cancel_id: u32) -> Result<()>; + + fn set_enable(&self, enable: bool) -> Result<()>; + + fn get_enable(&self) -> Result; + } + + pub trait OPCAsyncIO3: OPCAsyncIO2 { + fn read_max_age(&self, count: u32, server_handles: Vec, max_ages: Vec, transaction_id: u32) -> Result<(u32, Vec)>; + + fn write_vqt(&self, count: u32, server_handles: Vec, item_vqt: Vec, transaction_id: u32) -> Result<(u32, Vec)>; + + fn refresh_max_age(&self, max_age: u32, transaction_id: u32) -> Result; + } +} \ No newline at end of file diff --git a/opc_da/src/opc_browse.rs b/opc_da/src/opc_browse.rs new file mode 100644 index 0000000..1585a65 --- /dev/null +++ b/opc_da/src/opc_browse.rs @@ -0,0 +1,129 @@ +use windows_core::*; +use opc_da_bindings::*; +use opc_classic_utils::*; + +#[windows::core::implement(IOPCBrowse)] +pub struct OPCBrowse(T) +where + T: 'static + traits::OPCBrowse; + +impl IOPCBrowse_Impl for OPCBrowse_Impl { + fn GetProperties( + &self, + dwitemcount: u32, + pszitemids: *const PCWSTR, + breturnpropertyvalues: BOOL, + dwpropertycount: u32, + pdwpropertyids: *const u32, + ppitemproperties: *mut *mut tagOPCITEMPROPERTIES, + ) -> Result<()> { + let item_ids = unsafe { + std::slice::from_raw_parts(pszitemids, dwitemcount as usize) + }; + let property_ids = unsafe { + std::slice::from_raw_parts(pdwpropertyids, dwpropertycount as usize) + }; + + let item_ids: Vec = item_ids.iter().map(|pcwstr| { + unsafe { + CallerAllocatedWString::from_pcwstr(*pcwstr) + .to_string() + .unwrap_or_default() + } + }).collect(); + + let properties = self.0.get_properties(item_ids, breturnpropertyvalues.as_bool(), property_ids.to_vec())?; + + // Allocate and write properties + if !ppitemproperties.is_null() { + let properties_array = CallerAllocatedArray::from_slice(&properties)?; + unsafe { *ppitemproperties = properties_array.as_ptr() }; + } + + Ok(()) + } + + fn Browse( + &self, + szitemid: &PCWSTR, + pszcontinuationpoint: *mut PWSTR, + dwmaxelementsreturned: u32, + dwbrowsefilter: tagOPCBROWSEFILTER, + szelementnamefilter: &PCWSTR, + szvendorfilter: &PCWSTR, + breturnallproperties: BOOL, + breturnpropertyvalues: BOOL, + dwpropertycount: u32, + pdwpropertyids: *const u32, + pbmoreelements: *mut BOOL, + pdwcount: *mut u32, + ppbrowseelements: *mut *mut tagOPCBROWSEELEMENT, + ) -> Result<()> { + let item_id = unsafe { + CallerAllocatedWString::from_pcwstr(*szitemid) + .to_string() + .unwrap_or_default() + }; + let element_name_filter = unsafe { + CallerAllocatedWString::from_pcwstr(*szelementnamefilter) + .to_string() + .unwrap_or_default() + }; + let vendor_filter = unsafe { + CallerAllocatedWString::from_pcwstr(*szvendorfilter) + .to_string() + .unwrap_or_default() + }; + let property_ids = unsafe { + std::slice::from_raw_parts(pdwpropertyids, dwpropertycount as usize) + }; + + let (more_elements, count, elements) = self.0.browse( + item_id, + dwmaxelementsreturned, + dwbrowsefilter, + element_name_filter, + vendor_filter, + breturnallproperties.as_bool(), + breturnpropertyvalues.as_bool(), + property_ids.to_vec(), + )?; + + // Write output parameters + if !pszcontinuationpoint.is_null() { + unsafe { *pszcontinuationpoint = PWSTR::null() }; + } + if !pbmoreelements.is_null() { + unsafe { *pbmoreelements = BOOL::from(more_elements) }; + } + if !pdwcount.is_null() { + unsafe { *pdwcount = count }; + } + if !ppbrowseelements.is_null() { + let elements_array = CallerAllocatedArray::from_slice(&elements)?; + unsafe { *ppbrowseelements = elements_array.as_ptr() }; + } + + Ok(()) + } +} + +pub mod traits { + use super::*; + + pub trait OPCBrowse { + fn get_properties(&self, item_ids: Vec, return_property_values: bool, property_ids: Vec) -> Result>; + + fn browse( + &self, + item_id: String, + max_elements_returned: u32, + browse_filter: tagOPCBROWSEFILTER, + element_name_filter: String, + vendor_filter: String, + return_all_properties: bool, + return_property_values: bool, + property_ids: Vec, + ) -> Result<(bool, u32, Vec)>; + } +} \ No newline at end of file diff --git a/opc_da/src/opc_data_callback.rs b/opc_da/src/opc_data_callback.rs new file mode 100644 index 0000000..54f7277 --- /dev/null +++ b/opc_da/src/opc_data_callback.rs @@ -0,0 +1,166 @@ +use windows_core::*; +use opc_da_bindings::*; +use opc_classic_utils::*; + +#[windows::core::implement(IOPCDataCallback)] +pub struct OPCDataCallback(T) +where + T: 'static + traits::OPCDataCallback; + +impl IOPCDataCallback_Impl for OPCDataCallback_Impl { + fn OnDataChange( + &self, + dwtransid: u32, + hgroup: u32, + hrquality: HRESULT, + hrerror: HRESULT, + dwcount: u32, + phclientitems: *const u32, + pvvalues: *const windows::Win32::System::Variant::VARIANT, + pwqualities: *const u16, + pfttimestamps: *const windows::Win32::Foundation::FILETIME, + perrors: *const HRESULT, + ) -> Result<()> { + let client_items = unsafe { + std::slice::from_raw_parts(phclientitems, dwcount as usize) + }; + let values = unsafe { + std::slice::from_raw_parts(pvvalues, dwcount as usize) + }; + let qualities = unsafe { + std::slice::from_raw_parts(pwqualities, dwcount as usize) + }; + let timestamps = unsafe { + std::slice::from_raw_parts(pfttimestamps, dwcount as usize) + }; + let errors = unsafe { + std::slice::from_raw_parts(perrors, dwcount as usize) + }; + + self.0.on_data_change( + dwtransid, + hgroup, + hrquality, + hrerror, + client_items.to_vec(), + values.to_vec(), + qualities.to_vec(), + timestamps.to_vec(), + errors.to_vec(), + ) + } + + fn OnReadComplete( + &self, + dwtransid: u32, + hgroup: u32, + hrquality: HRESULT, + hrerror: HRESULT, + dwcount: u32, + phclientitems: *const u32, + pvvalues: *const windows::Win32::System::Variant::VARIANT, + pwqualities: *const u16, + pfttimestamps: *const windows::Win32::Foundation::FILETIME, + perrors: *const HRESULT, + ) -> Result<()> { + let client_items = unsafe { + std::slice::from_raw_parts(phclientitems, dwcount as usize) + }; + let values = unsafe { + std::slice::from_raw_parts(pvvalues, dwcount as usize) + }; + let qualities = unsafe { + std::slice::from_raw_parts(pwqualities, dwcount as usize) + }; + let timestamps = unsafe { + std::slice::from_raw_parts(pfttimestamps, dwcount as usize) + }; + let errors = unsafe { + std::slice::from_raw_parts(perrors, dwcount as usize) + }; + + self.0.on_read_complete( + dwtransid, + hgroup, + hrquality, + hrerror, + client_items.to_vec(), + values.to_vec(), + qualities.to_vec(), + timestamps.to_vec(), + errors.to_vec(), + ) + } + + fn OnWriteComplete( + &self, + dwtransid: u32, + hgroup: u32, + hrmastererr: HRESULT, + dwcount: u32, + pclienthandles: *const u32, + perrors: *const HRESULT, + ) -> Result<()> { + let client_items = unsafe { + std::slice::from_raw_parts(pclienthandles, dwcount as usize) + }; + let errors = unsafe { + std::slice::from_raw_parts(perrors, dwcount as usize) + }; + + self.0.on_write_complete( + dwtransid, + hgroup, + hrmastererr, + client_items.to_vec(), + errors.to_vec(), + ) + } + + fn OnCancelComplete(&self, dwtransid: u32, hgroup: u32) -> Result<()> { + self.0.on_cancel_complete(dwtransid, hgroup) + } +} + +pub mod traits { + use super::*; + + pub trait OPCDataCallback { + fn on_data_change( + &self, + transaction_id: u32, + group: u32, + quality: HRESULT, + error: HRESULT, + client_items: Vec, + values: Vec, + qualities: Vec, + timestamps: Vec, + errors: Vec, + ) -> Result<()>; + + fn on_read_complete( + &self, + transaction_id: u32, + group: u32, + quality: HRESULT, + error: HRESULT, + client_items: Vec, + values: Vec, + qualities: Vec, + timestamps: Vec, + errors: Vec, + ) -> Result<()>; + + fn on_write_complete( + &self, + transaction_id: u32, + group: u32, + master_error: HRESULT, + client_items: Vec, + errors: Vec, + ) -> Result<()>; + + fn on_cancel_complete(&self, transaction_id: u32, group: u32) -> Result<()>; + } +} \ No newline at end of file diff --git a/opc_da/src/opc_group.rs b/opc_da/src/opc_group.rs new file mode 100644 index 0000000..1a9c1e0 --- /dev/null +++ b/opc_da/src/opc_group.rs @@ -0,0 +1,203 @@ +use opc_classic_utils::*; +use opc_da_bindings::*; +use windows_core::*; + +#[windows::core::implement(IOPCGroupStateMgt, IOPCGroupStateMgt2)] +pub struct OPCGroup(T) +where + T: 'static + traits::OPCGroup + traits::OPCGroupStateMgt2; + +impl IOPCGroupStateMgt_Impl for OPCGroup_Impl { + fn GetState( + &self, + pupdateRate: *mut u32, + pactive: *mut BOOL, + ppname: *mut PWSTR, + ptimebias: *mut i32, + ppercentdeadband: *mut f32, + plcid: *mut u32, + phclientgroup: *mut u32, + phservergroup: *mut u32, + ) -> Result<()> { + let state = self.0.get_state()?; + + write_caller_allocated_ptr!(pupdateRate, state.update_rate)?; + write_caller_allocated_ptr!(pactive, BOOL::from(state.active))?; + write_caller_allocated_ptr!(ptimebias, state.time_bias)?; + write_caller_allocated_ptr!(ppercentdeadband, state.percent_deadband)?; + write_caller_allocated_ptr!(plcid, state.locale_id)?; + write_caller_allocated_ptr!(phclientgroup, state.client_group)?; + write_caller_allocated_ptr!(phservergroup, state.server_group)?; + + // Handle name separately as it's a string + if !ppname.is_null() { + let name_ptr = alloc_callee_wstring!(state.name)?; + unsafe { *ppname = name_ptr }; + } + + Ok(()) + } + + fn SetState( + &self, + prequestedupdaterate: *const u32, + previsedupdaterate: *mut u32, + pactive: *const BOOL, + ptimebias: *const i32, + ppercentdeadband: *const f32, + plcid: *const u32, + phclientgroup: *const u32, + ) -> Result<()> { + let requested_update_rate = if !prequestedupdaterate.is_null() { + Some(unsafe { *prequestedupdaterate }) + } else { + None + }; + + let active = if !pactive.is_null() { + Some(unsafe { (*pactive).as_bool() }) + } else { + None + }; + + let time_bias = if !ptimebias.is_null() { + Some(unsafe { *ptimebias }) + } else { + None + }; + + let percent_deadband = if !ppercentdeadband.is_null() { + Some(unsafe { *ppercentdeadband }) + } else { + None + }; + + let locale_id = if !plcid.is_null() { + Some(unsafe { *plcid }) + } else { + None + }; + + let client_group = if !phclientgroup.is_null() { + Some(unsafe { *phclientgroup }) + } else { + None + }; + + let ( + revised_update_rate, + new_active, + new_time_bias, + new_percent_deadband, + new_locale_id, + new_client_group, + ) = self.0.set_state( + requested_update_rate, + active, + time_bias, + percent_deadband, + locale_id, + client_group, + )?; + + write_caller_allocated_ptr!(previsedupdaterate, revised_update_rate)?; + + Ok(()) + } + + fn SetName(&self, szname: &PCWSTR) -> Result<()> { + let name = unsafe { + CallerAllocatedWString::from_pcwstr(*szname) + .to_string() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?; + + self.0.set_name(name) + } + + fn CloneGroup(&self, szname: &PCWSTR, riid: *const GUID) -> Result { + let name = unsafe { + CallerAllocatedWString::from_pcwstr(*szname) + .to_string() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?; + + self.0.clone_group(name, riid) + } +} + +impl IOPCGroupStateMgt2_Impl for OPCGroup_Impl { + fn SetKeepAlive(&self, dwkeepalivetime: u32) -> Result { + self.0.set_keep_alive(dwkeepalivetime) + } + + fn GetKeepAlive(&self) -> Result { + self.0.get_keep_alive() + } +} + +pub mod traits { + use super::*; + + #[derive(Debug, Clone)] + pub struct GroupState { + pub update_rate: u32, + pub active: bool, + pub name: String, + pub time_bias: i32, + pub percent_deadband: f32, + pub locale_id: u32, + pub client_group: u32, + pub server_group: u32, + } + + #[derive(Debug, Clone)] + pub struct GroupState2 { + pub update_rate: u32, + pub active: bool, + pub name: String, + pub time_bias: i32, + pub percent_deadband: f32, + pub locale_id: u32, + pub client_group: u32, + pub server_group: u32, + pub keep_alive: u32, + } + + pub trait OPCGroup { + fn get_state(&self) -> Result; + + fn set_state( + &self, + requested_update_rate: Option, + active: Option, + time_bias: Option, + percent_deadband: Option, + locale_id: Option, + client_group: Option, + ) -> Result<(u32, bool, i32, f32, u32, u32)>; + + fn set_name(&self, name: String) -> Result<()>; + + fn clone_group(&self, name: String, riid: *const GUID) -> Result; + } + + pub trait OPCGroupStateMgt2: OPCGroup { + fn get_state2(&self) -> Result; + + fn set_state2( + &self, + requested_update_rate: Option, + active: Option, + time_bias: Option, + percent_deadband: Option, + locale_id: Option, + client_group: Option, + keep_alive: Option, + ) -> Result<(u32, bool, i32, f32, u32, u32, u32)>; + + fn set_keep_alive(&self, keep_alive_time: u32) -> Result; + + fn get_keep_alive(&self) -> Result; + } +} diff --git a/opc_da/src/opc_item.rs b/opc_da/src/opc_item.rs new file mode 100644 index 0000000..6b45919 --- /dev/null +++ b/opc_da/src/opc_item.rs @@ -0,0 +1,247 @@ +use opc_classic_utils::*; +use opc_da_bindings::*; +use windows_core::*; + +#[windows::core::implement(IOPCItemMgt)] +pub struct OPCItem(T) +where + T: 'static + traits::OPCItem; + +impl IOPCItemMgt_Impl for OPCItem_Impl { + fn AddItems( + &self, + dwcount: u32, + pitemarray: *const tagOPCITEMDEF, + ppaddresults: *mut *mut tagOPCITEMRESULT, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let items = unsafe { std::slice::from_raw_parts(pitemarray, dwcount as usize) }; + + let item_defs: Vec = items + .iter() + .map(|item| traits::ItemDefinition { + access_path: unsafe { + CallerAllocatedWString::from_pwstr(item.szAccessPath) + .to_string() + .unwrap_or_default() + }, + item_id: unsafe { + CallerAllocatedWString::from_pwstr(item.szItemID) + .to_string() + .unwrap_or_default() + }, + active: item.bActive.as_bool(), + client_handle: item.hClient, + blob_size: item.dwBlobSize, + blob: if !item.pBlob.is_null() { + unsafe { + std::slice::from_raw_parts(item.pBlob, item.dwBlobSize as usize).to_vec() + } + } else { + Vec::new() + }, + requested_data_type: item.vtRequestedDataType, + }) + .collect(); + + let (results, errors) = self.0.add_items(item_defs)?; + + // Allocate and write results + let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?; + unsafe { *ppaddresults = results_ptr.as_ptr() }; + + // Allocate and write errors + let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; + unsafe { *pperrors = errors_ptr.as_ptr() }; + + Ok(()) + } + + fn ValidateItems( + &self, + dwcount: u32, + pitemarray: *const tagOPCITEMDEF, + bblobupdate: BOOL, + ppvalidationresults: *mut *mut tagOPCITEMRESULT, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let items = unsafe { std::slice::from_raw_parts(pitemarray, dwcount as usize) }; + + let item_defs: Vec = items + .iter() + .map(|item| traits::ItemDefinition { + access_path: unsafe { + if item.szAccessPath.is_null() { + String::new() + } else { + CallerAllocatedWString::from_pcwstr(PCWSTR(item.szAccessPath.0)) + .to_string() + .unwrap_or_default() + } + }, + item_id: unsafe { + if item.szItemID.is_null() { + String::new() + } else { + CallerAllocatedWString::from_pcwstr(PCWSTR(item.szItemID.0)) + .to_string() + .unwrap_or_default() + } + }, + active: item.bActive.as_bool(), + client_handle: item.hClient, + blob_size: item.dwBlobSize, + blob: if !item.pBlob.is_null() { + unsafe { + std::slice::from_raw_parts(item.pBlob, item.dwBlobSize as usize).to_vec() + } + } else { + Vec::new() + }, + requested_data_type: item.vtRequestedDataType, + }) + .collect(); + + let (results, errors) = self.0.validate_items(item_defs, bblobupdate.as_bool())?; + + // Allocate and write results + let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?; + unsafe { *ppvalidationresults = results_ptr.into_raw() }; + + // Allocate and write errors + let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; + unsafe { *pperrors = errors_ptr.into_raw() }; + + Ok(()) + } + + fn RemoveItems( + &self, + dwcount: u32, + phserver: *const u32, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { std::slice::from_raw_parts(phserver, dwcount as usize) }; + + let errors = self.0.remove_items(server_handles.to_vec())?; + + // Allocate and write errors + let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; + unsafe { *pperrors = errors_ptr.into_raw() }; + + Ok(()) + } + + fn SetActiveState( + &self, + dwcount: u32, + phserver: *const u32, + bactive: BOOL, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { std::slice::from_raw_parts(phserver, dwcount as usize) }; + + let errors = self + .0 + .set_active_state(server_handles.to_vec(), bactive.as_bool())?; + + // Allocate and write errors + let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; + unsafe { *pperrors = errors_ptr.into_raw() }; + + Ok(()) + } + + fn SetClientHandles( + &self, + dwcount: u32, + phserver: *const u32, + phclient: *const u32, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { std::slice::from_raw_parts(phserver, dwcount as usize) }; + let client_handles = unsafe { std::slice::from_raw_parts(phclient, dwcount as usize) }; + + let errors = self + .0 + .set_client_handles(server_handles.to_vec(), client_handles.to_vec())?; + + // Allocate and write errors + let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; + unsafe { *pperrors = errors_ptr.into_raw() }; + + Ok(()) + } + + fn SetDatatypes( + &self, + dwcount: u32, + phserver: *const u32, + pvrequesteddatatypes: *const u16, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { std::slice::from_raw_parts(phserver, dwcount as usize) }; + let requested_datatypes = + unsafe { std::slice::from_raw_parts(pvrequesteddatatypes, dwcount as usize) }; + + let errors = self + .0 + .set_datatypes(server_handles.to_vec(), requested_datatypes.to_vec())?; + + // Allocate and write errors + let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; + unsafe { *pperrors = errors_ptr.into_raw() }; + + Ok(()) + } + + fn CreateEnumerator(&self, riid: *const GUID) -> Result { + self.0.create_enumerator(riid) + } +} + +pub mod traits { + use super::*; + + #[derive(Debug, Clone)] + pub struct ItemDefinition { + pub access_path: String, + pub item_id: String, + pub active: bool, + pub client_handle: u32, + pub blob_size: u32, + pub blob: Vec, + pub requested_data_type: u16, + } + + pub trait OPCItem { + fn add_items( + &self, + items: Vec, + ) -> Result<(Vec, Vec)>; + + fn validate_items( + &self, + items: Vec, + blob_update: bool, + ) -> Result<(Vec, Vec)>; + + fn remove_items(&self, server_handles: Vec) -> Result>; + + fn set_active_state(&self, server_handles: Vec, active: bool) -> Result>; + + fn set_client_handles( + &self, + server_handles: Vec, + client_handles: Vec, + ) -> Result>; + + fn set_datatypes( + &self, + server_handles: Vec, + requested_datatypes: Vec, + ) -> Result>; + + fn create_enumerator(&self, riid: *const GUID) -> Result; + } +} diff --git a/opc_da/src/opc_server.rs b/opc_da/src/opc_server.rs new file mode 100644 index 0000000..765d08e --- /dev/null +++ b/opc_da/src/opc_server.rs @@ -0,0 +1,143 @@ +use windows_core::*; +use opc_da_bindings::*; +use opc_classic_utils::*; + +#[windows::core::implement(IOPCServer)] +pub struct OPCServer(T) +where + T: 'static + traits::OPCServer; + +impl IOPCServer_Impl for OPCServer_Impl { + fn AddGroup( + &self, + szname: &PCWSTR, + bactive: BOOL, + dwrequestedupdaterate: u32, + hclientgroup: u32, + ptimebias: *const i32, + ppercentdeadband: *const f32, + dwlcid: u32, + phservergroup: *mut u32, + previsedupdaterate: *mut u32, + riid: *const GUID, + ppunk: OutRef<'_, IUnknown>, + ) -> Result<()> { + let name = unsafe { + CallerAllocatedWString::from_pcwstr(*szname) + .to_string() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?; + + let time_bias = if !ptimebias.is_null() { + Some(unsafe { *ptimebias }) + } else { + None + }; + + let percent_deadband = if !ppercentdeadband.is_null() { + Some(unsafe { *ppercentdeadband }) + } else { + None + }; + + let (server_group, revised_update_rate, group_interface) = self.0.add_group( + name, + bactive.as_bool(), + dwrequestedupdaterate, + hclientgroup, + time_bias, + percent_deadband, + dwlcid, + )?; + + write_caller_allocated_ptr!(phservergroup, server_group)?; + write_caller_allocated_ptr!(previsedupdaterate, revised_update_rate)?; + *ppunk = group_interface; + + Ok(()) + } + + fn GetErrorString( + &self, + dwerror: HRESULT, + dwlocale: u32, + ) -> Result { + alloc_callee_wstring!(self.0.get_error_string(dwerror, dwlocale)?) + } + + fn GetGroupByName( + &self, + szname: &PCWSTR, + riid: *const GUID, + ) -> Result { + let name = unsafe { + CallerAllocatedWString::from_pcwstr(*szname) + .to_string() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?; + + self.0.get_group_by_name(name, riid) + } + + fn GetStatus(&self) -> Result<*mut tagOPCSERVERSTATUS> { + let status = self.0.get_status()?; + let ptr = CalleeAllocatedPtr::from_value(&status)?; + Ok(ptr.into_raw()) + } + + fn RemoveGroup( + &self, + hservergroup: u32, + bforce: BOOL, + ) -> Result<()> { + self.0.remove_group(hservergroup, bforce.as_bool()) + } + + fn CreateGroupEnumerator( + &self, + dwscope: tagOPCENUMSCOPE, + riid: *const GUID, + ) -> Result { + self.0.create_group_enumerator(dwscope, riid) + } +} + +pub mod traits { + use super::*; + use opc_da_bindings::tagOPCSERVERSTATUS; + + pub trait OPCServer { + fn add_group( + &self, + name: String, + active: bool, + requested_update_rate: u32, + client_group: u32, + time_bias: Option, + percent_deadband: Option, + locale_id: u32, + ) -> Result<(u32, u32, IUnknown)>; + + fn get_error_string( + &self, + error: HRESULT, + locale: u32, + ) -> Result; + + fn get_group_by_name( + &self, + name: String, + riid: *const GUID, + ) -> Result; + + fn get_status(&self) -> Result; + + fn remove_group(&self, server_group: u32, force: bool) -> Result<()>; + + fn create_group_enumerator( + &self, + scope: tagOPCENUMSCOPE, + riid: *const GUID, + ) -> Result; + } +} \ No newline at end of file diff --git a/opc_da/src/opc_sync_io.rs b/opc_da/src/opc_sync_io.rs new file mode 100644 index 0000000..b1a35e3 --- /dev/null +++ b/opc_da/src/opc_sync_io.rs @@ -0,0 +1,249 @@ +use windows_core::*; +use opc_da_bindings::*; +use opc_classic_utils::*; + +#[windows::core::implement(IOPCSyncIO, IOPCSyncIO2)] +pub struct OPCSyncIO(T) +where + T: 'static + traits::OPCSyncIO + traits::OPCSyncIO2; + +impl IOPCSyncIO_Impl for OPCSyncIO_Impl { + fn Read( + &self, + dwsource: tagOPCDATASOURCE, + dwcount: u32, + phserver: *const u32, + ppitemvalues: *mut *mut tagOPCITEMSTATE, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + + let (item_states, errors) = self.0.read(dwsource, server_handles.to_vec())?; + + // Allocate and write item states + if !ppitemvalues.is_null() { + let size = std::mem::size_of::() * item_states.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + item_states.as_ptr(), + ptr.cast(), + item_states.len() + ); + *ppitemvalues = ptr.cast(); + } + } + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } + + fn Write( + &self, + dwcount: u32, + phserver: *const u32, + pitemvalues: *const windows::Win32::System::Variant::VARIANT, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + let values = unsafe { + std::slice::from_raw_parts(pitemvalues, dwcount as usize) + }; + + let errors = self.0.write(server_handles.to_vec(), values.to_vec())?; + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } +} + +impl IOPCSyncIO2_Impl for OPCSyncIO_Impl { + fn ReadMaxAge( + &self, + dwcount: u32, + phserver: *const u32, + pdwmaxage: *const u32, + ppvvalues: *mut *mut windows::Win32::System::Variant::VARIANT, + ppwqualities: *mut *mut u16, + ppfttimestamps: *mut *mut windows::Win32::Foundation::FILETIME, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + let max_ages = unsafe { + std::slice::from_raw_parts(pdwmaxage, dwcount as usize) + }; + + let (values, qualities, timestamps, errors) = self.0.read_max_age(server_handles.to_vec(), max_ages.to_vec())?; + + // Allocate and write values + if !ppvvalues.is_null() { + let size = std::mem::size_of::() * values.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + values.as_ptr(), + ptr.cast(), + values.len() + ); + *ppvvalues = ptr.cast(); + } + } + + // Allocate and write qualities + if !ppwqualities.is_null() { + let size = std::mem::size_of::() * qualities.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + qualities.as_ptr(), + ptr.cast(), + qualities.len() + ); + *ppwqualities = ptr.cast(); + } + } + + // Allocate and write timestamps + if !ppfttimestamps.is_null() { + let size = std::mem::size_of::() * timestamps.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + timestamps.as_ptr(), + ptr.cast(), + timestamps.len() + ); + *ppfttimestamps = ptr.cast(); + } + } + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } + + fn WriteVQT( + &self, + dwcount: u32, + phserver: *const u32, + pitemvqt: *const tagOPCITEMVQT, + pperrors: *mut *mut HRESULT, + ) -> Result<()> { + let server_handles = unsafe { + std::slice::from_raw_parts(phserver, dwcount as usize) + }; + let item_vqt = unsafe { + std::slice::from_raw_parts(pitemvqt, dwcount as usize) + }; + + let errors = self.0.write_vqt(server_handles.to_vec(), item_vqt.to_vec())?; + + // Allocate and write errors + if !pperrors.is_null() { + let size = std::mem::size_of::() * errors.len(); + let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; + if ptr.is_null() { + return Err(windows::core::Error::from_win32()); + } + + unsafe { + std::ptr::copy_nonoverlapping( + errors.as_ptr(), + ptr.cast(), + errors.len() + ); + *pperrors = ptr.cast(); + } + } + + Ok(()) + } +} + +pub mod traits { + use super::*; + + pub trait OPCSyncIO { + fn read(&self, source: tagOPCDATASOURCE, server_handles: Vec) -> Result<(Vec, Vec)>; + + fn write(&self, server_handles: Vec, values: Vec) -> Result>; + } + + pub trait OPCSyncIO2: OPCSyncIO { + fn read_max_age(&self, server_handles: Vec, max_ages: Vec) -> Result<(Vec, Vec, Vec, Vec)>; + + fn write_vqt(&self, server_handles: Vec, item_vqt: Vec) -> Result>; + } +} \ No newline at end of file From c533f563f05d28ab8b0bb80996b0487b4b5c341c Mon Sep 17 00:00:00 2001 From: Ronbb Date: Sun, 27 Jul 2025 23:00:12 +0800 Subject: [PATCH 06/10] Rename `to_string()` to `to_string_lossy()` --- .../examples/convenience_functions.rs | 8 +- opc_classic_utils/src/memory/tests.rs | 8 +- opc_classic_utils/src/memory/wstring.rs | 2 +- opc_comn/src/client/mod.rs | 1 + opc_comn/src/lib.rs | 12 +- opc_comn/src/opc_common.rs | 307 ------------------ opc_comn/src/server/mod.rs | 2 + opc_comn/src/server/opc_server_base.rs | 57 ++++ opc_comn/src/server/traits/mod.rs | 2 + opc_comn/src/server/traits/opc_common.rs | 7 + opc_comn/src/server/traits/opc_shutdown.rs | 3 + opc_da/src/opc_browse.rs | 8 +- opc_da/src/opc_group.rs | 4 +- opc_da/src/opc_item.rs | 8 +- opc_da/src/opc_server.rs | 54 +-- 15 files changed, 110 insertions(+), 373 deletions(-) create mode 100644 opc_comn/src/client/mod.rs delete mode 100644 opc_comn/src/opc_common.rs create mode 100644 opc_comn/src/server/mod.rs create mode 100644 opc_comn/src/server/opc_server_base.rs create mode 100644 opc_comn/src/server/traits/mod.rs create mode 100644 opc_comn/src/server/traits/opc_common.rs create mode 100644 opc_comn/src/server/traits/opc_shutdown.rs diff --git a/opc_classic_utils/examples/convenience_functions.rs b/opc_classic_utils/examples/convenience_functions.rs index f572f2f..d51dacb 100644 --- a/opc_classic_utils/examples/convenience_functions.rs +++ b/opc_classic_utils/examples/convenience_functions.rs @@ -102,11 +102,11 @@ fn demonstrate_string_convenience_functions() { // Convert back to String unsafe { - let converted1 = wstring1.to_string().unwrap(); + let converted1 = wstring1.to_string_lossy().unwrap(); println!(" Converted back to String: '{}'", converted1); assert_eq!(converted1, str_slice); - let converted2 = wstring2.to_string().unwrap(); + let converted2 = wstring2.to_string_lossy().unwrap(); println!(" Converted back to String: '{}'", converted2); assert_eq!(converted2, owned_string); @@ -121,7 +121,7 @@ fn demonstrate_string_convenience_functions() { let null_wstring = CallerAllocatedWString::default(); unsafe { - let result = null_wstring.to_string(); + let result = null_wstring.to_string_lossy(); assert!(result.is_none()); println!(" Null string correctly returns None"); } @@ -147,7 +147,7 @@ fn demonstrate_real_world_scenario() { // Server would use these parameters unsafe { - let name = server_name.to_string().unwrap(); + let name = server_name.to_string_lossy().unwrap(); let count = *item_count.as_ptr(); println!(" Server received:"); println!(" - Server name: '{}'", name); diff --git a/opc_classic_utils/src/memory/tests.rs b/opc_classic_utils/src/memory/tests.rs index 0aeef95..ad7583a 100644 --- a/opc_classic_utils/src/memory/tests.rs +++ b/opc_classic_utils/src/memory/tests.rs @@ -94,7 +94,7 @@ fn test_caller_allocated_wstring_from_str() { // Verify the string was converted correctly unsafe { - let converted = wstring.to_string().unwrap(); + let converted = wstring.to_string_lossy().unwrap(); assert_eq!(converted, test_string); } } @@ -108,7 +108,7 @@ fn test_caller_allocated_wstring_from_string() { // Verify the string was converted correctly unsafe { - let converted = wstring.to_string().unwrap(); + let converted = wstring.to_string_lossy().unwrap(); assert_eq!(converted, test_string); } } @@ -177,7 +177,7 @@ fn test_wstring_null_conversion() { let callee_wstring = CalleeAllocatedWString::default(); unsafe { - assert!(caller_wstring.to_string().is_none()); + assert!(caller_wstring.to_string_lossy().is_none()); assert!(callee_wstring.to_string().is_none()); assert!(caller_wstring.to_os_string().is_none()); assert!(callee_wstring.to_os_string().is_none()); @@ -193,7 +193,7 @@ fn test_from_str_trait() { // Verify the string was converted correctly unsafe { - let converted = wstring.to_string().unwrap(); + let converted = wstring.to_string_lossy().unwrap(); assert_eq!(converted, test_string); } } diff --git a/opc_classic_utils/src/memory/wstring.rs b/opc_classic_utils/src/memory/wstring.rs index ccec571..e9a7648 100644 --- a/opc_classic_utils/src/memory/wstring.rs +++ b/opc_classic_utils/src/memory/wstring.rs @@ -80,7 +80,7 @@ impl CallerAllocatedWString { /// # Safety /// /// The caller must ensure the pointer is valid and points to a null-terminated wide string. - pub unsafe fn to_string(&self) -> Option { + pub unsafe fn to_string_lossy(&self) -> Option { if self.ptr.is_null() { return None; } diff --git a/opc_comn/src/client/mod.rs b/opc_comn/src/client/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/opc_comn/src/client/mod.rs @@ -0,0 +1 @@ + diff --git a/opc_comn/src/lib.rs b/opc_comn/src/lib.rs index f038f08..c07f47e 100644 --- a/opc_comn/src/lib.rs +++ b/opc_comn/src/lib.rs @@ -1,10 +1,2 @@ -mod opc_common; - -pub use opc_common::{ - OPCCommon, OPCEnumGUID, OPCServerList, OPCServerList2, OPCShutdown, - traits::{ - OPCCommon as OPCCommonTrait, OPCEnumGUID as OPCEnumGUIDTrait, - OPCServerList as OPCServerListTrait, OPCServerList2 as OPCServerList2Trait, - OPCShutdown as OPCShutdownTrait, - }, -}; +pub mod client; +pub mod server; diff --git a/opc_comn/src/opc_common.rs b/opc_comn/src/opc_common.rs deleted file mode 100644 index 3c4613d..0000000 --- a/opc_comn/src/opc_common.rs +++ /dev/null @@ -1,307 +0,0 @@ -#[windows::core::implement(opc_comn_bindings::IOPCCommon)] -pub struct OPCCommon(T) -where - T: 'static + traits::OPCCommon; - -impl opc_comn_bindings::IOPCCommon_Impl for OPCCommon_Impl { - fn SetLocaleID(&self, dwlcid: u32) -> windows_core::Result<()> { - self.0.set_locale_id(dwlcid) - } - - fn GetLocaleID(&self) -> windows_core::Result { - self.0.get_locale_id() - } - - fn QueryAvailableLocaleIDs( - &self, - pdwcount: *mut u32, - pdwlcid: *mut *mut u32, - ) -> windows_core::Result<()> { - let locale_ids = self.0.query_available_locale_ids()?; - opc_classic_utils::write_caller_allocated_ptr!(pdwcount, locale_ids.len().try_into()?)?; - opc_classic_utils::write_caller_allocated_array!(pdwlcid, locale_ids.as_slice())?; - - Ok(()) - } - - fn GetErrorString( - &self, - dwerror: windows_core::HRESULT, - ) -> windows_core::Result { - opc_classic_utils::alloc_callee_wstring!(self.0.get_error_string(dwerror)?) - } - - fn SetClientName(&self, szname: &windows_core::PCWSTR) -> windows_core::Result<()> { - self.0.set_client_name(unsafe { - opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szname) - .to_string() - .ok_or(windows::Win32::Foundation::E_POINTER) - }?) - } -} - -#[windows::core::implement(opc_comn_bindings::IOPCEnumGUID)] -pub struct OPCEnumGUID(T) -where - T: 'static + traits::OPCEnumGUID; - -impl opc_comn_bindings::IOPCEnumGUID_Impl for OPCEnumGUID_Impl { - fn Next( - &self, - celt: u32, - rgelt: *mut windows_core::GUID, - pceltfetched: *mut u32, - ) -> windows_core::Result<()> { - let guids = self.0.next(celt)?; - let fetched = guids.len() as u32; - - if !rgelt.is_null() { - opc_classic_utils::copy_to_caller_array!(rgelt, &guids)?; - } - - opc_classic_utils::write_caller_allocated_ptr!(pceltfetched, fetched)?; - - Ok(()) - } - - fn Skip(&self, celt: u32) -> windows_core::Result<()> { - self.0.skip(celt) - } - - fn Reset(&self) -> windows_core::Result<()> { - self.0.reset() - } - - fn Clone(&self) -> windows_core::Result { - self.0.clone_enum() - } -} - -#[windows::core::implement(opc_comn_bindings::IOPCServerList)] -pub struct OPCServerList(T) -where - T: 'static + traits::OPCServerList; - -impl opc_comn_bindings::IOPCServerList_Impl for OPCServerList_Impl { - fn EnumClassesOfCategories( - &self, - cimplemented: u32, - rgcatidimpl: *const windows_core::GUID, - crequired: u32, - rgcatidreq: *const windows_core::GUID, - ) -> windows_core::Result { - let implemented = if !rgcatidimpl.is_null() { - unsafe { - opc_classic_utils::CallerAllocatedArray::new( - rgcatidimpl as *mut windows_core::GUID, - cimplemented as usize, - ) - .as_slice() - .unwrap_or(&[]) - .to_vec() - } - } else { - Vec::new() - }; - - let required = if !rgcatidreq.is_null() { - unsafe { - opc_classic_utils::CallerAllocatedArray::new( - rgcatidreq as *mut windows_core::GUID, - crequired as usize, - ) - .as_slice() - .unwrap_or(&[]) - .to_vec() - } - } else { - Vec::new() - }; - - self.0.enum_classes_of_categories(implemented, required) - } - - fn GetClassDetails( - &self, - clsid: *const windows_core::GUID, - ppszprogid: *mut windows_core::PWSTR, - ppszusertype: *mut windows_core::PWSTR, - ) -> windows_core::Result<()> { - let (prog_id, user_type) = self.0.get_class_details(unsafe { *clsid })?; - - opc_classic_utils::write_caller_allocated_ptr!( - ppszprogid, - opc_classic_utils::alloc_callee_wstring!(prog_id)? - )?; - opc_classic_utils::write_caller_allocated_ptr!( - ppszusertype, - opc_classic_utils::alloc_callee_wstring!(user_type)? - )?; - - Ok(()) - } - - fn CLSIDFromProgID( - &self, - szprogid: &windows_core::PCWSTR, - ) -> windows_core::Result { - let prog_id = unsafe { - opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szprogid) - .to_string() - .ok_or(windows::Win32::Foundation::E_POINTER) - }?; - - self.0.clsid_from_prog_id(prog_id) - } -} - -#[windows::core::implement(opc_comn_bindings::IOPCServerList2)] -pub struct OPCServerList2(T) -where - T: 'static + traits::OPCServerList2; - -impl opc_comn_bindings::IOPCServerList2_Impl for OPCServerList2_Impl { - fn EnumClassesOfCategories( - &self, - cimplemented: u32, - rgcatidimpl: *const windows_core::GUID, - crequired: u32, - rgcatidreq: *const windows_core::GUID, - ) -> windows_core::Result { - let implemented = if !rgcatidimpl.is_null() { - unsafe { - opc_classic_utils::CallerAllocatedArray::new( - rgcatidimpl as *mut windows_core::GUID, - cimplemented as usize, - ) - .as_slice() - .unwrap_or(&[]) - .to_vec() - } - } else { - Vec::new() - }; - - let required = if !rgcatidreq.is_null() { - unsafe { - opc_classic_utils::CallerAllocatedArray::new( - rgcatidreq as *mut windows_core::GUID, - crequired as usize, - ) - .as_slice() - .unwrap_or(&[]) - .to_vec() - } - } else { - Vec::new() - }; - - self.0.enum_classes_of_categories(implemented, required) - } - - fn GetClassDetails( - &self, - clsid: *const windows_core::GUID, - ppszprogid: *mut windows_core::PWSTR, - ppszusertype: *mut windows_core::PWSTR, - ppszverindprogid: *mut windows_core::PWSTR, - ) -> windows_core::Result<()> { - let (prog_id, user_type, ver_ind_prog_id) = self.0.get_class_details(unsafe { *clsid })?; - - opc_classic_utils::write_caller_allocated_ptr!( - ppszprogid, - opc_classic_utils::alloc_callee_wstring!(prog_id)? - )?; - opc_classic_utils::write_caller_allocated_ptr!( - ppszusertype, - opc_classic_utils::alloc_callee_wstring!(user_type)? - )?; - opc_classic_utils::write_caller_allocated_ptr!( - ppszverindprogid, - opc_classic_utils::alloc_callee_wstring!(ver_ind_prog_id)? - )?; - - Ok(()) - } - - fn CLSIDFromProgID( - &self, - szprogid: &windows_core::PCWSTR, - ) -> windows_core::Result { - let prog_id = unsafe { - opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szprogid) - .to_string() - .ok_or(windows::Win32::Foundation::E_POINTER) - }?; - - self.0.clsid_from_prog_id(prog_id) - } -} - -#[windows::core::implement(opc_comn_bindings::IOPCShutdown)] -pub struct OPCShutdown(T) -where - T: 'static + traits::OPCShutdown; - -impl opc_comn_bindings::IOPCShutdown_Impl for OPCShutdown_Impl { - fn ShutdownRequest(&self, szreason: &windows_core::PCWSTR) -> windows_core::Result<()> { - let reason = unsafe { - opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szreason) - .to_string() - .ok_or(windows::Win32::Foundation::E_POINTER) - }?; - - self.0.shutdown_request(reason) - } -} - -pub mod traits { - pub trait OPCCommon { - fn set_locale_id(&self, dwlcid: u32) -> windows_core::Result<()>; - fn get_locale_id(&self) -> windows_core::Result; - fn query_available_locale_ids(&self) -> windows_core::Result>; - fn get_error_string(&self, dwerror: windows_core::HRESULT) -> windows_core::Result; - fn set_client_name(&self, szname: String) -> windows_core::Result<()>; - } - - pub trait OPCEnumGUID { - fn next(&self, celt: u32) -> windows_core::Result>; - fn skip(&self, celt: u32) -> windows_core::Result<()>; - fn reset(&self) -> windows_core::Result<()>; - fn clone_enum(&self) -> windows_core::Result; - } - - pub trait OPCServerList { - fn enum_classes_of_categories( - &self, - implemented: Vec, - required: Vec, - ) -> windows_core::Result; - - fn get_class_details( - &self, - clsid: windows_core::GUID, - ) -> windows_core::Result<(String, String)>; - - fn clsid_from_prog_id(&self, prog_id: String) -> windows_core::Result; - } - - pub trait OPCServerList2 { - fn enum_classes_of_categories( - &self, - implemented: Vec, - required: Vec, - ) -> windows_core::Result; - - fn get_class_details( - &self, - clsid: windows_core::GUID, - ) -> windows_core::Result<(String, String, String)>; - - fn clsid_from_prog_id(&self, prog_id: String) -> windows_core::Result; - } - - pub trait OPCShutdown { - fn shutdown_request(&self, reason: String) -> windows_core::Result<()>; - } -} diff --git a/opc_comn/src/server/mod.rs b/opc_comn/src/server/mod.rs new file mode 100644 index 0000000..6ae14f6 --- /dev/null +++ b/opc_comn/src/server/mod.rs @@ -0,0 +1,2 @@ +pub mod opc_server_base; +pub mod traits; diff --git a/opc_comn/src/server/opc_server_base.rs b/opc_comn/src/server/opc_server_base.rs new file mode 100644 index 0000000..d1529dd --- /dev/null +++ b/opc_comn/src/server/opc_server_base.rs @@ -0,0 +1,57 @@ +#[windows::core::implement(opc_comn_bindings::IOPCCommon, opc_comn_bindings::IOPCShutdown)] +pub struct OpcServerBase(T) +where + T: 'static + super::traits::opc_common::OpcCommon + super::traits::opc_shutdown::OpcShutdown; + +impl + opc_comn_bindings::IOPCCommon_Impl for OpcServerBase_Impl +{ + fn SetLocaleID(&self, dwlcid: u32) -> windows_core::Result<()> { + self.0.set_locale_id(dwlcid) + } + + fn GetLocaleID(&self) -> windows_core::Result { + self.0.get_locale_id() + } + + fn QueryAvailableLocaleIDs( + &self, + pdwcount: *mut u32, + pdwlcid: *mut *mut u32, + ) -> windows_core::Result<()> { + let locale_ids = self.0.query_available_locale_ids()?; + opc_classic_utils::write_caller_allocated_ptr!(pdwcount, locale_ids.len().try_into()?)?; + opc_classic_utils::write_caller_allocated_array!(pdwlcid, locale_ids.as_slice())?; + + Ok(()) + } + + fn GetErrorString( + &self, + dwerror: windows_core::HRESULT, + ) -> windows_core::Result { + opc_classic_utils::alloc_callee_wstring!(self.0.get_error_string(dwerror)?) + } + + fn SetClientName(&self, szname: &windows_core::PCWSTR) -> windows_core::Result<()> { + self.0.set_client_name(unsafe { + opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szname) + .to_string_lossy() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?) + } +} + +impl + opc_comn_bindings::IOPCShutdown_Impl for OpcServerBase_Impl +{ + fn ShutdownRequest(&self, szreason: &windows_core::PCWSTR) -> windows_core::Result<()> { + let reason = unsafe { + opc_classic_utils::CallerAllocatedWString::from_pcwstr(*szreason) + .to_string_lossy() + .ok_or(windows::Win32::Foundation::E_POINTER) + }?; + + self.0.shutdown_request(reason) + } +} diff --git a/opc_comn/src/server/traits/mod.rs b/opc_comn/src/server/traits/mod.rs new file mode 100644 index 0000000..9539b14 --- /dev/null +++ b/opc_comn/src/server/traits/mod.rs @@ -0,0 +1,2 @@ +pub mod opc_common; +pub mod opc_shutdown; diff --git a/opc_comn/src/server/traits/opc_common.rs b/opc_comn/src/server/traits/opc_common.rs new file mode 100644 index 0000000..7569879 --- /dev/null +++ b/opc_comn/src/server/traits/opc_common.rs @@ -0,0 +1,7 @@ +pub trait OpcCommon { + fn set_locale_id(&self, dwlcid: u32) -> windows_core::Result<()>; + fn get_locale_id(&self) -> windows_core::Result; + fn query_available_locale_ids(&self) -> windows_core::Result>; + fn get_error_string(&self, dwerror: windows_core::HRESULT) -> windows_core::Result; + fn set_client_name(&self, szname: String) -> windows_core::Result<()>; +} diff --git a/opc_comn/src/server/traits/opc_shutdown.rs b/opc_comn/src/server/traits/opc_shutdown.rs new file mode 100644 index 0000000..7beaae8 --- /dev/null +++ b/opc_comn/src/server/traits/opc_shutdown.rs @@ -0,0 +1,3 @@ +pub trait OpcShutdown { + fn shutdown_request(&self, reason: String) -> windows_core::Result<()>; +} diff --git a/opc_da/src/opc_browse.rs b/opc_da/src/opc_browse.rs index 1585a65..028741a 100644 --- a/opc_da/src/opc_browse.rs +++ b/opc_da/src/opc_browse.rs @@ -27,7 +27,7 @@ impl IOPCBrowse_Impl for OPCBrowse_Impl { let item_ids: Vec = item_ids.iter().map(|pcwstr| { unsafe { CallerAllocatedWString::from_pcwstr(*pcwstr) - .to_string() + .to_string_lossy() .unwrap_or_default() } }).collect(); @@ -61,17 +61,17 @@ impl IOPCBrowse_Impl for OPCBrowse_Impl { ) -> Result<()> { let item_id = unsafe { CallerAllocatedWString::from_pcwstr(*szitemid) - .to_string() + .to_string_lossy() .unwrap_or_default() }; let element_name_filter = unsafe { CallerAllocatedWString::from_pcwstr(*szelementnamefilter) - .to_string() + .to_string_lossy() .unwrap_or_default() }; let vendor_filter = unsafe { CallerAllocatedWString::from_pcwstr(*szvendorfilter) - .to_string() + .to_string_lossy() .unwrap_or_default() }; let property_ids = unsafe { diff --git a/opc_da/src/opc_group.rs b/opc_da/src/opc_group.rs index 1a9c1e0..8e8cd2e 100644 --- a/opc_da/src/opc_group.rs +++ b/opc_da/src/opc_group.rs @@ -108,7 +108,7 @@ impl IOPCGroupStateMgt_Impl for fn SetName(&self, szname: &PCWSTR) -> Result<()> { let name = unsafe { CallerAllocatedWString::from_pcwstr(*szname) - .to_string() + .to_string_lossy() .ok_or(windows::Win32::Foundation::E_POINTER) }?; @@ -118,7 +118,7 @@ impl IOPCGroupStateMgt_Impl for fn CloneGroup(&self, szname: &PCWSTR, riid: *const GUID) -> Result { let name = unsafe { CallerAllocatedWString::from_pcwstr(*szname) - .to_string() + .to_string_lossy() .ok_or(windows::Win32::Foundation::E_POINTER) }?; diff --git a/opc_da/src/opc_item.rs b/opc_da/src/opc_item.rs index 6b45919..775f233 100644 --- a/opc_da/src/opc_item.rs +++ b/opc_da/src/opc_item.rs @@ -22,12 +22,12 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { .map(|item| traits::ItemDefinition { access_path: unsafe { CallerAllocatedWString::from_pwstr(item.szAccessPath) - .to_string() + .to_string_lossy() .unwrap_or_default() }, item_id: unsafe { CallerAllocatedWString::from_pwstr(item.szItemID) - .to_string() + .to_string_lossy() .unwrap_or_default() }, active: item.bActive.as_bool(), @@ -75,7 +75,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { String::new() } else { CallerAllocatedWString::from_pcwstr(PCWSTR(item.szAccessPath.0)) - .to_string() + .to_string_lossy() .unwrap_or_default() } }, @@ -84,7 +84,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { String::new() } else { CallerAllocatedWString::from_pcwstr(PCWSTR(item.szItemID.0)) - .to_string() + .to_string_lossy() .unwrap_or_default() } }, diff --git a/opc_da/src/opc_server.rs b/opc_da/src/opc_server.rs index 765d08e..a07333f 100644 --- a/opc_da/src/opc_server.rs +++ b/opc_da/src/opc_server.rs @@ -1,6 +1,6 @@ -use windows_core::*; -use opc_da_bindings::*; use opc_classic_utils::*; +use opc_da_bindings::*; +use windows_core::*; #[windows::core::implement(IOPCServer)] pub struct OPCServer(T) @@ -24,16 +24,16 @@ impl IOPCServer_Impl for OPCServer_Impl { ) -> Result<()> { let name = unsafe { CallerAllocatedWString::from_pcwstr(*szname) - .to_string() + .to_string_lossy() .ok_or(windows::Win32::Foundation::E_POINTER) }?; - + let time_bias = if !ptimebias.is_null() { Some(unsafe { *ptimebias }) } else { None }; - + let percent_deadband = if !ppercentdeadband.is_null() { Some(unsafe { *ppercentdeadband }) } else { @@ -57,22 +57,14 @@ impl IOPCServer_Impl for OPCServer_Impl { Ok(()) } - fn GetErrorString( - &self, - dwerror: HRESULT, - dwlocale: u32, - ) -> Result { + fn GetErrorString(&self, dwerror: HRESULT, dwlocale: u32) -> Result { alloc_callee_wstring!(self.0.get_error_string(dwerror, dwlocale)?) } - fn GetGroupByName( - &self, - szname: &PCWSTR, - riid: *const GUID, - ) -> Result { + fn GetGroupByName(&self, szname: &PCWSTR, riid: *const GUID) -> Result { let name = unsafe { CallerAllocatedWString::from_pcwstr(*szname) - .to_string() + .to_string_lossy() .ok_or(windows::Win32::Foundation::E_POINTER) }?; @@ -85,11 +77,7 @@ impl IOPCServer_Impl for OPCServer_Impl { Ok(ptr.into_raw()) } - fn RemoveGroup( - &self, - hservergroup: u32, - bforce: BOOL, - ) -> Result<()> { + fn RemoveGroup(&self, hservergroup: u32, bforce: BOOL) -> Result<()> { self.0.remove_group(hservergroup, bforce.as_bool()) } @@ -117,27 +105,19 @@ pub mod traits { percent_deadband: Option, locale_id: u32, ) -> Result<(u32, u32, IUnknown)>; - - fn get_error_string( - &self, - error: HRESULT, - locale: u32, - ) -> Result; - - fn get_group_by_name( - &self, - name: String, - riid: *const GUID, - ) -> Result; - + + fn get_error_string(&self, error: HRESULT, locale: u32) -> Result; + + fn get_group_by_name(&self, name: String, riid: *const GUID) -> Result; + fn get_status(&self) -> Result; - + fn remove_group(&self, server_group: u32, force: bool) -> Result<()>; - + fn create_group_enumerator( &self, scope: tagOPCENUMSCOPE, riid: *const GUID, ) -> Result; } -} \ No newline at end of file +} From bd4bdbd70dbccdc5e04e357367ed163f97e9b83d Mon Sep 17 00:00:00 2001 From: Ronbb Date: Mon, 28 Jul 2025 00:02:33 +0800 Subject: [PATCH 07/10] OpcServerList --- .../examples/convenience_functions.rs | 39 ++++------- opc_classic_utils/examples/macro_usage.rs | 14 ++-- .../examples/transparent_repr_demo.rs | 10 +-- opc_classic_utils/src/memory/tests.rs | 14 ---- opc_classic_utils/src/memory/wstring.rs | 14 ++-- opc_comn/src/client/mod.rs | 3 +- opc_comn/src/client/opc_server_list.rs | 69 +++++++++++++++++++ opc_comn/src/client/utils.rs | 9 +++ 8 files changed, 115 insertions(+), 57 deletions(-) create mode 100644 opc_comn/src/client/opc_server_list.rs create mode 100644 opc_comn/src/client/utils.rs diff --git a/opc_classic_utils/examples/convenience_functions.rs b/opc_classic_utils/examples/convenience_functions.rs index d51dacb..c527f45 100644 --- a/opc_classic_utils/examples/convenience_functions.rs +++ b/opc_classic_utils/examples/convenience_functions.rs @@ -20,7 +20,7 @@ fn demonstrate_pointer_convenience_functions() { // Create a pointer from a simple value let int_value = 42; let int_ptr = CallerAllocatedPtr::from_value(&int_value).unwrap(); - println!(" Created pointer from int: {:?}", int_value); + println!(" Created pointer from int: {int_value:?}"); // Create a pointer from a struct let struct_value = TestStruct { @@ -28,7 +28,7 @@ fn demonstrate_pointer_convenience_functions() { value: std::f64::consts::PI, }; let struct_ptr = CallerAllocatedPtr::from_value(&struct_value).unwrap(); - println!(" Created pointer from struct: {:?}", struct_value); + println!(" Created pointer from struct: {struct_value:?}"); // Verify the values were copied correctly unsafe { @@ -58,14 +58,14 @@ fn demonstrate_pointer_convenience_functions() { // Use as_ref for read-only access unsafe { let ref_value = ptr.as_ref().unwrap(); - println!(" Read value through as_ref: {}", ref_value); + println!(" Read value through as_ref: {ref_value}"); } // Use as_mut for mutable access unsafe { let mut_value = ptr.as_mut().unwrap(); *mut_value = 456; - println!(" Modified value through as_mut: {}", mut_value); + println!(" Modified value through as_mut: {mut_value}"); } // Verify the change @@ -85,34 +85,25 @@ fn demonstrate_string_convenience_functions() { use std::str::FromStr; let str_slice = "Hello, World!"; let wstring1 = CallerAllocatedWString::from_str(str_slice).unwrap(); - println!(" Created from &str: '{}'", str_slice); - - // From String - let owned_string = String::from("Owned String"); - let wstring2 = CallerAllocatedWString::from_string(owned_string.clone()).unwrap(); - println!(" Created from String: '{}'", owned_string); + println!(" Created from &str: '{str_slice}'"); // From OsStr use std::ffi::OsStr; let os_string = OsStr::new("OS String"); - let wstring3 = CallerAllocatedWString::from_os_str(os_string).unwrap(); - println!(" Created from OsStr: '{:?}'", os_string); + let wstring2 = CallerAllocatedWString::from_os_str(os_string).unwrap(); + println!(" Created from OsStr: '{os_string:?}'"); println!("\n2. Converting wide strings back to Rust strings:"); // Convert back to String unsafe { let converted1 = wstring1.to_string_lossy().unwrap(); - println!(" Converted back to String: '{}'", converted1); + println!(" Converted back to String: '{converted1}'"); assert_eq!(converted1, str_slice); - let converted2 = wstring2.to_string_lossy().unwrap(); - println!(" Converted back to String: '{}'", converted2); - assert_eq!(converted2, owned_string); - - let converted3 = wstring3.to_os_string().unwrap(); - println!(" Converted back to OsString: '{:?}'", converted3); - assert_eq!(converted3, os_string); + let converted2 = wstring2.to_os_string().unwrap(); + println!(" Converted back to OsString: '{converted2:?}'"); + assert_eq!(converted2, os_string); } println!(" ✓ All string conversions work correctly"); @@ -150,8 +141,8 @@ fn demonstrate_real_world_scenario() { let name = server_name.to_string_lossy().unwrap(); let count = *item_count.as_ptr(); println!(" Server received:"); - println!(" - Server name: '{}'", name); - println!(" - Item count: {}", count); + println!(" - Server name: '{name}'"); + println!(" - Item count: {count}"); } println!("\n3. Simulating server returning output parameters:"); @@ -163,8 +154,8 @@ fn demonstrate_real_world_scenario() { // In a real scenario, the server would allocate this memory // and return pointers to the client println!(" Server would return:"); - println!(" - Result message: '{}'", result_string); - println!(" - Result code: {}", result_code); + println!(" - Result message: '{result_string}'"); + println!(" - Result code: {result_code}"); println!("\n4. Client would receive and process output:"); diff --git a/opc_classic_utils/examples/macro_usage.rs b/opc_classic_utils/examples/macro_usage.rs index 8a0faae..50c8b8d 100644 --- a/opc_classic_utils/examples/macro_usage.rs +++ b/opc_classic_utils/examples/macro_usage.rs @@ -20,7 +20,7 @@ fn main() -> windows::core::Result<()> { // Using the macro to write to a caller-allocated pointer write_caller_allocated_ptr!(count_ptr, 42u32)?; - println!(" Written value: {}", count); + println!(" Written value: {count}"); } // Example 2: Using write_caller_allocated_array! macro @@ -32,7 +32,7 @@ fn main() -> windows::core::Result<()> { let data = vec![1u32, 2u32, 3u32, 4u32, 5u32]; write_caller_allocated_array!(&mut array_ptr, &data)?; - println!(" Array pointer: {:?}", array_ptr); + println!(" Array pointer: {array_ptr:?}"); println!(" Array is not null: {}", !array_ptr.is_null()); } @@ -44,7 +44,7 @@ fn main() -> windows::core::Result<()> { // Using the macro to allocate a callee-allocated wide string let wide_string_ptr = alloc_callee_wstring!(error_message)?; - println!(" Wide string pointer: {:?}", wide_string_ptr); + println!(" Wide string pointer: {wide_string_ptr:?}"); } // Example 4: Simulating OPC Common interface usage @@ -63,8 +63,8 @@ fn main() -> windows::core::Result<()> { // Write array using macro write_caller_allocated_array!(&mut locale_ids_ptr, &available_locale_ids)?; - println!(" Available locale count: {}", count); - println!(" Locale IDs pointer: {:?}", locale_ids_ptr); + println!(" Available locale count: {count}"); + println!(" Locale IDs pointer: {locale_ids_ptr:?}"); } // Example 5: Error handling with macros @@ -74,8 +74,8 @@ fn main() -> windows::core::Result<()> { let hresult = windows::Win32::Foundation::E_POINTER; let error_string_ptr = alloc_callee_wstring!("Pointer is invalid")?; - println!(" HRESULT: {:?}", hresult); - println!(" Error string pointer: {:?}", error_string_ptr); + println!(" HRESULT: {hresult:?}"); + println!(" Error string pointer: {error_string_ptr:?}"); } println!("\n=== All examples completed successfully! ==="); diff --git a/opc_classic_utils/examples/transparent_repr_demo.rs b/opc_classic_utils/examples/transparent_repr_demo.rs index f62fda5..c6d1a24 100644 --- a/opc_classic_utils/examples/transparent_repr_demo.rs +++ b/opc_classic_utils/examples/transparent_repr_demo.rs @@ -34,9 +34,9 @@ fn demonstrate_transparent_repr() { let caller_ptr = CallerAllocatedPtr::from_raw(raw_ptr); let callee_ptr = CalleeAllocatedPtr::from_raw(raw_ptr); - println!(" Raw pointer: {:?}", raw_ptr); - println!(" CallerAllocatedPtr: {:?}", caller_ptr.as_ptr()); - println!(" CalleeAllocatedPtr: {:?}", callee_ptr.as_ptr()); + println!(" Raw pointer: {raw_ptr:?}"); + println!(" CallerAllocatedPtr: {caller_ptr:?}"); + println!(" CalleeAllocatedPtr: {callee_ptr:?}"); // All pointers should be identical due to transparent repr assert_eq!(raw_ptr, caller_ptr.as_ptr()); @@ -51,11 +51,11 @@ fn demonstrate_transparent_repr() { // For input parameters (caller-allocated) let input_ptr = CallerAllocatedPtr::from_raw(std::ptr::null_mut::()); - println!(" Input pointer created: {:?}", input_ptr.as_ptr()); + println!(" Input pointer created: {input_ptr:?}"); // For output parameters (callee-allocated) let output_ptr = CalleeAllocatedPtr::from_raw(std::ptr::null_mut::()); - println!(" Output pointer created: {:?}", output_ptr.as_ptr()); + println!(" Output pointer created: {output_ptr:?}"); println!(" ✓ Wrapper types are FFI-compatible"); diff --git a/opc_classic_utils/src/memory/tests.rs b/opc_classic_utils/src/memory/tests.rs index ad7583a..612ff51 100644 --- a/opc_classic_utils/src/memory/tests.rs +++ b/opc_classic_utils/src/memory/tests.rs @@ -99,20 +99,6 @@ fn test_caller_allocated_wstring_from_str() { } } -#[test] -fn test_caller_allocated_wstring_from_string() { - // Test creating wide string from String - let test_string = String::from("Test String"); - let wstring = CallerAllocatedWString::from_string(test_string.clone()).unwrap(); - assert!(!wstring.is_null()); - - // Verify the string was converted correctly - unsafe { - let converted = wstring.to_string_lossy().unwrap(); - assert_eq!(converted, test_string); - } -} - #[test] fn test_caller_allocated_wstring_from_os_str() { // Test creating wide string from OsStr diff --git a/opc_classic_utils/src/memory/wstring.rs b/opc_classic_utils/src/memory/wstring.rs index e9a7648..432a26e 100644 --- a/opc_classic_utils/src/memory/wstring.rs +++ b/opc_classic_utils/src/memory/wstring.rs @@ -57,12 +57,6 @@ impl CallerAllocatedWString { Ok(unsafe { Self::new(ptr.cast()) }) } - /// Creates a `CallerAllocatedWString` from a Rust string - pub fn from_string(s: String) -> Result { - use std::str::FromStr; - Self::from_str(&s) - } - /// Creates a `CallerAllocatedWString` from an `OsStr` pub fn from_os_str(os_str: &OsStr) -> Result { let wide_string: Vec = os_str.encode_wide().chain(std::iter::once(0)).collect(); @@ -188,6 +182,14 @@ impl std::str::FromStr for CallerAllocatedWString { } } +impl TryFrom for CallerAllocatedWString { + type Error = windows::core::Error; + + fn try_from(value: PCWSTR) -> Result { + Ok(Self::from_pcwstr(value)) + } +} + /// A smart pointer for wide string pointers that the **callee allocates and caller frees** /// /// This is used for output string parameters where the callee allocates memory diff --git a/opc_comn/src/client/mod.rs b/opc_comn/src/client/mod.rs index 8b13789..b73a025 100644 --- a/opc_comn/src/client/mod.rs +++ b/opc_comn/src/client/mod.rs @@ -1 +1,2 @@ - +pub mod opc_server_list; +pub mod utils; diff --git a/opc_comn/src/client/opc_server_list.rs b/opc_comn/src/client/opc_server_list.rs new file mode 100644 index 0000000..30b49e3 --- /dev/null +++ b/opc_comn/src/client/opc_server_list.rs @@ -0,0 +1,69 @@ +use std::mem::ManuallyDrop; + +use opc_comn_bindings::IOPCServerList; +use windows_core::Interface as _; + +#[derive(Default)] +pub struct CreateOpcServerListOptions<'a> { + pub class_id: Option, + pub context: Option, + pub outer: Option<&'a windows_core::IUnknown>, + pub server_info: Option<&'a windows::Win32::System::Com::COSERVERINFO>, +} + +pub struct OpcServerList { + pub inner: IOPCServerList, +} + +impl OpcServerList { + pub fn create_opc_server_list( + options: Option, + ) -> windows_core::Result { + let options = options.unwrap_or_default(); + + let class_id = match options.class_id { + Some(class_id) => class_id, + None => { + super::utils::get_class_id_from_program_id(windows_core::w!("OPC.ServerList.1"))? + } + }; + + let context = options + .context + .unwrap_or(windows::Win32::System::Com::CLSCTX_ALL); + + let outer = options.outer; + + let server_list: IOPCServerList = unsafe { + match options.server_info { + Some(info) => { + let mut results = [windows::Win32::System::Com::MULTI_QI { + pIID: &IOPCServerList::IID, + ..Default::default() + }]; + + windows::Win32::System::Com::CoCreateInstanceEx( + &class_id, + outer, + context, + Some(info), + &mut results, + )?; + + let result = results.into_iter().next().unwrap(); + + if result.hr.is_err() { + return Err(result.hr.into()); + } + + ManuallyDrop::into_inner(result.pItf) + .ok_or(windows::Win32::Foundation::E_POINTER)? + .cast::()? + } + None => windows::Win32::System::Com::CoCreateInstance(&class_id, outer, context)?, + } + }; + + Ok(OpcServerList { inner: server_list }) + } +} diff --git a/opc_comn/src/client/utils.rs b/opc_comn/src/client/utils.rs new file mode 100644 index 0000000..3fb713a --- /dev/null +++ b/opc_comn/src/client/utils.rs @@ -0,0 +1,9 @@ +pub fn get_class_id_from_program_id< + T: TryInto, +>( + program_id: T, +) -> windows_core::Result { + let program_id: opc_classic_utils::CallerAllocatedWString = program_id.try_into()?; + let id = unsafe { windows::Win32::System::Com::CLSIDFromProgID(program_id.as_pcwstr())? }; + Ok(id) +} From 0496fcde09846fb489c880279fe47c04bcdc2523 Mon Sep 17 00:00:00 2001 From: Ronbb Date: Mon, 28 Jul 2025 00:31:29 +0800 Subject: [PATCH 08/10] Fix errors --- opc_classic_utils/src/memory/array.rs | 6 +- opc_da/src/opc_item.rs | 24 ++-- opc_da/src/opc_server.rs | 2 +- opc_da/src/opc_sync_io.rs | 167 ++++++++++++-------------- 4 files changed, 87 insertions(+), 112 deletions(-) diff --git a/opc_classic_utils/src/memory/array.rs b/opc_classic_utils/src/memory/array.rs index bdc60fa..05c1705 100644 --- a/opc_classic_utils/src/memory/array.rs +++ b/opc_classic_utils/src/memory/array.rs @@ -457,15 +457,17 @@ macro_rules! write_caller_allocated_array { macro_rules! copy_to_caller_array { ($dst:expr, $src:expr) => { unsafe { - let mut dst_ptr = opc_classic_utils::CallerAllocatedArray::from_raw($dst, $src.len()); + let src = $src; + let mut dst_ptr = opc_classic_utils::CallerAllocatedArray::allocate(src.len())?; if dst_ptr.is_null() { return Err(windows::core::Error::new( windows::Win32::Foundation::E_INVALIDARG, "Destination pointer is null", )); } + *$dst = dst_ptr.as_ptr(); if let Some(dst_slice) = dst_ptr.as_mut_slice() { - dst_slice.copy_from_slice($src); + dst_slice.copy_from_slice(src); Ok(()) } else { Err(windows::core::Error::new( diff --git a/opc_da/src/opc_item.rs b/opc_da/src/opc_item.rs index 775f233..671a31a 100644 --- a/opc_da/src/opc_item.rs +++ b/opc_da/src/opc_item.rs @@ -47,12 +47,10 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { let (results, errors) = self.0.add_items(item_defs)?; // Allocate and write results - let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?; - unsafe { *ppaddresults = results_ptr.as_ptr() }; + copy_to_caller_array!(ppaddresults, &results); // Allocate and write errors - let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; - unsafe { *pperrors = errors_ptr.as_ptr() }; + copy_to_caller_array!(pperrors, &errors)?; Ok(()) } @@ -105,12 +103,10 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { let (results, errors) = self.0.validate_items(item_defs, bblobupdate.as_bool())?; // Allocate and write results - let results_ptr = CalleeAllocatedPtrArray::from_slice(&results)?; - unsafe { *ppvalidationresults = results_ptr.into_raw() }; + copy_to_caller_array!(ppvalidationresults, &results); // Allocate and write errors - let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; - unsafe { *pperrors = errors_ptr.into_raw() }; + copy_to_caller_array!(pperrors, &errors); Ok(()) } @@ -126,8 +122,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { let errors = self.0.remove_items(server_handles.to_vec())?; // Allocate and write errors - let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; - unsafe { *pperrors = errors_ptr.into_raw() }; + copy_to_caller_array!(pperrors, &errors); Ok(()) } @@ -146,8 +141,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { .set_active_state(server_handles.to_vec(), bactive.as_bool())?; // Allocate and write errors - let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; - unsafe { *pperrors = errors_ptr.into_raw() }; + copy_to_caller_array!(pperrors, &errors); Ok(()) } @@ -167,8 +161,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { .set_client_handles(server_handles.to_vec(), client_handles.to_vec())?; // Allocate and write errors - let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; - unsafe { *pperrors = errors_ptr.into_raw() }; + copy_to_caller_array!(pperrors, &errors); Ok(()) } @@ -189,8 +182,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { .set_datatypes(server_handles.to_vec(), requested_datatypes.to_vec())?; // Allocate and write errors - let errors_ptr = CalleeAllocatedPtrArray::from_slice(&errors)?; - unsafe { *pperrors = errors_ptr.into_raw() }; + copy_to_caller_array!(pperrors, &errors); Ok(()) } diff --git a/opc_da/src/opc_server.rs b/opc_da/src/opc_server.rs index a07333f..5deac3a 100644 --- a/opc_da/src/opc_server.rs +++ b/opc_da/src/opc_server.rs @@ -52,7 +52,7 @@ impl IOPCServer_Impl for OPCServer_Impl { write_caller_allocated_ptr!(phservergroup, server_group)?; write_caller_allocated_ptr!(previsedupdaterate, revised_update_rate)?; - *ppunk = group_interface; + ppunk.write(Some(group_interface))?; Ok(()) } diff --git a/opc_da/src/opc_sync_io.rs b/opc_da/src/opc_sync_io.rs index b1a35e3..9b2ecdf 100644 --- a/opc_da/src/opc_sync_io.rs +++ b/opc_da/src/opc_sync_io.rs @@ -1,6 +1,6 @@ -use windows_core::*; -use opc_da_bindings::*; use opc_classic_utils::*; +use opc_da_bindings::*; +use windows_core::*; #[windows::core::implement(IOPCSyncIO, IOPCSyncIO2)] pub struct OPCSyncIO(T) @@ -16,12 +16,10 @@ impl IOPCSyncIO_Impl for OPCSyncIO_Im ppitemvalues: *mut *mut tagOPCITEMSTATE, pperrors: *mut *mut HRESULT, ) -> Result<()> { - let server_handles = unsafe { - std::slice::from_raw_parts(phserver, dwcount as usize) - }; - + let server_handles = unsafe { std::slice::from_raw_parts(phserver, dwcount as usize) }; + let (item_states, errors) = self.0.read(dwsource, server_handles.to_vec())?; - + // Allocate and write item states if !ppitemvalues.is_null() { let size = std::mem::size_of::() * item_states.len(); @@ -29,17 +27,13 @@ impl IOPCSyncIO_Impl for OPCSyncIO_Im if ptr.is_null() { return Err(windows::core::Error::from_win32()); } - + unsafe { - std::ptr::copy_nonoverlapping( - item_states.as_ptr(), - ptr.cast(), - item_states.len() - ); + std::ptr::copy_nonoverlapping(item_states.as_ptr(), ptr.cast(), item_states.len()); *ppitemvalues = ptr.cast(); } } - + // Allocate and write errors if !pperrors.is_null() { let size = std::mem::size_of::() * errors.len(); @@ -47,13 +41,9 @@ impl IOPCSyncIO_Impl for OPCSyncIO_Im if ptr.is_null() { return Err(windows::core::Error::from_win32()); } - + unsafe { - std::ptr::copy_nonoverlapping( - errors.as_ptr(), - ptr.cast(), - errors.len() - ); + std::ptr::copy_nonoverlapping(errors.as_ptr(), ptr.cast(), errors.len()); *pperrors = ptr.cast(); } } @@ -68,15 +58,11 @@ impl IOPCSyncIO_Impl for OPCSyncIO_Im pitemvalues: *const windows::Win32::System::Variant::VARIANT, pperrors: *mut *mut HRESULT, ) -> Result<()> { - let server_handles = unsafe { - std::slice::from_raw_parts(phserver, dwcount as usize) - }; - let values = unsafe { - std::slice::from_raw_parts(pitemvalues, dwcount as usize) - }; + let server_handles = unsafe { std::slice::from_raw_parts(phserver, dwcount as usize) }; + let values = unsafe { std::slice::from_raw_parts(pitemvalues, dwcount as usize) }; let errors = self.0.write(server_handles.to_vec(), values.to_vec())?; - + // Allocate and write errors if !pperrors.is_null() { let size = std::mem::size_of::() * errors.len(); @@ -84,13 +70,9 @@ impl IOPCSyncIO_Impl for OPCSyncIO_Im if ptr.is_null() { return Err(windows::core::Error::from_win32()); } - + unsafe { - std::ptr::copy_nonoverlapping( - errors.as_ptr(), - ptr.cast(), - errors.len() - ); + std::ptr::copy_nonoverlapping(errors.as_ptr(), ptr.cast(), errors.len()); *pperrors = ptr.cast(); } } @@ -110,33 +92,28 @@ impl IOPCSyncIO2_Impl for OPCSyncIO_I ppfttimestamps: *mut *mut windows::Win32::Foundation::FILETIME, pperrors: *mut *mut HRESULT, ) -> Result<()> { - let server_handles = unsafe { - std::slice::from_raw_parts(phserver, dwcount as usize) - }; - let max_ages = unsafe { - std::slice::from_raw_parts(pdwmaxage, dwcount as usize) - }; - - let (values, qualities, timestamps, errors) = self.0.read_max_age(server_handles.to_vec(), max_ages.to_vec())?; - + let server_handles = unsafe { std::slice::from_raw_parts(phserver, dwcount as usize) }; + let max_ages = unsafe { std::slice::from_raw_parts(pdwmaxage, dwcount as usize) }; + + let (values, qualities, timestamps, errors) = self + .0 + .read_max_age(server_handles.to_vec(), max_ages.to_vec())?; + // Allocate and write values if !ppvvalues.is_null() { - let size = std::mem::size_of::() * values.len(); + let size = + std::mem::size_of::() * values.len(); let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; if ptr.is_null() { return Err(windows::core::Error::from_win32()); } - + unsafe { - std::ptr::copy_nonoverlapping( - values.as_ptr(), - ptr.cast(), - values.len() - ); + std::ptr::copy_nonoverlapping(values.as_ptr(), ptr.cast(), values.len()); *ppvvalues = ptr.cast(); } } - + // Allocate and write qualities if !ppwqualities.is_null() { let size = std::mem::size_of::() * qualities.len(); @@ -144,35 +121,28 @@ impl IOPCSyncIO2_Impl for OPCSyncIO_I if ptr.is_null() { return Err(windows::core::Error::from_win32()); } - + unsafe { - std::ptr::copy_nonoverlapping( - qualities.as_ptr(), - ptr.cast(), - qualities.len() - ); + std::ptr::copy_nonoverlapping(qualities.as_ptr(), ptr.cast(), qualities.len()); *ppwqualities = ptr.cast(); } } - + // Allocate and write timestamps if !ppfttimestamps.is_null() { - let size = std::mem::size_of::() * timestamps.len(); + let size = + std::mem::size_of::() * timestamps.len(); let ptr = unsafe { windows::Win32::System::Com::CoTaskMemAlloc(size) }; if ptr.is_null() { return Err(windows::core::Error::from_win32()); } - + unsafe { - std::ptr::copy_nonoverlapping( - timestamps.as_ptr(), - ptr.cast(), - timestamps.len() - ); + std::ptr::copy_nonoverlapping(timestamps.as_ptr(), ptr.cast(), timestamps.len()); *ppfttimestamps = ptr.cast(); } } - + // Allocate and write errors if !pperrors.is_null() { let size = std::mem::size_of::() * errors.len(); @@ -180,13 +150,9 @@ impl IOPCSyncIO2_Impl for OPCSyncIO_I if ptr.is_null() { return Err(windows::core::Error::from_win32()); } - + unsafe { - std::ptr::copy_nonoverlapping( - errors.as_ptr(), - ptr.cast(), - errors.len() - ); + std::ptr::copy_nonoverlapping(errors.as_ptr(), ptr.cast(), errors.len()); *pperrors = ptr.cast(); } } @@ -201,15 +167,13 @@ impl IOPCSyncIO2_Impl for OPCSyncIO_I pitemvqt: *const tagOPCITEMVQT, pperrors: *mut *mut HRESULT, ) -> Result<()> { - let server_handles = unsafe { - std::slice::from_raw_parts(phserver, dwcount as usize) - }; - let item_vqt = unsafe { - std::slice::from_raw_parts(pitemvqt, dwcount as usize) - }; - - let errors = self.0.write_vqt(server_handles.to_vec(), item_vqt.to_vec())?; - + let server_handles = unsafe { std::slice::from_raw_parts(phserver, dwcount as usize) }; + let item_vqt = unsafe { std::slice::from_raw_parts(pitemvqt, dwcount as usize) }; + + let errors = self + .0 + .write_vqt(server_handles.to_vec(), item_vqt.to_vec())?; + // Allocate and write errors if !pperrors.is_null() { let size = std::mem::size_of::() * errors.len(); @@ -217,13 +181,9 @@ impl IOPCSyncIO2_Impl for OPCSyncIO_I if ptr.is_null() { return Err(windows::core::Error::from_win32()); } - + unsafe { - std::ptr::copy_nonoverlapping( - errors.as_ptr(), - ptr.cast(), - errors.len() - ); + std::ptr::copy_nonoverlapping(errors.as_ptr(), ptr.cast(), errors.len()); *pperrors = ptr.cast(); } } @@ -236,14 +196,35 @@ pub mod traits { use super::*; pub trait OPCSyncIO { - fn read(&self, source: tagOPCDATASOURCE, server_handles: Vec) -> Result<(Vec, Vec)>; - - fn write(&self, server_handles: Vec, values: Vec) -> Result>; + fn read( + &self, + source: tagOPCDATASOURCE, + server_handles: Vec, + ) -> Result<(Vec, Vec)>; + + fn write( + &self, + server_handles: Vec, + values: Vec, + ) -> Result>; } pub trait OPCSyncIO2: OPCSyncIO { - fn read_max_age(&self, server_handles: Vec, max_ages: Vec) -> Result<(Vec, Vec, Vec, Vec)>; - - fn write_vqt(&self, server_handles: Vec, item_vqt: Vec) -> Result>; + fn read_max_age( + &self, + server_handles: Vec, + max_ages: Vec, + ) -> Result<( + Vec, + Vec, + Vec, + Vec, + )>; + + fn write_vqt( + &self, + server_handles: Vec, + item_vqt: Vec, + ) -> Result>; } -} \ No newline at end of file +} From 2c758e1457d33089aa26e0d93988810dc1e18009 Mon Sep 17 00:00:00 2001 From: Ronbb Date: Mon, 28 Jul 2025 00:39:50 +0800 Subject: [PATCH 09/10] Fix warnings --- opc_classic_utils/src/memory/array.rs | 8 +-- opc_classic_utils/src/memory/ptr.rs | 12 ++--- opc_classic_utils/src/memory/ptr_array.rs | 10 +--- opc_classic_utils/src/memory/wstring.rs | 12 ++--- opc_da/src/opc_data_callback.rs | 59 +++++++---------------- opc_da/src/opc_item.rs | 14 +++--- 6 files changed, 36 insertions(+), 79 deletions(-) diff --git a/opc_classic_utils/src/memory/array.rs b/opc_classic_utils/src/memory/array.rs index 05c1705..4ed4a0f 100644 --- a/opc_classic_utils/src/memory/array.rs +++ b/opc_classic_utils/src/memory/array.rs @@ -83,11 +83,9 @@ impl CallerAllocatedArray { /// Returns the raw pointer and transfers ownership to the caller /// /// After calling this method, the `CallerAllocatedArray` will not manage the memory. - pub fn into_raw(mut self) -> (*mut T, usize) { + pub fn into_raw(self) -> (*mut T, usize) { let ptr = self.ptr; let len = self.len; - self.ptr = ptr::null_mut(); - self.len = 0; (ptr, len) } @@ -250,11 +248,9 @@ impl CalleeAllocatedArray { /// Returns the raw pointer and transfers ownership to the caller /// /// After calling this method, the `CalleeAllocatedArray` will not free the memory. - pub fn into_raw(mut self) -> (*mut T, usize) { + pub fn into_raw(self) -> (*mut T, usize) { let ptr = self.ptr; let len = self.len; - self.ptr = ptr::null_mut(); - self.len = 0; (ptr, len) } diff --git a/opc_classic_utils/src/memory/ptr.rs b/opc_classic_utils/src/memory/ptr.rs index cb748b0..44cf4e0 100644 --- a/opc_classic_utils/src/memory/ptr.rs +++ b/opc_classic_utils/src/memory/ptr.rs @@ -62,10 +62,8 @@ impl CallerAllocatedPtr { /// Returns the raw pointer and transfers ownership to the caller /// /// After calling this method, the `CallerAllocatedPtr` will not manage the memory. - pub fn into_raw(mut self) -> *mut T { - let ptr = self.ptr; - self.ptr = ptr::null_mut(); - ptr + pub fn into_raw(self) -> *mut T { + self.ptr } /// Checks if the pointer is null @@ -179,10 +177,8 @@ impl CalleeAllocatedPtr { /// Returns the raw pointer and transfers ownership to the caller /// /// After calling this method, the `CalleeAllocatedPtr` will not free the memory. - pub fn into_raw(mut self) -> *mut T { - let ptr = self.ptr; - self.ptr = ptr::null_mut(); - ptr + pub fn into_raw(self) -> *mut T { + self.ptr } /// Checks if the pointer is null diff --git a/opc_classic_utils/src/memory/ptr_array.rs b/opc_classic_utils/src/memory/ptr_array.rs index e7fa715..6496bdf 100644 --- a/opc_classic_utils/src/memory/ptr_array.rs +++ b/opc_classic_utils/src/memory/ptr_array.rs @@ -84,11 +84,9 @@ impl CallerAllocatedPtrArray { /// Returns the raw pointer and transfers ownership to the caller /// /// After calling this method, the `CallerAllocatedPtrArray` will not manage the memory. - pub fn into_raw(mut self) -> (*mut *mut T, usize) { + pub fn into_raw(self) -> (*mut *mut T, usize) { let ptr = self.ptr; let len = self.len; - self.ptr = ptr::null_mut(); - self.len = 0; (ptr, len) } @@ -167,8 +165,6 @@ impl Drop for CallerAllocatedPtrArray { fn drop(&mut self) { // Do NOT free the memory - the callee is responsible for this // Just clear the pointer to prevent use-after-free - self.ptr = ptr::null_mut(); - self.len = 0; } } @@ -309,11 +305,9 @@ impl CalleeAllocatedPtrArray { /// Returns the raw pointer and transfers ownership to the caller /// /// After calling this method, the `CalleeAllocatedPtrArray` will not free the memory. - pub fn into_raw(mut self) -> (*mut *mut T, usize) { + pub fn into_raw(self) -> (*mut *mut T, usize) { let ptr = self.ptr; let len = self.len; - self.ptr = ptr::null_mut(); - self.len = 0; (ptr, len) } diff --git a/opc_classic_utils/src/memory/wstring.rs b/opc_classic_utils/src/memory/wstring.rs index 432a26e..51b627d 100644 --- a/opc_classic_utils/src/memory/wstring.rs +++ b/opc_classic_utils/src/memory/wstring.rs @@ -114,10 +114,8 @@ impl CallerAllocatedWString { } /// Returns the raw pointer and transfers ownership to the caller - pub fn into_raw(mut self) -> *mut u16 { - let ptr = self.ptr; - self.ptr = ptr::null_mut(); - ptr + pub fn into_raw(self) -> *mut u16 { + self.ptr } /// Checks if the pointer is null @@ -274,10 +272,8 @@ impl CalleeAllocatedWString { } /// Returns the raw pointer and transfers ownership to the caller - pub fn into_raw(mut self) -> *mut u16 { - let ptr = self.ptr; - self.ptr = ptr::null_mut(); - ptr + pub fn into_raw(self) -> *mut u16 { + self.ptr } /// Checks if the pointer is null diff --git a/opc_da/src/opc_data_callback.rs b/opc_da/src/opc_data_callback.rs index 54f7277..e1f90d8 100644 --- a/opc_da/src/opc_data_callback.rs +++ b/opc_da/src/opc_data_callback.rs @@ -1,6 +1,5 @@ -use windows_core::*; use opc_da_bindings::*; -use opc_classic_utils::*; +use windows_core::*; #[windows::core::implement(IOPCDataCallback)] pub struct OPCDataCallback(T) @@ -21,21 +20,11 @@ impl IOPCDataCallback_Impl for OPCDataCallback_Impl< pfttimestamps: *const windows::Win32::Foundation::FILETIME, perrors: *const HRESULT, ) -> Result<()> { - let client_items = unsafe { - std::slice::from_raw_parts(phclientitems, dwcount as usize) - }; - let values = unsafe { - std::slice::from_raw_parts(pvvalues, dwcount as usize) - }; - let qualities = unsafe { - std::slice::from_raw_parts(pwqualities, dwcount as usize) - }; - let timestamps = unsafe { - std::slice::from_raw_parts(pfttimestamps, dwcount as usize) - }; - let errors = unsafe { - std::slice::from_raw_parts(perrors, dwcount as usize) - }; + let client_items = unsafe { std::slice::from_raw_parts(phclientitems, dwcount as usize) }; + let values = unsafe { std::slice::from_raw_parts(pvvalues, dwcount as usize) }; + let qualities = unsafe { std::slice::from_raw_parts(pwqualities, dwcount as usize) }; + let timestamps = unsafe { std::slice::from_raw_parts(pfttimestamps, dwcount as usize) }; + let errors = unsafe { std::slice::from_raw_parts(perrors, dwcount as usize) }; self.0.on_data_change( dwtransid, @@ -63,21 +52,11 @@ impl IOPCDataCallback_Impl for OPCDataCallback_Impl< pfttimestamps: *const windows::Win32::Foundation::FILETIME, perrors: *const HRESULT, ) -> Result<()> { - let client_items = unsafe { - std::slice::from_raw_parts(phclientitems, dwcount as usize) - }; - let values = unsafe { - std::slice::from_raw_parts(pvvalues, dwcount as usize) - }; - let qualities = unsafe { - std::slice::from_raw_parts(pwqualities, dwcount as usize) - }; - let timestamps = unsafe { - std::slice::from_raw_parts(pfttimestamps, dwcount as usize) - }; - let errors = unsafe { - std::slice::from_raw_parts(perrors, dwcount as usize) - }; + let client_items = unsafe { std::slice::from_raw_parts(phclientitems, dwcount as usize) }; + let values = unsafe { std::slice::from_raw_parts(pvvalues, dwcount as usize) }; + let qualities = unsafe { std::slice::from_raw_parts(pwqualities, dwcount as usize) }; + let timestamps = unsafe { std::slice::from_raw_parts(pfttimestamps, dwcount as usize) }; + let errors = unsafe { std::slice::from_raw_parts(perrors, dwcount as usize) }; self.0.on_read_complete( dwtransid, @@ -101,12 +80,8 @@ impl IOPCDataCallback_Impl for OPCDataCallback_Impl< pclienthandles: *const u32, perrors: *const HRESULT, ) -> Result<()> { - let client_items = unsafe { - std::slice::from_raw_parts(pclienthandles, dwcount as usize) - }; - let errors = unsafe { - std::slice::from_raw_parts(perrors, dwcount as usize) - }; + let client_items = unsafe { std::slice::from_raw_parts(pclienthandles, dwcount as usize) }; + let errors = unsafe { std::slice::from_raw_parts(perrors, dwcount as usize) }; self.0.on_write_complete( dwtransid, @@ -138,7 +113,7 @@ pub mod traits { timestamps: Vec, errors: Vec, ) -> Result<()>; - + fn on_read_complete( &self, transaction_id: u32, @@ -151,7 +126,7 @@ pub mod traits { timestamps: Vec, errors: Vec, ) -> Result<()>; - + fn on_write_complete( &self, transaction_id: u32, @@ -160,7 +135,7 @@ pub mod traits { client_items: Vec, errors: Vec, ) -> Result<()>; - + fn on_cancel_complete(&self, transaction_id: u32, group: u32) -> Result<()>; } -} \ No newline at end of file +} diff --git a/opc_da/src/opc_item.rs b/opc_da/src/opc_item.rs index 671a31a..e888e8b 100644 --- a/opc_da/src/opc_item.rs +++ b/opc_da/src/opc_item.rs @@ -47,7 +47,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { let (results, errors) = self.0.add_items(item_defs)?; // Allocate and write results - copy_to_caller_array!(ppaddresults, &results); + copy_to_caller_array!(ppaddresults, &results)?; // Allocate and write errors copy_to_caller_array!(pperrors, &errors)?; @@ -103,10 +103,10 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { let (results, errors) = self.0.validate_items(item_defs, bblobupdate.as_bool())?; // Allocate and write results - copy_to_caller_array!(ppvalidationresults, &results); + copy_to_caller_array!(ppvalidationresults, &results)?; // Allocate and write errors - copy_to_caller_array!(pperrors, &errors); + copy_to_caller_array!(pperrors, &errors)?; Ok(()) } @@ -122,7 +122,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { let errors = self.0.remove_items(server_handles.to_vec())?; // Allocate and write errors - copy_to_caller_array!(pperrors, &errors); + copy_to_caller_array!(pperrors, &errors)?; Ok(()) } @@ -141,7 +141,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { .set_active_state(server_handles.to_vec(), bactive.as_bool())?; // Allocate and write errors - copy_to_caller_array!(pperrors, &errors); + copy_to_caller_array!(pperrors, &errors)?; Ok(()) } @@ -161,7 +161,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { .set_client_handles(server_handles.to_vec(), client_handles.to_vec())?; // Allocate and write errors - copy_to_caller_array!(pperrors, &errors); + copy_to_caller_array!(pperrors, &errors)?; Ok(()) } @@ -182,7 +182,7 @@ impl IOPCItemMgt_Impl for OPCItem_Impl { .set_datatypes(server_handles.to_vec(), requested_datatypes.to_vec())?; // Allocate and write errors - copy_to_caller_array!(pperrors, &errors); + copy_to_caller_array!(pperrors, &errors)?; Ok(()) } From cd3d8a76c2503e43f77186d57d09a441a9414920 Mon Sep 17 00:00:00 2001 From: Ronbb Date: Mon, 28 Jul 2025 01:18:26 +0800 Subject: [PATCH 10/10] Add wstring tools --- opc_classic_utils/src/memory/tests.rs | 2 +- opc_classic_utils/src/memory/wstring.rs | 46 +++++++++++++++---- opc_comn/src/client/opc_server_list.rs | 61 ++++++++++++++++++++++--- 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/opc_classic_utils/src/memory/tests.rs b/opc_classic_utils/src/memory/tests.rs index 612ff51..d485dce 100644 --- a/opc_classic_utils/src/memory/tests.rs +++ b/opc_classic_utils/src/memory/tests.rs @@ -164,7 +164,7 @@ fn test_wstring_null_conversion() { unsafe { assert!(caller_wstring.to_string_lossy().is_none()); - assert!(callee_wstring.to_string().is_none()); + assert!(callee_wstring.to_string_lossy().is_none()); assert!(caller_wstring.to_os_string().is_none()); assert!(callee_wstring.to_os_string().is_none()); } diff --git a/opc_classic_utils/src/memory/wstring.rs b/opc_classic_utils/src/memory/wstring.rs index 51b627d..d561b63 100644 --- a/opc_classic_utils/src/memory/wstring.rs +++ b/opc_classic_utils/src/memory/wstring.rs @@ -227,20 +227,24 @@ impl CalleeAllocatedWString { Self::from_str(&s) } + /// Null + pub fn null() -> Self { + Self { + ptr: ptr::null_mut(), + } + } + /// Converts the wide string to a Rust string slice /// /// # Safety /// /// The caller must ensure the pointer is valid and points to a null-terminated wide string. - pub unsafe fn to_string(&self) -> Option { + pub unsafe fn to_string_lossy(&self) -> Option { if self.ptr.is_null() { return None; } - let mut len = 0; - while unsafe { *self.ptr.add(len) } != 0 { - len += 1; - } + let len = unsafe { self.as_pcwstr().len() }; let slice = unsafe { std::slice::from_raw_parts(self.ptr, len) }; let os_string = OsString::from_wide(slice); @@ -257,15 +261,29 @@ impl CalleeAllocatedWString { return None; } - let mut len = 0; - while unsafe { *self.ptr.add(len) } != 0 { - len += 1; - } + let len = unsafe { self.as_pcwstr().len() }; let slice = unsafe { std::slice::from_raw_parts(self.ptr, len) }; Some(OsString::from_wide(slice)) } + /// Converts the wide string to a Rust string + /// + /// # Safety + /// + /// The caller must ensure the pointer is valid and points to a null-terminated wide string. + pub unsafe fn to_string(&self) -> windows::core::Result> { + if self.ptr.is_null() { + return Ok(None); + } + + let len = unsafe { self.as_pcwstr().len() }; + + let slice = unsafe { std::slice::from_raw_parts(self.ptr, len) }; + let string = String::from_utf16(slice)?; + Ok(Some(string)) + } + /// Returns the raw pointer without transferring ownership pub fn as_ptr(&self) -> *mut u16 { self.ptr @@ -286,10 +304,20 @@ impl CalleeAllocatedWString { PCWSTR(self.ptr) } + /// Converts to a `PCWSTR` for use with Windows APIs + pub fn as_pcwstr_mut_ptr(&mut self) -> *mut PCWSTR { + &mut self.ptr as *mut *mut u16 as *mut PCWSTR + } + /// Converts to a `PWSTR` for use with Windows APIs pub fn as_pwstr(&self) -> PWSTR { PWSTR(self.ptr) } + + /// Converts to a `PWSTR` for use with Windows APIs + pub fn as_pwstr_mut_ptr(&mut self) -> *mut PWSTR { + &mut self.ptr as *mut *mut u16 as *mut PWSTR + } } impl Drop for CalleeAllocatedWString { diff --git a/opc_comn/src/client/opc_server_list.rs b/opc_comn/src/client/opc_server_list.rs index 30b49e3..4aa60cd 100644 --- a/opc_comn/src/client/opc_server_list.rs +++ b/opc_comn/src/client/opc_server_list.rs @@ -1,6 +1,5 @@ -use std::mem::ManuallyDrop; +use std::{mem::ManuallyDrop, str::FromStr as _}; -use opc_comn_bindings::IOPCServerList; use windows_core::Interface as _; #[derive(Default)] @@ -12,7 +11,7 @@ pub struct CreateOpcServerListOptions<'a> { } pub struct OpcServerList { - pub inner: IOPCServerList, + pub inner: opc_comn_bindings::IOPCServerList, } impl OpcServerList { @@ -34,11 +33,11 @@ impl OpcServerList { let outer = options.outer; - let server_list: IOPCServerList = unsafe { + let server_list: opc_comn_bindings::IOPCServerList = unsafe { match options.server_info { Some(info) => { let mut results = [windows::Win32::System::Com::MULTI_QI { - pIID: &IOPCServerList::IID, + pIID: &opc_comn_bindings::IOPCServerList::IID, ..Default::default() }]; @@ -58,7 +57,7 @@ impl OpcServerList { ManuallyDrop::into_inner(result.pItf) .ok_or(windows::Win32::Foundation::E_POINTER)? - .cast::()? + .cast::()? } None => windows::Win32::System::Com::CoCreateInstance(&class_id, outer, context)?, } @@ -67,3 +66,53 @@ impl OpcServerList { Ok(OpcServerList { inner: server_list }) } } + +pub struct ClassDetails { + pub program_id: Option, + pub user_type: Option, +} + +impl OpcServerList { + pub fn enum_classes_of_categories( + &self, + implemented_categories: &[windows_core::GUID], + required_categories: &[windows_core::GUID], + ) -> windows_core::Result { + unsafe { + self.inner + .EnumClassesOfCategories(implemented_categories, required_categories) + } + } + + pub fn get_class_details( + &self, + clsid: &windows_core::GUID, + ) -> windows_core::Result { + let mut program_id = opc_classic_utils::CalleeAllocatedWString::null(); + let mut user_type = opc_classic_utils::CalleeAllocatedWString::null(); + + unsafe { + self.inner.GetClassDetails( + clsid, + program_id.as_pwstr_mut_ptr(), + user_type.as_pwstr_mut_ptr(), + )?; + + Ok(ClassDetails { + program_id: program_id.to_string()?, + user_type: user_type.to_string()?, + }) + } + } + + pub fn class_id_from_program_id( + &self, + program_id: &str, + ) -> windows_core::Result { + unsafe { + self.inner.CLSIDFromProgID( + opc_classic_utils::CallerAllocatedWString::from_str(program_id)?.as_pcwstr(), + ) + } + } +}