library %ModuleIdent; // This is the main library that creates the Shell Extension DLL // // Debugging Hints: // // 1) After compiling the library go to the folder where the was created. The // easyest way to register the server is to copy the Register and UnRegiser // batch files from one of the demos and edit it to point to your DLL. // Run the batch to register the DLL with the Shell. // The overridden UpdateRegistry method in the TComObjectFactory decendents // in EasyCOMFactories will be called and the necessary entries will be // added to create the new Junction Point for the NSE or Shell Handler. // // Note: // You can not use // Run > Register ActiveX Server // from within the IDE. This is because EasyNSE does not use ComServ or ComObj // units. There are a couple of reasons. // 1) I wanted to make the DLL as small as possible. // 2) I think the implementation of ComServ breaks COM rules in a couple of ways // 3) BCB can't compile Delphi files that use ComServ (so I am told) // // // 2) Now Explorer may be run to check the NSE for errors. // 3) Now Run the UnRegister batch file created in step (1) // 4) At this point you may not be able to recompile the dll as Windows // may have not released it yet: // // HINT: // To unload a DLL that Explorer won't let go of: // 1) On the Taskbar choose: Start > Shut Down // 2) Now hold down and click "Cancel" // 3) Explorer will be terminated // 4) Hit // 5) Choose "Task Manager" // 6) Choose "New Task" in the lower right corner // 7) Enter "Explorer.exe" in the edit box and hit "OK" // 8) A fresh instance of Explorer will be started and your // DLL will be unloaded // 5) It is suppose to be possible to Kill explorer by the above hint then // in Delphi choose // Run > Parameters // and in the "Host Application" edit box enter "Explorer.exe" // Unfortunately the Delphi Debugger will lock up Windows and you will need // to do a Power Down restart (at least with D7) // 6) // // HINT: Debugging your NSE with VirtualShellTools // 1) Open the VSTools Demo named "Namespace Browser" // 2) Choose // Project > Options > Compiler // Check all boxes in the Debugging area // 3) Choose // Project > Options > Linker // Check "Include Remote Debugging Symbols" // 4) Recomile // 5) Now open your NSE project // 6) Repeat steps 2 - 4 for this project // 7) Choose // Run > Parameters // and in the "Host Application" edit browse to the VSTools // NamespaceBrowserProject.exe file // 8) Now open the Unit1.pas file in the IDE that contains the // Namespace Browser form, DO NOT open the actual project // 9) On each Selection change the Browser loads about every possible // thing that is available from the NSE. Set a break point in the // procedure TForm1.FillInfo; // method // 10) Hit Run > Run // 11) You can now set break points in BOTH the demo and the DLL!!!! // NOTE: // The "blue dot" breakpoints in the DLL may not be visible // until the dll is loaded by the shell when the namespace is // first accessed (i.e. My Computer is expanded) // 12) Also using this method the DLL is almost always unloaded after // the NSE Browser is exited. // // 7) You may also include debugging traces within the interface encapsulation // 8) Currently the following Conditional Defines are available (for NSE's only not // Shell Handlers) // {$DEFINE DEBUGMESSAGES_ISHELLFOLDER} // {$DEFINE DEBUGMESSAGES_ATTRIBUTEOF} // {$DEFINE DEBUGMESSAGES_UIOBJECTOF} // {$DEFINE DEBUGMESSAGES_ISHELLFOLDER2} // {$DEFINE DEBUGMESSAGES_SHELLVIEW} // If you define any of these define through: // Project > Options > Conditionals/Defines // A message box will be shown as each Interface Method is called by // Explorer (or VSTools) // Replace the xxxxxHandler units in the "uses" clause below if using the "EasyIncludeHandler.inc" file in // the $(EASYNSE)\Include folder when trying to reduce the DLL size. These would automaticlly be inserted // below but the IDE will strip out the conditional defined when adding or removing DataModules so it must // be done manually..... sorry. // // Replace this in the uses clause: // // EasyQueryInfoHandler, // EasyPropertySheetHandler, // EasyThumbnailHandler, // EasyContextMenuHandler, // EasyDragDropHandler, // EasyColumnHandler, // EasyCopyHookHandler, // EasyIconHandler, // EasyDropHandler, // EasyDataHandler, // EasyIconOverlayHandler, // EasyNamespaceHandler, // // With this: // // {$IFNDEF NO_QUERYINFOHANDLER}EasyQueryInfoHandler,{$ENDIF} // {$IFNDEF NO_PROPERTYSHEETHANDLER}EasyPropertySheetHandler,{$ENDIF} // {$IFNDEF NO_THUMBNAILHANDLER}EasyThumbnailHandler,{$ENDIF} // {$IFNDEF NO_MENUHANDLERS}EasyContextMenuHandler, EasyDragDropHandler,{$ENDIF} // {$IFNDEF NO_COLUMNHANDLER}EasyColumnHandler,{$ENDIF} // {$IFNDEF NO_COPYHOOKHANDLER}EasyCopyHookHandler,{$ENDIF} // {$IFNDEF NO_ICONHANDLER}EasyIconHandler,{$ENDIF} // {$IFNDEF NO_DROPHANDLER}EasyDropHandler,{$ENDIF} // {$IFNDEF NO_DATAHANDLER}EasyDataHandler,{$ENDIF} // {$IFNDEF NO_ICONOVERLAYHANDLER}EasyIconOverlayHandler,{$ENDIF} // {$IFNDEF NO_NAMESPACEEXTENSION}EasyNamespaceHandler,{$ENDIF} // {$IFNDEF NO_BANDHANDLERS}EasyBandHandlers,{$ENDIF} // {$include Compilers.Inc} {$include EasyIncludeHandler.inc} uses Windows, Classes, ActiveX, {$IFNDEF NO_BANDHANDLERS} EasyThreadSafeMenus, {$ENDIF NO_BANDHANDLERS} EasyIDEComponents, EasyCommonObjects, EasyQueryInfoHandler, EasyPropertySheetHandler, EasyThumbnailHandler, EasyContextMenuHandler, EasyDragDropHandler, EasyColumnHandler, EasyCopyHookHandler, EasyIconHandler, EasyDropHandler, EasyDataHandler, EasyIconOverlayHandler, EasyNamespaceHandler, EasyBandHandlers, EasyUtilities; {$R EasyPropSheetExt.res} {$R Winxp_DLL.res} var // D5 Fixes this problem; {$IFNDEF COMPILER_5_UP} ControlWord: Word; {$ENDIF} Initialized: Boolean = False; // Ensures that the library is initialized (core objects are created) and is // initialized only once procedure Initialize; begin if not Initialized then begin {$IFNDEF NO_QUERYINFOHANDLER}RegisterClass(TEasyQueryInfoHandler);{$ENDIF} {$IFNDEF NO_PROPERTYSHEETHANDLER}RegisterClass(TEasyPropertySheetHandler);{$ENDIF} {$IFNDEF NO_THUMBNAILHANDLER}RegisterClass(TEasyThumbnailHandler);{$ENDIF} {$IFNDEF NO_MENUHANDLERS} RegisterClass(TEasyContextMenuHandler); RegisterClass(TEasyContextMenuItem); RegisterClass(TEasyDragDropHandler); {$ENDIF} {$IFNDEF NO_COLUMNHANDLER}RegisterClass(TEasyColumnHandler);{$ENDIF} {$IFNDEF NO_COPYHOOKHANDLER}RegisterClass(TEasyCopyHookHandler);{$ENDIF} {$IFNDEF NO_ICONHANDLER}RegisterClass(TEasyIconHandler);{$ENDIF} {$IFNDEF NO_DROPHANDLER}RegisterClass(TEasyDropHandler);{$ENDIF} {$IFNDEF NO_DATAHANDLER}RegisterClass(TEasyDataHandler);{$ENDIF} {$IFNDEF NO_ICONOVERLAYHANDLER}RegisterClass(TEasyIconOverlayHandler);{$ENDIF} {$IFNDEF NO_NAMESPACEEXTENSION} RegisterClass(TEasyNamespaceHandler); RegisterClass(TEasyNamespaceFolder); {$ENDIF} {$IFNDEF NO_BANDHANDLERS} RegisterClass(TEasyIEBandForm); RegisterClass(TEasyIEToolBandForm); RegisterClass(TEasyDeskBandForm); RegisterClass(TEasyInternetExplorerBandHandler); RegisterClass(TEasyInternetExplorerToolBandHandler); RegisterClass(TEasyDesktopBandHandler); {$ENDIF} RegisterClass(TEasyFactoryManager); OleInitialize(nil); Initialized := True end end; // Ensures that the library is finalized (core objects are destroyed) procedure Finalize; begin if Initialized then begin OLEUnInitialize; Initialized := False end end; function DllGetClassObject(const CLSID, IID: TGUID; var Obj: Pointer): HResult; stdcall; begin // Validate Obj Address if @Obj = nil then begin Result := E_POINTER; Exit end; Obj := nil; try Result := CLASS_E_CLASSNOTAVAILABLE; // Make sure the objects are initialized Initialize; if IsEqualIID(IID, IClassFactory) then begin if Assigned(FactoryManager) then begin IUnknown(Obj) := FactoryManager.CreateInstance(CLSID); if Assigned(Obj) then Result := S_OK; end end except Obj := nil; Result := CLASS_E_CLASSNOTAVAILABLE; end end; function DllCanUnloadNow: HResult; stdcall; begin try if ObjectCount = 0 then Result := S_OK else Result := S_False except Result := S_FALSE; end end; function DllRegisterServer: HResult; stdcall; begin try // Make sure the objects are initialized Initialize; if RegisterLibrary and Assigned(FactoryManager) then begin FactoryManager.RegisterHandlers; Result := S_OK; end else Result := E_FAIL; except Result := E_FAIL; end end; function DllUnregisterServer: HResult; stdcall; begin try // Make sure the objects are initialized Initialize; if UnRegisterLibrary and Assigned(FactoryManager) then begin FactoryManager.UnRegisterHandlers; Result := S_OK; end else Result := E_FAIL; except Result := E_FAIL; end end; exports DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer; // There has *always* been a stability problem with the Win32 Delphi RTL. // I finally got a demo to constantly crash and Mathias Rauen FINALLY tracked // it down! I now feel it is feasable to write Global Hooks and DLLs that // run in other processes address space, like a NSE. // Patch by Mathias Rauen // Until Borland fixed the RTL the fix is very clean to implement outside of the // RTL. { "I have found the bug in the RTL and I have a clean workaround. Let me explain the situation: DLL_PROCESS_ATTACH -> SysInit.InitProcessTLS -> SysInit.InitThreadTLS DLL_THREAD_ATTACH -> SysInit.InitThreadTLS DLL_THREAD_DETACH -> SysInit.ExitThreadTLS DLL_PROCESS_DETACH -> SysInit.ExitProcessTLS -> SysInit.ExitThreadTLS As you can see, DLL_XXX_ATTACH always ends up in "InitThreadTLS", while DLL_XXX_DETACH always ends up in "ExitThreadTLS". We can forget about "Init/ExitProcessTLS". Now let's go through some situations. For all of the following cases please note that the events are meant to be for the very same thread (that's important!!). (1) InitThreadTLS ExitThreadTLS -> perfectly fine (standard case) (2) ExitThreadTLS -> strange situation, but no problems (3) InitThreadTLS -> memory leak (but I think this situation will never occur) (4) InitThreadTLS InitThreadTLS ExitThreadTLS -> memory leak (but I think this situation will never occur) (5) InitThreadTLS ExitThreadTLS ExitThreadTLS -> LocalAlloc gets called twice for the same pointer Now what happens when doing CBT hooking in win98 is situation (5). The very same thread gets a DLL_PROCESS_ATTACH + DLL_THREAD_DETACH + DLL_PROCESS_DETACH event. And the end result is the Explorer crash. If you ask me, the Borland programmers didn't believe, that case (5) can happen - but it does. Now let's look at my patched "ExitThreadTLS" function. I just added one line: " } procedure madPatch_ExitThreadTLS; var p: Pointer; begin if @TlsLast = nil then Exit; if TlsIndex <> -1 then begin p := TlsGetValue(TlsIndex); if p <> nil then begin // D5 calls DLLEntryProc AFTER freeing the memory block // So all we need to do is nil it. // D6-D7 calls DLLEntryProc BEFORE so we will free and // nil it before the RTL does it. The RTL check for nil // so this is safe. {$IFDEF COMPILER_6_UP} LocalFree(Cardinal(p)); {$ENDIF} TlsSetValue(TlsIndex, nil); // <- this fixes case (5) end; end; end; procedure DLLEntryProc(EntryCode: integer); begin case EntryCode of DLL_PROCESS_DETACH: begin // D5 Fixes this problem; {$IFNDEF COMPILER_5_UP} Set8087CW(ControlWord); {$ENDIF} // The process is dumping us so clean up Finalize; end; DLL_PROCESS_ATTACH: begin // D5 Fixes this problem; {$IFNDEF COMPILER_5_UP} Set8087CW($133f); {$ENDIF} end; DLL_THREAD_ATTACH: begin end; DLL_THREAD_DETACH: begin {$IFNDEF NO_BANDHANDLERS} PopupManager.RemoveThreadID(GetCurrentThreadID); {$ENDIF NO_BANDHANDLERS} // We are freeing the TLS AND setting it to nil before // the RTL can get to it to fix the Delphi global DLL stablility // issue. madPatch_ExitThreadTLS; end; end; end; begin IsMultiThread := True; DLLProc := @DLLEntryProc; DLLEntryProc(DLL_PROCESS_ATTACH); end.