全國最多中醫師線上諮詢網站-台灣中醫網
發文 回覆 瀏覽次數:4447
推到 Plurk!
推到 Facebook!

如何動態使用 Package

答題得分者是:jow
seeing
初階會員


發表:49
回覆:131
積分:41
註冊:2002-11-07

發送簡訊給我
#1 引用回覆 回覆 發表時間:2009-12-01 00:00:41 IP:123.110.xxx.xxx 訂閱
小弟目前製作了一個 package1,產生了 package1.bpl, package1.bpi, package1.lib

開一個新的專案 Project1,在 Form 裡面使用了 Package1 內的 Function AAA()

在 Project->Option->Packages 裡面,將 builder with runtime packages 打勾,並把 edit box 清空後,加入 package1.bpi

但是在編譯時,總是會有錯誤

[Linker Error] Unresolved external '__fastcall TForm1::AAA()' referenced from D:\BCB練習區\PACKAGE練習\U_MAIN.OBJ

除非把 Package1.lib 加入才不會有錯誤


因為想要使用動態載入 package 的方式,也就是執行時,會去連結 package1.bpl,如果沒有這個檔案就會錯誤。如果 package1.bpl 有更動,只要抽換掉即可,不需要將主程式重新編譯

那小弟的問題是,在上面的步驟中,必須加入 package1.lib,程式才能運作,那這不是變成靜態載入的方式了嗎?抽換掉 package1.bpl 還是沒用的,還是必須重新編譯才行

因此要如何做才能像 DLL 一樣,只要抽換掉 *.bpl 就可以,而不需要重新編譯?
syntax
尊榮會員


發表:26
回覆:1139
積分:1258
註冊:2002-04-23

發送簡訊給我
#2 引用回覆 回覆 發表時間:2009-12-01 09:27:05 IP:59.125.xxx.xxx 訂閱
你何不這樣思考『加入 lib 後,是否就無法更換 bpl?如果還是可以,那 project 加入 lib 對你來說,有什麼困難?』 

你別太執著「動態」二字,如果編譯器,來對像是什麼都不知道,又如何編譯?
另外要注意:
1. 要加入的是 bpl (not lib)
2. 相關搜尋路徑,是否正確
3. 記得 using (即使是 BCB)

===================引 用 seeing 文 章===================
小弟目前製作了一個 package1,產生了 package1.bpl, package1.bpi, package1.lib

開一個新的專案 Project1,在 Form 裡面使用了 Package1 內的 Function AAA()

在 Project->Option->Packages 裡面,將 builder with runtime packages 打勾,並把 edit box 清空後,加入 package1.bpi

但是在編譯時,總是會有錯誤

[Linker Error] Unresolved external '__fastcall TForm1::AAA()' referenced from D:\BCB練習區\PACKAGE練習\U_MAIN.OBJ

除非把 Package1.lib 加入才不會有錯誤


因為想要使用動態載入 package 的方式,也就是執行時,會去連結 package1.bpl,如果沒有這個檔案就會錯誤。如果 package1.bpl 有更動,只要抽換掉即可,不需要將主程式重新編譯

那小弟的問題是,在上面的步驟中,必須加入 package1.lib,程式才能運作,那這不是變成靜態載入的方式了嗎?抽換掉 package1.bpl 還是沒用的,還是必須重新編譯才行

因此要如何做才能像 DLL 一樣,只要抽換掉 *.bpl 就可以,而不需要重新編譯?
seeing
初階會員


發表:49
回覆:131
積分:41
註冊:2002-11-07

發送簡訊給我
#3 引用回覆 回覆 發表時間:2009-12-01 10:10:43 IP:220.136.xxx.xxx 訂閱
您好:
加入 lib 後,更換 bpl 是無效的,還是必須把程式重新編譯才行

1. 程式中有加入 bpl,但也必須要加入lib,否則無法 link,這點很奇怪
2.3. 都已經確認過是正確的了

另外,編譯時,因為已經有using了,所以編譯器應該知道要編譯的對象是誰了吧

===================引 用 syntax 文 章===================
你何不這樣思考『加入 lib 後,是否就無法更換 bpl?如果還是可以,那 project 加入 lib 對你來說,有什麼困難?』

你別太執著「動態」二字,如果編譯器,來對像是什麼都不知道,又如何編譯?
另外要注意:
1. 要加入的是 bpl (not lib)
2. 相關搜尋路徑,是否正確
3. 記得 using (即使是 BCB)
jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#4 引用回覆 回覆 發表時間:2009-12-01 12:07:01 IP:211.74.xxx.xxx 未訂閱
動態載入DLL?
以下的function,是否都有用到?

