如何在Delphi中添加和使用自定义包/组件中的资源?

6
我想制作一个组件,使用在我包的项目中编译的一些资源。我的组件将在运行时(在其构造函数中)尝试使用此资源(一个PNG图像)。
我想在我的应用程序项目中使用它,但是只要组件被创建,就会出现以下异常:
First chance exception at $7579B9BC. Exception class EResNotFound with message 'Resource xxx not found'. Process yyy.exe (6060)

我在这里缺少什么?

编辑

包的项目中,调用资源的代码如下:

Png.LoadFromResourceName(HInstance, 'png_resource_name');

编辑2

如David所建议,我尝试使用GetModuleHandle函数,但如果我从包的项目应用程序的项目中调用它,它将始终返回0。在包的项目中被调用的代码如下:

PackageModuleHandle := GetModuleHandle(PChar('my_package.bpl'));
Png := TPngImage.Create;
Png .LoadFromResourceName(PackageModuleHandle, 'png_resource_name');

绝对路径指向bpl文件也不起作用。
编辑3:
基于新的回答,进行了新的尝试:
PackageModuleHandle := FindClassHInstance(TMyComponent);
Png := TPngImage.Create;
Png .LoadFromResourceName(PackageModuleHandle, 'png_resource_name');

出现了相同的异常。

编辑4

使用ResourceHacker,如果我用得对的话,资源似乎不在我的bpl文件中。我可能做错了什么?这个看起来是一个简单功能,为什么变得这么复杂呢?

结论

我需要在包的.dpr文件中,在{$R *.res}行后添加包的.res文件。就像这样:

{$R *.res}
{$R 'my_pacakge.res'}

另外,我需要将my_package.rc文件包含到我的项目中,这样每次构建后资源才会编译到.res文件中。我想这样就可以解决问题了。感谢所有的回答。


当你说“以相同的异常失败”时,你是指报告的文件名是.exe文件吗? - David Heffernan
它无法加载我的资源。正如问题开头所述,它会引发“找不到资源”的异常。 - ivarec
我看到你现在已经接受了一个答案。你能告诉我们你是如何解决这个问题的吗?我花了这么多时间在这上面,我想知道结果。 - David Heffernan
请发布您的.RC文件,并指定您添加了{$R xxx}声明的位置,谢谢。 - Warren P
不,GetModuleHandle 在你传递正确的文件名时是有效的(你可能没有这样做)。这是 Windows 的一个非常基本的部分。它是有效的。但是,FindClassHInstance 才是正确的方法。 - David Heffernan
显示剩余11条评论
3个回答

8

您需要使用FindClassHInstance(),指定您的组件类类型,而不是使用全局变量HInstanceGetModuleHandle()。这样,无论包被静态链接还是动态链接到主可执行文件中,您都可以获取正确的模块句柄。


+1 这是一个更好的方法,我同意,但我仍然不明白 GetModuleHandle 为什么会失败。 - David Heffernan
FindClassHInstance成功获取了句柄,但Delphi会抛出相同的异常。资源已经存在(在包的项目中),所有名称都正确。我已经测试了png数据和bmp数据,结果相同。 - ivarec
1
确认资源在包项目中后的下一步是确认它是否在已编译的包中。检查资源是否在BPL文件中。并确保您检查的BPL文件与您的应用程序加载的文件相同 - 不存在两个副本的BPL文件。 - Rob Kennedy
我该如何检查资源是否在BPL文件中?我已确认该资源确实在.res文件中。而且在ProcessExplorer中只加载了一个“my_package.bpl”文件。 - ivarec
使用资源编辑器,如XN Resource Editor或ResourceHacker。 - David Heffernan

3

您正在传递HInstance,即可执行模块的句柄,给资源加载函数。这会失败,因为该资源位于包模块中。因此,您需要传递包的模块句柄。您可以像这样获取包的模块句柄:

PackageModuleHandle := GetModuleHandle(PChar('MyPackage.bpl'));

如果您正在动态加载软件包,则调用LoadPackage会返回模块句柄。

更新: Remy的建议是使用FindClassHInstance来获得模块句柄,这显然是更好的方法。


我在我的包代码中使用HInstance。我将编辑并添加该行。 - ivarec
1
@haole 是的,我假设相关代码位于该包中。我的错误。这个答案应该可以帮你解决问题了。 - David Heffernan
快到了:函数GetModuleHandle总是返回0,无论我尝试MyPackage.bpl、只有MyPackage还是bpl的绝对路径。还有什么可能出错了吗?感谢您的关注。 - ivarec
GetModuleHandle可以工作。我猜你一定是以某种方式传递了错误的名称。你正在用你的包的名称替换MyPackage.bpl。 - David Heffernan
我相信 GetModuleHandle 是有效的。我会加载 Process Explorer 并使用它来检查进程中模块的名称。在 Process Explorer 的下方窗格中找到您的包并在调用 GetModuleHandle 时使用该名称。 - David Heffernan

2

使用 MyUnit1.pas 实现的资源的组件应包含以下行:

{$R MyUnitRc.res MyUnitRc.rc}

请注意,上述语法在某些旧版本的Delphi(Delphi 7)中不起作用。上述语法适用于2010、XE、XE2等版本,并且在项目构建该单元时将.rc编译为.res。或者,您可以将{$R}声明添加到每个静态链接应用程序以及BPL的.dpr文件中。
那个.RC文件mYUnitRc.res将包含一个或多个声明资源的行。我使用RCDATA声明来加载PNG文件,就像这样:
MYIMAGENAME RCDATA "MyFile.png"

如果您这样做,而不是仅添加到.dproj/.dpr的RC文件,那么它将在两个重要情况下起作用:
1. 当BPL在设计时加载时。 2. 当组件在运行时从未编译为运行时包的应用程序中加载时。
要处理第三种情况(Remy和David提供了其他答案),您确实需要调用FindClassHinstance,但我认为您还应该查看如何在您的包内以及使用组件的应用程序中包含资源文件并进行编译和链接。
以下是一些示例代码,展示如何将资源在运行时加载到TPngImage中,这是我自己组件中使用的:
procedure TSampleControl.LoadImageFromResource( button:TSubControl);
var
    png:TPngImage;
begin
  if button.DefaultResourceId='' then exit;
      png := TPngImage.Create;
      try
        try
        png.LoadFromResourceName(HInstance,button.DefaultResourceId);
          button.Glyph.Assign(png);
        except
          on E:Exception do begin
               OutputDebugString( PChar(button.DefaultResourceId+' load error:'+E.Message) );

          end;

        end;
      finally
        png.Free;
      end;

end;

请注意,我捕获资源加载异常,这会使得一些元素在我的控制下没有字形,但至少不会崩溃Delphi。HInstance可能需要更改,正如David和Remy指出的那样,使用LoadFromResourceName处理需要从.BPL加载的情况,但我不认为您可以假设使用您的组件的人总是会重新分发您的BPL文件,因此FindClassHinstanceGetModuleHandle更可取。
更新:我应该使用Remy建议的方法。
       png.LoadFromResourceName( FindClassHInstance(TSampleControl),
                 button.DefaultResourceId);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接