Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Changing a resourcestring at runtime

1 view
Skip to first unread message

ufo

unread,
Dec 20, 2004, 2:32:52 PM12/20/04
to
How can I change (=write to) a resourcestring at runtime. I've got the
compilerid of it. I don't want to use dll's etc.

Regards,
Felix


I choose Polesoft Lockspam to fight spam, and you?
http://www.polesoft.com/refer.html


Peter Below (TeamB)

unread,
Dec 21, 2004, 1:45:57 AM12/21/04
to
In article <41c7...@newsgroups.borland.com>, Ufo wrote:
> How can I change (=write to) a resourcestring at runtime. I've got the
> compilerid of it. I don't want to use dll's etc.

Changing resourcestrings on the fly

{: Modify a resourcestring constant
@param resstring is a pointer to a TResStringRec describing a
resourcestring
constant. If this parameter is nil the function returns false.
@param newvalue points to a zero-terminated string that should be the new
value of the resourcestring. This pointer must be guaranteed to stay
valid for the lifetime of the program! It should point to a constant
string (a literal).
@returns true if the change was successful, false if not.
@precondition newvalue <> nil
@Desc Will not work in Kylix! }
Function ChangeResourceString( resstring: PResStringRec; newvalue: Pchar
): Boolean;
Var
oldprotect: DWORD;
Begin
Result := false;
{$IFDEF LINUX}This function does not work in Kylix!{$ENDIF}
If resstring = nil Then Exit;
Assert( Assigned( newvalue ),
'ChangeResourceString: newvalue must be <> nil');

Result := VirtualProtect(
resstring,
Sizeof( resstring^ ),
PAGE_EXECUTE_READWRITE,
@oldProtect );
If Result Then Begin
resstring^.Identifier := Integer( newvalue );
VirtualProtect( resstring,
Sizeof( resstring^ ),
oldProtect,
@oldProtect );
End; { If }
End;

resourcestring
TestStr = 'Hello World';
procedure TForm1.Button1Click(Sender: TObject);
begin
label1.caption := TestStr;
ChangeResourceString( pResStringRec( @TestStr ), 'Some test text');
label2.caption := TestStr;
end;

If you are talking about STRINGTABLE resources: do you want to do that
from inside the program containing the resources, or from a separate
program? The API support for this (BeginUpdateResource, UpdateResource,
EndUpdateResource) is unfortunately platform-dependent.

--
Peter Below (TeamB)
Use the newsgroup archives :
http://www.mers.com/searchsite.html
http://www.tamaracka.com/search.htm
http://groups.google.com
http://www.prolix.be


ufo

unread,
Dec 21, 2004, 10:19:35 AM12/21/04
to
Peter thanks, this is excactely what I want to. But I've got one last
problem with it which I can't figure out after hours of debugging and
searching google (sorry, I'm a Database guy!):

In your example you write @TestStr. I understand wat's going on but in my
case I've got just the Id for example 65241 and the string to which it must
change. The problem now is how to get a pointer to the stringresource with
id 65241.

I've tried lots of function but can't figure it out till now, maybe you or
someone else has a last hand.

Regards,
Ufo

Wayne Niddery [TeamB]

unread,
Dec 21, 2004, 10:26:34 AM12/21/04
to
ufo wrote:
>
> In your example you write @TestStr. I understand wat's going on but
> in my case I've got just the Id for example 65241 and the string to
> which it must change. The problem now is how to get a pointer to the
> stringresource with id 65241.

In that case I think you need to use the Windows API functions to do this.

While it can be done, I wonder why you would want to do this instead of
using separate DLLs?

--
Wayne Niddery - Logic Fundamentals, Inc. (www.logicfundamentals.com)
RADBooks: http://www.logicfundamentals.com/RADBooks.html
Those who disdain wealth as a worthy goal for an individual or a
society seem not to realize that wealth is the only thing that can
prevent poverty. - Thomas Sowell


ufo

unread,
Dec 21, 2004, 10:32:30 AM12/21/04
to
This way I only have to provide a file (which can be edited by the user) for
the translations, no need for a dll. If he wants to translate to Danisch for
example, he only has to copy the English file, edit it and save it as
danish.x No recompiling, no DLL creation, no need for me ;-) I know it can
be done, more translators are working this way but I just can't find the
function to get this pointer to the stringresource in memory based on the
id. The rest of the train is ready, getting the resourceid's after a build,
acting when these have changed (they may change from compile to compile etc
etc). Just this last step...

Peter Below (TeamB)

unread,
Dec 21, 2004, 3:23:47 PM12/21/04
to
In article <41c8...@newsgroups.borland.com>, Ufo wrote:
> This way I only have to provide a file (which can be edited by the user) for
> the translations, no need for a dll. If he wants to translate to Danisch for
> example, he only has to copy the English file, edit it and save it as
> danish.x No recompiling, no DLL creation, no need for me ;-) I know it can
> be done, more translators are working this way but I just can't find the
> function to get this pointer to the stringresource in memory based on the
> id. The rest of the train is ready, getting the resourceid's after a build,
> acting when these have changed (they may change from compile to compile etc
> etc). Just this last step...

