How to load and launch a Control Panel applet (Views: 29)
Problem/Question/Abstract: How to load and launch a Control Panel applet Answer: { Unit AppletLauncher Version: 1.0 Created: 23.08.98, 11:14:39 Last Modified: 23.08.98, 11:14:39 Author : P. Below Project: Win32 utilities Delphi version: 3.x (not tested on 4.x) Description: Provides a class to load and launch a control panel applet. The applet is loaded when an instance of this class is created and unloaded when it is destroyed. Applet dialogs are not shown unless explicitely requested. The required API definitions are obtained from the Delphi unit CPL. Processing is complicated by the fact that even Win32 applets may not respond to CPL_NEWINQUIRE. This requires storage for both old (CPL_INQUIRE) and new style info records. For a dialog only one of the list will contain a valid pointer to an info record, the other will contain Nil in that slot. Note: There are several errors in the win32 docs for the return values of CPL_ messages. Generally the CPlApplet entry point will return 0 (False) if the function fails and <> 0 (TRUE) if it succeeds. The docs indicate the inverse. } unit AppletLauncher; interface uses Classes, Windows, CPL, Graphics, Sysutils; type TAppletLauncher = class private hCPL: HMODULE; { applet module handle } hParent: HWND; { container window handle } CPlApplet: TCPlApplet; { applet entry point } FDialogCount: Integer; { number of dialogs available } FDialogData: TList; { stores TNewCPLnfo records for the dialogs } FOldDialogData: TList; { stores TCPlInfo records for the dialog } FAppletName: string; { filename of applet } protected function GetDialogNames(index: Integer): string; function GetDialogInfotext(index: Integer): string; function GetDialogIcon(index: Integer): HICON; function GetDeleteIcon(index: Integer): Boolean; function DialogLData(index: Integer): Longint; function LoadCPLResourceString(strid: Integer): string; procedure ValidateIndex(index: Integer); virtual; public constructor Create(anAppletName: string; hwndParent: HWND); destructor Destroy; override; procedure ShowDialog(index: Integer); property DialogCount: Integer read FDialogCount; property DialogNames[index: Integer]: string read GetDialogNames; default; property DialogInfotext[index: Integer]: string read GetDialogInfotext; property DialogIcons[index: Integer]: HIcon read GetDialogIcon; property DeleteIcon[index: Integer]: Boolean read GetDeleteIcon; property AppletName: string read FAppletName; end; EAppletError = class(Exception); implementation resourcestring { change to Const for Win16 } errCannotLoadApplet = 'TAppletLauncher: cannot load applet %s, reason: %s.'; errInvalidApplet = 'TAppletLauncher: %s is not a control panel applet, it does ' + 'not export the CPlApplet entry point.'; errAppletInitializationFailed = 'TAppletLauncher: initialization of applet %s failed.'; errAppletHasNoDialogs = 'TAppletLauncher: applet %s provides no dialogs.'; errCannotGetDialogInfo = 'TAppletLauncher: applet %s does not respond to CPL_INQUIRE, ' + 'the launcher is unable to obtain the required information about ' + 'the applets dialogs.'; errSelectDialogFailed = 'TAppletLauncher: applet %s failed to open dialog #%d (%s).'; errIndexOutOfBounds = 'TAppletLauncher: dialog index %d is invalid for applet %s, ' + 'the allowed range is 0..%d.'; {Methods of TAppletLauncher} { Constructor TAppletLauncher.Create Parameters: anAppletName: file name of the applet to load. This name must contain the CPL extention and can contain a full path, if the applet does not reside in the windows or system directories. hwndParent: handle of window that serves as control panel replacement. Use the main forms handle, for example. This window is used as parent for the applets dialogs. If 0 is passed we use the active window as parent. Call method: static Description: Loads the applet DLL, creates the internal list and fills it with information records for the dialogs the applet provides. Several messages are send to the applets entry point during construction. Error Conditions: An exception will be raised if the applet could not be loaded or if it does not respond in the expected way to the send messages. The object is destroyed automatically in this case. } constructor TAppletLauncher.Create(anAppletName: string; hwndParent: HWND); var i: Integer; pData: PNewCPlInfo; pOldData: PCPlInfo; begin inherited Create; if hwndParent = 0 then begin hwndparent := GetActiveWindow; end; hParent := hwndParent; { Try to load the applet DLL. } hCPL := LoadLibrary(Pchar(anAppletName)); if hCPL = 0 then begin { Error, applet not found. Note: change logic for Win16! } raise EAppletError.CreateFmt(errCannotLoadApplet, [anAppletName, SysErrorMessage(GetLastError)]); end; FAppletName := anAppletName; { Find applet entry point } @CPlApplet := GetProcAddress(hCPL, 'CPlApplet'); if @CPlApplet = nil then begin { Entry point not found, this is not a control panel applet! } raise EAppletError.CreateFmt(errInvalidApplet, [anAppletName]); end; { Send CPL_INIT to the applet } if CPlApplet(hParent, CPL_INIT, 0, 0) = 0 then begin { Applet failed to initialize, bail out. } raise EAppletError.CreateFmt(errAppletInitializationFailed, [anAppletName]); end; { Get number of dialogs the applet supports } FDialogCount := CPlApplet(hParent, CPL_GETCOUNT, 0, 0); if FDialogCount = 0 then begin raise EAppletError.CreateFmt(errAppletHasNoDialogs, [anAppletName]); end; { Create list for the dialog information } FDialogData := TList.Create; FDialogData.Capacity := FDialogCount; FOldDialogData := TList.Create; FOldDialogData.Capacity := FDialogCount; { Get the information for the dialogs } for i := 0 to FDialogCount - 1 do begin New(pData); FillChar(pData^, Sizeof(pData^), 0); pData^.dwSize := Sizeof(pData^); if CPlApplet(hParent, CPL_NEWINQUIRE, i, longint(pData)) = 0 then begin { Failed, try CPL_INQUIRE instead } Dispose(pData); New(pOldData); if CPlApplet(hParent, CPL_INQUIRE, i, longint(pOldData)) = 0 then begin { Failed also, bail out } Dispose(pOldData); raise EAppletError.CreateFmt(errCannotGetDialogInfo, [anAppletName]); end else begin FOldDialogData.Add(pOldData); FDialogData.Add(nil); end; end { If } else begin { CPL_NEWINQUIRE succeeded, store the data } FDialogData.Add(pData); FOldDialogData.Add(nil); end; end; { Setup is complete } end; { Destructor TAppletLauncher.Destroy Parameters: none Call method: virtual, overridden Description: Releases memory for the dialog data records, destroys the list holding the records, tells the applet to clean up its act and finally unloads the applet. The destructor can be called on a partially initialized object if an exception is raised in the constructor. Error Conditions: none } destructor TAppletLauncher.Destroy; var i: Integer; begin { Tell applet to clean up its dialogs and release the dialog data, if initialization completed successfully } if Assigned(FDialogData) then begin for i := 0 to FDialogData.Count - 1 do begin CPlApplet(hParent, CPL_STOP, i, DialogLData(i)); if Assigned(FDialogData[i]) then begin Dispose(pNewCPlInfo(FDialogData[i])); end; end; FDialogData.Free; end; if Assigned(FOldDialogData) then begin for i := 0 to FOldDialogData.Count - 1 do begin if Assigned(FOldDialogData[i]) then begin Dispose(pCPlInfo(FOldDialogData[i])); end; end; FOldDialogData.Free; end; { Tell applet to clean up, if load was successful. Note: this code is executed even if CPL_INIT failed, I don't know if this may cause a problem. } if Assigned(@CPlApplet) then begin CPlApplet(hParent, CPL_EXIT, 0, 0); end; { Unload the applet, if it was loaded } if hCPL <> 0 then begin FreeLibrary(hCPL); end; inherited Destroy; end; { Procedure TAppletLauncher.ShowDialog Parameters: index: dialog index, has to be in the range 0..DialogCount-1 Call method: static Description: Tells the applet to open the requested dialog. Error Conditions: Exceptions will be raised if the passed index is out of bounds or the applet fails to launch the dialog. } procedure TAppletLauncher.ShowDialog(index: Integer); begin ValidateIndex(index); if CPlApplet(hParent, CPL_DBLCLK, index, DialogLData(index)) = 0 then begin raise EAppletError.CreateFmt(errSelectDialogFailed, [FAppletName, index, DialogNames[index]]); end; end; { Function TAppletLauncher.GetDialogNames Parameters: index: dialog index, has to be in the range 0..DialogCount-1 Returns: the szname field of the dialog info record for this dialog. Call method: static Description: This method implements read access to the DialogNames property. Error Conditions: An exceptions will be raised if the passed index is out of bounds. } function TAppletLauncher.GetDialogNames(index: Integer): string; begin ValidateIndex(index); if Assigned(FDialogData[index]) then begin result := Strpas(pNewCPlInfo(FDialogData[index])^.szName); end else result := LoadCPLResourceString(pCPlInfo(FOldDialogData[index])^.idName); end; { Function TAppletLauncher.GetDialogInfotext Parameters: index: dialog index, has to be in the range 0..DialogCount-1 Returns: the szinfo field of the dialog info record for this dialog. Call method: static Description: This method implements read access to the DialogInfotext property. Error Conditions: An exceptions will be raised if the passed index is out of bounds. } function TAppletLauncher.GetDialogInfotext(index: Integer): string; begin ValidateIndex(index); if Assigned(FDialogData[index]) then begin result := Strpas(pNewCPlInfo(FDialogData[index])^.szInfo); end else result := LoadCPLResourceString(pCPlInfo(FOldDialogData[index])^.idInfo); end; { Function TAppletLauncher.GetDialogIcon Parameters: index: dialog index, has to be in the range 0..DialogCount-1 Returns: the icon handle for the icon to display for this dialog. Note that the handle can be 0! Use DrawIconEx to display this icon on a canvas, or create a TIcon and assign the return value to its Handle property. Call method: static Description: This method implements read access to the DialogIcons property. PROBLEM ALERT! For applets that respond to CPL_NEWINQUIRE the icon handle is owned by the applet and must not be deleted by the application. For old-style applets that respond only to CPL_INQUIRE, however, the icon is created from a resource and the application must delete it to prevent a resource leak! Check the DeleteIcon property to determine what to do. Error Conditions: An exceptions will be raised if the passed index is out of bounds. } function TAppletLauncher.GetDialogIcon(index: Integer): HICon; begin ValidateIndex(index); if Assigned(FDialogData[index]) then begin result := pNewCPlInfo(FDialogData[index])^.hIcon; end else result := LoadIcon(hCPL, MakeIntResource(pCPlInfo(FOldDialogData[index])^.idIcon)); end; { Function TAppletLauncher.GetDeleteIcon Parameters: index: dialog index, has to be in the range 0..DialogCount-1 Returns: True if caller needs to delete an icon retrieved via DialogIcons for this index, false otherwise. Call method: static Description: See Problem Alert entry under GetDialogIcon. Error Conditions: An exceptions will be raised if the passed index is out of bounds. } function TAppletLauncher.GetDeleteIcon(index: Integer): Boolean; begin ValidateIndex(index); Result := not Assigned(FDialogData[index]); end; { Procedure TAppletLauncher.ValidateIndex Parameters: index: index to validate Call method: virtual Error Conditions: raises an exception if the passed index is out of bounds. You can override this method to change the behaviour. } procedure TAppletLauncher.ValidateIndex(index: Integer); begin if (index < 0) or (index >= FDialogCount) then begin raise EAppletError.CreateFmt(errIndexOutOfBounds, [index, FAppletName, FDialogCount - 1]); end; end; { Function TAppletLauncher.DialogLData Parameters: index: dialog index, has to be in the range 0..DialogCount-1 Returns: the ldata member of the dialog data. Call method: static Description: Helper function to deal with the different data record formats we can have. Error Conditions: none } function TAppletLauncher.DialogLData(index: Integer): Longint; begin if Assigned(FDialogData[index]) then Result := pNewCPlInfo(FDialogData[index])^.ldata else Result := pCPlInfo(FOldDialogData[index])^.ldata end; { Function TAppletLauncher.LoadCPLResourceString Parameters: strid: resource id of string to load Returns: the string Call method: static Description: Helper function to get a resource string from the applet. Error Conditions: none } function TAppletLauncher.LoadCPLResourceString(strid: Integer): string; begin SetLength(result, 1024); SetLength(result, LoadString(hCPL, strid, @result[1], 1024)); end; end. |