-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patharticle-2.html
237 lines (193 loc) · 24.7 KB
/
article-2.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
---
article: 2
title: "How to store files inside an executable program"
summary: "Its sometimes useful to be able to distribute read-only files embedded in your programs. This article explains why and discusses how."
meta-title: "Use Delphi Pascal to store files inside an executable program | How to"
meta-desc: "An article that shows how to use Delphi Pascal to embed data files in a Windows program's resources by creating a resource file that is then compiled into the program."
link-next: "/articles/article-3"
index: true
redirect_from:
- /articles/2
---
<section id="intro">
<h2>Why do it</h2>
<p>Have you ever needed to distribute one or more critical data files with a program? Often only your program needs to access the data file(s) and they don't need to be changed by it. How do we stop users from deleting the files? One answer is to store the data file inside our executable (or in a DLL) as a custom (<var>RCDATA</var>) resource and to link the resource into our application using the <code>{$R}</code> directive.</p>
<p>This article shows how to create a resource file containing a copy of any file.</p>
</section>
<section id="method">
<h2>How it's done</h2>
<p>The resource file we're going to create has the following format:</p>
<ul>
<li>a header that introduces the file</li>
<li>a header for our <var>RCDATA</var> resource</li>
<li>the data itself - an <var>RCDATA</var> resource is simply a sequence of bytes</li>
<li>any padding required so that any following resource begins on a <var>DWORD</var> boundary</li>
</ul>
<p>It's much simpler to create a resource file that is identified by an ordinal (e.g. <samp>200</samp>) than it is to create one identified by a string (e.g. <samp>'MY_RESOURCE'</samp>), since the resource header records are a fixed size in the first case and are variable in the second case. We will only consider the first case here. We will also just copy one file into the resource – it's simple to extend this to more than one.</p>
<p>Because we're sticking with ordinal IDs the resource header can be defined as:</p>
<div class="frame">
<div class="code-pascal">
<pre class="line"><span class="linenum"> 1</span><span class="kwd">type</span></pre>
<pre
class="line"><span class="linenum"> 2</span><span class="space"> </span><span class="ident">TResHeader</span><span class="space"> </span><span class="sym">=</span><span class="space"> </span><span class="kwd">record</span></pre>
<pre
class="line"><span class="linenum"> 3</span><span class="space"> </span><span class="ident">DataSize</span><span class="sym">:</span><span class="space"> </span><span class="ident">DWORD</span><span class="sym">;</span><span class="space"> </span><span class="comment">// size of our data </span></pre>
<pre
class="line"><span class="linenum"> 4</span><span class="space"> </span><span class="ident">HeaderSize</span><span class="sym">:</span><span class="space"> </span><span class="ident">DWORD</span><span class="sym">;</span><span class="space"> </span><span class="comment">// size of this record</span></pre>
<pre
class="line"><span class="linenum"> 5</span><span class="space"> </span><span class="ident">ResType</span><span class="sym">:</span><span class="space"> </span><span class="ident">DWORD</span><span class="sym">;</span><span class="space"> </span><span class="comment">// lo word = $FFFF => ordinal</span></pre>
<pre
class="line"><span class="linenum"> 6</span><span class="space"> </span><span class="ident">ResId</span><span class="sym">:</span><span class="space"> </span><span class="ident">DWORD</span><span class="sym">;</span><span class="space"> </span><span class="comment">// lo word = $FFFF => ordinal</span></pre>
<pre
class="line"><span class="linenum"> 7</span><span class="space"> </span><span class="ident">DataVersion</span><span class="sym">:</span><span class="space"> </span><span class="ident">DWORD</span><span class="sym">;</span><span class="space"> </span><span class="comment">// *</span></pre>
<pre
class="line"><span class="linenum"> 8</span><span class="space"> </span><span class="ident">MemoryFlags</span><span class="sym">:</span><span class="space"> </span><span class="ident">WORD</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 9</span><span class="space"> </span><span class="ident">LanguageId</span><span class="sym">:</span><span class="space"> </span><span class="ident">WORD</span><span class="sym">;</span><span class="space"> </span><span class="comment">// *</span></pre>
<pre
class="line"><span class="linenum"> 10</span><span class="space"> </span><span class="ident">Version</span><span class="sym">:</span><span class="space"> </span><span class="ident">DWORD</span><span class="sym">;</span><span class="space"> </span><span class="comment">// *</span></pre>
<pre
class="line"><span class="linenum"> 11</span><span class="space"> </span><span class="ident">Characteristics</span><span class="sym">:</span><span class="space"> </span><span class="ident">DWORD</span><span class="sym">;</span><span class="space"> </span><span class="comment">// *</span></pre>
<pre
class="line"><span class="linenum"> 12</span><span class="space"> </span><span class="kwd">end</span><span class="sym">;</span></pre>
</div>
<div class="caption">Listing 1</div>
</div>
<p>We will not be using the fields marked *.</p>
<p>Here's the code that creates the resource file and copies in a given file:</p>
<div class="frame">
<div class="code-pascal">
<pre
class="line"><span class="linenum"> 1</span><span class="kwd">procedure</span><span class="space"> </span><span class="ident">CreateResourceFile</span><span class="sym">(</span></pre>
<pre
class="line"><span class="linenum"> 2</span><span class="space"> </span><span class="ident">DataFile</span><span class="sym">,</span><span class="space"> </span><span class="ident">ResFile</span><span class="sym">:</span><span class="space"> </span><span class="kwd">string</span><span class="sym">;</span><span class="space"> </span><span class="comment">// file names</span></pre>
<pre
class="line"><span class="linenum"> 3</span><span class="space"> </span><span class="ident">ResID</span><span class="sym">:</span><span class="space"> </span><span class="ident">Integer</span><span class="space"> </span><span class="comment">// id of resource</span></pre>
<pre class="line"><span class="linenum"> 4</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 5</span><span class="kwd">var</span></pre>
<pre
class="line"><span class="linenum"> 6</span><span class="space"> </span><span class="ident">FS</span><span class="sym">,</span><span class="space"> </span><span class="ident">RS</span><span class="sym">:</span><span class="space"> </span><span class="ident">TFileStream</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 7</span><span class="space"> </span><span class="ident">FileHeader</span><span class="sym">,</span><span class="space"> </span><span class="ident">ResHeader</span><span class="sym">:</span><span class="space"> </span><span class="ident">TResHeader</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 8</span><span class="space"> </span><span class="ident">Padding</span><span class="sym">:</span><span class="space"> </span><span class="kwd">array</span><span class="sym">[</span><span class="num">0</span><span class="sym">..</span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">DWORD</span><span class="sym">)</span><span class="sym">-</span><span class="num">1</span><span class="sym">]</span><span class="space"> </span><span class="kwd">of</span><span class="space"> </span><span class="ident">Byte</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 9</span><span class="kwd">begin</span></pre>
<pre class="line"><span class="linenum"> 10</span></pre>
<pre
class="line"><span class="linenum"> 11</span><span class="space"> </span><span class="comment">{ Open input file and create resource file }</span></pre>
<pre
class="line"><span class="linenum"> 12</span><span class="space"> </span><span class="ident">FS</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="ident">TFileStream</span><span class="sym">.</span><span class="ident">Create</span><span class="sym">(</span><span class="space"> </span><span class="comment">// to read data file</span></pre>
<pre
class="line"><span class="linenum"> 13</span><span class="space"> </span><span class="ident">DataFile</span><span class="sym">,</span><span class="space"> </span><span class="ident">fmOpenRead</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 14</span><span class="space"> </span><span class="ident">RS</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="ident">TFileStream</span><span class="sym">.</span><span class="ident">Create</span><span class="sym">(</span><span class="space"> </span><span class="comment">// to write res file</span></pre>
<pre
class="line"><span class="linenum"> 15</span><span class="space"> </span><span class="ident">ResFile</span><span class="sym">,</span><span class="space"> </span><span class="ident">fmCreate</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 16</span></pre>
<pre
class="line"><span class="linenum"> 17</span><span class="space"> </span><span class="comment">{ Create res file header - all zeros except</span></pre>
<pre
class="line"><span class="linenum"> 18</span><span class="comment"> for HeaderSize, ResType and ResID }</span></pre>
<pre
class="line"><span class="linenum"> 19</span><span class="space"> </span><span class="ident">FillChar</span><span class="sym">(</span><span class="ident">FileHeader</span><span class="sym">,</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">FileHeader</span><span class="sym">)</span><span class="sym">,</span><span class="space"> </span><span class="str">#0</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 20</span><span class="space"> </span><span class="ident">FileHeader</span><span class="sym">.</span><span class="ident">HeaderSize</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">FileHeader</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 21</span><span class="space"> </span><span class="ident">FileHeader</span><span class="sym">.</span><span class="ident">ResId</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="hex">$0000FFFF</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 22</span><span class="space"> </span><span class="ident">FileHeader</span><span class="sym">.</span><span class="ident">ResType</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="hex">$0000FFFF</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 23</span></pre>
<pre
class="line"><span class="linenum"> 24</span><span class="space"> </span><span class="comment">{ Create data header for RC_DATA file </span></pre>
<pre
class="line"><span class="linenum"> 25</span><span class="comment"> NOTE: to create more than one resource just</span></pre>
<pre
class="line"><span class="linenum"> 26</span><span class="comment"> repeat the following process, using a different</span></pre>
<pre class="line"><span class="linenum"> 27</span><span class="comment"> resource ID each time }</span></pre>
<pre
class="line"><span class="linenum"> 28</span><span class="space"> </span><span class="ident">FillChar</span><span class="sym">(</span><span class="ident">ResHeader</span><span class="sym">,</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">ResHeader</span><span class="sym">)</span><span class="sym">,</span><span class="space"> </span><span class="str">#0</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 29</span><span class="space"> </span><span class="ident">ResHeader</span><span class="sym">.</span><span class="ident">HeaderSize</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">ResHeader</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 30</span><span class="space"> </span><span class="comment">// resource id - FFFF says "not a string!"</span></pre>
<pre
class="line"><span class="linenum"> 31</span><span class="space"> </span><span class="ident">ResHeader</span><span class="sym">.</span><span class="ident">ResId</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="hex">$0000FFFF</span><span class="space"> </span><span class="kwd">or</span><span class="space"> </span><span class="sym">(</span><span class="ident">ResId</span><span class="space"> </span><span class="kwd">shl</span><span class="space"> </span><span class="num">16</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 32</span><span class="space"> </span><span class="comment">// resource type - RT_RCDATA (from Windows unit)</span></pre>
<pre
class="line"><span class="linenum"> 33</span><span class="space"> </span><span class="ident">ResHeader</span><span class="sym">.</span><span class="ident">ResType</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="hex">$0000FFFF</span></pre>
<pre
class="line"><span class="linenum"> 34</span><span class="space"> </span><span class="kwd">or</span><span class="space"> </span><span class="sym">(</span><span class="ident">WORD</span><span class="sym">(</span><span class="ident">RT_RCDATA</span><span class="sym">)</span><span class="space"> </span><span class="kwd">shl</span><span class="space"> </span><span class="num">16</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 35</span><span class="space"> </span><span class="comment">// data file size is size of file</span></pre>
<pre
class="line"><span class="linenum"> 36</span><span class="space"> </span><span class="ident">ResHeader</span><span class="sym">.</span><span class="ident">DataSize</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="ident">FS</span><span class="sym">.</span><span class="ident">Size</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 37</span><span class="space"> </span><span class="comment">// set required memory flags </span></pre>
<pre
class="line"><span class="linenum"> 38</span><span class="space"> </span><span class="ident">ResHeader</span><span class="sym">.</span><span class="ident">MemoryFlags</span><span class="space"> </span><span class="sym">:=</span><span class="space"> </span><span class="hex">$0030</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 39</span></pre>
<pre
class="line"><span class="linenum"> 40</span><span class="space"> </span><span class="comment">{ Write the headers to the resource file }</span></pre>
<pre
class="line"><span class="linenum"> 41</span><span class="space"> </span><span class="ident">RS</span><span class="sym">.</span><span class="ident">WriteBuffer</span><span class="sym">(</span><span class="ident">FileHeader</span><span class="sym">,</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">FileHeader</span><span class="sym">)</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 42</span><span class="space"> </span><span class="ident">RS</span><span class="sym">.</span><span class="ident">WriteBuffer</span><span class="sym">(</span><span class="ident">ResHeader</span><span class="sym">,</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">ResHeader</span><span class="sym">)</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 43</span></pre>
<pre
class="line"><span class="linenum"> 44</span><span class="space"> </span><span class="comment">{ Copy the file into the resource }</span></pre>
<pre
class="line"><span class="linenum"> 45</span><span class="space"> </span><span class="ident">RS</span><span class="sym">.</span><span class="ident">CopyFrom</span><span class="sym">(</span><span class="ident">FS</span><span class="sym">,</span><span class="space"> </span><span class="ident">FS</span><span class="sym">.</span><span class="ident">Size</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 46</span></pre>
<pre
class="line"><span class="linenum"> 47</span><span class="space"> </span><span class="comment">{ Pad data out to DWORD boundary - any old </span></pre>
<pre class="line"><span class="linenum"> 48</span><span class="comment"> rubbish will do!}</span></pre>
<pre
class="line"><span class="linenum"> 49</span><span class="space"> </span><span class="kwd">if</span><span class="space"> </span><span class="ident">FS</span><span class="sym">.</span><span class="ident">Size</span><span class="space"> </span><span class="kwd">mod</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">DWORD</span><span class="sym">)</span><span class="space"> </span><span class="sym"><></span><span class="space"> </span><span class="num">0</span><span class="space"> </span><span class="kwd">then</span></pre>
<pre
class="line"><span class="linenum"> 50</span><span class="space"> </span><span class="ident">RS</span><span class="sym">.</span><span class="ident">WriteBuffer</span><span class="sym">(</span><span class="ident">Padding</span><span class="sym">,</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">DWORD</span><span class="sym">)</span><span class="space"> </span><span class="sym">-</span></pre>
<pre
class="line"><span class="linenum"> 51</span><span class="space"> </span><span class="ident">FS</span><span class="sym">.</span><span class="ident">Size</span><span class="space"> </span><span class="kwd">mod</span><span class="space"> </span><span class="ident">SizeOf</span><span class="sym">(</span><span class="ident">DWORD</span><span class="sym">)</span><span class="sym">)</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 52</span></pre>
<pre
class="line"><span class="linenum"> 53</span><span class="space"> </span><span class="comment">{ Close the files }</span></pre>
<pre
class="line"><span class="linenum"> 54</span><span class="space"> </span><span class="ident">FS</span><span class="sym">.</span><span class="ident">Free</span><span class="sym">;</span></pre>
<pre
class="line"><span class="linenum"> 55</span><span class="space"> </span><span class="ident">RS</span><span class="sym">.</span><span class="ident">Free</span><span class="sym">;</span></pre>
<pre class="line"><span class="linenum"> 56</span><span class="kwd">end</span><span class="sym">;</span></pre>
</div>
<div class="caption">Listing 2</div>
</div>
<p>The above code should be sufficient to illustrate the problem, but it is not very elegant – and the streams should be protected by <span class="kwd">try … finally</span> blocks. A better solution is to create a class that encapsulates the code. A further improvement would be to permit either strings or ordinals to be used to identify the resource.</p>
<p>On occasion you may want to write formatted data to the resource file rather than just copy a file – this is easy to do. You need to do five things:</p>
<ol class="wide">
<li>Write a place-holder header record for your resource and record its position in the stream.</li>
<li>Write the formatted data to the file (replace the code that copies the file with code that writes the data).</li>
<li>Keep a record of the size of the data you are writing.</li>
<li>Pad the data out to a <var>DWORD</var> boundary.</li>
<li>Store the length of data (excluding padding) in your header record, return to the position of the place-holder.</li>
</ol>
<p>Of course there's now the problem of getting the file information back out of the executable! This is quite a trivial process and is dealt with in <a href="./article-3">another article</a>.</p>
</section>
<section id="demo">
<h2>Demo Code</h2>
<p>A demo program to accompany this article (and the related <a href="./article-3">article</a>) can be found in the <code><a href="https://github.com/delphidabbler/article-demos">delphidabbler/article-demos</a></code> Git repository on GitHub.</p>
<p>You can view the code in the <code><a href="https://github.com/delphidabbler/article-demos/tree/master/article-02%2B03">article-02+03</a></code> sub-directory. Alternatively download a zip file containing all the available demos by going to the repository's landing page and clicking the <em>Clone or download</em> button and selecting <em>Download ZIP</em>.</p>
<p>The demo demonstrates what has been described here. It contains a pair of projects. The first is a program that embeds a supplied rich text file in a resource file. The second program includes the resource file and displays the rich text in a rich edit component.</p>
<div class="callout callout-info">
<p><span class="fa fa-code fa-x-pad-right fa-2x fa-pull-left fa-border text-muted"></span>This source code is merely a proof of concept and is intended only to illustrate this article. It is not designed for use in its current form in finished applications. The code is provided on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.</p>
<p>The demo is open source. See the demo's <a
href="https://github.com/delphidabbler/article-demos/blob/master/article-02%2B03/LICENSE.md">LICENSE.md</a> file
for licensing details.</p>
</div>
</section>
<section id="feedback">
<h2>Feedback</h2>
<p>I hope you found this article useful.</p>
<p>If you have any observations, comments, or have found any errors, there are two places you can report them.</p>
<ol class="wide">
<li>For anything to do with the article content, <em>but not the downloadable demo code</em>, please use this website's <a href="https://github.com/delphidabbler/delphidabbler.github.io/issues">Issues page</a> on GitHub. Make sure you mention that the issue relates to "article #2".</li>
<li>For bugs in the demo code see the <code>article-demo</code> project's <code><a href="https://github.com/delphidabbler/article-demos/blob/master/README.md#bug-reports">README.md</a></code> file for details of how to report them.</li>
</ol>
</section>