LoadLibrary()
GetProcAddress()
FreeLibrary()

另外,BPL的結構不同於一般的DLL ...

個人看法,僅供參考

seeing
初階會員


發表:49
回覆:131
積分:41
註冊:2002-11-07

發送簡訊給我
#5 引用回覆 回覆 發表時間:2009-12-01 12:08:10 IP:220.136.xxx.xxx 訂閱
您好,因為還要對VCL元件做更動,所以才考慮用BPL的方式

===================引 用 jow 文 章===================
動態載入DLL?
以下的function,是否都有用到?

LoadLibrary()
GetProcAddress()
FreeLibrary()

另外,BPL的結構不同於一般的DLL ...

個人看法,僅供參考

jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#6 引用回覆 回覆 發表時間:2009-12-01 12:27:27 IP:211.74.xxx.xxx 未訂閱
請參考這一篇
http://edn.embarcadero.com/article/27178


===================引 用 seeing 文 章===================
您好,因為還要對VCL元件做更動,所以才考慮用BPL的方式

===================引 用 jow 文 章===================
動態載入DLL?
以下的function,是否都有用到?

LoadLibrary()
GetProcAddress()
FreeLibrary()

另外,BPL的結構不同於一般的DLL ...

個人看法,僅供參考

syntax
尊榮會員


發表:26
回覆:1139
積分:1258
註冊:2002-04-23

發送簡訊給我
#7 引用回覆 回覆 發表時間:2009-12-01 13:11:40 IP:59.125.xxx.xxx 訂閱
必須要加入lib,否則無法 link,這點很奇怪 <--- 的確

jow 提供的 請參考這一篇
http://edn.embarcadero.com/article/27178

說得很清楚

另外我所指的加入 bpl,是加入在 Requires,凡是放在 Contains 的,都會重新編譯,而不只是「參考」

===================引 用 seeing 文 章===================
您好:
加入 lib 後,更換 bpl 是無效的,還是必須把程式重新編譯才行

1. 程式中有加入 bpl,但也必須要加入lib,否則無法 link,這點很奇怪
2.3. 都已經確認過是正確的了

另外,編譯時,因為已經有using了,所以編譯器應該知道要編譯的對象是誰了吧

===================引 用 syntax 文 章===================
你何不這樣思考『加入 lib 後,是否就無法更換 bpl?如果還是可以,那 project 加入 lib 對你來說,有什麼困難?』

你別太執著「動態」二字,如果編譯器,來對像是什麼都不知道,又如何編譯?
另外要注意:
1. 要加入的是 bpl (not lib)
2. 相關搜尋路徑,是否正確
3. 記得 using (即使是 BCB)
seeing
初階會員


發表:49
回覆:131
積分:41
註冊:2002-11-07

發送簡訊給我
#8 引用回覆 回覆 發表時間:2009-12-01 13:45:35 IP:220.136.xxx.xxx 訂閱
小弟將程式上傳上來了 http://delphi.ktop.com.tw/download.php?download=upload/4b14ac91d1b92_Package_Test.zip

Dear jow,
如果可以,小弟也想使用 LoadPackages 的方式,只是如 http://delphi.ktop.com.tw/board.php?cid=168&fid=913&tid=99902 所討論的,目前還試不出來

seeing
初階會員


發表:49
回覆:131
積分:41
註冊:2002-11-07

發送簡訊給我
#9 引用回覆 回覆 發表時間:2009-12-01 16:46:35 IP:220.136.xxx.xxx 訂閱
解決了!

小弟後來想到用物件的方式來做
  1. 建立一個物件(TTest),但是不要安裝,此時會產生 *.BPL, *.BPI, *.lib
  2. 建立一個專案,將 builder with runtime packages 打勾,並把 edit box 清空後,加入 *.bpi(如 Test.bpi)
  3. 在 Form 中建立一個 TTest
  4. 這時候就可以使用 TTest 物件內的 funtion 了
  5. 如果將 *.BPL 刪除,則程式無法運作;如果修改了 TTest 的物件,則編譯後馬上就能使用了,不需要將主程式重新編譯;這樣就符合可抽換的功能了
暫時先這樣用,等有想到比較好的方式再來修改…
syntax
尊榮會員


發表:26
回覆:1139
積分:1258
註冊:2002-04-23

發送簡訊給我
#10 引用回覆 回覆 發表時間:2009-12-02 08:29:24 IP:59.125.xxx.xxx 訂閱
安裝了恭喜你找到 6 種動態載入的方法之一
自己試過一次,必了然於胸
===================引 用 seeing 文 章===================
解決了!

