Author: Tomas Rutkauskas
How to load and launch a Control Panel applet
Answer:
1 {
2 Unit AppletLauncher
3
4 Version:
5 1.0 Created: 23.08.98, 11:14:39
6 Last Modified: 23.08.98, 11:14:39
7
8 Author : P. Below
9 Project: Win32 utilities
10 Delphi version: 3.x (not tested on 4.x)
11
12 Description:
13 Provides a class to load and launch a control panel applet. The applet is loaded
14 when an
15 instance of this class is created and unloaded when it is destroyed. Applet dialogs
16 are not
17 shown unless explicitely requested. The required API definitions are obtained from
18 the Delphi
19 unit CPL. Processing is complicated by the fact that even Win32 applets may not
20 respond to
21 CPL_NEWINQUIRE. This requires storage for both old (CPL_INQUIRE) and new style info
22 records. For a dialog only one of the list will contain a valid pointer to an info
23 record, the other
24 will contain Nil in that slot.
25
26 Note:
27 There are several errors in the win32 docs for the return values of CPL_ messages.
28 Generally
29 the CPlApplet entry point will return 0 (False) if the function fails and <> 0
30 (TRUE) if it succeeds.
31 The docs indicate the inverse.
32 }
33
34 unit AppletLauncher;
35
36 interface
37
38 uses
39 Classes, Windows, CPL, Graphics, Sysutils;
40
41 type
42 TAppletLauncher = class
43 private
44 hCPL: HMODULE; { applet module handle }
45 hParent: HWND; { container window handle }
46 CPlApplet: TCPlApplet; { applet entry point }
47 FDialogCount: Integer; { number of dialogs available }
48 FDialogData: TList; { stores TNewCPLnfo records for the dialogs }
49 FOldDialogData: TList; { stores TCPlInfo records for the dialog }
50 FAppletName: string; { filename of applet }
51 protected
52 function GetDialogNames(index: Integer): string;
53 function GetDialogInfotext(index: Integer): string;
54 function GetDialogIcon(index: Integer): HICON;
55 function GetDeleteIcon(index: Integer): Boolean;
56 function DialogLData(index: Integer): Longint;
57 function LoadCPLResourceString(strid: Integer): string;
58 procedure ValidateIndex(index: Integer); virtual;
59 public
60 constructor Create(anAppletName: string; hwndParent: HWND);
61 destructor Destroy; override;
62 procedure ShowDialog(index: Integer);
63 property DialogCount: Integer read FDialogCount;
64 property DialogNames[index: Integer]: string read GetDialogNames; default;
65 property DialogInfotext[index: Integer]: string read GetDialogInfotext;
66 property DialogIcons[index: Integer]: HIcon read GetDialogIcon;
67 property DeleteIcon[index: Integer]: Boolean read GetDeleteIcon;
68 property AppletName: string read FAppletName;
69 end;
70
71 EAppletError = class(Exception);
72
73 implementation
74
75 resourcestring { change to Const for Win16 }
76 errCannotLoadApplet = 'TAppletLauncher: cannot load applet %s, reason: %s.';
77 errInvalidApplet = 'TAppletLauncher: %s is not a control panel applet, it does '
78 + 'not export
79 the CPlApplet entry point.'
80 errAppletInitializationFailed = 'TAppletLauncher: initialization of applet %s
81 failed.'
82 errAppletHasNoDialogs = 'TAppletLauncher: applet %s provides no dialogs.';
83 errCannotGetDialogInfo = 'TAppletLauncher: applet %s does not respond to
84 CPL_INQUIRE, '
85 'the launcher is unable to obtain the required information about ' +
86 'the applets dialogs.';
87 errSelectDialogFailed = 'TAppletLauncher: applet %s failed to open dialog #%d
88 (%s).'
89 errIndexOutOfBounds = 'TAppletLauncher: dialog index %d is invalid for applet %s,
90 '+
91 'the allowed range is 0..%d.';
92
93 {Methods of TAppletLauncher}
94
95 {
96 Constructor TAppletLauncher.Create
97
98 Parameters:
99 anAppletName:
100 file name of the applet to load. This name must contain the CPL extention and can
101 contain a
102 full path, if the applet does not reside in the windows or system directories.
103 hwndParent:
104 handle of window that serves as control panel replacement. Use the main forms
105 handle, for
106 example. This window is used as parent for the applets dialogs. If 0 is passed we
107 use the
108 active window as parent.
109
110 Call method: static
111
112 Description:
113 Loads the applet DLL, creates the internal list and fills it with information
114 records for the dialogs
115 the applet provides. Several messages are send to the applets entry point during
116 construction.
117
118 Error Conditions:
119 An exception will be raised if the applet could not be loaded or if it does not
120 respond in the
121 expected way to the send messages. The object is destroyed automatically in this
122 case.
123 }
124
125 constructor TAppletLauncher.Create(anAppletName: string; hwndParent: HWND);
126 var
127 i: Integer;
128 pData: PNewCPlInfo;
129 pOldData: PCPlInfo;
130 begin
131 inherited Create;
132 if hwndParent = 0 then
133 begin
134 hwndparent := GetActiveWindow;
135 end;
136 hParent := hwndParent;
137 { Try to load the applet DLL. }
138 hCPL := LoadLibrary(Pchar(anAppletName));
139 if hCPL = 0 then
140 begin
141 { Error, applet not found. Note: change logic for Win16! }
142 raise EAppletError.CreateFmt(errCannotLoadApplet, [anAppletName,
143 SysErrorMessage(GetLastError)]);
144 end;
145 FAppletName := anAppletName;
146 { Find applet entry point }
147 @CPlApplet := GetProcAddress(hCPL, 'CPlApplet');
148 if @CPlApplet = nil then
149 begin
150 { Entry point not found, this is not a control panel applet! }
151 raise EAppletError.CreateFmt(errInvalidApplet, [anAppletName]);
152 end;
153 { Send CPL_INIT to the applet }
154 if CPlApplet(hParent, CPL_INIT, 0, 0) = 0 then
155 begin
156 { Applet failed to initialize, bail out. }
157 raise EAppletError.CreateFmt(errAppletInitializationFailed, [anAppletName]);
158 end;
159 { Get number of dialogs the applet supports }
160 FDialogCount := CPlApplet(hParent, CPL_GETCOUNT, 0, 0);
161 if FDialogCount = 0 then
162 begin
163 raise EAppletError.CreateFmt(errAppletHasNoDialogs, [anAppletName]);
164 end;
165 { Create list for the dialog information }
166 FDialogData := TList.Create;
167 FDialogData.Capacity := FDialogCount;
168 FOldDialogData := TList.Create;
169 FOldDialogData.Capacity := FDialogCount;
170 { Get the information for the dialogs }
171 for i := 0 to FDialogCount - 1 do
172 begin
173 New(pData);
174 FillChar(pData^, Sizeof(pData^), 0);
175 pData^.dwSize := Sizeof(pData^);
176 if CPlApplet(hParent, CPL_NEWINQUIRE, i, longint(pData)) = 0 then
177 begin
178 { Failed, try CPL_INQUIRE instead }
179 Dispose(pData);
180 New(pOldData);
181 if CPlApplet(hParent, CPL_INQUIRE, i, longint(pOldData)) = 0 then
182 begin
183 { Failed also, bail out }
184 Dispose(pOldData);
185 raise EAppletError.CreateFmt(errCannotGetDialogInfo, [anAppletName]);
186 end
187 else
188 begin
189 FOldDialogData.Add(pOldData);
190 FDialogData.Add(nil);
191 end;
192 end { If }
193 else
194 begin
195 { CPL_NEWINQUIRE succeeded, store the data }
196 FDialogData.Add(pData);
197 FOldDialogData.Add(nil);
198 end;
199 end;
200 { Setup is complete }
201 end;
202
203 {
204 Destructor TAppletLauncher.Destroy
205
206 Parameters: none
207
208 Call method: virtual, overridden
209
210 Description:
211 Releases memory for the dialog data records, destroys the list holding the records,
212 tells the
213 applet to clean up its act and finally unloads the applet. The destructor can be
214 called on
215 a partially initialized object if an exception is raised in the constructor.
216
217 Error Conditions: none
218 }
219
220 destructor TAppletLauncher.Destroy;
221 var
222 i: Integer;
223 begin
224 { Tell applet to clean up its dialogs and release the dialog data, if
225 initialization
226 completed successfully }
227 if Assigned(FDialogData) then
228 begin
229 for i := 0 to FDialogData.Count - 1 do
230 begin
231 CPlApplet(hParent, CPL_STOP, i, DialogLData(i));
232 if Assigned(FDialogData[i]) then
233 begin
234 Dispose(pNewCPlInfo(FDialogData[i]));
235 end;
236 end;
237 FDialogData.Free;
238 end;
239 if Assigned(FOldDialogData) then
240 begin
241 for i := 0 to FOldDialogData.Count - 1 do
242 begin
243 if Assigned(FOldDialogData[i]) then
244 begin
245 Dispose(pCPlInfo(FOldDialogData[i]));
246 end;
247 end;
248 FOldDialogData.Free;
249 end;
250 { Tell applet to clean up, if load was successful. Note: this code is executed
251 even if CPL_INIT
252 failed, I don't know if this may cause a problem. }
253 if Assigned(@CPlApplet) then
254 begin
255 CPlApplet(hParent, CPL_EXIT, 0, 0);
256 end;
257 { Unload the applet, if it was loaded }
258 if hCPL <> 0 then
259 begin
260 FreeLibrary(hCPL);
261 end;
262 inherited Destroy;
263 end;
264
265 {
266 Procedure TAppletLauncher.ShowDialog
267
268 Parameters:
269 index: dialog index, has to be in the range 0..DialogCount-1
270
271 Call method: static
272
273 Description:
274 Tells the applet to open the requested dialog.
275
276 Error Conditions:
277 Exceptions will be raised if the passed index is out of bounds or the applet fails
278 to launch the dialog.
279 }
280
281 procedure TAppletLauncher.ShowDialog(index: Integer);
282 begin
283 ValidateIndex(index);
284 if CPlApplet(hParent, CPL_DBLCLK, index, DialogLData(index)) = 0 then
285 begin
286 raise EAppletError.CreateFmt(errSelectDialogFailed, [FAppletName, index,
287 DialogNames[index]]);
288 end;
289 end;
290
291 {
292 Function TAppletLauncher.GetDialogNames
293
294 Parameters:
295 index: dialog index, has to be in the range 0..DialogCount-1
296
297 Returns:
298 the szname field of the dialog info record for this dialog.
299
300 Call method: static
301
302 Description:
303 This method implements read access to the DialogNames property.
304
305 Error Conditions:
306 An exceptions will be raised if the passed index is out of bounds.
307 }
308
309 function TAppletLauncher.GetDialogNames(index: Integer): string;
310 begin
311 ValidateIndex(index);
312 if Assigned(FDialogData[index]) then
313 begin
314 result := Strpas(pNewCPlInfo(FDialogData[index])^.szName);
315 end
316 else
317 result := LoadCPLResourceString(pCPlInfo(FOldDialogData[index])^.idName);
318 end;
319
320 {
321 Function TAppletLauncher.GetDialogInfotext
322
323 Parameters:
324 index: dialog index, has to be in the range 0..DialogCount-1
325
326 Returns:
327 the szinfo field of the dialog info record for this dialog.
328
329 Call method: static
330
331 Description:
332 This method implements read access to the DialogInfotext property.
333
334 Error Conditions:
335 An exceptions will be raised if the passed index is out of bounds.
336 }
337
338 function TAppletLauncher.GetDialogInfotext(index: Integer): string;
339 begin
340 ValidateIndex(index);
341 if Assigned(FDialogData[index]) then
342 begin
343 result := Strpas(pNewCPlInfo(FDialogData[index])^.szInfo);
344 end
345 else
346 result := LoadCPLResourceString(pCPlInfo(FOldDialogData[index])^.idInfo);
347 end;
348
349 {
350 Function TAppletLauncher.GetDialogIcon
351
352 Parameters:
353 index: dialog index, has to be in the range 0..DialogCount-1
354
355 Returns:
356 the icon handle for the icon to display for this dialog. Note that the handle can
357 be 0!
358 Use DrawIconEx to display this icon on a canvas, or create a TIcon and assign the
359 return value to its Handle property.
360
361 Call method: static
362
363 Description:
364 This method implements read access to the DialogIcons property.
365 PROBLEM ALERT!
366 For applets that respond to CPL_NEWINQUIRE the icon handle is owned by the applet
367 and
368 must not be deleted by the application. For old-style applets that respond only to
369 CPL_INQUIRE,
370 however, the icon is created from a resource and the application must delete it to
371 prevent a
372 resource leak! Check the DeleteIcon property to determine what to do.
373
374 Error Conditions:
375 An exceptions will be raised if the passed index is out of bounds.
376 }
377
378 function TAppletLauncher.GetDialogIcon(index: Integer): HICon;
379 begin
380 ValidateIndex(index);
381 if Assigned(FDialogData[index]) then
382 begin
383 result := pNewCPlInfo(FDialogData[index])^.hIcon;
384 end
385 else
386 result := LoadIcon(hCPL,
387 MakeIntResource(pCPlInfo(FOldDialogData[index])^.idIcon));
388 end;
389
390 {
391 Function TAppletLauncher.GetDeleteIcon
392
393 Parameters:
394 index: dialog index, has to be in the range 0..DialogCount-1
395
396 Returns:
397 True if caller needs to delete an icon retrieved via DialogIcons for this index,
398 false otherwise.
399
400 Call method: static
401
402 Description:
403 See Problem Alert entry under GetDialogIcon.
404
405 Error Conditions:
406 An exceptions will be raised if the passed index is out of bounds.
407 }
408
409 function TAppletLauncher.GetDeleteIcon(index: Integer): Boolean;
410 begin
411 ValidateIndex(index);
412 Result := not Assigned(FDialogData[index]);
413 end;
414
415 {
416 Procedure TAppletLauncher.ValidateIndex
417
418 Parameters:
419 index: index to validate
420
421 Call method: virtual
422
423 Error Conditions:
424 raises an exception if the passed index is out of bounds. You can override this
425 method to
426 change the behaviour.
427 }
428
429 procedure TAppletLauncher.ValidateIndex(index: Integer);
430 begin
431 if (index < 0) or (index >= FDialogCount) then
432 begin
433 raise EAppletError.CreateFmt(errIndexOutOfBounds, [index, FAppletName,
434 FDialogCount - 1]);
435 end;
436 end;
437
438 {
439 Function TAppletLauncher.DialogLData
440
441 Parameters:
442 index: dialog index, has to be in the range 0..DialogCount-1
443
444 Returns:
445 the ldata member of the dialog data.
446
447 Call method: static
448
449 Description:
450 Helper function to deal with the different data record formats we can have.
451
452 Error Conditions: none
453 }
454
455 function TAppletLauncher.DialogLData(index: Integer): Longint;
456 begin
457 if Assigned(FDialogData[index]) then
458 Result := pNewCPlInfo(FDialogData[index])^.ldata
459 else
460 Result := pCPlInfo(FOldDialogData[index])^.ldata
461 end;
462
463 {
464 Function TAppletLauncher.LoadCPLResourceString
465
466 Parameters:
467 strid: resource id of string to load
468
469 Returns:
470 the string
471
472 Call method: static
473
474 Description:
475 Helper function to get a resource string from the applet.
476
477 Error Conditions: none
478 }
479
480 function TAppletLauncher.LoadCPLResourceString(strid: Integer): string;
481 begin
482 SetLength(result, 1024);
483 SetLength(result, LoadString(hCPL, strid, @result[1], 1024));
484 end;
485
486 end.
|