Skip to content
Draft
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
11 changes: 11 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"opc_ae_bindings",
"opc_classic_utils",
"opc_comn",
"opc_comn_bindings",
"opc_da",
"opc_da_bindings",
Expand All @@ -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" }
Expand Down
35 changes: 35 additions & 0 deletions opc_classic_utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 18 additions & 27 deletions opc_classic_utils/examples/convenience_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ 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 {
id: 1,
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 {
Expand Down Expand Up @@ -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
Expand All @@ -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().unwrap();
println!(" Converted back to String: '{}'", converted1);
let converted1 = wstring1.to_string_lossy().unwrap();
println!(" Converted back to String: '{converted1}'");
assert_eq!(converted1, str_slice);

let converted2 = wstring2.to_string().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");
Expand All @@ -121,7 +112,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");
}
Expand All @@ -147,11 +138,11 @@ 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);
println!(" - Item count: {}", count);
println!(" - Server name: '{name}'");
println!(" - Item count: {count}");
}

println!("\n3. Simulating server returning output parameters:");
Expand All @@ -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:");

Expand Down
114 changes: 114 additions & 0 deletions opc_classic_utils/examples/macro_usage.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}
10 changes: 5 additions & 5 deletions opc_classic_utils/examples/transparent_repr_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -51,11 +51,11 @@ fn demonstrate_transparent_repr() {

// For input parameters (caller-allocated)
let input_ptr = CallerAllocatedPtr::from_raw(std::ptr::null_mut::<i32>());
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::<i32>());
println!(" Output pointer created: {:?}", output_ptr.as_ptr());
println!(" Output pointer created: {output_ptr:?}");

println!(" ✓ Wrapper types are FFI-compatible");

Expand Down
Loading