diff options
Diffstat (limited to 'third_party/rust/comedy')
-rw-r--r-- | third_party/rust/comedy/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/comedy/Cargo.toml | 29 | ||||
-rw-r--r-- | third_party/rust/comedy/LICENSE-APACHE | 201 | ||||
-rw-r--r-- | third_party/rust/comedy/LICENSE-MIT | 19 | ||||
-rw-r--r-- | third_party/rust/comedy/src/com.rs | 478 | ||||
-rw-r--r-- | third_party/rust/comedy/src/error.rs | 377 | ||||
-rw-r--r-- | third_party/rust/comedy/src/handle.rs | 91 | ||||
-rw-r--r-- | third_party/rust/comedy/src/lib.rs | 18 |
8 files changed, 1214 insertions, 0 deletions
diff --git a/third_party/rust/comedy/.cargo-checksum.json b/third_party/rust/comedy/.cargo-checksum.json new file mode 100644 index 0000000000..b930ad590b --- /dev/null +++ b/third_party/rust/comedy/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"69c5297806d38fda41398015067c5573c3cb9e09a92ccccf44aee4d9fac637dc","LICENSE-APACHE":"b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1","LICENSE-MIT":"9eeff459e61e81ab28896aa2df8545fe15dd6bfe0dc87c3384fdbc8b1b146661","src/com.rs":"5407ae11d8f4e37e2dee74c1f92ecc63e27c98c0bce9822cd4216450ea050c2d","src/error.rs":"c46ef326f6b350af226e09d15db1a81aec39ede4a3eeb3d1b4ef2dae5e4cdd31","src/handle.rs":"190a0e8fc8f00f4d0e21c0d2098773ff5384284d7967f92bae7226de6155c6cb","src/lib.rs":"b937b21338c823d203c5cd179baf1e4c276f554924f99c385d2ebde0947f01cd"},"package":"74428ae4f7f05f32f4448e9f42d371538196919c4834979f4f96d1fdebffcb47"}
\ No newline at end of file diff --git a/third_party/rust/comedy/Cargo.toml b/third_party/rust/comedy/Cargo.toml new file mode 100644 index 0000000000..c94079b84e --- /dev/null +++ b/third_party/rust/comedy/Cargo.toml @@ -0,0 +1,29 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "comedy" +version = "0.2.0" +authors = ["Adam Gashlin <agashlin@mozilla.com>"] +description = "Windows error handling, COM, and handles" +keywords = ["windows", "com", "win32"] +categories = ["api-bindings", "os::windows-apis"] +license = "MIT/Apache-2.0" +repository = "https://github.com/agashlin/comedy-rs" +[package.metadata.docs.rs] +default-target = "x86_64-pc-windows-msvc" +[dependencies.winapi] +version = "0.3.6" +features = ["basetsd", "combaseapi", "errhandlingapi", "handleapi", "impl-default", "minwindef", "objbase", "unknwnbase", "winbase", "winerror", "wtypes", "wtypesbase"] +[dev-dependencies.winapi] +version = "0.3.6" +features = ["bits", "fileapi", "guiddef"] diff --git a/third_party/rust/comedy/LICENSE-APACHE b/third_party/rust/comedy/LICENSE-APACHE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/third_party/rust/comedy/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/rust/comedy/LICENSE-MIT b/third_party/rust/comedy/LICENSE-MIT new file mode 100644 index 0000000000..0073c942c7 --- /dev/null +++ b/third_party/rust/comedy/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2019 The winapi-rs and comedy Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/rust/comedy/src/com.rs b/third_party/rust/comedy/src/com.rs new file mode 100644 index 0000000000..bce77576ac --- /dev/null +++ b/third_party/rust/comedy/src/com.rs @@ -0,0 +1,478 @@ +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option. +// All files in the project carrying such notice may not be copied, modified, or distributed +// except according to those terms. + +//! Utilities and wrappers for Microsoft COM interfaces in Windows. +//! +//! This works with the `Class` and `Interface` traits from the `winapi` crate. + +use std::marker::PhantomData; +use std::mem; +use std::ops::Deref; +use std::ptr::{self, null_mut, NonNull}; +use std::rc::Rc; +use std::slice; + +use winapi::shared::minwindef::LPVOID; +use winapi::shared::{ + winerror::HRESULT, + wtypesbase::{CLSCTX, CLSCTX_INPROC_SERVER, CLSCTX_LOCAL_SERVER}, +}; +use winapi::um::{ + combaseapi::{CoCreateInstance, CoInitializeEx, CoTaskMemFree, CoUninitialize}, + objbase::{COINIT_APARTMENTTHREADED, COINIT_MULTITHREADED}, + unknwnbase::IUnknown, +}; +use winapi::{Class, Interface}; + +use check_succeeded; +use error::{succeeded_or_err, HResult, ResultExt}; + +/// Wrap COM interfaces sanely +/// +/// Originally from [wio-rs](https://github.com/retep998/wio-rs) `ComPtr` +#[repr(transparent)] +pub struct ComRef<T>(NonNull<T>) +where + T: Interface; +impl<T> ComRef<T> +where + T: Interface, +{ + /// Creates a `ComRef` to wrap a raw pointer. + /// It takes ownership over the pointer which means it does __not__ call `AddRef`. + /// `T` __must__ be a COM interface that inherits from `IUnknown`. + pub unsafe fn from_raw(ptr: NonNull<T>) -> ComRef<T> { + ComRef(ptr) + } + + /// Casts up the inheritance chain + pub fn up<U>(self) -> ComRef<U> + where + T: Deref<Target = U>, + U: Interface, + { + unsafe { ComRef::from_raw(NonNull::new(self.into_raw().as_ptr() as *mut U).unwrap()) } + } + + /// Extracts the raw pointer. + /// You are now responsible for releasing it yourself. + pub fn into_raw(self) -> NonNull<T> { + let p = self.0; + mem::forget(self); + p + } + + fn as_unknown(&self) -> &IUnknown { + unsafe { &*(self.as_raw_ptr() as *mut IUnknown) } + } + + /// Get another interface via `QueryInterface`. + /// + /// If the call to `QueryInterface` fails or the resulting interface is null, return an error. + pub fn cast<U>(&self) -> Result<ComRef<U>, HResult> + where + U: Interface, + { + let mut obj = null_mut(); + let hr = + succeeded_or_err(unsafe { self.as_unknown().QueryInterface(&U::uuidof(), &mut obj) })?; + NonNull::new(obj as *mut U) + .map(|obj| unsafe { ComRef::from_raw(obj) }) + .ok_or_else(|| HResult::new(hr).function("IUnknown::QueryInterface")) + } + + /// Obtains the raw pointer without transferring ownership. + /// + /// Do __not__ release this pointer because it is still owned by the `ComRef`. + /// + /// Do __not__ use this pointer beyond the lifetime of the `ComRef`. + pub fn as_raw(&self) -> NonNull<T> { + self.0 + } + + /// Obtains the raw pointer without transferring ownership. + /// + /// Do __not__ release this pointer because it is still owned by the `ComRef`. + /// + /// Do __not__ use this pointer beyond the lifetime of the `ComRef`. + pub fn as_raw_ptr(&self) -> *mut T { + self.0.as_ptr() + } +} +impl<T> Deref for ComRef<T> +where + T: Interface, +{ + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.as_raw_ptr() } + } +} +impl<T> Clone for ComRef<T> +where + T: Interface, +{ + fn clone(&self) -> Self { + unsafe { + self.as_unknown().AddRef(); + ComRef::from_raw(self.as_raw()) + } + } +} +impl<T> Drop for ComRef<T> +where + T: Interface, +{ + fn drop(&mut self) { + unsafe { + self.as_unknown().Release(); + } + } +} + +/// A scope for automatic COM initialization and deinitialization. +/// +/// Functions that need COM initialized can take a `&ComApartmentScope` argument and be sure that +/// it is so. It's recommended to use a thread local for this through the +/// [`INIT_MTA`](constant.INIT_MTA.html) or [`INIT_STA`](constant.INIT_STA.html) statics. +#[derive(Debug, Default)] +pub struct ComApartmentScope { + /// PhantomData used in lieu of unstable impl !Send + !Sync. + /// It must be dropped on the same thread it was created on so it can't be Send, + /// and references are meant to indicate that COM has been inited on the current thread so it + /// can't be Sync. + _do_not_send: PhantomData<Rc<()>>, +} + +impl ComApartmentScope { + /// This thread should be the sole occupant of a single thread apartment + pub fn init_sta() -> Result<Self, HResult> { + unsafe { check_succeeded!(CoInitializeEx(ptr::null_mut(), COINIT_APARTMENTTHREADED)) }?; + + Ok(Default::default()) + } + + /// This thread should join the process's multithreaded apartment + pub fn init_mta() -> Result<Self, HResult> { + unsafe { check_succeeded!(CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED)) }?; + + Ok(Default::default()) + } +} + +impl Drop for ComApartmentScope { + fn drop(&mut self) { + unsafe { + CoUninitialize(); + } + } +} + +thread_local! { + // TODO these examples should probably be in convenience functions. + /// A single thread apartment scope for the duration of the current thread. + /// + /// # Example + /// ``` + /// use comedy::com::{ComApartmentScope, INIT_STA}; + /// + /// fn do_com_stuff(_com: &ComApartmentScope) { + /// } + /// + /// INIT_STA.with(|com| { + /// let com = match com { + /// Err(e) => return Err(e.clone()), + /// Ok(ref com) => com, + /// }; + /// do_com_stuff(com); + /// Ok(()) + /// }).unwrap() + /// ``` + pub static INIT_STA: Result<ComApartmentScope, HResult> = ComApartmentScope::init_sta(); + + /// A multithreaded apartment scope for the duration of the current thread. + /// + /// # Example + /// ``` + /// use comedy::com::{ComApartmentScope, INIT_MTA}; + /// + /// fn do_com_stuff(_com: &ComApartmentScope) { + /// } + /// + /// INIT_MTA.with(|com| { + /// let com = match com { + /// Err(e) => return Err(e.clone()), + /// Ok(ref com) => com, + /// }; + /// do_com_stuff(com); + /// Ok(()) + /// }).unwrap() + /// ``` + pub static INIT_MTA: Result<ComApartmentScope, HResult> = ComApartmentScope::init_mta(); +} + +/// Create an instance of a COM class. +/// +/// This is mostly just a call to `CoCreateInstance` with some error handling. +/// The CLSID of the class and the IID of the interface come from the winapi `RIDL` macro, which +/// defines `Class` and `Interface` implementations. +/// +/// [`create_instance_local_server`](fn.create_instance_local_server.html) and +/// [`create_instance_inproc_server`](fn.create_instance_inproc_server.html) are convenience +/// functions for typical contexts. +pub fn create_instance<C, I>(ctx: CLSCTX) -> Result<ComRef<I>, HResult> +where + C: Class, + I: Interface, +{ + get(|interface| unsafe { + CoCreateInstance( + &C::uuidof(), + ptr::null_mut(), // pUnkOuter + ctx, + &I::uuidof(), + interface as *mut *mut _, + ) + }) + .function("CoCreateInstance") +} + +/// Create an instance of a COM class in the current process (`CLSCTX_LOCAL_SERVER`). +pub fn create_instance_local_server<C, I>() -> Result<ComRef<I>, HResult> +where + C: Class, + I: Interface, +{ + create_instance::<C, I>(CLSCTX_LOCAL_SERVER) +} + +/// Create an instance of a COM class in a separate process space on the same machine +/// (`CLSCTX_INPROC_SERVER`). +pub fn create_instance_inproc_server<C, I>() -> Result<ComRef<I>, HResult> +where + C: Class, + I: Interface, +{ + create_instance::<C, I>(CLSCTX_INPROC_SERVER) +} + +/// Call a COM method, returning a `Result`. +/// +/// An error is returned if the call fails. The error is augmented with the name of the interface +/// and method, and the file name and line number of the macro usage. +/// +/// `QueryInterface` is not used, the receiving interface must already be the given type. +/// +/// # Example +/// +/// ```no_run +/// # extern crate winapi; +/// # +/// # use winapi::um::bits::IBackgroundCopyJob; +/// # +/// # use comedy::com::ComRef; +/// # use comedy::{com_call, HResult}; +/// # +/// fn cancel_job(job: &ComRef<IBackgroundCopyJob>) -> Result<(), HResult> { +/// unsafe { +/// com_call!(job, IBackgroundCopyJob::Cancel())?; +/// } +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! com_call { + ($obj:expr, $interface:ident :: $method:ident ( $($arg:expr),* )) => {{ + use $crate::error::ResultExt; + $crate::error::succeeded_or_err({ + let obj: &$interface = &*$obj; + obj.$method($($arg),*) + }).function(concat!(stringify!($interface), "::", stringify!($method))) + .file_line(file!(), line!()) + }}; + + // support for trailing comma in method argument list + ($obj:expr, $interface:ident :: $method:ident ( $($arg:expr),+ , )) => {{ + $crate::com_call!($obj, $interface::$method($($arg),+)) + }}; +} + +/// Get an interface pointer that is returned through an output parameter. +/// +/// If the call returns a failure `HRESULT`, return an error. +/// +/// +/// # Null and Enumerators +/// If the method succeeds but the resulting interface pointer is null, this will return an +/// `HResult` error with the successful return code. In particular this can happen with +/// enumeration interfaces, which return `S_FALSE` when they write less than the requested number +/// of results. +pub fn get<I, F>(getter: F) -> Result<ComRef<I>, HResult> +where + I: Interface, + F: FnOnce(*mut *mut I) -> HRESULT, +{ + let mut interface: *mut I = ptr::null_mut(); + + let hr = succeeded_or_err(getter(&mut interface as *mut *mut I))?; + + NonNull::new(interface) + .map(|interface| unsafe { ComRef::from_raw(interface) }) + .ok_or_else(|| HResult::new(hr)) +} + +/// Call a COM method, create a [`ComRef`](com/struct.ComRef.html) from an output parameter. +/// +/// An error is returned if the call fails. The error is augmented with the name of the interface +/// and method, and the file name and line number of the macro usage. +/// +/// # Null and Enumerators +/// If the method succeeds but the resulting interface pointer is null, this will return an +/// `HResult` error with the successful return code. In particular this can happen with +/// enumeration interfaces, which return `S_FALSE` when they write less than the requested number +/// of results. +/// +/// # Example +/// +/// ```no_run +/// # extern crate winapi; +/// # use winapi::shared::guiddef::GUID; +/// # use winapi::um::bits::{IBackgroundCopyManager, IBackgroundCopyJob}; +/// # use comedy::com::ComRef; +/// # use comedy::{com_call_getter, HResult}; +/// +/// fn create_job(bcm: &ComRef<IBackgroundCopyManager>, id: &GUID) +/// -> Result<ComRef<IBackgroundCopyJob>, HResult> +/// { +/// unsafe { +/// com_call_getter!( +/// |job| bcm, +/// IBackgroundCopyManager::GetJob(id, job) +/// ) +/// } +/// } +/// ``` +#[macro_export] +macro_rules! com_call_getter { + (| $outparam:ident | $obj:expr, $interface:ident :: $method:ident ( $($arg:expr),* )) => {{ + use $crate::error::ResultExt; + let obj: &$interface = &*$obj; + $crate::com::get(|$outparam| { + obj.$method($($arg),*) + }).function(concat!(stringify!($interface), "::", stringify!($method))) + .file_line(file!(), line!()) + }}; + + // support for trailing comma in method argument list + (| $outparam:ident | $obj:expr, $interface:ident :: $method:ident ( $($arg:expr),+ , )) => { + $crate::com_call_getter!(|$outparam| $obj, $interface::$method($($arg),+)) + }; +} + +/// Get a task memory pointer that is returned through an output parameter. +/// +/// If the call returns a failure `HRESULT`, return an error. +/// +/// # Null +/// If the method succeeds but the resulting pointer is null, this will return an +/// `Err(HResult)` with the successful return code. +pub fn get_cotaskmem<F, T>(getter: F) -> Result<CoTaskMem<T>, HResult> +where + F: FnOnce(*mut *mut T) -> HRESULT, +{ + let mut ptr = ptr::null_mut() as *mut T; + + let hr = succeeded_or_err(getter(&mut ptr))?; + + NonNull::new(ptr) + .map(|ptr| unsafe { CoTaskMem::new(ptr) }) + .ok_or_else(|| HResult::new(hr)) +} + +/// Call a COM method, create a [`CoTaskMem`](handle/struct.CoTaskMem.html) from an output +/// parameter. +/// +/// An error is returned if the call fails or if the pointer is null. The error is augmented with +/// the name of the interface and method, and the file name and line number of the macro usage. +/// +/// # Null +/// If the method succeeds but the resulting pointer is null, this will return an `HResult` +/// error with the successful return code. +/// +/// # Example +/// +/// ```no_run +/// # extern crate winapi; +/// # use winapi::shared::winerror::HRESULT; +/// # use winapi::um::bits::IBackgroundCopyManager; +/// # use comedy::com::{ComRef, CoTaskMem}; +/// # use comedy::{com_call_taskmem_getter, HResult}; +/// # +/// fn get_error_description(bcm: &ComRef<IBackgroundCopyManager>, lang_id: u32, hr: HRESULT) +/// -> Result<CoTaskMem<u16>, HResult> +/// { +/// unsafe { +/// com_call_taskmem_getter!( +/// |desc| bcm, +/// IBackgroundCopyManager::GetErrorDescription(hr, lang_id, desc) +/// ) +/// } +/// } +/// ``` +#[macro_export] +macro_rules! com_call_taskmem_getter { + (| $outparam:ident | $obj:expr, $interface:ident :: $method:ident ( $($arg:expr),* )) => {{ + use $crate::error::ResultExt; + $crate::com::get_cotaskmem(|$outparam| { + $obj.$method($($arg),*) + }).function(concat!(stringify!($interface), "::", stringify!($method))) + .file_line(file!(), line!()) + }}; + + // support for trailing comma in method argument list + (| $outparam:ident | $obj:expr, $interface:ident :: $method:ident ( $($arg:expr),+ , )) => { + $crate::com_call_taskmem_getter!(|$outparam| $obj, $interface::$method($($arg),+)) + }; +} + +/// A Windows COM task memory pointer that will be automatically freed. +// I have only found this useful with strings, so that is the only access provided here by +// `as_slice_until_null()`. `Deref<Target=T>` would not be generally useful as task memory +// is usually encountered as variable length structures. +#[repr(transparent)] +#[derive(Debug)] +pub struct CoTaskMem<T>(NonNull<T>); + +impl<T> CoTaskMem<T> { + /// Take ownership of COM task memory, which will be freed with `CoTaskMemFree()` upon drop. + /// + /// # Safety + /// + /// `p` should be the only copy of the pointer. + pub unsafe fn new(p: NonNull<T>) -> CoTaskMem<T> { + CoTaskMem(p) + } +} + +impl<T> Drop for CoTaskMem<T> { + fn drop(&mut self) { + unsafe { + CoTaskMemFree(self.0.as_ptr() as LPVOID); + } + } +} + +impl CoTaskMem<u16> { + /// Interpret as a null-terminated `u16` array, return as a slice without terminator. + pub unsafe fn as_slice_until_null(&self) -> &[u16] { + for i in 0.. { + if *self.0.as_ptr().offset(i) == 0 { + return slice::from_raw_parts(self.0.as_ptr(), i as usize); + } + } + unreachable!() + } +} diff --git a/third_party/rust/comedy/src/error.rs b/third_party/rust/comedy/src/error.rs new file mode 100644 index 0000000000..fdaae493c1 --- /dev/null +++ b/third_party/rust/comedy/src/error.rs @@ -0,0 +1,377 @@ +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option. +// All files in the project carrying such notice may not be copied, modified, or distributed +// except according to those terms. + +//! Wrap several flavors of Windows error into a `Result`. + +use std::error::Error; +use std::fmt; + +use winapi::shared::minwindef::DWORD; +use winapi::shared::winerror::{ + ERROR_SUCCESS, FACILITY_WIN32, HRESULT, HRESULT_FROM_WIN32, SUCCEEDED, S_OK, +}; +use winapi::um::errhandlingapi::GetLastError; + +/// An error code, optionally with information about the failing call. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ErrorAndSource<T: ErrorCode> { + code: T, + function: Option<&'static str>, + file_line: Option<FileLine>, +} + +/// A wrapper for an error code. +pub trait ErrorCode: + Copy + fmt::Debug + Eq + PartialEq + fmt::Display + Send + Sync + 'static +{ + type InnerT: Copy + Eq + PartialEq; + + fn get(&self) -> Self::InnerT; +} + +impl<T> ErrorAndSource<T> +where + T: ErrorCode, +{ + /// Get the underlying error code. + pub fn code(&self) -> T::InnerT { + self.code.get() + } + + /// Add the name of the failing function to the error. + pub fn function(self, function: &'static str) -> Self { + Self { + function: Some(function), + ..self + } + } + + /// Get the name of the failing function, if known. + pub fn get_function(&self) -> Option<&'static str> { + self.function + } + + /// Add the source file name and line number of the call to the error. + pub fn file_line(self, file: &'static str, line: u32) -> Self { + Self { + file_line: Some(FileLine(file, line)), + ..self + } + } + + /// Get the source file name and line number of the failing call. + pub fn get_file_line(&self) -> &Option<FileLine> { + &self.file_line + } +} + +impl<T> fmt::Display for ErrorAndSource<T> +where + T: ErrorCode, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + if let Some(function) = self.function { + if let Some(ref file_line) = self.file_line { + write!(f, "{} ", file_line)?; + } + + write!(f, "{} ", function)?; + + write!(f, "error: ")?; + } + + write!(f, "{}", self.code)?; + + Ok(()) + } +} + +impl<T> Error for ErrorAndSource<T> where T: ErrorCode {} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FileLine(pub &'static str, pub u32); + +impl fmt::Display for FileLine { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}:{}", self.0, self.1) + } +} + +/// A [Win32 error code](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d), +/// usually from `GetLastError()`. +/// +/// Includes optional function name, source file name, and line number. See +/// [`ErrorAndSource`](struct.ErrorAndSource.html) for additional methods. +pub type Win32Error = ErrorAndSource<Win32ErrorInner>; + +impl Win32Error { + /// Create from an error code. + pub fn new(code: DWORD) -> Self { + Win32Error { + code: Win32ErrorInner(code), + function: None, + file_line: None, + } + } + + /// Create from `GetLastError()` + pub fn get_last_error() -> Self { + Win32Error::new(unsafe { GetLastError() }) + } +} + +#[doc(hidden)] +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Win32ErrorInner(DWORD); + +impl ErrorCode for Win32ErrorInner { + type InnerT = DWORD; + + fn get(&self) -> DWORD { + self.0 + } +} + +impl fmt::Display for Win32ErrorInner { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{:#010x}", self.0) + } +} + +/// An [HRESULT error code](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a). +/// These usually come from COM APIs. +/// +/// Includes optional function name, source file name, and line number. See +/// [`ErrorAndSource`](struct.ErrorAndSource.html) for additional methods. +pub type HResult = ErrorAndSource<HResultInner>; + +impl HResult { + /// Create from an `HRESULT`. + pub fn new(hr: HRESULT) -> Self { + HResult { + code: HResultInner(hr), + function: None, + file_line: None, + } + } + + /// Get the result code portion of the `HRESULT` + pub fn extract_code(&self) -> HRESULT { + // from winerror.h HRESULT_CODE macro + self.code.0 & 0xFFFF + } + + /// Get the facility portion of the `HRESULT` + pub fn extract_facility(&self) -> HRESULT { + // from winerror.h HRESULT_FACILITY macro + (self.code.0 >> 16) & 0x1fff + } + + /// If the `HResult` corresponds to a Win32 error, convert. + /// + /// Returns the original `HResult` as an error on failure. + pub fn try_into_win32_err(self) -> Result<Win32Error, Self> { + let code = if self.code() == S_OK { + // Special case, facility is not set. + ERROR_SUCCESS + } else if self.extract_facility() == FACILITY_WIN32 { + self.extract_code() as DWORD + } else { + return Err(self); + }; + + Ok(Win32Error { + code: Win32ErrorInner(code), + function: self.function, + file_line: self.file_line, + }) + } +} + +#[doc(hidden)] +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct HResultInner(HRESULT); + +impl ErrorCode for HResultInner { + type InnerT = HRESULT; + + fn get(&self) -> HRESULT { + self.0 + } +} + +impl fmt::Display for HResultInner { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "HRESULT {:#010x}", self.0) + } +} + +/// Extra functions to work with a `Result<T, ErrorAndSource>`. +pub trait ResultExt<T, E> { + type Code; + + /// Add the name of the failing function to the error. + fn function(self, function: &'static str) -> Self; + + /// Add the source file name and line number of the call to the error. + fn file_line(self, file: &'static str, line: u32) -> Self; + + /// Replace `Err(code)` with `Ok(replacement)`. + fn allow_err(self, code: Self::Code, replacement: T) -> Self; + + /// Replace `Err(code)` with `Ok(replacement())`. + fn allow_err_with<F>(self, code: Self::Code, replacement: F) -> Self + where + F: FnOnce() -> T; +} + +impl<T, EC> ResultExt<T, ErrorAndSource<EC>> for Result<T, ErrorAndSource<EC>> +where + EC: ErrorCode, +{ + type Code = EC::InnerT; + + fn function(self, function: &'static str) -> Self { + self.map_err(|e| e.function(function)) + } + + fn file_line(self, file: &'static str, line: u32) -> Self { + self.map_err(|e| e.file_line(file, line)) + } + + fn allow_err(self, code: Self::Code, replacement: T) -> Self { + self.or_else(|e| { + if e.code() == code { + Ok(replacement) + } else { + Err(e) + } + }) + } + + fn allow_err_with<F>(self, code: Self::Code, replacement: F) -> Self + where + F: FnOnce() -> T, + { + self.or_else(|e| { + if e.code() == code { + Ok(replacement()) + } else { + Err(e) + } + }) + } +} + +impl From<Win32Error> for HResult { + fn from(win32_error: Win32Error) -> Self { + HResult { + code: HResultInner(HRESULT_FROM_WIN32(win32_error.code())), + function: win32_error.function, + file_line: win32_error.file_line, + } + } +} + +/// Convert an `HRESULT` into a `Result`. +pub fn succeeded_or_err(hr: HRESULT) -> Result<HRESULT, HResult> { + if !SUCCEEDED(hr) { + Err(HResult::new(hr)) + } else { + Ok(hr) + } +} + +/// Call a function that returns an `HRESULT`, convert to a `Result`. +/// +/// The error will be augmented with the name of the function and the file and line number of +/// the macro usage. +/// +/// # Example +/// ```no_run +/// # extern crate winapi; +/// # use std::ptr; +/// # use winapi::um::combaseapi::CoUninitialize; +/// # use winapi::um::objbase::CoInitialize; +/// # use comedy::{check_succeeded, HResult}; +/// # +/// fn coinit() -> Result<(), HResult> { +/// unsafe { +/// check_succeeded!(CoInitialize(ptr::null_mut()))?; +/// +/// CoUninitialize(); +/// } +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! check_succeeded { + ($f:ident ( $($arg:expr),* )) => { + { + use $crate::error::ResultExt; + $crate::error::succeeded_or_err($f($($arg),*)) + .function(stringify!($f)) + .file_line(file!(), line!()) + } + }; + + // support for trailing comma in argument list + ($f:ident ( $($arg:expr),+ , )) => { + $crate::check_succeeded!($f($($arg),+)) + }; +} + +/// Convert an integer return value into a `Result`, using `GetLastError()` if zero. +pub fn true_or_last_err<T>(rv: T) -> Result<T, Win32Error> +where + T: Eq, + T: From<bool>, +{ + if rv == T::from(false) { + Err(Win32Error::get_last_error()) + } else { + Ok(rv) + } +} + +/// Call a function that returns a integer, convert to a `Result`, using `GetLastError()` if zero. +/// +/// The error will be augmented with the name of the function and the file and line number of +/// the macro usage. +/// +/// # Example +/// ```no_run +/// # extern crate winapi; +/// # use winapi::shared::minwindef::BOOL; +/// # use winapi::um::fileapi::FlushFileBuffers; +/// # use winapi::um::winnt::HANDLE; +/// # use comedy::{check_true, Win32Error}; +/// # +/// fn flush(file: HANDLE) -> Result<(), Win32Error> { +/// unsafe { +/// check_true!(FlushFileBuffers(file))?; +/// } +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! check_true { + ($f:ident ( $($arg:expr),* )) => { + { + use $crate::error::ResultExt; + $crate::error::true_or_last_err($f($($arg),*)) + .function(stringify!($f)) + .file_line(file!(), line!()) + } + }; + + // support for trailing comma in argument list + ($f:ident ( $($arg:expr),+ , )) => { + $crate::check_true!($f($($arg),+)) + }; +} diff --git a/third_party/rust/comedy/src/handle.rs b/third_party/rust/comedy/src/handle.rs new file mode 100644 index 0000000000..e31d53773e --- /dev/null +++ b/third_party/rust/comedy/src/handle.rs @@ -0,0 +1,91 @@ +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option. +// All files in the project carrying such notice may not be copied, modified, or distributed +// except according to those terms. + +//! Wrapping and automatically closing handles. + +use winapi::shared::minwindef::DWORD; +use winapi::shared::ntdef::NULL; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; +use winapi::um::winnt::HANDLE; + +/// Check and automatically close a Windows `HANDLE`. +#[repr(transparent)] +#[derive(Debug)] +pub struct Handle(HANDLE); + +impl Handle { + /// Take ownership of a `HANDLE`, which will be closed with `CloseHandle` upon drop. + /// Returns an error in case of `INVALID_HANDLE_VALUE` or `NULL`. + /// + /// # Safety + /// + /// `h` should be the only copy of the handle. `GetLastError()` is called to + /// return an error, so the last Windows API called on this thread should have been + /// what produced the invalid handle. + pub unsafe fn new(h: HANDLE) -> Result<Handle, DWORD> { + if h == NULL || h == INVALID_HANDLE_VALUE { + Err(GetLastError()) + } else { + Ok(Handle(h)) + } + } + + /// Obtains the raw `HANDLE` without transferring ownership. + /// + /// Do __not__ close this handle because it is still owned by the `Handle`. + /// + /// Do __not__ use this handle beyond the lifetime of the `Handle`. + pub fn as_raw(&self) -> HANDLE { + self.0 + } +} + +impl Drop for Handle { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } +} + +/// Call a function that returns a `HANDLE` (`NULL` or `INVALID_HANDLE_VALUE` on failure), wrap result. +/// +/// The handle is wrapped in a [`Handle`](handle/struct.Handle.html) which will automatically call +/// `CloseHandle()` on it. If the function fails, the error is retrieved via `GetLastError()` and +/// augmented with the name of the function and the file and line number of the macro usage. +/// +/// # Example +/// +/// ```no_run +/// # extern crate winapi; +/// # use std::ptr; +/// # use winapi::um::fileapi::FindFirstFileW; +/// # use winapi::um::minwinbase::WIN32_FIND_DATAW; +/// # use comedy::handle::Handle; +/// # use comedy::{call_handle_getter, Win32Error}; +/// # +/// unsafe fn find_first(name: &[u16], data: &mut WIN32_FIND_DATAW) -> Result<Handle, Win32Error> { +/// call_handle_getter!(FindFirstFileW(name.as_ptr(), data)) +/// } +/// ``` +#[macro_export] +macro_rules! call_handle_getter { + ($f:ident ( $($arg:expr),* )) => { + { + use $crate::error::{ErrorCode, FileLine, ResultExt, Win32Error}; + $crate::handle::Handle::new($f($($arg),*)) + .map_err(Win32Error::new) + .function(stringify!($f)) + .file_line(file!(), line!()) + } + }; + + // support for trailing comma in argument list + ($f:ident ( $($arg:expr),+ , )) => { + $crate::call_valid_handle_getter!($f($($arg),*)) + }; +} diff --git a/third_party/rust/comedy/src/lib.rs b/third_party/rust/comedy/src/lib.rs new file mode 100644 index 0000000000..3fd906ba36 --- /dev/null +++ b/third_party/rust/comedy/src/lib.rs @@ -0,0 +1,18 @@ +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option. +// All files in the project carrying such notice may not be copied, modified, or distributed +// except according to those terms. +#![cfg(windows)] + +//! Windows error handling, COM, and handles +//! +//! See macros for examples. + +extern crate winapi; + +pub mod com; +pub mod error; +pub mod handle; + +pub use error::{HResult, ResultExt, Win32Error}; |