Skip to content

Commit cd79043

Browse files
committed
Use overlapped IO for COM
win: Perform zero read size check only when timeout zero win: Add note on how COM port is created Add COM::set_timeouts
1 parent 7ace596 commit cd79043

File tree

5 files changed

+171
-67
lines changed

5 files changed

+171
-67
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ regex = "1.5.5"
3434
version = "0.3.9"
3535
features = [
3636
"cguid", "commapi", "errhandlingapi", "fileapi", "guiddef", "handleapi", "minwinbase",
37-
"minwindef", "ntdef", "setupapi", "winbase", "winerror", "winnt",
37+
"minwindef", "ntdef", "setupapi", "winbase", "winerror", "winnt", "ioapiset", "synchapi"
3838
]
3939

4040
[dependencies]

src/windows/com.rs

Lines changed: 162 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ use std::time::Duration;
44
use std::{io, ptr};
55

66
use winapi::shared::minwindef::*;
7+
use winapi::shared::ntdef::NULL;
8+
use winapi::shared::winerror::{ERROR_IO_PENDING, ERROR_OPERATION_ABORTED};
79
use winapi::um::commapi::*;
10+
use winapi::um::errhandlingapi::GetLastError;
811
use winapi::um::fileapi::*;
912
use winapi::um::handleapi::*;
13+
use winapi::um::ioapiset::GetOverlappedResult;
14+
use winapi::um::minwinbase::OVERLAPPED;
1015
use winapi::um::processthreadsapi::GetCurrentProcess;
16+
use winapi::um::synchapi::CreateEventW;
1117
use winapi::um::winbase::*;
1218
use winapi::um::winnt::{
1319
DUPLICATE_SAME_ACCESS, FILE_ATTRIBUTE_NORMAL, GENERIC_READ, GENERIC_WRITE, HANDLE,
@@ -19,12 +25,56 @@ use crate::{
1925
SerialPortBuilder, StopBits,
2026
};
2127

28+
const fn duration_to_win_timeout(time: Duration) -> DWORD {
29+
time.as_secs()
30+
.saturating_mul(1000)
31+
.saturating_add((time.subsec_nanos() as u64).saturating_div(1_000_000)) as DWORD
32+
}
33+
34+
struct OverlappedHandle(pub HANDLE);
35+
36+
impl OverlappedHandle {
37+
#[inline]
38+
fn new() -> io::Result<Self> {
39+
match unsafe { CreateEventW(ptr::null_mut(), TRUE, FALSE, ptr::null_mut()) } {
40+
NULL => Err(io::Error::last_os_error()),
41+
handle => Ok(Self(handle)),
42+
}
43+
}
44+
45+
#[inline]
46+
fn close(self) {
47+
//drop
48+
}
49+
50+
#[inline]
51+
fn create_overlapped(&self) -> OVERLAPPED {
52+
OVERLAPPED {
53+
Internal: 0,
54+
InternalHigh: 0,
55+
u: unsafe { MaybeUninit::zeroed().assume_init() },
56+
hEvent: self.0,
57+
}
58+
}
59+
}
60+
61+
impl Drop for OverlappedHandle {
62+
#[inline(always)]
63+
fn drop(&mut self) {
64+
unsafe {
65+
CloseHandle(self.0);
66+
}
67+
}
68+
}
69+
2270
/// A serial port implementation for Windows COM ports
2371
///
2472
/// The port will be closed when the value is dropped. However, this struct
2573
/// should not be instantiated directly by using `COMPort::open()`, instead use
2674
/// the cross-platform `serialport::open()` or
2775
/// `serialport::open_with_settings()`.
76+
///
77+
/// Port is created using `CreateFileW` syscall with following set of flags: `FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED`
2878
#[derive(Debug)]
2979
pub struct COMPort {
3080
handle: HANDLE,
@@ -63,7 +113,7 @@ impl COMPort {
63113
0,
64114
ptr::null_mut(),
65115
OPEN_EXISTING,
66-
FILE_ATTRIBUTE_NORMAL,
116+
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
67117
0 as HANDLE,
68118
)
69119
};
@@ -105,27 +155,28 @@ impl COMPort {
105155
///
106156
/// This function returns an error if the serial port couldn't be cloned.
107157
pub fn try_clone_native(&self) -> Result<COMPort> {
108-
let process_handle: HANDLE = unsafe { GetCurrentProcess() };
109-
let mut cloned_handle: HANDLE = INVALID_HANDLE_VALUE;
110-
unsafe {
158+
// duplicate communications device handle
159+
let mut duplicate_handle = INVALID_HANDLE_VALUE;
160+
let process = unsafe { GetCurrentProcess() };
161+
let res = unsafe {
111162
DuplicateHandle(
112-
process_handle,
163+
process,
113164
self.handle,
114-
process_handle,
115-
&mut cloned_handle,
165+
process,
166+
&mut duplicate_handle,
116167
0,
117-
TRUE,
168+
FALSE,
118169
DUPLICATE_SAME_ACCESS,
119-
);
120-
if cloned_handle != INVALID_HANDLE_VALUE {
121-
Ok(COMPort {
122-
handle: cloned_handle,
123-
port_name: self.port_name.clone(),
124-
timeout: self.timeout,
125-
})
126-
} else {
127-
Err(super::error::last_os_error())
128-
}
170+
)
171+
};
172+
173+
match res {
174+
0 => Err(super::error::last_os_error()),
175+
_ => Ok(COMPort {
176+
handle: duplicate_handle,
177+
port_name: self.port_name.clone(),
178+
timeout: self.timeout,
179+
}),
129180
}
130181
}
131182

@@ -154,6 +205,33 @@ impl COMPort {
154205
port_name: None,
155206
}
156207
}
208+
209+
///Sets COM port timeouts.
210+
///
211+
///Comparing to `SerialPort::set_timeout` which only sets `read` timeout, this function allows
212+
///to specify all available timeouts.
213+
///
214+
///- `data` - This timeout specifies how long to wait for next byte, since arrival of at least
215+
///one byte. Once timeout expires, read returns with available data.
216+
///`SerialPort::set_timeout` uses 0 which means no timeout.
217+
///- `read` - Specifies overall timeout for `read` as `SerialPort::set_timeout`
218+
///- `write` - Specifies overall timeout for `write` operations.
219+
pub fn set_timeouts(&mut self, data: Duration, read: Duration, write: Duration) -> Result<()> {
220+
let mut timeouts = COMMTIMEOUTS {
221+
ReadIntervalTimeout: duration_to_win_timeout(data),
222+
ReadTotalTimeoutMultiplier: 0,
223+
ReadTotalTimeoutConstant: duration_to_win_timeout(read),
224+
WriteTotalTimeoutMultiplier: 0,
225+
WriteTotalTimeoutConstant: duration_to_win_timeout(write),
226+
};
227+
228+
if unsafe { SetCommTimeouts(self.handle, &mut timeouts) } == 0 {
229+
return Err(super::error::last_os_error());
230+
}
231+
232+
self.timeout = read;
233+
Ok(())
234+
}
157235
}
158236

