왜 패키지를 사용하나요?
답은 간단합니다. 패키지의 강력한 기능 때문입니다. 디자인 타임 패키지는 사용자 정의 구성 요소의 릴리스 및 설치를 단순화합니다. 런타임 패키지는 기존 프로그래밍에 새로운 힘을 불어넣습니다. 재사용 가능한 코드를 런타임 라이브러리로 컴파일하면 여러 애플리케이션에서 공유할 수 있습니다. 모든 애플리케이션은 패키지를 통해 표준 구성 요소에 액세스할 수 있으며, 이것이 Delphi 자체의 방식입니다. 응용 프로그램은 실행 파일에 별도의 구성 요소 라이브러리를 복사할 필요가 없으므로 시스템 리소스와 디스크 공간이 크게 절약됩니다. 또한 패키지는 애플리케이션별 코드만 컴파일하면 되므로 컴파일에 소요되는 시간을 줄여줍니다.
패키지를 동적으로 사용할 수 있다면 더 많은 혜택을 얻을 수 있습니다. 패키지는 애플리케이션 개발에 대한 새로운 모듈식 접근 방식을 제공합니다. 때로는 선택적 HR 모듈과 함께 제공되는 회계 시스템과 같이 특정 모듈을 응용 프로그램의 선택적 구성 요소로 만들고 싶을 수도 있습니다. 기본 애플리케이션만 설치하면 되는 경우도 있고, 추가 HR 모듈을 설치해야 하는 경우도 있습니다. 이 모듈식 접근 방식은 패키지 기술을 사용하여 쉽게 구현할 수 있습니다. 과거에는 DLL을 동적으로 로드해야만 이 작업을 수행할 수 있었지만 Delphi의 패키징 기술을 사용하면 애플리케이션의 각 모듈 유형을 번들로 "패키징"할 수 있습니다. 특히, 패키지에서 생성된 클래스 개체는 응용 프로그램이 소유하므로 응용 프로그램의 개체와 상호 작용할 수 있습니다.
런타임 패키지 및 애플리케이션
많은 개발자는 Delphi 패키지를 구성 요소를 배치하는 장소로만 생각합니다. 실제로 패키지는 모듈식 애플리케이션 설계에 사용될 수 있고 사용해야 합니다.
패키지를 사용하여 애플리케이션을 모듈화하는 방법을 보여주기 위해 다음 예제를 만듭니다.
1. Form1과 Form2의 두 가지 형식으로 새 Delphi 프로그램을 만듭니다.
2 . 자동으로 생성된 양식 목록(프로젝트 | 옵션 | 양식)에서 Form2를 제거합니다.
3. Form1에 버튼을 배치하고 버튼의 OnClick 이벤트 핸들러 코드에 다음을 입력합니다.
with TForm2.Create(Application) do begin ShowModal; Free; End;
4.
5. 프로젝트를 저장하고 실행하세요.
버튼이 있는 양식을 표시하는 간단한 애플리케이션을 만들었습니다. 이 버튼을 클릭하면 다른 양식이 생성되어 표시됩니다.
그런데 위 예시의 Form2를 재사용 가능한 모듈에 포함시키고 여전히 정상적으로 작동하게 하려면 어떻게 해야 할까요?
답은 보장됩니다!
Form2용 패키지를 생성하려면 다음 작업이 필요합니다.
1. 프로젝트 관리자(보기 | 프로젝트 관리자)를 엽니다.
2. 프로젝트 그룹을 선택하고 "새 프로젝트 추가..."를 선택합니다.
3. "새" 프로젝트 목록에서 "패키지"를 선택합니다.
4. 이제 패키지 편집기를 볼 수 있습니다. ;
5. "Contains" 프로젝트를 선택한 다음 "Add" 버튼을 클릭합니다.
6. 그런 다음 "Browse..." 버튼을 클릭하고 "Unit2.pas"를 선택합니다. ";
7. 이제 패키지에는 "Unit2.pas" 유닛이 포함되어야 합니다.
8. 마지막으로 패키지를 저장하고 컴파일합니다.
이제 패키지가 완성되었습니다. ProjectBPL 디렉터리에 "package1.bpl"이라는 파일이 있어야 합니다. (BPL은 Borland Package Library의 약자이고, DCP는 Delphi CompiledPackage의 약자입니다.)
이 패키지가 완성되었습니다. 이제 패키지 옵션 스위치
를 켜고 원본 애플리케이션을 다시 컴파일해야 합니다.
1. 프로젝트 관리자에서 "Project1.exe"를 두 번 클릭하여 프로젝트를 선택합니다.
2. 마우스 오른쪽 버튼을 클릭하고 "옵션..."을 선택합니다. 메뉴 프로젝트 | 옵션...)
3. "패키지" 옵션 페이지를 선택합니다.
4. "런타임 패키지로 빌드" 확인란을 선택합니다. >5. "런타임 패키지" 편집 상자: "Vcl50;Package1"을 클릭하고 "확인" 버튼을 클릭합니다.
6. 참고: 애플리케이션에서 Unit2를 제거하지 마십시오. .앱을 저장하고 실행하세요.
애플리케이션은 이전과 동일하게 실행되지만 파일 크기에서 차이를 확인할 수 있습니다.
Project1.exe의 크기는 이전의 293K에 비해 이제 14K에 불과합니다. 리소스 브라우저를 사용하여 EXE 및 BPL 파일의 내용을 보면 이제 Form2 DFM 및 코드가 패키지에 저장되어 있음을 알 수 있습니다.
Delphi는 컴파일 중에 패키지의 정적 링크를 완료합니다. (이것이 EXE 프로젝트에서 Unit2를 제거할 수 없는 이유입니다.)
이로부터 얻을 수 있는 이점에 대해 생각해 보십시오. 패키지에서 데이터 액세스 모듈을 생성할 수 있고, 데이터 액세스 규칙을 변경할 때( 예를 들어 BDE 연결을 ADO 연결로 변환하고 약간 수정한 후 패키지를 다시 게시하세요. 또는 "이 옵션은 현재 버전에서는 사용할 수 없습니다"라는 메시지를 표시하는 한 패키지에서 양식을 만든 다음 동일한 이름을 가진 다른 패키지에서 완전한 기능을 갖춘 양식을 만들 수 있습니다. 이제 우리는 별다른 노력 없이도 "Pro" 및 "Enterprise" 버전의 제품을 보유하게 되었습니다.
패키지 동적 로드 및 언로드
在大多数情况下,静态连接的DLL或BPL已经可以满足要求了。但是如果我们不想发布BPL呢? “在指定目录中找不到动态链接库Package1.bpl”,这是在应用程序终止前,我们所能得到 的唯一消息。或者,在模块化应用程序程序中,我们是否可以使用任意数量的插件?
我们需要在运行期动态连接到BPL。
对于DLL 来说,有一个简单的方法,就是使用LoadLibrary函数:
function LoadLibrary(lpLibFileName: Pchar): HMODULE;stdcall;
装载了DLL之后,我们可以使用GetProcAddress函数来调用DLL的导出函 数和方法:
function GetProcAddress(hModule: HMODULE; lpProcName:LPCSTR): FARPROC; stdcall;
最后,我们使用FreeLibrary卸载DLL:
function FreeLibrary(hLibModule: HMODULE): BOOL;stdcall;
下面这个例子中我们动态装载Microsoft的HtmlHelp库:
function TForm1.ApplicationEvents1Help(Command: Word; Data: Integer; var CallHelp: Boolean):Boolean; type TFNHtmlHelpA = function(hwndCaller: HWND; pszFile: PansiChar; uCommand: UINT;dwData: Dword): HWND; stdcall; var HelpModule: Hmodule; HtmlHelp: TFNHtmlHelpA; begin Result := False; HelpModule := LoadLibrary('HHCTRL.OCX'); if HelpModule <> 0 then begin @HtmlHelp := GetProcAddress(HelpModule, 'HtmlHelpA'); if @HtmlHelp <> nil then Result := HtmlHelp(Application.Handle,Pchar(Application.HelpFile), Command,Data) <> 0; FreeLibrary(HelpModule); end; CallHelp := False; end;
动态装载BPL
我们可以用同样简单的方法来对付BPL,或者应该说基本上同 样简单。
我们可以使用LoadPackage函数动态装载包:
function LoadPackage(const Name: string): HMODULE;
然后使用GetClass 函数创建一个TPersistentClass类型对象:
function GetClass(const AclassName: string):TPersistentClass;
完成所有操作后,使用UnLoadPackage(Module:HModule);
让我们对原来的代码作一些小小的改动:
1、 在工程管理器中选中“Project1.exe”;
2、 右击之并选择“Options...”;
3、 选中“Packages”选项页;
4 、 从“Runtime packages”编辑框中移除“Package1”,并点击OK按钮;
5、 在Delphi的工具栏中,点击“Remove file from project”按钮;
6、 选择“Unit2 | Form2”,并点击OK;
7、 现在在“Unit1.pas”的源代码中,从uses子句中移除Unit2;
8、 进入Button1 的OnClick时间代码中;
9、 添加两个HModule和TPersistentClass类型的变量:
var
PackageModule: HModule;
AClass: TPersistentClass;
10、使用LoadPackage 函数装载Pacakge1包:
PackageModule := LoadPackage('Package1.bpl');
11、检查PackageModule是否为0;
12、使用GetClass函数创建一个持久类型:
AClass := GetClass('TForm2');
13、如果这个持久类型不为nil,我们就可以向从前
一样创建并使用该类型的对象了:
with TComponentClass(AClass).Create(Application) as TcustomForm do begin ShowModal; Free; end;
14、最后,使用UnloadPackage 过程卸载包:
UnloadPackage(PackageModule);
15、保存工程。
下面是OnClick事件处理器的完整清单:
procedure TForm1.Button1Click(Sender: Tobject); var PackageModule: HModule; AClass: TPersistentClass; begin PackageModule := LoadPackage('Package1.bpl'); if PackageModule <> 0 then begin AClass := GetClass('TForm2'); if AClass <> nil then with TComponentClass(AClass).Create(Application) as TcustomForm do begin ShowModal; Free; end; UnloadPackage(PackageModule); end; end;
不幸的是,并不是这样就万事大吉了。
问题在于,GetClass函数只能搜索到已经注册的类型。 通常在窗体中引用的窗体类和组件类会在窗体装载时自动注册。但是在我们的例子中,窗体无法提前装载。那么我们在哪里注册类型呢?答案是,在包中。包中的每 个单元都会在包装载的时候初始化,并在包卸载时清理。
现在回到我们的例子中:
1、 在工程管理器双击“Package1.bpl”;
2、 点击“Contains”部分“Unit2”旁的+号;
3、 双击“Unit2.pas”激活单元源代码编辑器;
4、 在文件的最后加入initialization部分;
5、 使用RegisterClass过程注册窗体的类型:
RegisterClass(TForm2);
6、 添加一个finalization部分;
7、 使用UnRegisterClass过程反注册窗体的类 型:
UnRegisterClass(TForm2);
8、 最后,保存并编译包。
现在我们可以安全的运行“Project1”,它还会像从前 一样工作,但是现在你可以随心所欲的装载包了。
尾声
记住,无论你是静态还是动态的使用包,都要打开Project | Options | Packages | Build with runtime packages 选项。
在你卸载一个包之前,记得销毁所有该包中的类对象,并反注册所有已注册的类。下面的过程可能会对你有所帮助:
procedure DoUnloadPackage(Module: HModule); var i: Integer; M: TMemoryBasicInformation; begin for i := Application.ComponentCount - 1 downto 0 do begin VirtualQuery(GetClass(Application.Components[i].ClassName), M, Sizeof(M)); if (Module = 0) or (HMODULE(M.AllocationBase) = Module) then Application.Components[i].Free; end; UnregisterModuleClasses(Module); UnloadPackage(Module); end;
在装载包之前,应用程序需要知道所有已注册类的名字。改善这一情况的方法是建立一个注册机制,以便告诉应用程序所有 由包注册的类的名字。
实例
多重包:包不支持循环引用。也就是说,一个单元不能引用一个已经引用了该单元的单元(嘿嘿)。这使得调用窗体中的某 些值难以由被调用的方法设置。
解决这个问题的方法是,创建一些额外的包,这些包同时由调用对象和包中的对象引用。设想一下我们如何使Application成为所有窗体的拥有者?变量Application创 建于Forms.pas 中,并包含在VCL50.bpl包 中。你大概注意到了你的应用程序既要将VCL50.pas编译进来,也同时你的包也需要(require) VCL50。
세 번째 예에서는 고객 정보를 표시하고 요청 시 고객 주문을 (동적으로) 표시하는 애플리케이션을 설계합니다.
그럼 어디서부터 시작할까요? 모든 데이터베이스 애플리케이션과 마찬가지로 연결이 필요합니다. TDataBase 연결을 포함하는 메인 데이터 모듈을 생성합니다. 그런 다음 이 데이터 모듈을 패키지(cst_main)에 캡슐화합니다.
이제 애플리케이션에서 고객 양식을 생성하고 DataModuleMain을 참조합니다(VCL50과 cst_main을 정적으로 연결합니다).
그런 다음 고객 주문 양식이 포함되고 cst_main이 필요한 새 패키지(cst_ordr)를 생성합니다. 이제 애플리케이션에서 cst_ordr을 동적으로 로드할 수 있습니다. 동적 패키징이 로드되기 전에 기본 데이터 모듈이 이미 존재하므로 cst_ordr은 애플리케이션의 기본 데이터 모듈 인스턴스를 직접 사용할 수 있습니다.
위 그림은 이 애플리케이션의 기능 다이어그램입니다.
교체 가능한 패키지: 패키지의 또 다른 응용 예는 교체 가능한 패키지를 만드는 것입니다. 이 기능을 구현하는 데는 패키지의 동적 로드 기능이 필요하지 않습니다. 프로그램의 시간 제한이 있는 평가판을 출시한다고 가정해 보겠습니다. 이를 달성하는 방법은 무엇입니까?
먼저 일반적으로 "Trial"이라는 단어가 포함된 그림인 "Splash" 양식을 만들어 애플리케이션 시작 프로세스 중에 표시합니다. 그런 다음 응용 프로그램에 대한 일부 정보를 제공하는 "정보" 양식을 만듭니다. 마지막으로 소프트웨어가 최신 버전인지 테스트하는 함수를 만듭니다. 우리는 이 두 가지 형태와 이 기능을 패키지로 캡슐화하여 소프트웨어 평가판과 함께 출시합니다.
유료 버전의 경우 이전 두 양식과 동일한 클래스 이름을 사용하여 "Splash" 양식과 "About" 양식을 만들고 테스트 기능(아무 것도 하지 않음)을 생성하여 같은 이름의 패키지.
뭐요? 이것이 유용한가요? 음, 우리는 소프트웨어의 평가판을 대중에게 공개할 수 있습니다. 고객이 앱을 구매하는 경우 평가판이 아닌 패키지만 보내면 됩니다. 단 한 번의 설치와 한 번의 등록 패키지 업그레이드만 필요하므로 소프트웨어 릴리스 프로세스가 크게 단순화됩니다.
패키지는 Delphi 및 C++ Builder 개발 커뮤니티를 위한 모듈식 설계의 또 다른 문을 열어줍니다. 패키지를 사용하면 더 이상 창 핸들을 전달할 필요가 없고, 더 이상 콜백 함수나 다른 DLL 기술이 필요하지 않습니다. 이는 또한 모듈식 프로그래밍의 개발 주기를 단축시킵니다. 우리가 해야 할 일은 Delphi의 패키지가 작동하도록 하는 것뿐입니다.