Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,28 @@
resolver = "2"
members = [
"opc_ae_bindings",
"opc_classic_utils",
"opc_comn_bindings",
"opc_da",
"opc_da_bindings",
"opc_hda_bindings",
]

[workspace.dependencies]
actix = "0.13.5"
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"] }
windows = { version = "0.61.3", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_System_Com",
"Win32_System_Com_StructuredStorage",
"Win32_System_Ole",
"Win32_System_Variant",
"Win32_System_WinRT",
] }
windows-bindgen = "0.62.1"
windows-core = "0.61.2"
Expand Down
13 changes: 13 additions & 0 deletions opc_classic_utils/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "opc_classic_utils"
version = "0.3.0"
edition = "2024"
description = "OPC Classic utilities and common functionality"
readme = "README.md"
repository = "https://github.com/Ronbb/rust_opc"
license = "MIT"
keywords = ["classic", "opc", "utilities", "utils"]

[dependencies]
windows = { workspace = true }
windows-core = { workspace = true }
100 changes: 100 additions & 0 deletions opc_classic_utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# OPC Classic Utils

OPC Classic utilities and common functionality for the rust_opc project.

## Features

- **Dual Memory Management Patterns**: Supports both COM memory management conventions
- **Caller-allocated**: Caller allocates, callee frees (for input parameters)
- **Callee-allocated**: Callee allocates, caller frees (for output parameters)
- Automatic memory management with `CoTaskMemFree`
- Common utility structures and traits
- Shared functionality for OPC Classic implementations

## Memory Management Patterns

### Caller-allocated Memory (Input Parameters)

Use these types when **you** allocate memory that will be freed by the COM function:

```rust
use opc_classic_utils::memory::{CallerAllocatedPtr, CallerAllocatedWString};

// For input parameters - you allocate, COM function frees
let input_string = CallerAllocatedWString::from_raw(your_allocated_string);
let input_data = CallerAllocatedPtr::from_raw(your_allocated_data);

// Pass to COM function - it will free the memory
some_com_function(&input_string, &input_data);
// Memory is NOT freed by our wrapper (COM function's responsibility)
```

### Callee-allocated Memory (Output Parameters)

Use these types when the **COM function** allocates memory that you must free:

```rust
use opc_classic_utils::memory::{CalleeAllocatedPtr, CalleeAllocatedWString};

// For output parameters - COM function allocates, you free
let (output_string, output_data) = some_com_function();

// Use the returned data
println!("String: {:?}", output_string.as_ptr());
println!("Data: {:?}", output_data.as_ptr());

// Memory is automatically freed when variables go out of scope
```

## Usage Examples

### Basic Usage

```rust
use opc_classic_utils::memory::{
CallerAllocatedPtr, CalleeAllocatedPtr,
CallerAllocatedWString, CalleeAllocatedWString,
};

// Caller-allocated (input parameters)
let input_ptr = CallerAllocatedPtr::from_raw(some_pointer);
// Memory will NOT be freed by our wrapper

// Callee-allocated (output parameters)
let output_ptr = CalleeAllocatedPtr::from_raw(com_returned_pointer);
// Memory will be automatically freed when dropped
```

### OPC Client-Server Scenario

```rust
// Client side - preparing input parameters
let client_input = CallerAllocatedWString::from_raw(client_allocated_string);

// Call server method
let server_output = server_method(&client_input);

// Server output is automatically freed when it goes out of scope
// Client input is NOT freed (server's responsibility)
```

## Type Overview

| Type | Purpose | Memory Management |
|------|---------|-------------------|
| `CallerAllocatedPtr<T>` | Input parameters | Caller allocates, callee frees |
| `CalleeAllocatedPtr<T>` | Output parameters | Callee allocates, caller frees |
| `CallerAllocatedWString` | Input strings | Caller allocates, callee frees |
| `CalleeAllocatedWString` | Output strings | Callee allocates, caller frees |

