Skip to content

Instantly share code, notes, and snippets.

@DrMetallius
Created August 31, 2019 20:36
Show Gist options
  • Save DrMetallius/084115493deb21148a6bef9326b47ea6 to your computer and use it in GitHub Desktop.
Save DrMetallius/084115493deb21148a6bef9326b47ea6 to your computer and use it in GitHub Desktop.
COM in Rust
use winapi::shared::ntdef::LPCWSTR;
use winapi::um::winuser::{BS_DEFPUSHBUTTON, SW_SHOW, WS_CHILD, WS_VISIBLE, WS_TABSTOP, WS_OVERLAPPEDWINDOW, WNDCLASSW};
use winapi::shared::minwindef::{HINSTANCE, UINT, WPARAM, LPARAM, LPVOID, DWORD};
use winapi::shared::windef::{POINT, HBRUSH, HMENU, HWND};
use winapi::um::winuser;
use winapi::um::winuser::{BS_TEXT, GetWindowLongW};
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::shared::minwindef::LRESULT;
use winapi::shared::wtypesbase::CLSCTX_INPROC_SERVER;
use std::ffi::{OsStr, OsString};
use std::os::windows::ffi::OsStrExt;
use super::text::WINDOW_NAME;
use winapi::um::winuser::GWL_HINSTANCE;
use winapi::um::combaseapi::{CoCreateInstanceEx, CoTaskMemFree};
use std::ptr::{null, null_mut};
use winapi::um::shobjidl::{FOS_FORCEFILESYSTEM, FOS_PICKFOLDERS, IFileDialog, IFileDialogEvents};
use winapi::um::shobjidl_core::{IShellItem, SIGDN_FILESYSPATH};
use winapi::um::combaseapi::CoCreateInstance;
use winapi::shared::guiddef::GUID;
use winapi::ctypes::c_void;
use winapi::shared::ntdef::HRESULT;
use winapi::Interface;
use winapi::um::unknwnbase::IUnknown;
use std::mem;
use winapi::um::winnt::PWSTR;
use std::os::windows::ffi::OsStringExt;
use std::slice;
use winapi::um::winnt::WCHAR;
use libc::wcslen;
use ui::text::MAIN_OPEN_BTN;
use std::ops::Deref;
macro_rules! com {
($call:ident($($args:tt)*)) => {
com!(@parse () $call () ($($args)*));
};
(@parse ($($out_vars:ident)*) $call:ident ($($parsed_args:tt)*) (out $arg:ident: $type:ty)) => {
com!(@parse ($($out_vars)* $arg) $call ($($parsed_args)* ComPtr::<$type>::as_out_param(&mut $arg) as *mut _,) ());
};
(@parse ($($out_vars:ident)*) $call:ident ($($parsed_args:tt)*) (out $arg:ident: $type:ty, $($rest:tt)*)) => {
com!(@parse ($($out_vars)* $arg) $call ($($parsed_args)* ComPtr::<$type>::as_out_param(&mut $arg) as *mut _,) ($($rest)*));
};
(@parse ($($out_vars:ident)*) $call:ident ($($parsed_args:tt)*) ($arg:expr)) => {
com!(@parse ($($out_vars)*) $call ($($parsed_args)* $arg,) ());
};
(@parse ($($out_vars:ident)*) $call:ident ($($parsed_args:tt)*) ($arg:expr, $($rest:tt)*)) => {
com!(@parse ($($out_vars)*) $call ($($parsed_args)* $arg,) ($($rest)*));
};
(@parse ($($out_vars:ident)*) $call:ident ($($parsed_args:tt)*) ()) => {
$(
let mut $out_vars = ComPtr::new();
)*
let result = $call($($parsed_args)*);
if result != 0 {
return ::std::result::Result::Err(result);
}
};
}
macro_rules! try_com {
($expr:expr) => (if $expr != 0 {
return ::std::result::Result::Err($expr)
}
)
}
macro_rules! uninitialized_ref {
($param:ident, $type:ty) => {let mut $param: &mut $type = mem::uninitialized();}
}
macro_rules! out_param {
($param:ident) => {&mut $param as *mut _ as *mut _}
}
pub fn run() {
unsafe {
let class_name = to_wstring("my_window");
let wnd = WNDCLASSW {
style: 0,
lpfnWndProc: Some(window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: 0 as HINSTANCE,
hIcon: winuser::LoadIconW(0 as HINSTANCE, winuser::IDI_APPLICATION),
hCursor: winuser::LoadCursorW(0 as HINSTANCE, winuser::IDC_ARROW),
hbrBackground: 16 as HBRUSH,
lpszMenuName: 0 as LPCWSTR,
lpszClassName: class_name.as_ptr(),
};
winuser::RegisterClassW(&wnd);
let h_wnd_window = winuser::CreateWindowExW(0, class_name.as_ptr(),
to_wstring(WINDOW_NAME).as_ptr(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
0, 0, 400, 400, 0 as HWND, 0 as HMENU, 0 as HINSTANCE, null_mut());
let mut msg = winuser::MSG {
hwnd: 0 as HWND,
message: 0 as UINT,
wParam: 0 as WPARAM,
lParam: 0 as LPARAM,
time: 0 as DWORD,
pt: POINT { x: 0, y: 0 },
};
winuser::ShowWindow(h_wnd_window, SW_SHOW);
let hwndButton = winuser::CreateWindowExW(
BS_TEXT,
to_wstring("BUTTON").as_ptr(), // Predefined class; Unicode assumed
to_wstring(MAIN_OPEN_BTN).as_ptr(), // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD, // Styles
50, // x position
50, // y position
100, // Button width
30, // Button height
h_wnd_window, // Parent window
null_mut() as _, // No menu.
GetWindowLongW(h_wnd_window, GWL_HINSTANCE) as HINSTANCE,
null_mut() as LPVOID); // Pointer not needed.
// Finally we run the standard application loop -
loop {
let pm = winuser::GetMessageW(&mut msg, 0 as HWND, 0, 0);
if pm == 0 {
break;
}
// println!("Msg on the main loop: {}", msg.message);
match msg.message {
winuser::WM_QUIT => { break; }
winuser::WM_COMMAND => {
// println!("Command");
show_open_file_dialog(h_wnd_window);
continue;
}
_ => {
winuser::TranslateMessage(&mut msg);
winuser::DispatchMessageW(&mut msg);
}
};
}
}
}
fn to_wstring(string: &str) -> Vec<u16> {
let mut data: Vec<u16> = OsStr::new(string).encode_wide().collect();
data.push(0);
data
}
fn from_wstring(string: PWSTR) -> String {
let string_slice = unsafe {
let mut len = wcslen(string);
slice::from_raw_parts(string, len)
};
OsString::from_wide(string_slice).into_string().unwrap()
}
pub unsafe extern "system" fn window_proc(h_wnd: HWND, msg: UINT, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
// println!("Msg on window proc: {}", msg);
if msg == winuser::WM_COMMAND {
println!("Command!");
}
match msg {
winuser::WM_DESTROY => {
winuser::PostQuitMessage(0);
0
}
winuser::WM_COMMAND => {
winuser::PostMessageW(h_wnd, msg, w_param, l_param);
0
}
_ => winuser::DefWindowProcW(h_wnd, msg, w_param, l_param)
}
}
const GUID_FILE_DIALOG: GUID = GUID {
Data1: 0xdc1c5a9c,
Data2: 0xe88a,
Data3: 0x4dde,
Data4: [0xa5, 0xa1, 0x60, 0xf8, 0x2a, 0x20, 0xae, 0xf7],
};
unsafe fn show_open_file_dialog(owner: HWND) -> Result<(), HRESULT> {
uninitialized_ref!(file_dialog, IFileDialog);
try_com!(CoCreateInstance(&GUID_FILE_DIALOG,
null_mut(),
CLSCTX_INPROC_SERVER,
&IFileDialog::uuidof() as *const _,
out_param!(file_dialog)));
let mut options = 0u32;
try_com!(file_dialog.GetOptions(out_param!(options)));
try_com!(file_dialog.SetOptions(options | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM));
try_com!(file_dialog.Show(owner));
let mut dialog_result = ComPtr::<IShellItem>::new();
try_com!(file_dialog.GetResult(dialog_result.as_out_param()));
uninitialized_ref!(display_name, WCHAR);
try_com!(dialog_result.GetDisplayName(SIGDN_FILESYSPATH, out_param!(display_name)));
println!("Got display name: {}", from_wstring(display_name));
CoTaskMemFree(display_name as *mut _ as *mut _);
dialog_result.Release();
file_dialog.Release();
Ok(())
}
unsafe fn test() -> Result<(), HRESULT> {
trace_macros!(true);
com!(CoCreateInstance(&GUID_FILE_DIALOG, null_mut(), CLSCTX_INPROC_SERVER, &IFileDialog::uuidof() as *const _, out file_dialog: IFileDialog));
trace_macros!(false);
file_dialog.Show(null_mut());
Ok(())
}
fn func(string: &str, string2: &str) {
println!("{}", string);
}
struct ComPtr<T: Interface> {
ptr: *mut T
}
impl<'a, T: Interface> ComPtr<T> {
fn new() -> ComPtr<T> {
ComPtr {
ptr: null_mut()
}
}
fn as_out_param(&mut self) -> *mut *mut T {
&mut self.ptr as *mut _ as *mut _
}
}
impl<'a, T: Interface> Drop for ComPtr<T> {
fn drop(&mut self) {
unsafe {
if let Some(ptr) = (self.ptr as *const IUnknown).as_ref() {
ptr.Release();
}
}
}
}
impl<T: Interface> Deref for ComPtr<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe {
self.ptr.as_ref().unwrap()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment