托管资源全攻略
作者:caeser2
本文内容适用于所有使用.net v1.1及以上框架的语言。
前传1:提出问题(急于了解正式内容的读者请转到下面的正传部分^_^)
前段时间写了一篇题为“VC.NET轻松实现按钮控件自绘”的文章,其中按钮按下时的效果是由一张图片呈现的。这时问题就来了,最初我将该图片加入资源列表(.rc)中,然后使用下面的2个读取资源的方法;编译通过,可执行时程序抛出异常,提示找不到资源文件。
//方法1,从指定的hinstance中名为bitmapName的资源创建Bitmap对象
public: static Bitmap* System::Drawing::Bitmap::FromResource(IntPtr hinstance,String* bitmapName);
//方法2,使用构造函数从指定的type类中提取的resource资源初始化Bitmap对象
public: System::Drawing::Bitmap::Bitmap(Type* type,String* resource);
前传2:分析问题 接着我就犯难了,在论坛上提出该问题,没有得到建设性的回答,为了赶时间只好匆匆将图片和程序放在一起发布了。事后回想一下心里挺不是滋味,将分离的资源文件和程序文件一起发布,搞的目录里乱七八糟很不好看,为什么加入资源的图片不能用呢?会不会是资源没有打包进.exe文件呢?于是我用2进制查看工具打开资源图片和.exe文件,做了下对比,结果发现资源图片已打包进.exe文件:
资源虽已打包进.exe文件,可是除了上面提到的那两个方法,似乎再没有其它读取内部资源的函数了,那怎么办呢?
前传3:看到解决问题的曙光
打开MSDN,查找有关"资源"的索引,看了半天,最后终于弄明白了,原来.net框架对通过资源编辑器(.rc)打包进程序的资源不再提供支持,而改用一种名为"托管资源"的资源。那么这种资源与普通的非托管资源有何异同?既然不能使用Visual Stdio 200x提供的资源编辑器(.rc)打包资源,这种资源又该怎么打包进程序?打包成功后又应该怎样去使用呢?
正传1:什么是托管资源
资源可以理解为是在逻辑上由应用程序部署的任何非可执行数据,托管资源其本质也大抵如此。那么mfc使用的rc资源和.net使用的托管资源有什么区别呢?请看下表:
资源类型
Windows支持度(注1)
索引方式
可否打包"对象"
独立于.exe打包的难易度
资源的部署
其它
.rc资源
95/98/me/nt/2000及以上
依靠资源ID找到资源
不可以
使用纯资源DLL,比较麻烦
由程序员决定,管理混乱
还没想好:)
.net托管资源
目前还没有支持的
取消资源ID,改为依靠资源名,也可以依靠资源的值(内容)
可序列化的就可以
使用专用的资源文件,很简单
由程序集统一管理(注2)
注1:比如说,对一个文件夹右键选"属性"->"自定义"->"更改图标",在"浏览"中选择.exe文件,如果被系统支持则会读出.exe中打包的所有图标,反之只能读出1个;
注2:
有关程序集的讨论超出了本文的范围,感兴趣的读者可以参考MSDN;
从上表可以看出,虽然托管资源的本质概念和以往的"资源"没有区别,但是使用方式却被彻底颠覆了。托管资源将一个资源视为一个对象,对资源的查找引用不再使用资源ID,改为资源名,就像使用一个对象一样;反过来给可序列化的任意对象起个好记的名字就可以当作资源保存起来。.net托管资源的打包方式有两种,分别是打包进程序的资源和独立的资源文件。独立资源文件的组织方式也有两种形式,一种是扩展名为resX的xml文件,其内部是用xml文本方式组织的;另一种是扩展名为resources的2进制文件,其内部是以2进制方式组织的。
.net资源文件类型
扩展名
文件的组织方式
文件体积
资源的保存形式
其它
xml文件
.resX
由xml标签构成的文本文件
文本构成的,体积当然很大啦
文本资源不变,其它对象用Base64算法翻译成文本
在写.net窗体程序时VS使用.resX文件保存资源,编译时一边将resX中的资源打包进.exe文件,一边生成.resources文件
resources文件
.resources
由2进制数据构成的2进制文件
相对于同样内容的xml文件,体积要小很多
文本资源转换为2进制数据,其它对象不变
正传2:对托管资源的操作
下面的几个System::Resources命名空间中的操作类基本可以满足所有的资源操作:
资源的打包方式
操作类
功能
独立于.exe的文件
资源文件类型
xml文件
ResXResourceSet()
直接索引或枚举XML 资源 (.resx) 文件或流中的资源
ResXResourceReader()
枚举 XML 资源 (.resx) 文件或流中的资源
ResXResourceWriter()
将资源写入.resx文件或输出流
resources文件
ResourceSet()
直接索引或枚举 .resources 文件和流或流中的资源
ResourceReader()
枚举 .resources 文件和流或流中的资源
ResourceWriter()
将资源写入.resources文件或输出流
包含在.exe文件中
ResourceManager()
按名称直接访问文件内特定区域的资源,只能索引
正传3:实战
假设我们有一个窗体叫做form1.h,那么他的资源文件就是form1.resX,那么我们是不是通过修改这个.resX文件,就能实现资源打包进程序呢?simple1中描述的就是这个例子,设计思想如下,具体内容请下载全部代码文件,那里有详细的说明,注意我均使用文件操作方式,读者可以自己试验一下流操作方式:
操作
设计思想
备注
生成
将生成资源打包进程序
必须操作窗体的资源文件(比如form1.resX)。先用ResXResourceReader("form1.resX")读入,再用ResXResourceWriter("form1.resX")打开同一个文件,用AddResource()将读入的资源输出,然后将自己的资源加上,最后用Generate()覆盖form1.resX
1>如果添加资源的操作是在程序编译后做的,则需要重新编译一下!
2>一定要在窗口布局彻底完成后再做该操作,否则编译器会在每次窗口布局后自动清理窗体自身的资源文件!
生成独立的资源文件
直接用ResourceWriter()/ResXResourceWriter()构造函数创建一个资源文件,然后AddResource(),最后Generate()生成
这种情况建议生成体积较小的.resources文件
关于打包自定义资源
如果是对象则需要序列化,如果是某种文件则做成Byte[]数组的形式比较好
不要打包体积太大的媒体流文件喔:)
使用
使用本进程本窗体(form1)的资源
ResourceManager * resources = new ResourceManager(__typeof(My::Form1));
resources->GetObject("资源名");
直接用资源名索引到资源,但不可以枚举!
使用本进程其它窗体(form2)的资源
ResourceManager * resources = new ResourceManager(__typeof(My::Form2));
resources->GetObject("资源名");
同上
使用外部资源
ResXResourceReader* s1 = new ResXResourceReader("External.resX");然后枚举或
ResXResourceSet *s2=new ResXResourceSet("External.resX");
然后直接索引
s2-GetObject("资源名");
对于.resources文件思想一样
关于使用自定义资源
如果是对象则反序列化即可,如果是其它格式则需要自己去解析了
对于其它格式,建议打包时做成Byte[]数组比较好
为了给大家一个使用托管资源的映像,我将simple1的程序稍微改动了一下,将已经打好包的demo1.exe拿给大家,效果如下:
外传:应用simple2是一个具有商业价值的托管资源生成工具,不过偶也不是那种小气的人,于是就将其中的核心部分拿过来开源了^_^,使用它可以很方便的生成托管资源,而这种资源又可以给ASP.net、VB.net、C#.net、J++.net、Delphi.net、SmallTalk.net等等等等等等的.net语言使用,应用范围非常广。程序效果如下:
该程序仿Visual Stdio资源编辑器的树界面,支持拖拽,背景为半透明^_^在树中按下"Del"可以快速删除。树下面那个滚动条不关偶的事哦,那是.net v1.1的Bug之一:) 另一个Bug是不可以开启System效果,否则树将无法绘制图标,我猜测可能是操作系统不支持托管的图标资源所致。
由于程序是用Visual C++7.0 写的,有些地方语法十分古怪,和标准ISO C++及微软自己的CLI C++均不一样,整个程序基本上是用指针堆出来的,大家就当伪代码着吧^_^
总结天呐,还没完啊,写完程序写文章,累死我了,再坚持一下下就好.....:)
System::Resources命名空间中还有很多用于操作资源的类,这里就不一一列出了,感兴趣的读者可以自己去参考MSDN,需要注意的是该命名空间中的所有类实际上是封装了IResourceReader/IResourceWriter接口,高手们也可以自己封装出比它更好的类:)
我在Simple1、Simple2中做了大量的注释,如果还有什么问题,可以在下面留言或者联系我,我会尽量给予解答。
本文全部内容均为caeser2本人原创,如要引用本文内容或代码或程序,请说明出处。