-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace which with a manual implementation using SearchPathW #96
base: main
Are you sure you want to change the base?
Conversation
fn get_path_extensions() -> Vec<String> { | ||
env::var("PATHEXT") | ||
.unwrap_or_else(|_| { | ||
// If PATHEXT isn't set, use the default | ||
".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC".to_string() | ||
}) | ||
.split(';') | ||
.map(|ext| ext.to_string()) | ||
.collect() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this was inside search_path_with_extensions
it wouldn't need to collect into a vector first, nor allocate each string chunk.
Like this:
let pathext = env::var("PATHEXT");
let extensions = pathext
.as_deref()
// If PATHEXT isn't set, use the default
.unwrap_or(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC")
.split(';');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about it some more, I think it'd be preferable if we still did this, but it's not something I would block over.
// the path, including the terminating null character. | ||
// | ||
// Includes some padding just in case and because it's cheap. | ||
buffer.resize((len + 64) as usize, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, doesn't this mean that if it fails to fit in the buffer we will move on to the next extension after growing the buffer? That doesn't seem correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hah. I didn't even catch this! My initial suggestion had a loop, so this looked quite similar. 😅 But it needs a loop in a loop.
let extension_hstring = HSTRING::from(extension); | ||
// SearchPathW will ignore the extension if the filename already has one. | ||
let len: u32 = | ||
unsafe { SearchPathW(None, &filename, &extension_hstring, Some(&mut buffer), None) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am fairly certain that SearchPathW doesn't take an HSTRING
o_O
Is this a weird rust-win32
ism?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SearchPathW
expects a 16-bit wide string but you're starting with a UTF-8 string. HSTRING
provides a 16-bite wide string and comprehensive conversion from various Rust Standard Library types, so it's very convenient in cases like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unlike BSTR
, HSTRING
is not compatible when passed as a LPWSTR
. It does not point directly to the character data. Why is passing it like a LPWSTR
to an API that expects LPWSTR
OK, in contravention of all Windows API design to-date?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I am happy to accept “there are magic conversions in the rust Windows crate,” but that is exactly what I mean when I ask if it is a weirdness. I also want somebody to say it on-record.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume by LPWSTR
you really mean PCWSTR
. 😉
In which case, HSTRING
is actually a far better choice than BSTR
. While BSTR
is literally the pointer, that pointer is far more dangerous in general and BSTR
semantics are far more nebulous than HSTRING
. An HSTRING
is perfectly compatible with PCWSTR
and even has an API for that - WindowsGetStringRawBuffer
- but I implemented it inline for both C++/WinRT and Rust and there is thus no performance penalty for pointer access. HSTRING
is generally more efficient, can avoid heap allocations, and while windows-strings does provide all kinds of conveniences for both HSTRING
and BSTR
, the former is preferred.
// not including the terminating null character. | ||
if len < buffer.len() as u32 { | ||
buffer.truncate(len as usize); | ||
return Ok(String::from_utf16(&buffer)?); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW you could also consider returning an OsString
or even better Path
here, as that would be consistent with other parts of Rust and this app. It would also allow you to revert the change to absolute_path
below.
This comment was marked as outdated.
This comment was marked as outdated.
(sshhh bot, I was on vacation) |
This pull request removes our dependency on
which
, which Kenny so helpfully pointed out we don't need: https://gist.github.com/kennykerr/a4375597c7507182570576cf9e7b6ae5