## Benefits

- **Prevents Memory Leaks**: Automatic cleanup for callee-allocated memory
- **Prevents Double-free**: Clear ownership semantics prevent errors
- **COM Convention Compliance**: Follows standard COM memory management rules
- **Type Safety**: Compile-time guarantees about memory ownership
- **RAII**: Resource management through Rust's ownership system

## License

MIT
221 changes: 221 additions & 0 deletions opc_classic_utils/examples/array_functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
//! Array functions demonstration for opc_classic_utils
//!
//! This example shows how to use the array and pointer array functions for
//! COM memory management in OPC Classic scenarios.

use opc_classic_utils::memory::{
CallerAllocatedArray, CallerAllocatedPtr, CallerAllocatedPtrArray, CallerAllocatedWString,
};
use std::str::FromStr;

#[derive(Debug, Clone, Copy, PartialEq)]
struct OPCItem {
id: i32,
value: f64,
quality: u16,
}

fn demonstrate_array_functions() {
println!("Array Functions Demonstration");
println!("============================");

println!("\n1. Creating arrays from Rust slices:");

// Create arrays from Rust data
let item_data = vec![
OPCItem {
id: 1,
value: 10.5,
quality: 192,
},
OPCItem {
id: 2,
value: 20.7,
quality: 192,
},
OPCItem {
id: 3,
value: 30.2,
quality: 192,
},
];

let mut caller_array = CallerAllocatedArray::from_slice(&item_data).unwrap();
println!(
" Created caller-allocated array with {} items",
caller_array.len()
);

// Verify the data was copied correctly
unsafe {
let slice = caller_array.as_slice().unwrap();
assert_eq!(slice, item_data.as_slice());
println!(" ✓ Data was correctly copied to COM memory");
}

println!("\n2. Allocating uninitialized arrays:");

// Allocate memory for later initialization
let mut uninit_array = CallerAllocatedArray::<i32>::allocate(5).unwrap();
println!(" Allocated uninitialized array for 5 i32 values");

// Initialize the array
unsafe {
let mut_slice = uninit_array.as_mut_slice().unwrap();
for (i, value) in mut_slice.iter_mut().enumerate() {
*value = ((i + 1) * 10) as i32;
}
}
println!(" Initialized array with values: [10, 20, 30, 40, 50]");

println!("\n3. Accessing array elements:");

// Test individual element access
unsafe {
assert_eq!(*caller_array.get(0).unwrap(), item_data[0]);
assert_eq!(*caller_array.get(1).unwrap(), item_data[1]);
assert_eq!(*caller_array.get(2).unwrap(), item_data[2]);
assert!(caller_array.get(3).is_none()); // Out of bounds
}
println!(" ✓ Array element access works correctly");

// Test mutable access
unsafe {
let mut item = *caller_array.get(1).unwrap();
item.value = 25.0;
*caller_array.as_mut_slice().unwrap().get_mut(1).unwrap() = item;
assert_eq!(caller_array.get(1).unwrap().value, 25.0);
}
println!(" ✓ Mutable array access works correctly");
}

