Skip to content

Commit 41dff64

Browse files
authored
Migrate Windows backend from winapi to windows (#9)
Replace hand-rolled COM helpers with the official windows crate (0.62) for ISpellChecker.
1 parent 4d9c867 commit 41dff64

3 files changed

Lines changed: 61 additions & 152 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ This project follows [Semantic Versioning](https://semver.org/).
66

77
## [Unreleased]
88

9+
### Changed
10+
- Windows backend now uses the official `windows` crate instead of `winapi`
11+
- Bump `cfg-if` to 1.x
912
### Fixed
1013
- Pass null-terminated dictionary paths to Hunspell on Linux (stops spurious
1114
`cannot open …aff` stderr noise)

Cargo.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ readme = "README.md"
1818
cocoa = "0.20.0"
1919
objc = "0.2"
2020

21-
[target.'cfg(windows)'.dependencies.winapi]
22-
version = "0.3"
23-
features = ["combaseapi", "objbase", "spellcheck"]
21+
[target.'cfg(windows)'.dependencies.windows]
22+
version = "0.62"
23+
features = [
24+
"Win32_Foundation",
25+
"Win32_Globalization",
26+
"Win32_System_Com",
27+
]
2428

2529
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
2630
hunspell-sys = "0.3.1"

src/win.rs

Lines changed: 51 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,24 @@
11
use crate::Error;
22

33
use std::ffi::OsStr;
4-
use std::fmt::{self, Debug};
54
use std::iter;
6-
use std::ops::Deref;
75
use std::os::windows::ffi::OsStrExt;
8-
use std::ptr::{self, NonNull};
96

10-
use winapi::{
11-
shared::{
12-
ntdef::ULONG,
13-
winerror::{SUCCEEDED, S_FALSE, S_OK},
14-
wtypesbase::CLSCTX_INPROC_SERVER,
15-
},
16-
um::{
17-
combaseapi::{CoCreateInstance, CoInitializeEx, CoTaskMemFree},
18-
objbase::COINIT_MULTITHREADED,
19-
spellcheck::{
20-
IEnumSpellingError, ISpellChecker, ISpellCheckerFactory, SpellCheckerFactory,
7+
use windows::{
8+
core::{HSTRING, PWSTR},
9+
Win32::{
10+
Foundation::S_FALSE,
11+
Globalization::{
12+
IEnumSpellingError, ISpellChecker, ISpellCheckerFactory, ISpellingError,
13+
SpellCheckerFactory,
14+
},
15+
System::Com::{
16+
CoCreateInstance, CoInitializeEx, CoTaskMemFree, CLSCTX_INPROC_SERVER,
17+
COINIT_MULTITHREADED,
2118
},
22-
unknwnbase::IUnknown,
2319
},
24-
Class, Interface,
2520
};
2621

27-
struct ComPtr<T>(NonNull<T>);
28-
29-
impl<T> ComPtr<T> {
30-
fn new(p: *mut T) -> ComPtr<T>
31-
where
32-
T: Interface,
33-
{
34-
ComPtr(NonNull::new(p).unwrap())
35-
}
36-
}
37-
38-
impl<T> Deref for ComPtr<T> {
39-
type Target = T;
40-
41-
fn deref(&self) -> &T {
42-
unsafe { &*self.0.as_ptr() }
43-
}
44-
}
45-
46-
impl<T> Debug for ComPtr<T> {
47-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48-
f.debug_tuple("ComPtr")
49-
.field(&format_args!("{:p}", self.0.as_ptr()))
50-
.finish()
51-
}
52-
}
53-
54-
impl<T> Drop for ComPtr<T> {
55-
fn drop(&mut self) {
56-
unsafe {
57-
let unknown = self.0.as_ptr() as *mut IUnknown;
58-
(*unknown).Release();
59-
}
60-
}
61-
}
62-
6322
fn wide_string(s: &str) -> Vec<u16> {
6423
OsStr::new(s).encode_wide().chain(iter::once(0)).collect()
6524
}
@@ -75,53 +34,22 @@ fn utf16_offset_to_utf8(s: &str, utf16_units: usize) -> usize {
7534
s.len()
7635
}
7736

78-
fn create_factory() -> Result<ComPtr<ISpellCheckerFactory>, Error> {
79-
let hr = unsafe { CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED) };
80-
if hr != S_OK && hr != S_FALSE {
81-
return Err(Error::Unavailable);
82-
}
83-
84-
let mut obj = ptr::null_mut();
85-
let hr = unsafe {
86-
CoCreateInstance(
87-
&SpellCheckerFactory::uuidof(),
88-
ptr::null_mut(),
89-
CLSCTX_INPROC_SERVER,
90-
&ISpellCheckerFactory::uuidof(),
91-
&mut obj,
92-
)
93-
};
94-
if !SUCCEEDED(hr) {
95-
return Err(Error::Unavailable);
96-
}
97-
Ok(ComPtr::new(obj as *mut ISpellCheckerFactory))
98-
}
37+
fn open_for_language(bcp47: &str) -> Result<ISpellChecker, Error> {
38+
// S_OK / S_FALSE (already initialized) both succeed via windows::Result
39+
let _ = unsafe { CoInitializeEx(None, COINIT_MULTITHREADED) };
9940

100-
fn open_for_language(bcp47: &str) -> Result<ComPtr<ISpellChecker>, Error> {
101-
let factory = create_factory()?;
102-
let lang = wide_string(bcp47);
103-
let mut checker = ptr::null_mut();
104-
let hr = unsafe { (*factory).CreateSpellChecker(lang.as_ptr(), &mut checker) };
105-
if !SUCCEEDED(hr) {
106-
return Err(Error::Unavailable);
41+
let factory: ISpellCheckerFactory = unsafe {
42+
CoCreateInstance(&SpellCheckerFactory, None, CLSCTX_INPROC_SERVER)
10743
}
108-
Ok(ComPtr::new(checker))
109-
}
44+
.map_err(|_| Error::Unavailable)?;
11045

111-
unsafe fn wide_ptr_to_string(p: *mut u16) -> Option<String> {
112-
if p.is_null() {
113-
return None;
114-
}
115-
let mut len = 0usize;
116-
while *p.add(len) != 0 {
117-
len += 1;
118-
}
119-
String::from_utf16(std::slice::from_raw_parts(p, len)).ok()
46+
let tag = HSTRING::from(bcp47);
47+
unsafe { factory.CreateSpellChecker(&tag) }.map_err(|_| Error::Unavailable)
12048
}
12149

12250
#[derive(Debug)]
12351
pub struct Checker {
124-
checker: ComPtr<ISpellChecker>,
52+
checker: ISpellChecker,
12553
}
12654

12755
impl Checker {
@@ -142,27 +70,25 @@ impl Checker {
14270
return Vec::new();
14371
}
14472

145-
let wide = wide_string(word);
146-
let mut enum_str = ptr::null_mut();
147-
let hr = unsafe { (*self.checker).Suggest(wide.as_ptr(), &mut enum_str) };
148-
if !SUCCEEDED(hr) || enum_str.is_null() {
73+
let Ok(enum_str) = (unsafe { self.checker.Suggest(&HSTRING::from(word)) }) else {
14974
return Vec::new();
150-
}
151-
let enum_str = ComPtr::new(enum_str);
75+
};
15276

15377
let mut out = Vec::new();
15478
while out.len() < MAX {
155-
let mut item: *mut u16 = ptr::null_mut();
156-
let mut fetched: ULONG = 0;
157-
let hr = unsafe { (*enum_str).Next(1, &mut item, &mut fetched) };
158-
if hr == S_FALSE || fetched == 0 || item.is_null() {
79+
let mut item = [PWSTR::null()];
80+
let mut fetched = 0u32;
81+
let hr = unsafe { enum_str.Next(&mut item, Some(&mut fetched)) };
82+
if fetched == 0 || item[0].is_null() {
15983
break;
16084
}
161-
if let Some(s) = unsafe { wide_ptr_to_string(item) } {
85+
if let Ok(s) = unsafe { item[0].to_string() } {
16286
out.push(s);
16387
}
164-
unsafe { CoTaskMemFree(item as *mut _) };
165-
if !SUCCEEDED(hr) && hr != S_FALSE {
88+
unsafe {
89+
CoTaskMemFree(Some(item[0].as_ptr() as *const _));
90+
}
91+
if hr.is_err() && hr != S_FALSE {
16692
break;
16793
}
16894
}
@@ -180,41 +106,27 @@ impl Checker {
180106

181107
let original = text.to_owned();
182108
let wide = wide_string(text);
183-
let mut errors = ptr::null_mut();
184-
let hr = unsafe { (*self.checker).ComprehensiveCheck(wide.as_ptr(), &mut errors) };
185-
if !SUCCEEDED(hr) {
186-
return ErrorIter {
187-
original,
188-
text: wide,
189-
iter: None,
190-
};
191-
}
192-
let errors = ComPtr::new(errors);
109+
let iter = unsafe { self.checker.ComprehensiveCheck(&HSTRING::from(text)) }.ok();
193110

194111
ErrorIter {
195112
original,
196113
text: wide,
197-
iter: Some(errors),
114+
iter,
198115
}
199116
}
200117

201118
pub fn ignore(&mut self, word: &str) {
202119
if word.is_empty() {
203120
return;
204121
}
205-
206-
let word = wide_string(word);
207-
let hr = unsafe { (*self.checker).Ignore(word.as_ptr()) };
208-
if !SUCCEEDED(hr) {
209-
return;
210-
}
122+
let _ = unsafe { self.checker.Ignore(&HSTRING::from(word)) };
211123
}
212124
}
213125

214126
struct ErrorIter {
215127
original: String,
216128
text: Vec<u16>,
217-
iter: Option<ComPtr<IEnumSpellingError>>,
129+
iter: Option<IEnumSpellingError>,
218130
}
219131

220132
impl Iterator for ErrorIter {
@@ -223,35 +135,25 @@ impl Iterator for ErrorIter {
223135
fn next(&mut self) -> Option<SpellingError> {
224136
let iter = self.iter.as_ref()?;
225137

226-
let mut err = ptr::null_mut();
227-
if unsafe { (*iter).Next(&mut err) } != S_FALSE {
228-
let err = ComPtr::new(err);
229-
230-
let mut start = 0;
231-
let mut length = 0;
232-
233-
unsafe {
234-
(*err).get_Length(&mut length);
235-
(*err).get_StartIndex(&mut start);
236-
}
237-
238-
let utf16_start = start as usize;
239-
let utf16_len = length as usize;
138+
let mut err: Option<ISpellingError> = None;
139+
let hr = unsafe { iter.Next(&mut err) };
140+
if hr == S_FALSE {
141+
return None;
142+
}
143+
let err = err?;
240144

241-
let err_text =
242-
String::from_utf16(&self.text[utf16_start..utf16_start + utf16_len]).ok()?;
145+
let start = unsafe { err.StartIndex() }.ok()? as usize;
146+
let length = unsafe { err.Length() }.ok()? as usize;
243147

244-
let byte_start = utf16_offset_to_utf8(&self.original, utf16_start);
245-
let byte_end = utf16_offset_to_utf8(&self.original, utf16_start + utf16_len);
148+
let err_text = String::from_utf16(&self.text[start..start + length]).ok()?;
149+
let byte_start = utf16_offset_to_utf8(&self.original, start);
150+
let byte_end = utf16_offset_to_utf8(&self.original, start + length);
246151

247-
return Some(SpellingError {
248-
text: err_text,
249-
start: byte_start,
250-
end: byte_end,
251-
});
252-
} else {
253-
None
254-
}
152+
Some(SpellingError {
153+
text: err_text,
154+
start: byte_start,
155+
end: byte_end,
156+
})
255157
}
256158
}
257159

0 commit comments

Comments
 (0)