Вопрос или проблема
Мы относительно новички в разработке оболочке расширений и в данный момент работаем над реализацией интерфейсов IContextMenu
и IShellExtInit
. Пока что мы успешно реализовали метод QueryContextMenu
, который добавляет элементы в контекстное меню. Однако у нас возникают проблемы с следующим:
- Метод
InvokeCommand
не срабатывает при клике на элемент меню. - Метод
GetCommandString
не вызывается при наведении на элемент меню.
Учитывая наш ограниченный опыт с расширениями оболочки, мы подозреваем, что могли упустить какой-то важный шаг или деталь. Мы будем очень благодарны за любые советы или помощь в решении этих проблем.
Манифест crate
[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
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,
},
};
// Константы для идентификаторов элементов меню
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 {
// Реализуйте логику регистрации здесь
S_OK
}
#[no_mangle]
pub extern "system" fn DllUnregisterServer() -> HRESULT {
// Реализуйте логику отмены регистрации здесь
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::() 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, // что-то вроде NULL/ParentFolder
idata_obj: Option<&IDataObject>, // элемент по которому мы кликнули
_hkey_prog_id: HKEY, // это HKEY элемента, чтобы вы могли получить больше деталей о нем из реестра
) -> 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 {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
Мы пытались разместить отладочные уведомления (попапы с сообщением) в обеих функциях InvokeCommand
и GetCommandString
в первой строке. Но мы заметили, что эти функции вообще не вызываются.
Ответ или решение
Ваша проблема с вызовом метода IContextMenu_Impl::InvokeCommand
, который не вызывается при клике на элемент контекстного меню, а также с тем, что метод GetCommandString
не срабатывает при наведении, может быть связана с несколькими ключевыми аспектами, которые мы рассмотрим далее.
1. Проверка вызова QueryContextMenu
Первым делом необходимо удостовериться, что ваш метод QueryContextMenu
правильно добавляет элементы в контекстное меню, и что идентификаторы команд (ID) уникальны и соответствуют ожидаемым значениям в методе InvokeCommand
. Вы добавили элемент с помощью InsertMenuItemW
и задали ID равным index_menu
. Убедитесь, что значения IDM_ONLINE
, IDM_OFFLINE
, и IDM_SYNC
корректно сопоставляются с ID, которые вы объявили.
Ошибки в сопоставлении ID могут привести к тому, что InvokeCommand
будет вызван с неправильным значением, что приведет к тому, что условие match
не будет выполнено.
2. Правильная реализация InvokeCommand
Ваша реализация метода InvokeCommand
предполагает, что вы правильно извлекаете lpVerb
из CMINVOKECOMMANDINFO
. Однако, вероятнее всего, lpVerb
содержит строку, а не идентификатор команды. Вам нужно будет преобразовать строку, на которую указывает lpVerb
, в численный идентификатор, который вы используете для управления логикой ответа. Попробуйте изменить подход к получению идентификатора:
fn InvokeCommand(&self, lpici: *const CMINVOKECOMMANDINFO) -> Result<()> {
notify("invoke command");
let verb = unsafe { CStr::from_ptr((*lpici).lpVerb as *const c_char) }.to_string_lossy();
match verb.as_ref() {
"online" => {
MessageBoxW(None, w!("Online selected"), w!("Context Menu"), MB_OK);
}
"offline" => {
MessageBoxW(None, w!("Offline selected"), w!("Context Menu"), MB_OK);
}
"sync" => {
MessageBoxW(None, w!("Sync selected"), w!("Context Menu"), MB_OK);
}
_ => {}
}
Ok(())
}
3. Проверка регистрации и устройства
Не забывайте, что для правильной работы вашего COM-объекта необходимо, чтобы он был корректно зарегистрирован. Убедитесь, что ваш DllRegisterServer
и DllUnregisterServer
реализованы и корректно вызываются. Например, нужно убедиться, что в реестре Windows создана запись для вашего контекстного меню, и что все необходимые параметрические значения для CLSID указаны правильно.
4. Определение свойств GetCommandString
Метод GetCommandString
, согласно документации, вызывается для предоставления дополнительной информации о команде (например, описание или подсказки). Вам также следует убедиться, что вы реализуете его правильно.
fn GetCommandString(
&self,
idcmd: usize,
uflags: u32,
reserved: *const u32,
pszname: PSTR,
cchmax: u32,
) -> Result<()> {
notify("get command string");
if uflags == GCS_HELPTEXT {
let help_text = to_pcwstr("This will set your status to online.");
unsafe {
pszname.copy_from_nonoverlapping(help_text.as_ptr() as *const i8, help_text.len().min(cchmax as usize));
}
}
Ok(())
}
5. Использование инструментов для отладки
Если вышеупомянутые проверки не сработают, воспользуйтесь инструментами для отладки, такими как Sysinternals Suite, чтобы отследить вызовы и исключения, возникающие в вашем коде. Так вы сможете более точно определить точку, где возникает проблема.
Заключение
Убедитесь, что ваш код и регистрация жестко следуют спецификациям COM и хорошо обрабатывают различные условия. Применяйте предложенные изменения и контрольные проверки, чтобы диагностировать и устранить текущие проблемы. Если вам понадобится дальнейшая помощь, всегда полезно обратиться к сообществу разработчиков Windows или форумам по решению вопросов, связанным с COM и расширениями оболочки.