How about a totally different approach to the problem? At program start (in a
unit initialization section) hook the LoadStringA API function, that is what
the VCL uses to get the strings out of STRINGTABLE resources. Your replacement
could look for an external file with the replacement strings, if found it is
loaded on first call and the function returns the string from that source, if
not found the hook is undone and the API function is called instead.

You can find some tools for hooking API functions on Mathias Rauens site
http://www.madshi.net.

Peter Below (TeamB)

unread,
Dec 21, 2004, 3:23:47 PM12/21/04
to
In article <41c83f0a$1...@newsgroups.borland.com>, Ufo wrote:
> Peter thanks, this is excactely what I want to. But I've got one last
> problem with it which I can't figure out after hours of debugging and
> searching google (sorry, I'm a Database guy!):
>
> In your example you write @TestStr. I understand wat's going on but in my
> case I've got just the Id for example 65241 and the string to which it must
> change. The problem now is how to get a pointer to the stringresource with
> id 65241.

No chance, you cannot use the ResourceString changing code i gave you if you
do not know the Name of the resourcestring "variable", since that is what
identifies the record Delphi uses to interface with the actual STRINGTABLE
resource. I don't know of a generic way to change a STRINGTABLE resource of a
program from inside a running instance of that program. That is really not the
way to work with such resources, they are meant to be defined at compile-time.

That does not mean that you cannot change them, simply that it is difficult
and not really possible if the replacement for a string is longer than the
existing entry in the STRINGTABLE (since you cannot extend the table, only
rearrange its content). It also requires a detailed knowledge of the internal
structure of STRINGTABLE resources.

ufo

unread,
Dec 21, 2004, 4:27:37 PM12/21/04
to
> No chance, you cannot use the ResourceString changing code i gave you if
> you
> do not know the Name of the resourcestring "variable", since that is what
> identifies the record Delphi uses to interface with the actual STRINGTABLE
> resource.

But I know about the resourcestring as I have the identifier from the
programm.dcr for example:

#define FUMemberEdit_SaveAgenda 64813 (='some text')

I even can get the text based on the identifier with loadStr(64813). I just
need to know the adres with something like FindResource to get this pointer
I can feed do ChangeResourceString, but my knowledge for the winapi etc is
very poor.

> That does not mean that you cannot change them, simply that it is
> difficult
> and not really possible if the replacement for a string is longer than the
> existing entry in the STRINGTABLE (since you cannot extend the table, only
> rearrange its content).

But why should this be a problem here as it doesn't seem to be a problem
with the ChangeResourceString function? They are both changing the same
stuf!?

Your idea in the other post about loading at initializing could have been a
good one if it wasn't so that the specs say that these things must change
'On the fly' (it's for a user terminal where French/English/German/Dutch
people are hanging around!)

Peter Below (TeamB)

unread,
Dec 22, 2004, 2:08:42 PM12/22/04
to
In article <41c89553$1...@newsgroups.borland.com>, Ufo wrote:
> > No chance, you cannot use the ResourceString changing code i gave you if
> > you
> > do not know the Name of the resourcestring "variable", since that is what
> > identifies the record Delphi uses to interface with the actual STRINGTABLE
> > resource.
>
> But I know about the resourcestring as I have the identifier from the
> programm.dcr for example:

You know the entry in the STRINGTABLE resource behind the programs
Resourcestrings, but that is not the same as the named Resourcestring you are
using inside your code to refer to the stringtable entry.

> But why should this be a problem here as it doesn't seem to be a problem
> with the ChangeResourceString function? They are both changing the same
> stuf!?

No, they are not. A Delphi Resourcestring is represented inside the program as
a record of type TResStringRec. This is defined in the System unit as

TResStringRec = packed record
Module: ^Cardinal;
Identifier: Integer;
end;

The compiler builds a list of these things, the first field is set at run-time
to refer to the module the resource is in, the second is set to the numeric ID
of the string as used in the STRINGTABLE resource. When you use a
Resourcestring in code the compiler creates a call to the LoadResString
function in the System unit.

function LoadResString(ResStringRec: PResStringRec): string;
var
Buffer: array [0..4095] of char;
begin
if ResStringRec = nil then Exit;
if ResStringRec.Identifier < 64*1024 then
SetString(Result, Buffer,
LoadString(FindResourceHInstance(ResStringRec.Module^),
ResStringRec.Identifier, Buffer, SizeOf(Buffer)))
else
Result := PChar(ResStringRec.Identifier);
end;

As you can see there are two ways this function can operate. If the Identifier
is a number < 64K it is assumed to be a resource ID and LoadString is called
to fetch the string from the resource. If Identifier is > 64K it is assumed to
be a pointer to a zero-terminated string, which is returned directly. The
ChangeResourceString function i posted makes use of this feature by replacing
the Identifier with a PChar pointer (which needs to stay valid for the runtime
of the program, of course).

What you would need to be able to use the function is a way to find the
address of a TResStringRec given only the value of the Identifier field it
holds. As far as i know there is no way to accomplish that.

> Your idea in the other post about loading at initializing could have been a
> good one if it wasn't so that the specs say that these things must change
> 'On the fly' (it's for a user terminal where French/English/German/Dutch
> people are hanging around!)

Then language DLLs are your only choice if you do not want to fall back on
3rd-party solutions. The richedit sample project that comes with Delphi shows
how to switch language DLLs at run-time.

0 new messages