PureBasic - форум

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » PureBasic - форум » Вопросы по PureBasic » Выбор нескольких файлов через конт.меню (?)


Выбор нескольких файлов через конт.меню (?)

Сообщений 1 страница 18 из 18

1

Я уже описывал задачу здесь, но видимо либо непонятно описание, либо нужные люди не успели прочитать пока сообщение было в топе.

В общем есть задача когда требуется выбрать несколько файлов в эксплорее/проводнике, нажать ПКМ,  вменю выбрать пункт, например "Архивировать" и выбранные файлы/папки отправятся в архив. Проблема в том, что обычный пункт контекстного меню в такой ситуации запускает каждый элемент (папку файл) с исполняемым файлом, то есть архиватор запустился бы 5 раз для каждого отдельно взятого элемента. Такой проблемы нет для пункт "Отправить" (SendTo), то есть кидаем ярлык в "C:\Users\юзер\AppData\Roaming\Microsoft\Windows\SendTo" и выделенные файлы будут запущены не каждый самостоятельно, а все как параметры одной ком-строки, то есть батник возьмёт все файлы/папки и сожмёт их в один архив, а не по отдельности. То есть хочется как у 7zip есть своё меню и он именно захватывает все файлы, аналогично у Notepad++ регистрирует свою dll, которая также открывает в одном экземпляре программы, хотя можно было бы предположить, что прога при запуске проверяет что один экземпляр уже запущен и передаёт файл открытой программе, но подозреваю, что сделано именно сразу передача файлов программе, а не запуск Notepad++ многократно с последующим закрытием.

Ещё ссылка по теме

готовый комплект, скачать (благодаря рабочему решению Петра)
.
.



.

pkm_dll

Скачать: yandex upload.ee

Создаёт пункт меню в контекстном меню проводника.
Сформировать ini-файл, зарегистрировать dll, пользоваться.

Описание ini-файла

[set] ; секция настроек
HotReadINI=0 ; предназначен для теста dll. При регистрации dll установить в 1, тогда каждый клик пункта меню сопровождается чтением ini-файла, что позволяет на лету менять параметры пункта (кроме NamePlag, Text и Bitmap). Когда пункт протестирован перейдите HotReadINI = 0 и горячее чтение прекратится. Чтобы начать тест заново надо перевести в 1 и сделать перелогин аккаунта.
Path=pkm.bat ; любой исполняемый файл, абсолютный путь, относительный от папки DLL и имя файла с поиском в %Path% или в папке DLL
Arg=/i ; дополнительные аргументы, кроме путей к выделенным файлам. Аргумент вставляется перед файлами.
ModeWorkDir=1 ; определяет рабочую папку. 0 - рабочая папка там где выбранные файлы, 1 - рабочая папка исполняемого файла
Text=My Item ; текст пункта, то что будет отображаться в меню. На любом языке, с пробелами.
Bitmap=1 ; если 1 то показывать значок пукнта. Это bmp-файл (icon.bmp), размером 16x16 пиксел в папке DLL. 0 - не отображать.
Select=1 ; что выбрать. 1 - файлы, 2 - папки, 3 - файлы и папки. Остальные будут игнорироваться.
Separator=| ; задаёт разделитель между файлами. Например в батнике можно задать разделитель в цикле, чтобы не использовать пробел, являющийся частью имени файла. Тогда %1 является вся строка с файлами.
Debug=0 ; режим отладки или вывода. 1, 2, 4, 8, 16. Итак, 1 и 4 выдают сообщение, причём  4 выдаёт только имена, а 1 исполняемый файл и рабочий каталог, 2 и 8 - буфер обмена, но 8 выдаёт только имена, а 2 исполняемый файл и рабочий каталог. Причём если в сообщении нажать ОК, то выполнится команда, получается контроль файлов и согласие выполнения, а вариант с буфером обмена не запускает исполняемый файл поэтому этот вариант можно использовать чтобы получить имена в буфер обмена для использования списка файлов на форуме и т.д. 16 - при 1 и 2 создаёт список файлов без путей. 1 и 2 выдаёт многострочный список вне зависимости от разделителя и кавычек.
Quotes=" ; задаёт символ вокруг строки файлов, чтобы bat-файл воспринимал строку как 1 параметр, не разделяя на пробелы. Но иногда нужен вывод без них.

[dllreg] ; секция регистрации DLL
NamePlag=MyPlag1 ; внутреннее имя плага, не особо важно, но если DLL регистрируется более одного раза, то имя плага всегда должно быть другое, иначе это перезапишет секцию в реестре. Использовать латинские буквы и цифры без пробелов.
WhereAssoc=1 ; где регистрировать пункт: 1 - файлы, 2 - папки, 4 - диски. Сумма флагов даёт комбинации вариантов, например 1+2+4 = 7, то есть флаг 7 регистрирует пункт во всех.

ini-файл читается только при регистрации и входе в аккаунт (при старте ОС). Это значит, что если необходимо поменять параметры в ini-файле, например пункт меню или исполняемый файл, или иконку (но не имя плагина), то это применится только после перезапуска эксплорера, аналогично после Выход -> Вход в аккаунт или перезагрузки. Но для теста введён параметр HotReadINI = 0, если HotReadINI = 1, то чтение ini-файла производится при каждом клике на пункте, это режим тестирования пункта без необходимости каждый раз делать перевыход, чтобы протестировать рабочую папку (ModeWorkDir), параметры запуска (Arg). После завершения теста переключить в HotReadINI = 0. начать тест снова горячим переключением в HotReadINI = 1 не получится, надо делать перелогин.

