2004. augusztus 31., kedd

How to load and launch a Control Panel applet


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.

Nincsenek megjegyzések:

Megjegyzés küldése