IContextMenu_Impl::InvokeCommand not getting called when menu option is clicked
Closed this issue · 9 comments
Summary
We are relatively new to developing shell extensions and are currently working on implementing both the IContextMenu
and IShellExtInit
interfaces. So far, we have successfully implemented the QueryContextMenu
method, which is adding items to the context menu. However, we are encountering issues with the following:
- The
InvokeCommand
method is not being triggered when clicking on a menu item. - The
GetCommandString
method is not being called when hovering over a menu item.
Given our limited experience with shell extensions, we suspect there might be some crucial step or detail we are overlooking. We would greatly appreciate any guidance or assistance in resolving these issues.
Crate manifest
[package]
name = "hello"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
windows-core = "0.58.0"
# winreg = "0.52.0"
[dependencies.windows]
version = "0.58.0"
features = [
"Win32_Foundation",
"Win32_System_SystemServices",
"Win32_UI_WindowsAndMessaging",
"Win32_System_Threading",
"Win32_System_Com",
"UI",
"UI_Shell",
"Win32_UI_Shell",
"Win32_UI_WindowsAndMessaging",
"implement",
"Win32_Graphics_Gdi",
"Win32_Storage_FileSystem",
"Win32_System_Diagnostics_Debug",
"Win32_System_LibraryLoader",
"Win32_System_ProcessStatus",
"Win32_UI_Shell_PropertiesSystem",
"Win32_System_Registry",
"Win32_UI_Shell_Common"
]
Crate code
use std::ffi::c_void;
use windows::Win32::Foundation::*;
use windows::Win32::System::Threading::GetCurrentProcessId;
use windows::{core::*, Win32::UI::WindowsAndMessaging::MessageBoxA};
use windows::Win32::UI::WindowsAndMessaging::*;
use windows::Win32::{
Foundation::BOOL,
System::Com::{IClassFactory, IClassFactory_Impl},
UI::WindowsAndMessaging::{
InsertMenuItemW, HMENU, MENUITEMINFOW, MFT_STRING, MIIM_FTYPE, MIIM_STATE, MIIM_STRING,
},
};
use windows::Win32::{System::Registry::HKEY, UI::Shell::Common::ITEMIDLIST};
use windows::{
core::*,
Win32::System::Com::IDataObject,
Win32::UI::Shell::{
IContextMenu, IContextMenu_Impl, IShellExtInit, IShellExtInit_Impl, CMINVOKECOMMANDINFO,
},
};
// Constants for menu item IDs
const IDM_ONLINE: u32 = 0;
const IDM_OFFLINE: u32 = 1;
const IDM_SYNC: u32 = 2;
#[no_mangle]
#[allow(non_snake_case, unused_variables)]
pub extern "system" fn DllMain(dll_module: HINSTANCE, call_reason: u32, _: *mut ()) -> bool {
println!("DllMain");
// match call_reason {
// DLL_PROCESS_ATTACH => notify("attach"),
// DLL_PROCESS_DETACH => notify("detach"),
// some => {
// // notify(&format!("DllMain called"));
// }
// }
true
}
#[no_mangle]
unsafe extern "system" fn DllGetClassObject(
rclsid: *const GUID,
riid: *const GUID,
ppv: *mut *mut c_void,
) -> HRESULT {
notify("get class object");
match *rclsid {
// OVERLAY_CLSID => IClassFactory::from(WatchedOverlayFactory).query(riid, ppv),
_ => IClassFactory::from(CustomContextMenuFactory).query(riid, ppv), // _ => IContextMenu::from(CustomContextMenuFactory).query(iid, interface)
// PROPERTY_STORE_CLSID => IClassFactory::from(WatchedPropertyStoreFactory).query(riid, ppv),
// _ => CLASS_E_CLASSNOTAVAILABLE,
}
}
#[no_mangle]
unsafe extern "system" fn DllCanUnloadNow() -> HRESULT {
// notify("DllCanUnloadNow");
S_OK
}
#[no_mangle]
pub extern "system" fn DllRegisterServer() -> HRESULT {
// Implement registration logic here
S_OK
}
#[no_mangle]
pub extern "system" fn DllUnregisterServer() -> HRESULT {
// Implement unregistration logic here
S_OK
}
fn notify(msg: &str) {
unsafe {
let pid = GetCurrentProcessId();
MessageBoxA(
HWND::default(),
PCSTR(msg.as_ptr()),
s!("hello.dll"),
Default::default(),
);
};
}
#[implement(IContextMenu, IShellExtInit)]
struct CustomContextMenu;
impl IContextMenu_Impl for CustomContextMenu_Impl {
fn QueryContextMenu(
&self,
hmenu: HMENU,
index_menu: u32,
id_cmd_first: u32,
id_cmd_last: u32,
u_flags: u32,
) -> Result<()> {
notify(&format!(
"query context menu: id: first:{}, last:{}",
id_cmd_first, id_cmd_last
));
unsafe {
let menu_item = MENUITEMINFOW {
cbSize: std::mem::size_of::<MENUITEMINFOW>() as u32,
fMask: MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_STATE,
fType: MFT_STRING,
dwTypeData: PWSTR(to_pcwstr("online").as_mut_ptr()),
cch: 2 * "online".len() as u32,
wID: index_menu,
fState: MFS_ENABLED,
..Default::default()
};
InsertMenuItemW(hmenu, index_menu, true, &menu_item).unwrap();
// AppendMenuW(hmenu, MF_STRING, index_menu as usize, w!("Sync"));
}
Ok(())
}
fn InvokeCommand(&self, lpici: *const CMINVOKECOMMANDINFO) -> Result<()> {
notify("invoke command");
unsafe {
let val = (*lpici).lpVerb;
let cmd_id = val.as_ptr() as u32;
match cmd_id {
IDM_ONLINE => {
MessageBoxW(None, w!("Online selected"), w!("Context Menu"), MB_OK);
}
IDM_OFFLINE => {
MessageBoxW(None, w!("Offline selected"), w!("Context Menu"), MB_OK);
}
IDM_SYNC => {
MessageBoxW(None, w!("Sync selected"), w!("Context Menu"), MB_OK);
}
_ => {}
}
}
Ok(())
}
fn GetCommandString(
&self,
idcmd: usize,
uflags: u32,
reserved: *const u32,
pszname: PSTR,
cchmax: u32,
) -> Result<()> {
notify("get command string");
Ok(())
// Err(Error::from(E_NOTIMPL))
}
}
impl IShellExtInit_Impl for CustomContextMenu_Impl {
fn Initialize(
&self,
_pidl_folder: *const ITEMIDLIST, // something like NULL/ParentFolder
idata_obj: Option<&IDataObject>, // the item we click
_hkey_prog_id: HKEY, // this the HKEY of the item so you can get more details of it from registory
) -> Result<()> {
Ok(())
}
}
#[implement(IClassFactory)]
pub struct CustomContextMenuFactory;
impl IClassFactory_Impl for CustomContextMenuFactory_Impl {
fn CreateInstance(
&self,
_punkouter: Option<&IUnknown>,
riid: *const windows_core::GUID,
ppvobject: *mut *mut ::core::ffi::c_void,
) -> Result<()> {
// notify("creating instance");
let obj_1: IContextMenu = CustomContextMenu.into();
let obj = IInspectable::from(CustomContextMenu);
unsafe {
obj_1.query(riid, ppvobject).unwrap();
obj.query(riid, ppvobject).ok()
}
}
fn LockServer(&self, _flock: BOOL) -> windows_core::Result<()> {
Ok(())
}
}
pub fn to_pcwstr(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
It seems like your question is more suited for a forum covering Windows API usage. I recommend posting it on Microsoft Q&A (https://learn.microsoft.com/en-us/answers/tags/224/windows-api-win32) or Stack Overflow (https://stackoverflow.com/search?q=windows-rs) for more assistance.
Implementing these interfaces can be very challenging, so make sure to carefully review the docs: https://learn.microsoft.com/windows/win32/shell/how-to-implement-the-icontextmenu-interface. Some issues I noticed in your implementation (at a glance) include not returning help text properly, assuming lpVerb
is always a u32
when it can also be a string, and miscalculating command IDs without considering the correct start and end bounds.
Thank you for your response and the helpful pointers.
We posted about our issue in stackoverflow, but we did not get anything that fixes our issue.
We went through the microsoft documentations and we notice that in c implementations QueryContextMenu
is also returning a integer value:
return an HRESULT value with the severity set to SEVERITY_SUCCESS. Set the code value to the offset of the largest command identifier that was assigned, plus one (1). For example, assume that idCmdFirst is set to 5 and you add three items to the menu with command identifiers of 5, 7, and 8. The return value should be MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1).
In the rust implementation the QueryContextMenu
is returning Result<()>
, we assume this is not the cause for our issue?
@ajaykumargdr Ah. I filed a bug on that one microsoft/win32metadata#2011
We still couldn't return multiple values although above issue is close.
@ajaykumargdr Are you pointing to master
or are you still using the released version (0.58
)? Not sure that change made it into 0.58.
I was using 0.58
and got the update after pointing to master
branch.
@ajaykumargdr Looks good now? Just double checking with you.
@riverar Ya it seems good now. Thank you again :).