Bitmap не имеет прозрачных пикселов (хотя не тестировал розовую маску), поэтому весь квадрат значка должен быть заполнен, чтобы в белой и чёрной теме это выглядело одинаково.

Регистрация DLL

Положить папку с dll например в системную папку. Для начала путь к dll без пробела для надёжности.
Указать параметры в ini-файле, подробнее см. выше.
Зарегистрировать dll с правами админа (zRegister_DLL.cmd). (чтобы перерегистрировать нужно выполнить zUnregister_DLL.cmd, сделать выход из аккаунта, чтобы подменить dll новой версией и снова зарегистрировать).

Использование

Выделить несколько файлов, нажать правую кнопку мыши, вызвать пункт меню.
Выполняется файл, указанный в "Path = "
Файлы передаются исполняемому файлу в виде ком-строки.
Повторить выше описанное, чтобы создать сколько угодно пунктов. Создать новую папку с копией dll с новыми параметрами ini-файла с созданием нового пункта меню.

Обновление

Добавлен параметр Quotes = " - задаёт символ вокруг строки файлов
Добавлен параметр Debug = 0 включает отладку или режим вывода.
Добавлен параметр Separator = | задаёт разделитель между файлами
Добавлен параметр Select = 3 определяет файлы или папки захватывать
Добавлен параметр WhereAssoc = 3 определяет для каких объектов будет прописан пункт меню (диски, папки, файлы)
Добавлен параметр ModeWorkDir в ini-файл, определяющий рабочую папку 0 - папка выбранных файлов, 1 - папка исполняемого файла

Отредактировано AZJIO (13.09.2022 15:25:44)

0

2

AZJIO написал(а):

То есть хочется как у 7zip есть своё меню и он именно захватывает все файлы, аналогично у Notepad++ регистрирует свою dll

Контекстное меню проводника

0

3

Пётр
Нашёл на форуме
https://www.purebasic.fr/english/viewtopic.php?t=68101
но все варианты у меня выдают ошибку 0x80009e41. Там указали на проблему, типа x64 или x32, но я компилировал и тот и тот вариант, а ошибка во всех случаях одинаковая, а в \SysWOW64 и в \System32 один и тот же regsvr32, а regsvr64 нет или просто regsvr.

0

4

AZJIO написал(а):

0x80009e41

Это #SELFREG_E_CLASS
В исходнике полно строк с кодом этой ошибки.
Запуск выполняется с правами администратора и разрядность dll такая же как у проводника?

0

5

Пётр написал(а):

Это #SELFREG_E_CLASS
В исходнике полно строк с кодом этой ошибки.

Там когда код копируется в IDE там двойное двоеточие, константа  ведь определена выше.

Код:
#SELFREG_E_CLASS::EndIf

В общем попробовал от админа, в конт. меню всё равно ничего не появилось. Для x64 ещё Data.l заменил на Data.i для указателей функций. Может это всего лишь функционал который позволяет зарегистрировать dll, а сделать с помощью этого меню надо ещё постараться, то есть само меню прописать. Надо ещё примеры mksoft посмотреть, может в них есть меню, там просто про COMATE упоминают, типа доступ к объектам проводника.

Вот оно

Видно что процедура IShellExtInit_Initialize() содержит SHGetPathFromIDList_() и формирует список файлов. А функция IContextMenu_InvokeCommand() вызывает событие и перебирает файлы в цикле. Снизу метка CommandString: содержит текст пункта меню.

Отредактировано AZJIO (07.09.2022 03:20:25)

0

6

Для x64.

Код:
EnableExplicit

Macro qu 
  "
EndMacro

Macro CLSID_Build(p1, p2, p3, p4, p5, CLSID_Const = CLSID$, CLSID__Label = CLSID_m1)
  
  #CLSID_Const = "{"+qu#p1#qu+"-"+qu#p2#qu+"-"+qu#p3#qu+"-"+qu#p4#qu+"-"+qu#p5#qu+"}"
  
  DataSection
    CLSID__Label#:
    Data.l $p1
    Data.w $p2, $p3
    Data.a $p4>>8&255, $p4&255
    Data.a $p5>>40&255, $p5>>32&255, $p5>>24&255, $p5>>16&255, $p5>>8&255, $p5&255
  EndDataSection
  
EndMacro

CLSID_Build(851aab5c, 2008, 4157, 9c5d, a28dfa7b2660, CLSID_ID, CLSID_ContextMenuHandler)

#PlugName$ = "FastView.Image"
#CommandString = "Show file name" 
#CommandHelpLine = "Shows the file name" 

Procedure Error(message$) 
  Protected wError = GetLastError_()
  Protected *ErrorBuffer 
  
  If wError
    *ErrorBuffer = AllocateMemory(1024) 
    FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, 0, wError, 0, *ErrorBuffer, 1024, 0) 
    message$+Chr(10)+PeekS(*ErrorBuffer) 
    FreeMemory(*ErrorBuffer) 
  EndIf 
  MessageRequester("Error", message$) 
EndProcedure 

Structure ClassFactoryObject 
  *lpVtbl 
  nRefCount.l 
  nLockCount.l 
EndStructure 