小弟後來想到用物件的方式來做
  1. 建立一個物件(TTest),但是不要安裝,此時會產生 *.BPL, *.BPI, *.lib
  2. 建立一個專案,將 builder with runtime packages 打勾,並把 edit box 清空後,加入 *.bpi(如 Test.bpi)
  3. 在 Form 中建立一個 TTest
  4. 這時候就可以使用 TTest 物件內的 funtion 了
  5. 如果將 *.BPL 刪除,則程式無法運作;如果修改了 TTest 的物件,則編譯後馬上就能使用了,不需要將主程式重新編譯;這樣就符合可抽換的功能了
暫時先這樣用,等有想到比較好的方式再來修改…
seeing
初階會員


發表:49
回覆:131
積分:41
註冊:2002-11-07

發送簡訊給我
#11 引用回覆 回覆 發表時間:2009-12-02 10:17:02 IP:220.136.xxx.xxx 訂閱
不過還是有問題,我的物件是繼承自 TComponent,一般 function 都沒事,只要一宣告 Graphics::TBitmap,在使用時,就一定會出錯 Access violation at address 7C93B21A in module 'ntdll.dll' Write of address 00000010。物件內的程式為
[code cpp]
*.h

#ifndef Component1H
#define Component1H
//---------------------------------------------------------------------------
#include
#include
#include
//---------------------------------------------------------------------------
class PACKAGE TComponent1 : public TComponent
{
private:
Graphics::TBitmap *Bmp;
protected:
public:
__fastcall TComponent1(TComponent* Owner);
__published:
};
//---------------------------------------------------------------------------
#endif

*.cpp

#include <basepch.h><br />
#pragma hdrstop

#include "Component1.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------
// ValidCtrCheck is used to assure that the components created do not have
// any pure virtual functions.
//

static inline void ValidCtrCheck(TComponent1 *)
{
new TComponent1(NULL);
}
//---------------------------------------------------------------------------
__fastcall TComponent1::TComponent1(TComponent* Owner)
: TComponent(Owner)
{
Bmp = new Graphics::TBitmap;
Bmp->PixelFormat = pf24bit;
}
//---------------------------------------------------------------------------
namespace Component1
{
void __fastcall PACKAGE Register()
{
TComponentClass classes[1] = {__classid(TComponent1)};
RegisterComponents("Test", classes, 0);
}
}
//---------------------------------------------------------------------------
[/code]
只要這樣就死了,這是沒有安裝物件的方式;如果將物件安裝上去,並且取消 builder with runtime packages,這樣又沒事,不知道是哪邊沒做好嗎?

完整程式如下:
4b15cc57c9287_Test1.zip

編輯記錄
seeing 重新編輯於 2009-12-02 10:17:40, 註解 無‧
jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#12 引用回覆 回覆 發表時間:2009-12-02 21:32:42 IP:123.193.xxx.xxx 未訂閱
用D6寫的範例, 提供你參考

http://delphi.ktop.com.tw/download.php?download=upload/4b166e6da351f_TEST12.zip

專案群組 PRG12.BPG 中內含三個專案
a. TYPE12.BPL(內含 fTypeDef.pas)
b. BPL12.BPL(內含 fFunc2.pas: TForm2)
c. TEST12.EXE(內含 fMain.pas: TForm1)

TYPE12.BPL:
(1)宣告一些function type
(2)自訂虛擬抽象類別, TMyBaseComponent
(3)定義一些公用變數
(4)這個BPL提供給 TEST12.EXE 和 BPL12.BPL 靜態載入使用
也就是說, TEST12.EXE 和 BPL12.BPL 專案中
都有

BPL12.BPL:
(1)TMyComponent 繼承並改寫 TMyBaseComponent
為求精簡, 實作程式碼也寫在 fFunc2.pas 中
(2)自訂類別 TForm2
(3)這個BPL提供給 TEST12.EXE 動態載入使用
也就是說, TEST12.EXE 專案中, 不會有 TYPE12.BPL

[code delphi]
unit fTypeDef;

interface

uses
Windows,
Classes,
Dialogs,
SysUtils,
Graphics;

type
TMethod1 = function: string of object;
TMethod2 = function(S: string): string of object;
TMethod3 = function(const FileName: string; var Bitmap: TBitmap): Boolean of object;
TMethod4 = procedure(c: TCanvas; r: TRect; b: TBitmap) of object;