159237
impl Drop for COMPort {
@@ -178,48 +256,88 @@ impl FromRawHandle for COMPort {
178256

179257
impl io::Read for COMPort {
180258
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
181-
let mut len: DWORD = 0;
259+
let mut read_size = buf.len();
260+
261+
if self.timeout.as_secs() == 0 && self.timeout.subsec_nanos() == 0 {
262+
//If zero timeout then make sure we can read, before proceeding
263+
//Note that zero timeout will make read operation to wait until at least
264+
//1 byte becomes available.
265+
let bytes_to_read = self.bytes_to_read()? as usize;
266+
if bytes_to_read < read_size {
267+
read_size = bytes_to_read;
268+
}
269+
if read_size == 0 {
270+
return Ok(0);
271+
}
272+
}
182273

183-
match unsafe {
274+
let evt_handle = OverlappedHandle::new()?;
275+
let mut overlapped = evt_handle.create_overlapped();
276+
let mut len: DWORD = 0;
277+
let read_result = unsafe {
184278
ReadFile(
185279
self.handle,
186280
buf.as_mut_ptr() as LPVOID,
187-
buf.len() as DWORD,
281+
read_size as DWORD,
188282
&mut len,
189-
ptr::null_mut(),
283+
&mut overlapped,
190284
)
191-
} {
192-
0 => Err(io::Error::last_os_error()),
193-
_ => {
194-
if len != 0 {
195-
Ok(len as usize)
196-
} else {
197-
Err(io::Error::new(
198-
io::ErrorKind::TimedOut,
199-
"Operation timed out",
200-
))
201-
}
202-
}
285+
};
286+
let last_error = unsafe { GetLastError() };
287+
if read_result == 0
288+
&& last_error != ERROR_IO_PENDING
289+
&& last_error != ERROR_OPERATION_ABORTED
290+
{
291+
return Err(io::Error::last_os_error());
292+
}
293+
let overlapped_result =
294+
unsafe { GetOverlappedResult(self.handle, &mut overlapped, &mut len, TRUE) };
295+
evt_handle.close();
296+
let last_error = unsafe { GetLastError() };
297+
if overlapped_result == 0 && last_error != ERROR_OPERATION_ABORTED {
298+
return Err(io::Error::last_os_error());
299+
}
300+
if len != 0 {
301+
Ok(len as usize)
302+
} else {
303+
Err(io::Error::new(
304+
io::ErrorKind::TimedOut,
305+
"Operation timed out",
306+
))
203307
}
204308
}
205309
}
206310

207311
impl io::Write for COMPort {
208312
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
313+
let evt_handle = OverlappedHandle::new()?;
314+
let mut overlapped = evt_handle.create_overlapped();
209315
let mut len: DWORD = 0;
210-
211-
match unsafe {
316+
let write_result = unsafe {
212317
WriteFile(
213318
self.handle,
214319
buf.as_ptr() as LPVOID,
215320
buf.len() as DWORD,
216321
&mut len,
217-
ptr::null_mut(),
322+
&mut overlapped,
218323
)
219-
} {
220-
0 => Err(io::Error::last_os_error()),
221-
_ => Ok(len as usize),
324+
};
325+
let last_error = unsafe { GetLastError() };
326+
if write_result == 0
327+
&& last_error != ERROR_IO_PENDING
328+
&& last_error != ERROR_OPERATION_ABORTED
329+
{
330+
return Err(io::Error::last_os_error());
222331
}
332+
let overlapped_result =
333+
unsafe { GetOverlappedResult(self.handle, &mut overlapped, &mut len, TRUE) };
334+
evt_handle.close();
335+
336+
let last_error = unsafe { GetLastError() };
337+
if overlapped_result == 0 && last_error != ERROR_OPERATION_ABORTED {
338+
return Err(io::Error::last_os_error());
339+
}
340+
Ok(len as usize)
223341
}
224342

225343
fn flush(&mut self) -> io::Result<()> {
@@ -231,31 +349,19 @@ impl io::Write for COMPort {
231349
}
232350

233351
impl SerialPort for COMPort {
352+
#[inline]
234353
fn name(&self) -> Option<String> {
235354
self.port_name.clone()
236355
}
237356

357+
#[inline]
238358
fn timeout(&self) -> Duration {
239359
self.timeout
240360
}
241361

362+
#[inline]
242363
fn set_timeout(&mut self, timeout: Duration) -> Result<()> {
243-
let milliseconds = timeout.as_secs() * 1000 + timeout.subsec_nanos() as u64 / 1_000_000;
244-
245-
let mut timeouts = COMMTIMEOUTS {
246-
ReadIntervalTimeout: 0,
247-
ReadTotalTimeoutMultiplier: 0,
248-
ReadTotalTimeoutConstant: milliseconds as DWORD,
249-
WriteTotalTimeoutMultiplier: 0,
250-
WriteTotalTimeoutConstant: 0,
251-
};
252-
253-
if unsafe { SetCommTimeouts(self.handle, &mut timeouts) } == 0 {
254-
return Err(super::error::last_os_error());
255-
}
256-
257-
self.timeout = timeout;
258-
Ok(())
364+
self.set_timeouts(Duration::from_secs(0), timeout, Duration::from_secs(0))
259365
}
260366

261367
fn write_request_to_send(&mut self, level: bool) -> Result<()> {

src/windows/dcb.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ pub(crate) fn get_dcb(handle: HANDLE) -> Result<DCB> {
1111
dcb.DCBlength = std::mem::size_of::<DCB>() as u32;
1212

1313
if unsafe { GetCommState(handle, &mut dcb) } != 0 {
14-
return Ok(dcb);
14+
Ok(dcb)
1515
} else {
16-
return Err(super::error::last_os_error());
16+
Err(super::error::last_os_error())
1717
}
1818
}
1919

@@ -57,9 +57,9 @@ pub(crate) fn init(dcb: &mut DCB) {
5757

5858
pub(crate) fn set_dcb(handle: HANDLE, mut dcb: DCB) -> Result<()> {
5959
if unsafe { SetCommState(handle, &mut dcb as *mut _) != 0 } {
60-
return Ok(());
60+
Ok(())
6161
} else {
62-
return Err(super::error::last_os_error());
62+
Err(super::error::last_os_error())
6363
}
6464
}
6565

src/windows/enumerate.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ fn get_ports_guids() -> Result<Vec<GUID>> {
2828

2929
// Size vector to hold 1 result (which is the most common result).
3030
let mut num_guids: DWORD = 0;
31-
let mut guids: Vec<GUID> = Vec::new();
32-
guids.push(GUID_NULL); // Placeholder for first result
31+
let mut guids = vec![GUID_NULL]; // Placeholder for first result
3332

3433
// Find out how many GUIDs are associated with "Ports". Initially we assume
3534
// that there is only 1. num_guids will tell us how many there actually are.
@@ -271,7 +270,6 @@ impl PortDevice {
271270
ptr::null_mut(),
272271
)
273272
};
274-
275273
if res == FALSE {
276274
if unsafe { GetLastError() } != ERROR_INSUFFICIENT_BUFFER {
277275
return None;
@@ -309,7 +307,7 @@ pub fn available_ports() -> Result<Vec<SerialPortInfo>> {
309307
}
310308

311309
ports.push(SerialPortInfo {
312-
port_name: port_name,
310+
port_name,
313311
port_type: port_device.port_type(),
314312
});
315313
}

src/windows/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use winapi::um::errhandlingapi::GetLastError;
77
use winapi::um::winbase::{
88
FormatMessageW, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS,
99
};
10-
use winapi::um::winnt::{LANG_SYSTEM_DEFAULT, MAKELANGID, SUBLANG_SYS_DEFAULT, WCHAR};
10+
use winapi::um::winnt::{LANG_SYSTEM_DEFAULT, MAKELANGID, SUBLANG_SYS_DEFAULT};
1111

1212
use crate::{Error, ErrorKind};
1313

@@ -35,7 +35,7 @@ fn error_string(errnum: u32) -> String {
3535
// MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT)
3636
let langId = MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) as DWORD;
3737

38-
let mut buf = [0 as WCHAR; 2048];
38+
let mut buf = [0u16; 2048];
3939

4040
unsafe {
4141
let res = FormatMessageW(

0 commit comments

Comments
 (0)