fn demonstrate_pointer_array_functions() {
println!("\n\nPointer Array Functions Demonstration");
println!("=====================================");

println!("\n1. Creating pointer arrays:");

// Create some pointers to simulate OPC item handles
let ptr1 = CallerAllocatedPtr::<i32>::from_value(&100).unwrap();
let ptr2 = CallerAllocatedPtr::<i32>::from_value(&200).unwrap();
let ptr3 = CallerAllocatedPtr::<i32>::from_value(&300).unwrap();

let ptrs = vec![ptr1.as_ptr(), ptr2.as_ptr(), ptr3.as_ptr()];
let caller_ptr_array = CallerAllocatedPtrArray::from_ptr_slice(&ptrs).unwrap();
println!(
" Created caller-allocated pointer array with {} pointers",
caller_ptr_array.len()
);

// Verify the pointers were copied correctly
unsafe {
let slice = caller_ptr_array.as_slice().unwrap();
assert_eq!(slice, ptrs.as_slice());
println!(" ✓ Pointers were correctly copied to COM memory");
}

println!("\n2. Allocating uninitialized pointer arrays:");

// Allocate memory for pointer array
let mut uninit_ptr_array = CallerAllocatedPtrArray::<i32>::allocate(3).unwrap();
println!(" Allocated uninitialized pointer array for 3 pointers");

// Set pointers in the array
unsafe {
let test_ptr1 = std::ptr::null_mut::<i32>();
let test_ptr2 = std::ptr::null_mut::<i32>();
let test_ptr3 = std::ptr::null_mut::<i32>();

assert!(uninit_ptr_array.set(0, test_ptr1));
assert!(uninit_ptr_array.set(1, test_ptr2));
assert!(uninit_ptr_array.set(2, test_ptr3));
assert!(!uninit_ptr_array.set(3, test_ptr1)); // Out of bounds
}
println!(" ✓ Pointer array set operations work correctly");

println!("\n3. Accessing pointer array elements:");

// Test individual pointer access
unsafe {
assert_eq!(caller_ptr_array.get(0).unwrap(), ptrs[0]);
assert_eq!(caller_ptr_array.get(1).unwrap(), ptrs[1]);
assert_eq!(caller_ptr_array.get(2).unwrap(), ptrs[2]);
assert!(caller_ptr_array.get(3).is_none()); // Out of bounds
}
println!(" ✓ Pointer array element access works correctly");
}

fn demonstrate_real_world_opc_scenario() {
println!("\n\nReal-World OPC Array Scenario");
println!("==============================");

println!("\n1. Simulating OPC client preparing batch read request:");

// Client prepares item IDs for batch read
let item_ids = ["Item1", "Item2", "Item3", "Item4", "Item5"];
let item_id_wstrings: Vec<CallerAllocatedWString> = item_ids
.iter()
.map(|id| CallerAllocatedWString::from_str(id).unwrap())
.collect();
let item_id_ptrs: Vec<*mut u16> = item_id_wstrings.iter().map(|ws| ws.as_ptr()).collect();

let item_id_array = CallerAllocatedPtrArray::from_ptr_slice(&item_id_ptrs).unwrap();
println!(
" Client prepared {} item IDs for batch read",
item_id_array.len()
);

println!("\n2. Simulating OPC server processing batch request:");

// Server would process the request and return results
println!(" Server would process the batch request");
println!(" Server would allocate memory for results");

println!("\n3. Simulating server returning batch results:");

// Server would return arrays of results
let result_values = vec![42.0, 84.0, 126.0, 168.0, 210.0];
let result_qualities = vec![192u16, 192, 192, 192, 192];
let result_timestamps = vec![
1234567890u64,
1234567891,
1234567892,
1234567893,
1234567894,
];

// In a real scenario, the server would allocate these arrays
// and return pointers to the client
println!(" Server would return:");
println!(" - {} values: {:?}", result_values.len(), result_values);
println!(
" - {} qualities: {:?}",
result_qualities.len(),
result_qualities
);
println!(
" - {} timestamps: {:?}",
result_timestamps.len(),
result_timestamps
);

println!("\n4. Client would receive and process batch results:");

// Client would receive CalleeAllocatedArray types
// and they would be automatically freed when they go out of scope
println!(" Client would receive CalleeAllocatedArray types");
println!(" Client would automatically free result memory");
println!(" when the wrapper types go out of scope");

println!("\n ✓ Complete OPC batch operation cycle demonstrated");
}

fn main() {
demonstrate_array_functions();
demonstrate_pointer_array_functions();
demonstrate_real_world_opc_scenario();

println!("\n\n✅ Array functions demonstration completed!");
println!(" These functions make COM array management much easier.");
println!(" Perfect for batch operations in OPC Classic applications.");
}
Loading