Recently, I started working on Delphi and found that many packages were used in the original program, but I was always in a ignorant state. It may take some time to study this issue. So first list the questions that need to be analyzed:
What is a bag? What is an exe? What are their composition differences? What is the relationship between package and dcu? What does dcp do? What is the relationship between these files at compile time? How is it loaded? How to operate the package after loading? Dll can exports, but why does the delphi help not bring up the exports of the package, but some code uses exPRots in the package?
First, let’s take a look at the compilation process of delphi. There are two types of projects in delphi: packages and programs. The former has the suffix dpk and the latter is dpr. Start with a simple one, let’s start with dpr. According to Delphi's help documentation, the structure of a typical dpr file is as follows:
1 program Editor;
2
3 uses
4 Forms, {change to QForms in linux}
5 REAbout in 'REAbout.pas' {AboutBox},
6 REMain in 'REMain.pas' {MainForm};
7
8 {$R *.res}
9
10 begin
11 application.Title := 'Text Editor';
12 Application.CreateForm(TMainForm, MainForm);
13 Application.Run;
14 end.
Among them, 10 to 14 lines, begin…end is naturally the execution entrance of the program. The uses part indicates some Units that the program needs to use, which is rather vague. Why do some use in to indicate the location of the source code (this part was added to the project itself), and some like Forms part, but not need? Then each Unit will use other Units, and this problem seems to be getting more and more complicated. Let’s look at the structure of the entire source code first:
I guess the first step of the compiler is to traverse this directed graph, compile each Unit, if necessary, and generate the corresponding dcu. As for this "necessary" issue, I initially thought that the Unit statement of use was in, but later I found that it was wrong. Because in the above case, Unit3 does not specify the path in the Uses clause of Unit1, but the corresponding dcu file is still generated correctly. Later, filemon is used to monitor the file opening situation, and the discovery process is as follows: For each node in the graph, the compiler searches the corresponding nodes in the search path in the current directory - project attribute - library path in the library environment. If you don't find the pas file, do it again, but this time I searched for the dcu file corresponding to the node.
Now that the compilation is done, each Unit (i.e., the pas file) has generated the dcu file for it. The following problem is the connection. When it comes to connections, the problem is complicated. There are two types of connections: static and dynamic. Static connection means combining all these dcu together. In this way, the call of one Unit to another Unit becomes an internal matter of the program. This benefit is that it is fast and simple, and problems such as concurrent sharing are easy to deal with. The disadvantage is that the target program is large, and if another program is to be written now and Unit3 can be reused, Unit3.dcu will be copied again when connected. In this way, when two programs run at the same time, there will be two copies of Unit3 in memory, which is a waste. Dynamic connection means that when two programs connect, they only retain references to Unit3, and do not copy Unit3's content. At runtime, load Unit3 into memory and make the two programs common. Dll and BPL are both solutions for dynamic connections. The problem is that the only option for connection in delphi appears in the project|Options|packages menu, and the sentence "Build with runtime packages" is really too vague. So we need to study it again.
When the program is executed, we can use view|debug window|moudles to see what things are loaded into memory and what content they contain.
To be simple, we set up a program with the following structure:
program ProjectEXE;
uses
Forms,
Windows,
UnitFormMain in 'UnitFormMain.pas' {FormMain};
{$R *.res}
Begin
Application.Initialize;
Application.CreateForm(TFormMain, FormMain);
Application.Run;
end.
unit UnitFormMain;
interface
uses
Windows, StdCtrls, Forms, UnitFormAnother, Classes, Controls;
type
TFormMain = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
Private
{ Private declarations }
public
{ Public declarations }
end;
var
FormMain: TFormMain;
Implementation
{$R *.dfm}
procedure TFormMain.Button1Click(Sender: TObject);
var
LForm:TFormAnother;
Begin
LForm:=TFormAnother.Create(Application);
LForm.ShowModal;
LForm.Free;
end;
end.
unit UnitFormAnother;
interface
uses
Forms;
type
TFormAnother = class(TForm)
Private
{ Private declarations }
public
{ Public declarations }
end;
Implementation
{$R *.dfm}
end.
Let’s get the news now. "Build with runtime packages" is checked, and now it is found that the runtime ProjectEXE.exe file only contains four parts: two Form, one SysInit.pas, and one ProjectEXE.dpr; at the same time, there are two more bpl in the process tree: rtl60 and vcl60, their content is the Units that appeared in the static connection just now. ProjectEXE.exe is only 16k now. In other words, part of the Unit in the directed graph is placed in the exe and the other part is placed in the bpl. But what is the division based on? Is it based on the uses clause, or based on the list in "Build with runtime packages" here? Continuing the test, I found that if the list only contains vcl60, then the two bpl plus one exe loaded into memory are still loaded into memory; if the list only contains rtl60, only rtl60 and exe are included in memory, but the content of the exe has changed: The number of Units inside has increased, and they are basically in the vcl60 package. I guess there should be a requirement relationship between rtl and vcl packages. This will be tested in the next step. However, during the initial estimation process, the package list will definitely be used to exclude Units that already exist in the package from the exe.
After dynamic connection, there is another problem: loading. There are two strategies for loading, static, also known as automatic, which generates code from delphi, and automatically loads the package before loading the exe; the other is dynamic, that is, when the program is running, specify a package, and loads it. Memory. The problem is that I have to figure out under what circumstances will delphi automatically load a package and under what circumstances can I avoid delphi being smart so that I can use the package flexibly. In the previous experiment, it can only be seen that before the dpr file is executed to begin, the statically connected package has been loaded into memory. I don’t know the specific process. I will wait until the next chapter starts writing my own package and then do the experiment.