About the author
Delphi 8 doesn't ship with a Control Panel applet wizard, so I was working on figuring out what's going on.
I ported the Control Panel units, CtlPanel.pas, Cpl.pas, CtlConsts and tried porting the Control Panel applet demo, Date, from Delphi 7.
The code for the Delphi 8 Control Panel applet looked like this.
library Library1;{$UNSAFECODE ON}{$E CPL}
{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\system.windows.forms.dll'}{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\system.dll'}{%DelphiDotNetAssemblyCompiler '$(SystemRoot)\microsoft.net\framework\v1.1.4322\system.drawing.dll'}
uses System.Windows.Forms, SysUtils, Classes, CtlPanel, main in 'main.pas', ufrmdt in 'ufrmdt.pas' {frmDateTime};
procedure TestProc;begin MessageBox.Show('Reflection succeeded');end;
exports CPlApplet;begin // main unit initialization code Application.Initialize; Application.CreateForm(TDTConfig, DTConfig); Application.Run;end.
And when I loaded the applet into Control Panel, I was getting an out of range error. Then, I finally figured out that the main unit initialization code is not getting initialized.
In Delphi 8, each unit implements at least one class. In fact, every unit is considered a class. The name of the class for the unit is UnitName.Unit. So if you have a unit name Forms, the unit class name is Forms.Unit, and the initialization code for the unit class is in the static method Forms.Unit.Forms. You can discover this using Lutz Roeder's Reflector.
So, in CtlPanel.pas, there is a function named CPlApplet that is exported. Since CPlApplet is a procedure, and therefore, a public static method of the class CtlPanel.Unit, therefore, the CLR .NET Framework creates an instance of the class CtlPanel.Unit, and then calls the class constructor, which in turn calls the unit initialization code, CtlPanel.Unit.CtlPanel, before calling CtlPanel.Unit.CPlApplet. The reason the CLR calls the class constructor is because the unit itself is a class. Referring to your Delphi 7 source (if you have it), at line 249 (or thereabouts), you see “with Application, Application.Modules[lParam1] do”. The problem now is, an out of range error, which is caused by the fact that the main unit initialization code did not execute, because no code touches it at all.
I'm guessing that the Control Panel application LoadLibrary the applet I wrote, but doesn't call the main unit initialization code. Since the main unit initialization did not execute, therefore, no applet modules are being created, and Application.Modules.Count remain at 0, and therefore, given that lParam1 is 0, which in the context of Application.Modules[lParam1], is trying to access a module that doesn't exist at all, throws an out of range error!
Now, here comes the goodie. Since my initialization code in CtlPanel.pas is running, I decided to use reflection to call the main unit initialization code. CtlPanel.pas. Looking at line 233 of CtlPanel.pas (in Delphi 7), which reads “Application := TAppletApplication.Create(nil);”, I added the following code after it, after declaring the appropriate variables.
// Invoke the main unit's initialization code by Reflection,// the method to invoke is the name of the module.// Eg, if the main module is name Library1, then the// main module class is Library1.Unit// and the startup code is Library1.Unit.Library1, which is a global procedure
MainAssembly := Assembly.GetExecutingAssembly; MainUnit := MainAssembly.GetName.Name; MainUnitName := MainUnit+'.Unit'; EntryName := MainUnit; // For testing, set EntryName := 'TestProc'; Method := MainAssembly.GetType(MainUnitName).GetMethod(EntryName); Method.Invoke(nil, nil);
Now, once I use Method.Invoke to call the main initialization code, the program starts to go into an infinite loop. At this point in time, it is questionable that I have written some recursive code that I did not even discover.
If I do not call the main initialization code, the applet modules will never get added, and an out of range exception will occur. If I call the main initialization code, the applet goes into a recursive loop, which I do not think is due to DllReason (DLL_PROCESS_ATTACH, DLL_THREAD_ATTACH, ...), since, in Delphi 7, the main unit initialization code is called with a DllReason.
To be continued...
This article discusses the new Delphi 8 property access specifiers.