TMyBaseComponent = class(TComponent)
public
function Method1: string; virtual; abstract;
function Method2(S: string): string; virtual; abstract;
function Method3(const FileName: string; var Bitmap: TBitmap): Boolean; virtual; abstract;
procedure Method4(c: TCanvas; r: TRect; b: TBitmap); virtual; abstract;
end;

TMyBaseComponentClass = class of TMyBaseComponent;

{ Public Function }
function FindMethod(Obj: TObject; MethodName: string; var TheMethod: TMethod): Boolean;
function GetBitmap(const FileName: string; var Bitmap: TBitmap): Boolean;
function MyGetClass(LibName, ClsName: string; var h: HMODULE; var AClass: TPersistentClass): Boolean;
procedure DrawBitmep(c: TCanvas; r: TRect; b: TBitmap);

implementation

function FindMethod(Obj: TObject; MethodName: string; var TheMethod: TMethod): Boolean;
begin
Result := False;
TheMethod.Code := Obj.MethodAddress(MethodName);
if Assigned(TheMethod.Code) then
begin
Result := True;
TheMethod.Data := Pointer(Obj);
end;
end;

function GetBitmap(const FileName: string; var Bitmap: TBitmap): Boolean;
begin
Bitmap := nil;
if FileExists(FileName) then
begin
Bitmap := TBitmap.Create;
Bitmap.LoadFromFile(FileName);
end;
Result := Bitmap <> nil;
end;

function MyGetClass(LibName, ClsName: string; var h: HMODULE; var AClass: TPersistentClass): Boolean;
begin
h := 0;
AClass := nil;
if not FileExists(LibName)
then ShowMessage('Library not found: ' LibName)
else begin
h := LoadPackage(LibName);
if h = 0
then ShowMessage('LoadPackage Failure: ' LibName)
else AClass := GetClass(ClsName);
end;
Result := AClass <> nil;

if not Result and (h<>0) then
begin
UnloadPackage(h);
end;

end;

procedure DrawBitmep(c: TCanvas; r: TRect; b: TBitmap);
begin
if b <> nil then
begin
c.Lock;
try
c.StretchDraw(r,b);
finally
c.Unlock;
end;
end;
end;

end.
[/code]

TEST12.EXE
[code delphi]
unit fMain;

interface

uses
Windows,
SysUtils,
Classes,
Graphics,
Controls,
Dialogs,
Forms,
StdCtrls,
fTypeDef;

type

TForm1 = class(TForm)
btnUseForm2: TButton;
btnUseMyComponent: TButton;
procedure btnUseForm2Click(Sender: TObject);
procedure btnUseMyComponentClick(Sender: TObject);
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnUseMyComponentClick(Sender: TObject);
var
h: HMODULE;
AClass: TPersistentClass;
O: TMyBaseComponent;
b: TBitmap;
rt: TRect;
begin
if MyGetClass('BPL12.BPL','TMyComponent',h,AClass) then
try
O := TMyBaseComponentClass(AClass).Create(Self);
try
O.Method1;
O.Method2('0123456789');

if O.Method3('BACK1.BMP',b) then
try
rt := Rect(0,0,b.Width,b.Height);
OffsetRect(rt, 5, 5);
O.Method4(Canvas,rt,b);
finally
FreeAndNil(b);
end;

finally
FreeAndNil(O);
end;
finally
UnLoadPackage(h);
end;
end;

procedure TForm1.btnUseForm2Click(Sender: TObject);
var
h: HMODULE;
AClass: TFormClass;
F: TForm;
m: TMethod;
b: TBitmap;
rt: TRect;
begin
if MyGetClass('BPL12.BPL','TForm2',h,TPersistentClass(AClass)) then
try
F := AClass.Create(Self);
try
if FindMethod(F,'Method1',m) then TMethod1(m);
if FindMethod(F,'Method2',m) then TMethod2(m)('0123456789');

if FindMethod(F,'Method3',m) and TMethod3(m)('BACK1.BMP',b) then
try
if FindMethod(F,'Method4',m) then
begin
rt := Rect(0,0,b.Width,b.Height);
OffsetRect(rt,b.Width 10, 5);
TMethod4(m)(Canvas,rt,b);
end;
finally
FreeAndNil(b);
end;

F.ShowModal;

finally
FreeAndNil(F);
end;
finally
UnLoadPackage(h);
end;
end;

end.
[/code]


編輯記錄
jow 重新編輯於 2009-12-02 21:35:31, 註解 無‧
jow 重新編輯於 2009-12-02 21:42:31, 註解 無‧
jow 重新編輯於 2009-12-02 22:23:24, 註解 修正程式碼, 忘了Call UnloadPackage()‧
seeing
初階會員


