将资源正确包含到 Delphi 包中
Include resource to Delphi package properly
我编写了一个 Delphi 程序包,其中两个组件使用相同的 PNG 资源,我使用以下脚本将其放置在 GraphContour.res
文件中:
compile_res.bat:
@brcc32 GraphContour.rc
GraphContour.rc:
GRAPH_CONTOUR RCDATA GraphContour.png
如果组件的两个单元都包含 {$R GraphContour.res}
指令,组件可以工作,但我收到编译器警告:
[dcc32 Hint] H2161 Warning: Duplicate resource: Type 10 (RCDATA), ID GRAPH_CONTOUR; File C:\Borland\Components\MyControls\GraphContour.res resource kept; file C:\Borland\Components\MyControls\GraphContour.res resource discarded.
如果我从单元中删除 {$R GraphContour.res}
指令,将其放入 DPK 文件:
{$R *.res}
{$R GraphContour.res}
警告消失,我可以编译包,资源在设计时显示,但在 运行 时我收到错误:
Project ComponentTest.exe raised exception class EResNotFound with message 'Resource GRAPH_CONTOUR not found'.
来自这两个控件之一的一些代码:
procedure TMyDisplay.CreateWnd();
var png: TPngImage;
begin
inherited;
//......................
png := TPngImage.Create;
try
png.LoadFromResourceName(HInstance, 'GRAPH_CONTOUR'); //Error is here
_knob.Center.Picture.Graphic := png;
finally
png.Free();
end;
//......................
end;
我用二进制查看器查看了 BPL 文件,发现那里有 GRAPH_CONTOUR 字符串名称。
我尝试使用 FindClassHInstance
而不是 HInstance
。没用。
png.LoadFromResourceName(FindClassHInstance(Self.ClassType), 'GRAPH_CONTOUR');
如何正确地将资源包含到 BPL 中?
更新
这是我用来检查资源可用性的测试应用程序:
program Loadres;
uses Winapi.Windows;
procedure PrintLastError();
var
cchMsg, code: Cardinal;
buf: array[0..512] of WideChar;
begin
code := GetLastError();
cchMsg := FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS,
nil, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), @buf, SizeOf(buf), nil);
MessageBoxW(0, @buf, 'Error', MB_ICONERROR);
end;
var
hm, hResInfo, hResData, hPngFile: NativeUInt;
rp: Pointer;
wb, rs: Cardinal;
begin
hm := LoadLibrary('C:\Documents and Settings\All Users\Documents\Embarcadero\Studio.0\Bpl\MyControls.bpl');
if hm = 0 then begin PrintLastError(); Exit; end;
try
hResInfo := FindResource(hm, 'GRAPH_CONTOUR', RT_RCDATA);
if hResInfo = 0 then begin PrintLastError(); Exit; end;
hResData := LoadResource(hm, hResInfo);
if hResData = 0 then begin PrintLastError(); Exit; end;
rs := SizeofResource(hm, hResInfo);
rp := LockResource(hResData);
try
hPngFile := CreateFile('TheContour.png', GENERIC_WRITE, 0, nil, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0);
try
WriteFile(hPngFile, rp^, rs, wb, nil);
finally
CloseHandle(hPngFile);
end;
finally
UnlockResource(hResData);
end;
finally
FreeLibrary(hm);
end;
end.
它找到资源,报告正确的资源大小,并将资源写入文件。该文件与原始文件一致。
根据您所做的调试和评论中的描述,问题是您的可执行程序没有使用运行时包。相反,它将源文件直接编译成可执行文件。由于它们不再 link 资源,因此它不存在于可执行文件中。
部分选项:
- 使用运行时包。这可能需要您将当前包拆分为设计时和运行时包。
- 将资源放入一个单独的单元(不需要任何代码,只需要 linked 的资源)。将该单元包含在您的设计时包和可执行文件中。如果你安排你的组件源文件使用那个资源单元,那么它将被强制包含在任何需要资源的模块中。
我编写了一个 Delphi 程序包,其中两个组件使用相同的 PNG 资源,我使用以下脚本将其放置在 GraphContour.res
文件中:
compile_res.bat:
@brcc32 GraphContour.rc
GraphContour.rc:
GRAPH_CONTOUR RCDATA GraphContour.png
如果组件的两个单元都包含 {$R GraphContour.res}
指令,组件可以工作,但我收到编译器警告:
[dcc32 Hint] H2161 Warning: Duplicate resource: Type 10 (RCDATA), ID GRAPH_CONTOUR; File C:\Borland\Components\MyControls\GraphContour.res resource kept; file C:\Borland\Components\MyControls\GraphContour.res resource discarded.
如果我从单元中删除 {$R GraphContour.res}
指令,将其放入 DPK 文件:
{$R *.res}
{$R GraphContour.res}
警告消失,我可以编译包,资源在设计时显示,但在 运行 时我收到错误:
Project ComponentTest.exe raised exception class EResNotFound with message 'Resource GRAPH_CONTOUR not found'.
来自这两个控件之一的一些代码:
procedure TMyDisplay.CreateWnd();
var png: TPngImage;
begin
inherited;
//......................
png := TPngImage.Create;
try
png.LoadFromResourceName(HInstance, 'GRAPH_CONTOUR'); //Error is here
_knob.Center.Picture.Graphic := png;
finally
png.Free();
end;
//......................
end;
我用二进制查看器查看了 BPL 文件,发现那里有 GRAPH_CONTOUR 字符串名称。
我尝试使用 FindClassHInstance
而不是 HInstance
。没用。
png.LoadFromResourceName(FindClassHInstance(Self.ClassType), 'GRAPH_CONTOUR');
如何正确地将资源包含到 BPL 中?
更新
这是我用来检查资源可用性的测试应用程序:
program Loadres;
uses Winapi.Windows;
procedure PrintLastError();
var
cchMsg, code: Cardinal;
buf: array[0..512] of WideChar;
begin
code := GetLastError();
cchMsg := FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS,
nil, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), @buf, SizeOf(buf), nil);
MessageBoxW(0, @buf, 'Error', MB_ICONERROR);
end;
var
hm, hResInfo, hResData, hPngFile: NativeUInt;
rp: Pointer;
wb, rs: Cardinal;
begin
hm := LoadLibrary('C:\Documents and Settings\All Users\Documents\Embarcadero\Studio.0\Bpl\MyControls.bpl');
if hm = 0 then begin PrintLastError(); Exit; end;
try
hResInfo := FindResource(hm, 'GRAPH_CONTOUR', RT_RCDATA);
if hResInfo = 0 then begin PrintLastError(); Exit; end;
hResData := LoadResource(hm, hResInfo);
if hResData = 0 then begin PrintLastError(); Exit; end;
rs := SizeofResource(hm, hResInfo);
rp := LockResource(hResData);
try
hPngFile := CreateFile('TheContour.png', GENERIC_WRITE, 0, nil, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, 0);
try
WriteFile(hPngFile, rp^, rs, wb, nil);
finally
CloseHandle(hPngFile);
end;
finally
UnlockResource(hResData);
end;
finally
FreeLibrary(hm);
end;
end.
它找到资源,报告正确的资源大小,并将资源写入文件。该文件与原始文件一致。
根据您所做的调试和评论中的描述,问题是您的可执行程序没有使用运行时包。相反,它将源文件直接编译成可执行文件。由于它们不再 link 资源,因此它不存在于可执行文件中。
部分选项:
- 使用运行时包。这可能需要您将当前包拆分为设计时和运行时包。
- 将资源放入一个单独的单元(不需要任何代码,只需要 linked 的资源)。将该单元包含在您的设计时包和可执行文件中。如果你安排你的组件源文件使用那个资源单元,那么它将被强制包含在任何需要资源的模块中。