Page 1 of 3

Context Menu Item Shell Extension example (Windows)

Posted: Tue Mar 15, 2005 7:26 pm
by El_Choni
Hi,

Behind that long and pompus name hides a very simple task, which is adding a menu item to the explorer's context menu, much like you do when you associate a file extension with a program, but in this case the items calls some predefined functions and methods in a COM dll.

To use it, you must build this source as a DLL and put the DLL in some known place (not in C:\Windows\Temp ;), so you remember where it is when you want to unregister it.

But you must register it first, with this DOS command:

Code: Select all

C:\WINDOWS\system32>regsvr32 C:\PathToMyDLL\mydll.dll
To unregister it:

Code: Select all

C:\WINDOWS\system32>regsvr32 /u C:\PathToMyDLL\mydll.dll
Then launch an explorer window and right click on any file. The context menu should show a "View file name" item which, if selected, should show "Shows the file name" in the status bar. If clicked, a MessageRequester should appear with the name of the right-clicked file.

I've tested it in Windows XP SP1, feedback, bug reports and comments are welcome. I hope you find it useful:

Code: Select all

Procedure Error(message$)
  wError = GetLastError_()
  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 SHITEMID
  cb.w
  abID.b[1]
EndStructure

Structure ITEMIDLIST
  mkid.SHITEMID
EndStructure

Structure ClassFactoryObject
  lpVtbl.l
  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$

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

QueryInterface:
Procedure IUnknown_QueryInterface(*ti_unk.IUnknown, *riid.GUID, *ppvObject.LONG)
  If *ppvObject
    If CompareMemory(*riid, ?IID_IUnknown, SizeOf(GUID))
      MessageRequester("", "iunknown")
      *ppvObject\l = i_Unk
    ElseIf CompareMemory(*riid, ?IID_IShellExtInit, SizeOf(GUID))
      *ppvObject\l = i_SEI
    ElseIf CompareMemory(*riid, ?IID_IContextMenu, SizeOf(GUID))
      *ppvObject\l = i_QCM
    Else
      ProcedureReturn #E_NOINTERFACE
    EndIf
  Else
    ProcedureReturn #S_FALSE
  EndIf
  i_Unk\AddRef()
  ProcedureReturn #S_OK
EndProcedure

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

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

CreateInstance:
Procedure IClassFactory_CreateInstance(*ti_cf.IClassFactory, *pUnkOuter.IUnknown, *riid.GUID, *ppvObject)
  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

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

Structure FORMATETC
  cfFormat.l
  ptd.l
  dwAspect.l
  lindex.l
  tymed.l
EndStructure

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

Structure STGMEDIUM
  tymed.l
  hGlobal.l
  pUnkForRelease.l
EndStructure