發表:49
回覆:131
積分:41
註冊:2002-11-07

發送簡訊給我
#13 引用回覆 回覆 發表時間:2009-12-03 23:18:24 IP:123.110.xxx.xxx 訂閱
小弟後來用物件的方式來做
  1. 建立一個物件(TTest),但是不要安裝,此時會產生 *.BPL, *.BPI, *.lib
  2. 建立一個專案,將 builder with runtime packages 打勾,並把 edit box 清空後,加入 *.bpi(如 Test.bpi)
  3. 在 Form 中建立一個 TTest
  4. 這時候就可以使用 TTest 物件內的 funtion 了
  5. 如果將 *.BPL 刪除,則程式無法運作;如果修改了 TTest 的物件,則編譯後馬上就能使用了,不需要將主程式重新編譯;這樣就符合可抽換的功能了
但是又在builder with runtime packages內的edit box,多加入了 vcl; rtl; vclx 這三個,這樣就沒問題了,不會有奇怪的問題出現;將來要發佈程式的時候倒是要多把 vcl; rtl; vclx 這幾個也一起發佈才行

Dear Jow,
您的程式小弟會再仔細研讀的,謝謝您
jow
尊榮會員


發表:66
回覆:751
積分:1253
註冊:2002-03-13

發送簡訊給我
#14 引用回覆 回覆 發表時間:2009-12-04 01:10:19 IP:123.193.xxx.xxx 未訂閱
哈, 我果然想太多了!
雖然花了一點時間實作
我還是認為這是一個很有趣的應用...

觀念上,以下的方式還是一種靜態引用
LoadPackage(), UnLoadPackage() 應該用不上吧

因為 以下的第三點所提到在 雖然是builder with runtime packages
因為類別資訊與實作碼在同一個BPL中
應用程式執行時便會自動載入BPL檔

我的做法是:
在TYPE12.BPL中宣告一個虛擬抽象類別與其類別型態
TMyBaseComponentClass = class of TMyBaseComponent;
其下所有Method皆為虛擬抽象的(virtual; abstract;)

然後在BPL12.BPL 中 以TMyComponent 繼承實作
這種做法可以達到作應用程式中,同時使用任意數量的不同實作
可以是同一檔案中的不同實作 或是 跨越不同檔案的實作
並且只在需要時動態載入, 這部分端視應用程式如何去定義其
引用的動作

注意: MyGetClass() 傳入的參數LibName, ClsName
可以是讓應用程式以某種方式自行取得的

並且應用程式在編譯時,並不需要在 builder with runtime packages 的Edit Box 中
將BPL12.BPL 寫死在裡面

另外, 對於 TEST12.EXE 而言 TForm2 是一個完全未知的類別
其下所有的Method 也是以FindMethod() 動態取得的

總之, 隱約感覺這樣的寫法, 似乎可以達到 plug-in 的需求...

個人看法, 僅供參考

===================引 用 seeing 文 章===================
小弟後來用物件的方式來做
  1. 建立一個物件(TTest),但是不要安裝,此時會產生 *.BPL, *.BPI, *.lib
  2. 建立一個專案,將 builder with runtime packages 打勾,並把 edit box 清空後,加入 *.bpi(如 Test.bpi)
  3. 在 Form 中建立一個 TTest
  4. 這時候就可以使用 TTest 物件內的 funtion 了
  5. 如果將 *.BPL 刪除,則程式無法運作;如果修改了 TTest 的物件,則編譯後馬上就能使用了,不需要將主程式重新編譯;這樣就符合可抽換的功能了
但是又在builder with runtime packages內的edit box,多加入了 vcl; rtl; vclx 這三個,這樣就沒問題了,不會有奇怪的問題出現;將來要發佈程式的時候倒是要多把 vcl; rtl; vclx 這幾個也一起發佈才行

Dear Jow,
您的程式小弟會再仔細研讀的,謝謝您
seeing
初階會員


發表:49
回覆:131
積分:41
註冊:2002-11-07

發送簡訊給我
#15 引用回覆 回覆 發表時間:2009-12-05 14:38:46 IP:123.110.xxx.xxx 訂閱
Dear Jow,
謝謝您特意撥空幫小弟測試 ! 萬分感謝 !

如果能做到 plugin,那境界就更高了,只是目前還沒有這個實力去做,只能慢慢練習,從做中學,以後再朝向 plugin 試試

非常謝謝您
系統時間:2024-04-27 9:09:45
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!