Global m_pDataObj.IDataObject, cmd, hModule 
Global i_Unk.IUnknown, i_SEI.IShellExtInit, i_QCM.IContextMenu 
Global *p_Unk.ClassFactoryObject 
Global File$
Global GLINDEX_9.l
Global Dim GLINDEX_S.s(100)
Global g_Instance

Procedure Ansi2Uni1(*st, *Buffer, blen) 
  If Len(PeekS(*st, -1, #PB_Ascii))<blen 
    ProcedureReturn MultiByteToWideChar_(#CP_ACP, 0, *st, -1, *Buffer, blen) 
  Else 
    ProcedureReturn 0 
  EndIf 
EndProcedure 

Procedure IUnknown_QueryInterface(*ti_unk.IUnknown, *riid.GUID, *ppvObject.Integer) 
  If *ppvObject 
    If CompareMemory(*riid, ?IID_IUnknown, SizeOf(GUID))
      *ppvObject\i = i_Unk 
    ElseIf CompareMemory(*riid, ?IID_IShellExtInit, SizeOf(GUID)) 
      *ppvObject\i = i_SEI 
    ElseIf CompareMemory(*riid, ?IID_IContextMenu, SizeOf(GUID)) 
      *ppvObject\i = i_QCM 
    Else 
      ProcedureReturn #E_NOINTERFACE 
    EndIf 
  Else 
    ProcedureReturn #S_FALSE 
  EndIf 
  i_Unk\AddRef() 
  ProcedureReturn #S_OK 
EndProcedure 

Procedure IUnknown_AddRef(*ti_unk.IUnknown):*p_Unk\nRefCount+1:ProcedureReturn *p_Unk\nRefCount:EndProcedure 

Procedure IUnknown_Release(*ti_unk.IUnknown):*p_Unk\nRefCount-1:ProcedureReturn *p_Unk\nRefCount:EndProcedure 

Procedure IClassFactory_CreateInstance(*ti_cf.IClassFactory, *pUnkOuter.IUnknown, *riid.GUID, *ppvObject) 
  Protected hr
  
  If *pUnkOuter 
    ProcedureReturn #CLASS_E_NOAGGREGATION 
  Else 
    If i_SEI=#Null 
      ProcedureReturn #E_OUTOFMEMORY 
    Else 
      hr = i_SEI\QueryInterface(*riid, *ppvObject) 
    EndIf 
  EndIf 
  ProcedureReturn hr 
EndProcedure 

Procedure IClassFactory_LockServer(*ti_cf.IClassFactory, fLock) 
  ProcedureReturn #E_FAIL 
EndProcedure 

#CF_HDROP = $0f 
#DVASPECT_CONTENT = 1 
#TYMED_HGLOBAL = 1 
#TYMED_FILE = 2 

Procedure IShellExtInit_Initialize(*ti_sei.IShellExtInit, *pidlFolder.ITEMIDLIST, *pdtobj.IDataObject, hkeyProgID) 
  Protected fe.FORMATETC\cfFormat = #CF_HDROP
  Protected medium.STGMEDIUM, uCount, dd, *m_szFile
  
  If m_pDataObj 
    m_pDataObj\Release() 
  EndIf 
  If *pdtobj 
    m_pDataObj = *pdtobj 
    *pdtobj\AddRef() 
    fe\ptd = #Null 
    fe\dwAspect = #DVASPECT_CONTENT 
    fe\lindex = -1 
    fe\tymed = #TYMED_HGLOBAL 
    If *pdtobj\GetData(@fe, @medium)=#S_OK 
      uCount = DragQueryFile_(medium\hGlobal, -1, #Null, 0) 
      GLINDEX_9=uCount
      If uCount>=1
        For dd=0 To uCount-1
        *m_szFile = AllocateMemory(#MAX_PATH) 
        DragQueryFile_(medium\hGlobal, dd, *m_szFile, #MAX_PATH) 
        If Len(PeekS(*m_szFile))=0 
          uCount = 0 
        Else 
          GLINDEX_S(dd) = PeekS(*m_szFile) 
        EndIf 
        FreeMemory(*m_szFile) 
        Next
      EndIf 
      ReleaseStgMedium_(@medium) 
      If uCount>=1 
        ProcedureReturn #S_OK 
      EndIf 
    EndIf 
  EndIf 
  ProcedureReturn #S_FALSE 
EndProcedure 

#MIIM_ID = 2 
#MIIM_STRING = $40 
#MF_STRING = 0 
#MFT_STRING = #MF_STRING 
#CMF_DEFAULTONLY = 1 

Procedure IContextMenu_QueryContextMenu(*i_icm.IContextMenu, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags) 
  Static s.s=#CommandString
  If #CMF_DEFAULTONLY&uFlags:ProcedureReturn 0:EndIf 
  cmd = indexMenu 
  Protected mii.MENUITEMINFO 
  mii\cbSize = SizeOf(MENUITEMINFO) 
  mii\fMask = #MIIM_STRING|#MIIM_ID 
  mii\fType = #MFT_STRING 
  mii\wID = idCmdFirst 
  mii\dwTypeData = @s
  If InsertMenuItem_(hmenu, 0, #True, @mii)=#False 
    ProcedureReturn 1<<31 
  EndIf 
  ProcedureReturn 1 
EndProcedure 

Enumeration 
  #GCS_VERBA 
  #GCS_HELPTEXTA 
  #GCS_VALIDATEA 
  #GCS_VERBW 
  #GCS_HELPTEXTW 
  #GCS_VALIDATEW 
EndEnumeration 
#GCS_UNICODE = 4 
#GCS_VERB = #GCS_VERBA 
#GCS_HELPTEXT = #GCS_HELPTEXTA 
#GCS_VALIDATE = #GCS_VALIDATEA 

Procedure IContextMenu_GetCommandString(*ti_icm.IContextMenu, idCmd, uFlags, pwReserved, *pszName, cchMax) 
  If idCmd=cmd 
    Select uFlags 
      Case #GCS_HELPTEXTA ; Sets pszName To an ANSI string containing the Help text For the command. 
        PokeS(*pszName, #CommandHelpLine, -1, #PB_Ascii)
      Case #GCS_HELPTEXTW ; Sets pszName To a Unicode string containing the Help text For the command. 
        PokeS(*pszName, #CommandHelpLine)
      Case #GCS_VALIDATEA ; Returns S_OK If the menu item exists, Or S_FALSE otherwise. 
        ProcedureReturn #S_OK 
      Case #GCS_VALIDATEW ; Returns S_OK If the menu item exists, Or S_FALSE otherwise. 
        ProcedureReturn #S_OK 
      Case #GCS_VERBA ; Sets pszName To an ANSI string containing the language-independent command name For the menu item. 
        PokeS(*pszName, #CommandString, -1, #PB_Ascii)
      Case #GCS_VERBW ; Sets pszName To a Unicode string containing the language-independent command name For the menu item. 
        PokeS(*pszName, #CommandString)
      Default 
        ProcedureReturn #S_FALSE 
    EndSelect 
  Else 
    ProcedureReturn #S_FALSE 
  EndIf 
  ProcedureReturn #S_OK 
EndProcedure 

Structure CMINVOKECOMMANDINFO
  cbSize.l 
  fMask.l 
  hwnd.i 
  *lpVerb
  *lpParameters 
  *lpDirectory
  nShow.l 
  dwHotKey.l 
  hIcon.i 
EndStructure 

Structure CMINVOKECOMMANDINFOEX Extends CMINVOKECOMMANDINFO
  *lpTitle 
  *lpVerbW
  *lpParametersW
  *lpDirectoryW
  *lpTitleW
  ptInvoke.POINT 
EndStructure 

#SEE_MASK_UNICODE = $4000 
#CMIC_MASK_UNICODE = #SEE_MASK_UNICODE 

Procedure IContextMenu_InvokeCommand(*ti_icm.IContextMenu, *pici.CMINVOKECOMMANDINFOEX) 
  Protected dd, inputstring.s
  
  If *pici\cbSize=SizeOf(CMINVOKECOMMANDINFOEX) 
    If *pici\fMask&#CMIC_MASK_UNICODE 
      If (*pici\lpVerbW&$ffff)=cmd 
        For dd=0 To GLINDEX_9-1
          inputstring = inputstring + GLINDEX_S(dd) + Chr(10)
        Next 
        MessageRequester(Str(dd),inputstring.s)
        ProcedureReturn #NOERROR 
      EndIf 
    EndIf 
  ElseIf *pici\cbSize=SizeOf(CMINVOKECOMMANDINFO) 
    If (*pici\lpVerb&$ffff)=cmd 
      For dd=0 To GLINDEX_9-1
        inputstring.s = inputstring.s + GLINDEX_S(dd) + Chr(10)
       Next 
        MessageRequester(Str(dd),inputstring.s)
      ProcedureReturn #NOERROR 
    EndIf 
  Else 
    ProcedureReturn #S_FALSE 
  EndIf 
EndProcedure 

#SELFREG_E_FIRST = $80009E40 
#SELFREG_E_CLASS = #SELFREG_E_FIRST+1 

#GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 4 

ProcedureDLL DllRegisterServer() 
  Protected hKey1, *szBuffer, str.s
  
  If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "*\shellex\ContextMenuHandlers\"+#PlugName$, 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #Null, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  RegSetValueEx_(hKey1, "", 0, #REG_SZ, #CLSID_ID, StringByteLength(#CLSID_ID)) 
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf 
  If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, #PlugName$+"\shellex\ContextMenuHandlers", 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #Null, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  RegSetValueEx_(hKey1, "", 0, #REG_SZ, #CLSID_ID, StringByteLength(#CLSID_ID)) 
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf 
  If RegCreateKeyEx_(#HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #Null, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  RegSetValueEx_(hKey1, #CLSID_ID, 0, #REG_SZ, #PlugName$, StringByteLength(#PlugName$)) 
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf 
  *szBuffer = AllocateMemory(#MAX_PATH) 
  If *szBuffer And GetModuleFileName_(g_Instance, *szBuffer, #MAX_PATH) 
  If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\"+#CLSID_ID, 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #Null, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
    RegSetValueEx_(hKey1, "", 0, #REG_SZ, #PlugName$, StringByteLength(#PlugName$)) 
    If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf 
    If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\"+#CLSID_ID+"\InProcServer32", 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #Null, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
    RegSetValueEx_(hKey1, "", 0, #REG_SZ, *szBuffer, StringByteLength(PeekS(*szBuffer))+SizeOf(Character)) 
    str="Apartment"
    RegSetValueEx_(hKey1, "ThreadingModel", 0, #REG_SZ, str, StringByteLength(str)) 
    If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf 
  Else 
    ProcedureReturn #SELFREG_E_CLASS 
  EndIf 
  FreeMemory(*szBuffer) 
  ProcedureReturn #S_OK 
EndProcedure 

ProcedureDLL DllUnregisterServer() 
  Protected hKey1
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "*\shellex\ContextMenuHandlers\"+#PlugName$)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, #PlugName$+"\shellex\ContextMenuHandlers")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, #PlugName$+"\shellex")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, #PlugName$)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If RegOpenKeyEx_(#HKEY_LOCAL_MACHINE, "Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", 0, #KEY_ALL_ACCESS, @hKey1)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If RegDeleteValue_(hKey1, #CLSID_ID)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf 
  If RegOpenKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\"+#CLSID_ID, 0, #KEY_ALL_ACCESS, @hKey1)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If RegDeleteKey_(hKey1, "InProcServer32")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf 
  If RegOpenKeyEx_(#HKEY_CLASSES_ROOT, "CLSID", 0, #KEY_ALL_ACCESS, @hKey1)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If RegDeleteKey_(hKey1, #CLSID_ID)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf 
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf 
  ProcedureReturn #S_OK 
EndProcedure 

ProcedureDLL AttachProcess(Instance)
  g_Instance = Instance
  DisableThreadLibraryCalls_(Instance) 
  hModule = Instance 
  i_QCM = ?lpVT_IContextMenu 
  i_SEI = ?lpVT_IShellExtInit 
  i_Unk = ?lpVT_IUnknown 
  *p_Unk = ?lpVT_IUnknown 
  ProcedureReturn #True 
EndProcedure 

ProcedureDLL DllGetClassObject(*rclsid.GUID, *riid.GUID, *ppv.Integer) 
  If *ppv 
    If CompareMemory(*rclsid, ?CLSID_ContextMenuHandler, SizeOf(GUID)) 
      If CompareMemory(*riid, ?IID_IClassFactory, SizeOf(GUID)) 
        *ppv\i = ?lpVT_IClassFactory 
      Else 
        *ppv\i = 0 
        ProcedureReturn #CLASS_E_CLASSNOTAVAILABLE 
      EndIf 
    Else 
      *ppv\i = 0 
      ProcedureReturn #CLASS_E_CLASSNOTAVAILABLE 
    EndIf 
  Else 
    *ppv\i = 0 
    ProcedureReturn #CLASS_E_CLASSNOTAVAILABLE 
  EndIf 
  ProcedureReturn #S_OK 
EndProcedure 

ProcedureDLL DllCanUnloadNow()
  If *p_Unk\nRefCount<=0 And *p_Unk\nLockCount<=0 
    ProcedureReturn #S_OK 
  Else 
    ProcedureReturn #S_FALSE 
  EndIf 
EndProcedure 

DataSection 
  lpVT_IUnknown: 
    Data.i ?VT_IUnknown, 0, 0 
  VT_IUnknown: 
    Data.i @IUnknown_QueryInterface(), @IUnknown_AddRef(), @IUnknown_Release() 
  lpVT_IClassFactory: 
    Data.i ?VT_IClassFactory 
  VT_IClassFactory: 
    Data.i @IUnknown_QueryInterface(), @IUnknown_AddRef(), @IUnknown_Release() 
    Data.i @IClassFactory_CreateInstance(), @IClassFactory_LockServer() 
  lpVT_IShellExtInit: 
    Data.i ?VT_IShellExtInit 
  VT_IShellExtInit: 
    Data.i @IUnknown_QueryInterface(), @IUnknown_AddRef(), @IUnknown_Release() 
    Data.i @IShellExtInit_Initialize() 
  lpVT_IContextMenu: 
    Data.i ?VT_IContextMenu 
  VT_IContextMenu: 
    Data.i @IUnknown_QueryInterface(), @IUnknown_AddRef(), @IUnknown_Release() 
    Data.i @IContextMenu_QueryContextMenu() 
    Data.i @IContextMenu_InvokeCommand() 
    Data.i @IContextMenu_GetCommandString() 
EndDataSection 

DataSection
  IID_IUnknown: 
  Data.l $00000000 
  Data.w $0000, $0000 
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46 
  IID_IClassFactory: 
  Data.l $00000001 
  Data.w $0, $0 
  Data.b $C0, $0, $0, $0, $0, $0, $0, $46 
  IID_IShellExtInit: 
  Data.l $000214E8 
  Data.w 0, 0 
  Data.b $C0, 0, 0, 0, 0, 0, 0, $46 
  IID_IContextMenu: 
  Data.l $000214E4 
  Data.w 0, 0 
  Data.b $C0, 0, 0, 0, 0, 0, 0, $46
EndDataSection

+1

7

Пётр
Отлично работает.

Я добавил в исходник прописку для папок и комментарии - в архиве
Буду думать как настройки перенести в ini-файл. Одни настройки можно для регистрации dll использовать (имя пункта, имя плага), другие во время выполнения, например какую команду выполнить. Тогда dll будет универсальной, каждый подстроит под себя.

Вот ещё надо будет подумать как добавить несколько пунктов меню. Разобраться почему в коде две команды

Код:
If *pici\cbSize = SizeOf(CMINVOKECOMMANDINFOEX)
ElseIf *pici\cbSize = SizeOf(CMINVOKECOMMANDINFO)

то есть условия два.

Ещё бы поддержку иконки сделать. Думаю тут WinAPI надо использовать, так как пункт встраивается в системное меню.
В Notepad++ при регистрации такой dll сделан ключ, чтобы показать GUI с настройками, в котором указывается имя пункта и другие. И пункт меню прописывается в реестре и оттуда берётся.

Отредактировано AZJIO (07.09.2022 22:04:05)

0

8

AZJIO написал(а):

Разобраться почему в коде две команды

Разные структуры CMINVOKECOMMANDINFOEX и CMINVOKECOMMANDINFO.
Вероятно в зависимости от ОС или еще чего-то передаются разные данные.

AZJIO написал(а):

Ещё бы поддержку иконки сделать.

Пункт добавляется в процедуре

Код:
Procedure IContextMenu_QueryContextMenu(*i_icm.IContextMenu, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags) 
  Static s.s=#CommandString
  If #CMF_DEFAULTONLY&uFlags:ProcedureReturn 0:EndIf 
  cmd = indexMenu 
  Protected mii.MENUITEMINFO 
  mii\cbSize = SizeOf(MENUITEMINFO) 
  mii\fMask = #MIIM_STRING|#MIIM_ID 
  mii\fType = #MFT_STRING 
  mii\wID = idCmdFirst 
  mii\dwTypeData = @s
  If InsertMenuItem_(hmenu, 0, #True, @mii)=#False 
    ProcedureReturn 1<<31 
  EndIf 
  ProcedureReturn 1 
EndProcedure

Там же можно добавить иконку.
http://www.vsokovikov.narod.ru/New_MSDN … nuitem.htm
http://www.vsokovikov.narod.ru/New_MSDN … eminfo.htm

0

9

Пётр
По описанию добавил так:

Код:
	Protected mii.MENUITEMINFO
	mii\cbSize = SizeOf(MENUITEMINFO)
	mii\fMask = #MIIM_STRING | #MIIM_ID | #MIIM_BITMAP | #MIIM_TYPE
	mii\fType = #MFT_BITMAP
	mii\wID = idCmdFirst
; 	pt\y << 32 + pt\x
	ExtractIconEx_("Shell32.dll", 69, 0, hicon, 1)
	mii\dwTypeData = hicon

В описании сказано что #MIIM_STRING и #MIIM_BITMAP несовместим, но решил рискнуть, пункт вообще не появился. И там mii\dwTypeData = hicon строка была в младшем и иконка конкретно указано в младшем слове, тоже непонятно как это совместить. Надо погуглить на форуме структуру MENUITEMINFO, может готовое решение есть.
Вторая попытка, нашёл вариант назначение позже, может проблема что не BITMAP, иконка то с прозрачностью. Тоже не сработало.

Код:
	If InsertMenuItem_(hmenu, 0, #True, @mii) = #False
    ProcedureReturn 1 << 31
	EndIf
	ExtractIconEx_("Shell32.dll", 69, 0, @hicon, 1)
	SetMenuItemBitmaps_(hmenu, mii\wID, #MF_BYCOMMAND, hicon, hicon)

Вот пример, тут явно что пункт полностью нарисован

Код:
CreateImage(1,60,25,32)
StartDrawing(ImageOutput(1))
  Box(0,0,60,25,RGB(200,130,0))
  DrawText(5,5,"Пункт 1",RGB(255,255,255),RGB(200,130,0))
StopDrawing()

If OpenWindow(0,0,0,500,250,"Пример",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
  If CreateMenu(0,WindowID(0))
    MenuTitle("Меню")
      MenuItem(1,"Пункт 1",0)
      MenuItem(2,"Пункт 2",0)
      MenuItem(3,"Пункт 3",0)
      MenuItem(4,"Пункт 4",0)
      MenuItem(5,"Пункт 5",0)
  EndIf

  mii.MENUITEMINFO\cbSize = SizeOf(MENUITEMINFO)
  mii\fMask = #MIIM_TYPE
  mii\fType = #MFT_BITMAP
  mii\dwTypeData = ImageID(1)
  SetMenuItemInfo_(MenuID(0),1,0,mii)
Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf

Отредактировано AZJIO (08.09.2022 00:16:02)

0

10

AZJIO написал(а):

ExtractIconEx_("Shell32.dll", 69, 0, @hicon, 1)
SetMenuItemBitmaps_(hmenu, mii\wID, #MF_BYCOMMAND, hicon, hicon)

SetMenuItemBitmaps требует hBitmap, а не hIcon. Если передавать hIcon, GetLastError_() возвращает  ошибку 87 - Параметр задан неверно.
Попробуйте

Код:
Procedure NewMenuIcon(id,color) 
  CreateImage(id, 16, 16) 
  
  StartDrawing(ImageOutput(id)) 
  Box(0,0,16,16,color) 
  StopDrawing() 
  
  ProcedureReturn ImageID(id) 
EndProcedure 

SetMenuItemBitmaps_(hmenu, mii\wID, #MF_BYCOMMAND, NewMenuIcon(0,$FF0000), 0)

0

11

Пётр
Спасибо!

Добавил свой архив в первый пост.
Добавил ini-файл, позволяя пользователю самостоятельно выбирать имя пункта, значок, исполняемый файл, параметры запуска, рабочую директорию, возможность горячего изменения ini-файла на время теста параметров пункта.

0

12

Пётр
Если прописать пункт в Folder или Directory (в реестре, для захвата папок), то папки выдаёт как элементы списка. Но если вызвать конт меню в дереве файлов, то проводник падает.

Я сделал лог событий для всех функций.

Регистрация

00000001 0.00000000 [7132] AttachProcess
00000002 0.00026840 [7132] DllRegisterServer

Вызов меню и клик

00000001 0.00000000 [6936] DllGetClassObject
00000002 0.00001850 [6936] IClassFactory_CreateInstance
00000003 0.00004800 [6936] IUnknown_QueryInterface
00000004 0.00007040 [6936] IUnknown_AddRef
00000005 0.00008230 [6936] IUnknown_Release
00000006 0.00012860 [6936] IUnknown_QueryInterface
00000007 0.00014170 [6936] IUnknown_AddRef
00000008 0.00017760 [6936] IShellExtInit_Initialize
00000009 0.00029280 [6936] IUnknown_Release
00000010 0.00032940 [6936] IUnknown_QueryInterface
00000011 0.00035310 [6936] IUnknown_QueryInterface
00000012 0.00043190 [6936] IContextMenu_QueryContextMenu
00000013 0.00060190 [6936] IUnknown_AddRef
00000014 0.00061460 [6936] IUnknown_Release
00000015 0.01638430 [6936] IContextMenu_GetCommandString
--//-- ещё 5-20раз
00000030 2.94208431 [6936] IContextMenu_InvokeCommand - 1
00000031 2.94384909 [6936] IUnknown_QueryInterface
00000032 2.94386888 [6936] IUnknown_QueryInterface
00000033 2.94403005 [6936] IUnknown_QueryInterface
00000034 2.94404602 [6936] IUnknown_QueryInterface
00000035 2.94406319 [6936] IUnknown_Release

причём вызов меню первые 15 пунктов. Хотя по разному бывает (видимо зависит от объекта на котором вызов и набор пунктов меню разный), я пробовал папки, файлы, оба вместе, много, мало, в среднем похоже.

При падении Проводника (вызов меню в дереве) лог выдаёт тоже самое добавляя в конце вызов функции DllCanUnloadNow. И кстати если выделить несколько папок, то DllCanUnloadNow нет, а если одну, то DllCanUnloadNow есть, при этом пункт продолжает работать, то есть не выгружает DLL (Dll Can Unload Now = Dll может выгружаться сейчас).

Падение завершается этими (две последние - системные, их нет в dll)

00000025 0.04490370 [3460] DllCanUnloadNow
00000026 3.70142555 [5972] StartUI.SplitViewFrame
00000027 4.25681257 [5972] Suspending

И ещё, я вчера выкладывал промежуточную версию, которая была поломана и падала сразу, там была ошибка в PathFind(), была одна закачка этой сбойной версии, перед тем как я исправил и выложил.

Добавил в архив исходник со строками отладки в Dbgview.exe

Нашёл флаг #CMF_EXPLORE = 4, но не смог с помощью него отключить меню для дерева.

Добавил параметры Quotes = " - задаёт символ вокруг строки файлов и Debug = 0 включает отладку или режим вывода.

Отредактировано AZJIO (11.09.2022 08:20:39)

0

13

Добавил справку в архив и в шапку

0

14

Пётр
Тут переменные  i_QCM, i_SEI, i_Unk похожи на указатели, так как "?" указывает на метку, которая возвращает указатель. Значит переменные должны быть *i_QCM, *i_SEI, *i_Unk. Почитал в справке, особенность указателя в отличии от Integer, при одинаковости байтов, указатель является существующим указателем памяти, по этому это не может быть какое нибудь другое число, так как оно может не оказаться указателем из-за нахождения за пределами доступной программе памяти.

Код:
ProcedureDLL AttachProcess(Instance)
  g_Instance = Instance
  DisableThreadLibraryCalls_(Instance)
  hModule = Instance
  i_QCM = ?lpVT_IContextMenu
  i_SEI = ?lpVT_IShellExtInit
  i_Unk = ?lpVT_IUnknown
  *p_Unk = ?lpVT_IUnknown
  ProcedureReturn #True
EndProcedure

тут сократил функцию.

Код:
ProcedureDLL DllGetClassObject(*rclsid.GUID, *riid.GUID, *ppv.Integer)
	Protected *Object.IUnknown
	If *ppv And CompareMemory(*rclsid, ?CLSID_ContextMenuHandler, SizeOf(GUID)) And CompareMemory(*riid, ?IID_IClassFactory, SizeOf(GUID))
    *ppv\i = ?lpVT_IClassFactory
    ProcedureReturn #S_OK
	EndIf
	*ppv\i = 0
	ProcedureReturn #CLASS_E_CLASSNOTAVAILABLE
EndProcedure

хотя просматривая другие коды нашёл вариант с проверкой объекта перед использованием

Код:
ProcedureDLL DllGetClassObject(*rclsid.GUID, *riid.GUID, *ppv.Integer)
	Protected *Object.IUnknown
	If *ppv And CompareMemory(*rclsid, ?CLSID_ContextMenuHandler, SizeOf(GUID)) And CompareMemory(*riid, ?IID_IClassFactory, SizeOf(GUID))
    *Object = ?lpVT_IClassFactory
    If *Object
    	*ppv\i = *Object
    	ProcedureReturn #S_OK
    Else
    	ProcedureReturn #E_OUTOFMEMORY
    EndIf
	EndIf
	*ppv\i = 0
	ProcedureReturn #CLASS_E_CLASSNOTAVAILABLE
EndProcedure

Немного разобрался. Функция AttachProcess() получает метки функций i_QCM = ?lpVT_IContextMenu, здесь i_QCM и подобные объявлены как интерфейсы. Если посмотреть в справке структур, то это интерфейсы с указателями на функции, например интерфейс IContextMenu содержит указатели на 6 функций, смотрим раздел Data и там lpVT_IContextMenu ссылается на указатель ?VT_IContextMenu, который ссылается на метку VT_IContextMenu, которая содержит указатели на ранее указанные 6 функций, которые являются именами этих функций в коде, то есть указатели на них.

И кстати обновлял, добавив флаг 32 для сохранения списка файлов, если не удаётся передавать иначе, то есть запускаемая прога должна уметь парсить список файлов, чтобы сделать с ними что-то.

Отредактировано AZJIO (09.10.2022 04:17:44)

0

15

AZJIO написал(а):

Пётр
Тут переменные  i_QCM, i_SEI, i_Unk похожи на указатели, так как "?" указывает на метку, которая возвращает указатель. Значит переменные должны быть *i_QCM, *i_SEI, *i_Unk.

Размер у Integer и указателя одинаков и адрес можно хранить в том или другом.

AZJIO написал(а):

по этому это не может быть какое нибудь другое число

Записать можно все что угодно

Код:
*point = 1234
Debug *point

0

16

Я так понял что вы этот сервер прописали в реестр для запуска ?
Вот поанализировав ситуацию пришол к выводу openlybrary() не допилина как следует в плане флагов загрузки dll
из за этого и загрузка наверное в процесс однобокая и сервер незапускается поэтой причине в процессе
вообщем пример заключается в том что запускаем и пока прога подвисает на мессаге  смотрим два варианта в диспетчере задач
какие дллки в процессе типа активированы.
Ps:CoUninitialize_() это побочное не рассматриваем.

Код:

Procedure debug_ohibka(s$)
Protected *Bufer
*Bufer=AllocateMemory(200)
FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM,0, GetLastError_(), #LANG_NEUTRAL, *Bufer, 200,0)
MessageRequester("Событие "+s$,Str(GetLastError_())+" "+PeekS(*Bufer,-1,#PB_Unicode))
FreeMemory(*Bufer)
EndProcedure


#DONT_RESOLVE_DLL_REFERENCES=$00000001
#LOAD_IGNORE_CODE_AUTHZ_LEVEL=$00000010
#LOAD_LIBRARY_AS_DATAFILE=$00000002
#LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE=$00000040
#LOAD_LIBRARY_SEARCH_SYSTEM32=$00000800
#LOAD_WITH_ALTERED_SEARCH_PATH=$00000008
;| #LOAD_WITH_ALTERED_SEARCH_PATH



;deskriptor.i = LoadLibraryEx_("C:\Windows\System32\PortableDeviceApi.dll",0,#LOAD_IGNORE_CODE_AUTHZ_LEVEL | #LOAD_LIBRARY_SEARCH_SYSTEM32 )
debug_ohibka("LoadLibraryEx_OK")
If deskriptor=0
  debug_ohibka("LoadLibraryEx_OF")
 End
EndIf
OpenLibrary(0,"C:\Windows\System32\PortableDeviceApi.dll")
 ; If CoInitialize_(0)=#iS_OK
 ;   debug_ohibka("CoInitialize_OK")
 ;  Else
 ;    debug_ohibka("CoInitialize_OF")
 ;    End
 ; EndIf
  debug_ohibka("LoadLibraryEx_OF")


;CoUninitialize_()
CloseLibrary(0)
 ;FreeLibrary_(deskriptor)
debug_ohibka("FreeLibrary_")

0

17

Sergeihik
Изначально из источников код позиционируется для файлов, а не для папок, поэтому не учтён этот момент. А человек (который просил) делал скрипт автоматизации из контекстного меню, выбирая и папки и файлы и создавал архив, возможно с паролем, поэтому я и хотел чтобы код мог получать любые элементы окна, естественно чтобы вызвать меню на папках надо добавить в реестре пункт и в "Directory", но после этого пункт добавляется в меню папок, а это и в дереве тоже. То есть где-то ошибка, может стоит посмотреть структуру сервера созданную для других пунктов (в реестре), посмотреть отличия, может действительно стоит только подправить раздел регистрации и добавить какие-то недостающие ключи.

Отредактировано AZJIO (13.10.2022 20:26:36)

0

18

Да просто по коду это как бы делается через com  обект(аля сервер с интерфесом(и)) а значит эта дллка с ним должна быть загружена.
Чтобы клиент мог обращаться к её интерфейсам с методами(функциями)
Ps: в вашем коде как раз _GUID(ключ) на класс или интерфейс и реализация интерфейса IUnknown для обслуживания com объекта(этот IUnknown как обслуживающее звено объекта не поддерживается теперь вроде как с других процессов com объектов,соответственно его реализация своя)

Отредактировано Sergeihik (13.10.2022 20:54:19)

0


Вы здесь » PureBasic - форум » Вопросы по PureBasic » Выбор нескольких файлов через конт.меню (?)