Initialize:
Procedure IShellExtInit_Initialize(*ti_sei.IShellExtInit, *pidlFolder.ITEMIDLIST, *pdtobj.IDataObject, hkeyProgID)
  If m_pDataObj
    m_pDataObj\Release()
  EndIf
  If *pdtobj
    m_pDataObj = *pdtobj
    *pdtobj\AddRef()
    fe.FORMATETC\cfFormat = #CF_HDROP
    fe\ptd = #NULL
    fe\dwAspect = #DVASPECT_CONTENT
    fe\lindex = -1
    fe\tymed = #TYMED_HGLOBAL
    If *pdtobj\GetData(@fe, @medium.STGMEDIUM)=#S_OK
      uCount = DragQueryFile_(medium\hGlobal, -1, #NULL, 0)
      If uCount=1
        *m_szFile = AllocateMemory(#MAX_PATH)
        DragQueryFile_(medium\hGlobal, 0, *m_szFile, #MAX_PATH)
        If Len(PeekS(*m_szFile))=0
          uCount = 0
        Else
          File$ = PeekS(*m_szFile)
        EndIf
        FreeMemory(*m_szFile)
      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

QueryContextMenu:
Procedure IContextMenu_QueryContextMenu(*i_icm.IContextMenu, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags)
  If #CMF_DEFAULTONLY&uFlags:ProcedureReturn 0:EndIf
  cmd = indexMenu
  mii.MENUITEMINFO
  mii\cbSize = SizeOf(MENUITEMINFO)
  mii\fMask = #MIIM_STRING|#MIIM_ID
  mii\fType = #MFT_STRING
  mii\wID = idCmdFirst
  mii\dwTypeData = ?CommandString
  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

GetCommandString:
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.
        CopyMemory(?CommandHelpLine, pszName, Len(PeekS(?CommandHelpLine)))
      Case #GCS_HELPTEXTW ; Sets pszName To a Unicode string containing the Help text For the command. 
        If Ansi2Uni(?CommandHelpLine, pszName, cchMax)=0
          ProcedureReturn #S_FALSE
        EndIf
      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, PeekS(?CommandString))
      Case #GCS_VERBW ; Sets pszName To a Unicode string containing the language-independent command name For the menu item. 
        If Ansi2Uni(?CommandString, pszName, cchMax)=0
          ProcedureReturn #S_FALSE
        EndIf
      Default
        ProcedureReturn #S_FALSE
    EndSelect
  Else
    ProcedureReturn #S_FALSE
  EndIf
  ProcedureReturn #S_OK
EndProcedure

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

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

#SEE_MASK_UNICODE = $4000
#CMIC_MASK_UNICODE = #SEE_MASK_UNICODE

InvokeCommand:
Procedure IContextMenu_InvokeCommand(*ti_icm.IContextMenu, *pici.CMINVOKECOMMANDINFOEX)
  If *pici\cbSize=SizeOf(CMINVOKECOMMANDINFOEX)
    If *pici\fMask&#CMIC_MASK_UNICODE
      If (*pici\lpVerbW&$ffff)=cmd
        MessageRequester("Show file name", File$)
        ProcedureReturn #NOERROR
      EndIf
    EndIf
  ElseIf *pici\cbSize=SizeOf(CMINVOKECOMMANDINFO)
    If (*pici\lpVerb&$ffff)=cmd
      MessageRequester("Show file name", File$)
      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()
  InDLL:
  If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "*\shellex\ContextMenuHandlers\FastView.Image", 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #NULL, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf
  RegSetValueEx_(hKey1, "", 0, #REG_SZ, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 38)
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "FastView.Image\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, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 38)
  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, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 0, #REG_SZ, "FastView.Image", 15)
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  *szBuffer = AllocateMemory(#MAX_PATH)
  If *szBuffer And GetModuleFileName_(?InDLL&$FFFF0000, *szBuffer, #MAX_PATH)
    If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #NULL, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf
    RegSetValueEx_(hKey1, "", 0, #REG_SZ, "FastView.Image", 15)
    If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
    If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\{851aab5c-2008-4157-9c5d-a28dfa7b2660}\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, Len(PeekS(*szBuffer))+1)
    RegSetValueEx_(hKey1, "ThreadingModel", 0, #REG_SZ, "Apartment", 10)
    If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  Else
    ProcedureReturn #SELFREG_E_CLASS
  EndIf
  FreeMemory(*szBuffer)
  ProcedureReturn #S_OK
EndProcedure

ProcedureDLL DllUnregisterServer()
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "*\shellex\ContextMenuHandlers\FastView.Image")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "FastView.Image\shellex\ContextMenuHandlers")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "FastView.Image\shellex")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "FastView.Image")<>#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, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  If RegOpenKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 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, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  ProcedureReturn #S_OK
EndProcedure

ProcedureDLL AttachProcess(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.LONG)
  If *ppv
    If CompareMemory(*rclsid, ?CLSID_ContextMenuHandler, SizeOf(GUID))
      If CompareMemory(*riid, ?IID_IClassFactory, SizeOf(GUID))
        *ppv\l = ?lpVT_IClassFactory
      Else
        *ppv\l = 0
        ProcedureReturn #CLASS_E_CLASSNOTAVAILABLE
      EndIf
    Else
      *ppv\l = 0
      ProcedureReturn #CLASS_E_CLASSNOTAVAILABLE
    EndIf
  Else
    *ppv\l = 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

Procedure VTable() ; This is NOT a real procedure: PureBasic ignores ASM commands in the DataSection, so I had to put the vtable here
!section '.data' Data readable writeable
  lpVT_IUnknown:
  !dd l_vt_iunknown
  !dd 0 ; *p_Unk\nRefCount
  !dd 0 ; *p_Unk\nLockCount
  VT_IUnknown:
  !dd l_queryinterface+5
  !dd l_addref+2
  !dd l_release+2
  lpVT_IClassFactory:
  !dd l_vt_iclassfactory
  VT_IClassFactory:
  !dd l_queryinterface+5
  !dd l_addref+2
  !dd l_release+2
  !dd l_createinstance+5
  !dd l_lockserver+2
  lpVT_IShellExtInit:
  !dd l_vt_ishellextinit
  VT_IShellExtInit:
  !dd l_queryinterface+5
  !dd l_addref+2
  !dd l_release+2
  !dd l_initialize+5
  lpVT_IContextMenu:
  !dd l_vt_icontextmenu
  VT_IContextMenu:
  !dd l_queryinterface+5
  !dd l_addref+2
  !dd l_release+2
  !dd l_querycontextmenu+5
  !dd l_invokecommand+5
  !dd l_getcommandstring+5
EndProcedure

DataSection ; 
CLSID_ContextMenuHandler: ; {851aab5c-2008-4157-9c5d-a28dfa7b2660}
Data.l $851aab5c
Data.w $2008, $4157
Data.b $9c, $5d, $a2, $8d, $fa, $7b, $26, $60
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
CommandString:
Data.s "Show file name"
CommandHelpLine:
Data.s "Shows the file name"
EndDataSection
Regards,

Re: Context Menu Item Shell Extension example (Windows)

Posted: Tue Mar 15, 2005 7:32 pm
by traumatic
Great stuff as always, El_Choni! :)

BTW: I know SHITEMID stands for shell item identifier but... *lol* :lol:

Posted: Tue Mar 15, 2005 9:09 pm
by Henrik
Hi El_Choni
I don't know if this was ment to be an XP only but i tried it anyway on win98.
Well "View file name" appeared in the menu, but that was how far it went, nothing in the statusbar, so...

well anyway Win98se...
Wish i could write stuff like that :shock:

Henrik

Posted: Wed Mar 16, 2005 9:50 am
by gnozal
Henrik wrote:Hi El_Choni
Wish i could write stuff like that :shock:
Henrik
Me too :shock:

Posted: Wed Mar 16, 2005 11:31 am
by Le Soldat Inconnu
very nice :wink:


to register or unregister the dll, it's

Code: Select all

C:\WINDOWS\system32\regsvr32 C:\PathToMyDLL\mydll.dll
You writed a ">"

Posted: Wed Mar 16, 2005 12:12 pm
by dagcrack
Le Soldat Inconnu wrote:very nice :wink:


to register or unregister the dll, it's

Code: Select all

C:\WINDOWS\system32\regsvr32 C:\PathToMyDLL\mydll.dll
You writed a ">"
He was still in maths programmer mode :P

Posted: Wed Mar 16, 2005 1:58 pm
by El_Choni
Well, this is how it's shown here:

Image[/img]

Posted: Wed Mar 16, 2005 3:00 pm
by Le Soldat Inconnu
I use the run command in the start menu and not the dos command.
That's why i need a "\" to register or unregister the dll :roll: :lol:

Posted: Thu Mar 17, 2005 4:30 am
by El_Choni
Now, an application of this code which actually does something (kind of a PicaView clone). Probably a bit buggy yet:

Usage: just like the first one. To test, right click on an image file (jpg, tiff, tga, bmp, png, you know...).

Code: Select all

Procedure Error(message$)
  wError = GetLastError_()
  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 SHITEMID
  cb.w
  abID.b[1]
EndStructure

Structure ITEMIDLIST
  mkid.SHITEMID
EndStructure

Structure ClassFactoryObject
  lpVtbl.l
  nRefCount.l
EndStructure

Global m_pDataObj.IDataObject, cmd, hModule
Global i_Unk.IUnknown, i_SEI.IShellExtInit, i_QCM.IContextMenu, i_QCM2.IContextMenu2, i_QCM3.IContextMenu3
Global *p_Unk.ClassFactoryObject
Global File$, hImage, newImage, iWidth, iHeight, mDC, mObj

#MAX_WIDTH = 128
#MAX_HEIGHT = 64

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

QueryInterface:
Procedure IUnknown_QueryInterface(*ti_unk.IUnknown, *riid.GUID, *ppvObject.LONG)
  If *ppvObject
    If CompareMemory(*riid, ?IID_IUnknown, SizeOf(GUID))
      MessageRequester("", "iunknown")
      *ppvObject\l = i_Unk
    ElseIf CompareMemory(*riid, ?IID_IShellExtInit, SizeOf(GUID))
      *ppvObject\l = i_SEI
    ElseIf CompareMemory(*riid, ?IID_IContextMenu, SizeOf(GUID))
      *ppvObject\l = i_QCM
    ElseIf CompareMemory(*riid, ?IID_IContextMenu2, SizeOf(GUID))
      *ppvObject\l = i_QCM2
    ElseIf CompareMemory(*riid, ?IID_IContextMenu3, SizeOf(GUID))
      *ppvObject\l = i_QCM3
    Else
      *ppvObject\l = 0
      ProcedureReturn #E_NOINTERFACE
    EndIf
  Else
    ProcedureReturn #S_FALSE
  EndIf
  i_Unk\AddRef()
  ProcedureReturn #S_OK
EndProcedure

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

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

CreateInstance:
Procedure IClassFactory_CreateInstance(*ti_cf.IClassFactory, *pUnkOuter.IUnknown, *riid.GUID, *ppvObject)
  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

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

Structure FORMATETC
  cfFormat.l
  ptd.l
  dwAspect.l
  lindex.l
  tymed.l
EndStructure

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

Structure STGMEDIUM
  tymed.l
  hGlobal.l
  pUnkForRelease.l
EndStructure

Initialize:
Procedure IShellExtInit_Initialize(*ti_sei.IShellExtInit, *pidlFolder.ITEMIDLIST, *pdtobj.IDataObject, hkeyProgID)
  If m_pDataObj
    m_pDataObj\Release()
  EndIf
  If *pdtobj
    m_pDataObj = *pdtobj
    *pdtobj\AddRef()
    fe.FORMATETC\cfFormat = #CF_HDROP
    fe\ptd = #NULL
    fe\dwAspect = #DVASPECT_CONTENT
    fe\lindex = -1
    fe\tymed = #TYMED_HGLOBAL
    If *pdtobj\GetData(@fe, @medium.STGMEDIUM)=#S_OK
      uCount = DragQueryFile_(medium\hGlobal, -1, #NULL, 0)
      If uCount=1
        *m_szFile = AllocateMemory(#MAX_PATH)
        DragQueryFile_(medium\hGlobal, 0, *m_szFile, #MAX_PATH)
        If Len(PeekS(*m_szFile))=0
          uCount = 0
        Else
          File$ = PeekS(*m_szFile)
        EndIf
        FreeMemory(*m_szFile)
      EndIf
      ReleaseStgMedium_(@medium)
      If uCount=1
        If hImage
          If IsGadget(0)
            FreeGadget(0)
          EndIf
          If IsWindow(0)
            CloseWindow(0)
          EndIf
          FreeImage(0)
          hImage = 0
          If newImage
            FreeImage(1)
            newImage = 0
          EndIf
        EndIf
        hImage = LoadImage(0, File$)
        If hImage
          ProcedureReturn #S_OK
        EndIf
      EndIf
    EndIf
  EndIf
  ProcedureReturn #S_FALSE
EndProcedure

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

QueryContextMenu:
Procedure IContextMenu_QueryContextMenu(*i_icm.IContextMenu, hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags)
  If #CMF_DEFAULTONLY&uFlags:ProcedureReturn 0:EndIf
  If hImage
    cmd = indexMenu
    mii.MENUITEMINFO
    mii\cbSize = SizeOf(MENUITEMINFO)
    mii\fMask = #MIIM_TYPE|#MIIM_ID
    mii\fType = #MFT_OWNERDRAW
    mii\wID = idCmdFirst
    mii\dwTypeData = hImage
    If InsertMenuItem_(hmenu, 0, #TRUE, @mii)=#FALSE
      ProcedureReturn 1<<31
    EndIf
    ProcedureReturn 1
  EndIf
  ProcedureReturn 1<<31
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

GetCommandString:
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.
        CopyMemory(?CommandHelpLine, pszName, Len(PeekS(?CommandHelpLine)))
      Case #GCS_HELPTEXTW ; Sets pszName To a Unicode string containing the Help text For the command. 
        If Ansi2Uni(?CommandHelpLine, pszName, Len(PeekS(?CommandHelpLine)))=0
          ProcedureReturn #S_FALSE
        EndIf
      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, PeekS(?CommandString))
      Case #GCS_VERBW ; Sets pszName To a Unicode string containing the language-independent command name For the menu item. 
        If Ansi2Uni(?CommandString, pszName, cchMax)=0
          ProcedureReturn #S_FALSE
        EndIf
      Default
        ProcedureReturn #S_FALSE
    EndSelect
  Else
    ProcedureReturn #S_FALSE
  EndIf
  ProcedureReturn #S_OK
EndProcedure

Procedure WndProc(hWnd, uMsg, wParam, lParam)
  result = #PB_ProcessPureBasicEvents
  Select uMsg
    Case #WM_CLOSE
      FreeImage(0)
      hImage = 0
      FreeGadget(0)
      CloseWindow(0)
  EndSelect
  ProcedureReturn result
EndProcedure

Procedure ShowIt()
  If hImage
    UseImage(0)
    If OpenWindow(0, 0, 0, ImageWidth(), ImageHeight(), #PB_Window_SystemMenu|#PB_Window_MinimizeGadget|#PB_Window_ScreenCentered, "FastView demo")
      If CreateGadgetList(WindowID())
        ImageGadget(0, 0, 0, ImageWidth(), ImageHeight(), hImage)
        newImage = 0
        FreeImage(1)
        SetWindowCallback(@WndProc())
      Else
        FreeImage(0)
        hImage = 0
        FreeImage(1)
        newImage = 0
        CloseWindow(0)
      EndIf
    EndIf
  EndIf
EndProcedure

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

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

#SEE_MASK_UNICODE = $4000
#CMIC_MASK_UNICODE = #SEE_MASK_UNICODE

InvokeCommand:
Procedure IContextMenu_InvokeCommand(*ti_icm.IContextMenu, *pici.CMINVOKECOMMANDINFOEX)
  If *pici\cbSize=SizeOf(CMINVOKECOMMANDINFOEX)
    If *pici\fMask&#CMIC_MASK_UNICODE
      If (*pici\lpVerbW&$ffff)=cmd
        ShowIt()
        ProcedureReturn #NOERROR
      EndIf
    EndIf
  ElseIf *pici\cbSize=SizeOf(CMINVOKECOMMANDINFO)
    If (*pici\lpVerb&$ffff)=cmd
      ShowIt()
      ProcedureReturn #NOERROR
    EndIf
  Else
    ProcedureReturn #S_FALSE
  EndIf
EndProcedure

HandleMenuMsg:
Procedure IContextMenu2_HandleMenuMsg(*ti_icm2.IContextMenu2, uMsg, wParam, *lParam.MEASUREITEMSTRUCT)
  Select uMsg
    Case #WM_DRAWITEM
      *dis.DRAWITEMSTRUCT = *lParam
      left = ((*dis\rcItem\right-*dis\rcItem\left)/2)-(iWidth/2)
      top = ((*dis\rcItem\bottom-*dis\rcItem\top)/2)-(iHeight/2)
      If *dis\itemState&#ODS_SELECTED
        FillRect_(*dis\hDC, @*dis\rcItem, #COLOR_HIGHLIGHT+1)
        BitBlt_(*dis\hDC, left+*dis\rcItem\left, top+*dis\rcItem\top, iWidth, iHeight, mDC, 0, 0, #SRCCOPY)
        RasterOperation = #DSTINVERT
      Else
        FillRect_(*dis\hDC, @*dis\rcItem, #COLOR_MENU+1)
        RasterOperation = #SRCCOPY
      EndIf
      BitBlt_(*dis\hDC, left+*dis\rcItem\left, top+*dis\rcItem\top, iWidth, iHeight, mDC, 0, 0, RasterOperation)
    Case #WM_MEASUREITEM
      iWidth = ImageWidth()
      iHeight = ImageHeight()
      If iWidth>#MAX_WIDTH
        iHeight = (#MAX_WIDTH*iHeight)/iWidth
        iWidth = #MAX_WIDTH
      EndIf
      If iHeight>#MAX_HEIGHT
        iWidth = (#MAX_HEIGHT*iWidth/iHeight)
        iHeight = #MAX_HEIGHT
      EndIf
      *lParam\itemWidth = iWidth
      *lParam\itemHeight = iHeight
      CopyImage(0, 1)
      newImage = ResizeImage(1, iWidth, iHeight)
      mDC = CreateCompatibleDC_(GetDC_(GetDesktopWindow_()))
      mObj = SelectObject_(mDC, newImage)
  EndSelect
  ProcedureReturn #NOERROR
EndProcedure

HandleMenuMsg2:
Procedure IContextMenu3_HandleMenuMsg2(*ti_icm3.IContextMenu3, uMsg, wParam, lParam, *plResult.LONG)
  If *ti_icm3\HandleMenuMsg(uMsg, wParam, lParam)=#NOERROR
    *plResult\l = #TRUE
    ProcedureReturn #NOERROR
  EndIf
  *plResult\l = #FALSE
  ProcedureReturn #E_FAIL
EndProcedure

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

#GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 4

ProcedureDLL DllRegisterServer()
  InDLL:
  If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "*\shellex\ContextMenuHandlers\FastView.Image", 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #NULL, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf
  RegSetValueEx_(hKey1, "", 0, #REG_SZ, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 38)
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "FastView.Image\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, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 38)
  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, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 0, #REG_SZ, "FastView.Image", 15)
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  *szBuffer = AllocateMemory(#MAX_PATH)
  If *szBuffer
    If ~GetModuleFileName_(?InDLL&$FFFF0000, *szBuffer, #MAX_PATH)
      GetModuleFileName_(hModule, *szBuffer, #MAX_PATH)
    EndIf
  EndIf
  If Len(PeekS(*szBuffer))
    If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 0, 0, #REG_OPTION_NON_VOLATILE, #KEY_ALL_ACCESS, #NULL, @hKey1, 0)<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS:EndIf
    RegSetValueEx_(hKey1, "", 0, #REG_SZ, "FastView.Image", 15)
    If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
    If RegCreateKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\{851aab5c-2008-4157-9c5d-a28dfa7b2660}\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, Len(PeekS(*szBuffer))+1)
    RegSetValueEx_(hKey1, "ThreadingModel", 0, #REG_SZ, "Apartment", 10)
    If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  Else
    ProcedureReturn #SELFREG_E_CLASS
  EndIf
    If *szBuffer:FreeMemory(*szBuffer):EndIf
  ProcedureReturn #S_OK
EndProcedure

ProcedureDLL DllUnregisterServer()
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "*\shellex\ContextMenuHandlers\FastView.Image")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "FastView.Image\shellex\ContextMenuHandlers")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "FastView.Image\shellex")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If RegDeleteKey_(#HKEY_CLASSES_ROOT, "FastView.Image")<>#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, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  If RegOpenKeyEx_(#HKEY_CLASSES_ROOT, "CLSID\{851aab5c-2008-4157-9c5d-a28dfa7b2660}", 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, "{851aab5c-2008-4157-9c5d-a28dfa7b2660}")<>#ERROR_SUCCESS:ProcedureReturn #SELFREG_E_CLASS::EndIf
  If hKey1:RegCloseKey_(hKey1):hKey1 = 0:EndIf
  ProcedureReturn #S_OK
EndProcedure

ProcedureDLL AttachProcess(Instance)
  DisableThreadLibraryCalls_(Instance)
  hModule = Instance
  i_QCM = ?lpVT_IContextMenu
  i_QCM2 = ?lpVT_IContextMenu2
  i_QCM3 = ?lpVT_IContextMenu3
  i_SEI = ?lpVT_IShellExtInit
  i_Unk = ?lpVT_IUnknown
  *p_Unk = ?lpVT_IUnknown
  UseJPEGImageDecoder()
  UsePNGImageDecoder()
  UseTGAImageDecoder()
  UseTIFFImageDecoder()
;   UseEC_IFFImageDecoder()
;   UseEC_PBMImageDecoder()
;   UseEC_PGMImageDecoder()
;   UseEC_PPMImageDecoder()
;   UseEC_XPMImageDecoder()
;   UseEC_WBMPImageDecoder()
;   UseEC_XBMImageDecoder()
;   UseEC_OLEImageDecoder()
  ProcedureReturn #TRUE
EndProcedure

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

ProcedureDLL DllCanUnloadNow()
  If *p_Unk\nRefCount<=0
    If mDC
      SelectObject_(mDC, mObj)
      DeleteDC_(mDC)
      mDC = 0
    EndIf
    ProcedureReturn #S_OK
  Else
    ProcedureReturn #S_FALSE
  EndIf
EndProcedure

Procedure VTable() ; This is NOT a real procedure: PureBasic ignores ASM commands in the DataSection, so I had to put the vtable here
!section '.data' Data readable writeable
  lpVT_IUnknown:
  !dd l_vt_iunknown
  !dd 0 ; *p_Unk\nRefCount
  !dd 0 ; *p_Unk\nLockCount
  VT_IUnknown:
  !dd l_queryinterface+5
  !dd l_addref+2
  !dd l_release+2
  lpVT_IClassFactory:
  !dd l_vt_iclassfactory
  VT_IClassFactory:
  !dd l_queryinterface+5
  !dd l_addref+2
  !dd l_release+2
  !dd l_createinstance+5
  !dd l_lockserver+2
  lpVT_IShellExtInit:
  !dd l_vt_ishellextinit
  VT_IShellExtInit:
  !dd l_queryinterface+5
  !dd l_addref+2
  !dd l_release+2
  !dd l_initialize+5
  lpVT_IContextMenu:
  lpVT_IContextMenu2:
  lpVT_IContextMenu3:
  !dd l_vt_icontextmenu
  VT_IContextMenu:
  VT_IContextMenu2:
  VT_IContextMenu3:
  !dd l_queryinterface+5
  !dd l_addref+2
  !dd l_release+2
  !dd l_querycontextmenu+5
  !dd l_invokecommand+5
  !dd l_getcommandstring+5
  !dd l_handlemenumsg+5
  !dd l_handlemenumsg2+5
EndProcedure

DataSection ; 
CLSID_ContextMenuHandler: ; {851aab5c-2008-4157-9c5d-a28dfa7b2660}
Data.l $851aab5c
Data.w $2008, $4157
Data.b $9c, $5d, $a2, $8d, $fa, $7b, $26, $60
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
IID_IContextMenu2:
Data.l $000214F4
Data.w 0, 0
Data.b $C0, 0, 0, 0, 0, 0, 0, $46
IID_IContextMenu3:
Data.l $bcfce0a0
Data.w $ec17, $11d0
Data.b $8d, $10, 0, $a0, $c9, $0f, $27, $19
CommandString:
Data.s "Show image"
CommandHelpLine:
Data.s "Shows the image"
EndDataSection

Posted: Thu Mar 17, 2005 10:01 am
by Psychophanta
Choni,
that's great and needed to me.
2 questions:
1) Couldn't be done with smaller source code?
2) As an idea; can you easely make this, but using a UI to build a customized (personalized) context menu (DLL) ? I would congratulate you very much. :wink: :D ... and of course, i will add "by EL_Choni" in your code inside my projects.

Posted: Thu Mar 17, 2005 10:45 am
by Edwin Knoppert
FYI, as mentioned buggy.
Well it crashes here on XP.
Right clicking a JPG.

:)

Posted: Thu Mar 17, 2005 1:06 pm
by El_Choni
@Edwin: could you send or show me that jpg? or it happens on all jpg?

@Psychophanta: you could probably strip some stuff out, but I don't know which, I'm trying to do it "by the book". About using this examples as a template, it's quite easy, tell me what you want to do and I'll tell you where to put your code.

Regards,

Posted: Thu Mar 17, 2005 1:44 pm
by Psychophanta
El_Choni wrote:@Psychophanta: you could probably strip some stuff out, but I don't know which, I'm trying to do it "by the book". About using this examples as a template, it's quite easy, tell me what you want to do and I'll tell you where to put your code.

Regards,
Yes,
i want to add a new item only after dragging using RMB from a window to another. The new item should be after "Move here" menu-item, and should be an item called "Verified Copy/Move..." with more subitems: "Copy if not equal file content found in target", "Copy if not equal file content and sub-path found in target", "Move if not equal file content found in target", "Move if not equal file content and sub-path found in target", "Move if equal file content found in target", "Move if equal file content and sub-path found in target", ..., and could be more, verify creation date, file flags, etc.

Posted: Thu Mar 17, 2005 2:38 pm
by Edwin Knoppert
All images, during right click the whole explorer (desktop!) reloads.
The menu itself does never show.

Posted: Thu Mar 17, 2005 3:14 pm
by El_Choni
@Edwin: I just can't reproduce that here. Could you fill the procedures with MessageRequesters to see which one is the last executed? Just if you have the time... TIA.

@Psychophanta: you're talking about a Drag and Drop Shell Handler, which is different from this one. I can have a look at that when I arrive home.