October 09, 2024, 05:05:50 PM

News:

IWBasic runs in Windows 11!


String Resources

Started by Mike Stefanik, March 04, 2006, 03:57:58 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Mike Stefanik

March 04, 2006, 03:57:58 AM Last Edit: March 04, 2006, 04:23:31 AM by Mike Stefanik
When you're creating an application that has a lot of strings and/or you plan on localizing in the future to support multiple languages, one thing that you should consider doing is using string resources, rather than hard-coding strings in your code. String resources are simply strings added to the project resource file, identified by a unique number.

For example, if you have a project named TestProgram then the resource file will be named TestProgram.rc. If it doesn't exist, you can create it by hand. Currently the IDE doesn't support the creation of string resources, so this is something that you'll need to do manually for the time being. The format of the string table is really simple. Let's say that you're creating a new TestProgram.rc file:


STRINGTABLE DISCARDABLE
BEGIN
    1 "Unable to load the fizzleworg in the snoopfrazzle"
    2 "The whirleyghig is not available now, please try again later"
    3 "The zoppledorp has been created successfully"
END


That's all there is to it. The number is the value that you'll identify the string as, and the string is what will be returned. You can embed newlines, etc. in the string and the maximum length is 4,096 characters per string. You can have a total of 65536 (0-65535) string resources. Internally, string resources are stored in blocks of 16 strings, with each string prefixed with its length and stored in Unicode (UTF-16) format. You don't specifically need to have strings numbered sequentially, but it's generally a bit more efficient if you do.

Now that you have your strings defined in your resource file, you need a way to access them. The Windows API provides a LoadString function, however one drawback is that you have to provide the maximum length of the string buffer -- and there's no easy way to pre-determine the length of a resource string. So, you have to use the lower level resource functions to access the string block directly.  To make things easy, here's an Aurora function that will return the string value, or an empty string if it doesn't exist:


// LoadString
// Load a string resource from the specified module; if no
// module handle is provided, it will load the string from
// the current module. If no string can be found with the
// specified ID, this function will return an empty string.
//
// To use this function, include it in your project and then
// add the following line to the appropriate source or
// include file:
//
// declare extern LoadString(unsigned int nStringId, opt unsigned int hModule = 0), heap;

// Constants
#define RT_STRING 6

// Windows API Functions
declare import,FindResourceA(unsigned int hModule, unsigned int uID, unsigned int nType), unsigned int;
declare import,GetModuleHandleA(pointer lpModuleName), unsigned int;
declare import,LockResource(unsigned int hResData), pointer;
declare import,LoadResource(unsigned int hModule, unsigned int hResInfo), unsigned int;
declare import,LoadStringA(unsigned int hModule, unsigned int uID, pointer lpBuffer, int nBufferMax), int;
declare import,RtlZeroMemory(pointer lpBuffer, unsigned int nLength);

global sub LoadString(unsigned int nStringId, unsigned int hModule), heap
{
unsigned int hResInfo = 0;
unsigned int hResData = 0;
unsigned int nStringBlock = 0;
unsigned int nStringIndex = 0;
pointer pBuffer = null;
int nResult = 0;
int nLength = 0;

// String resources are stored in blocks of 16, with the first
// block starting at number 1
nStringBlock = (nStringId / 16) + 1;
nStringIndex = (nStringId % 16);

// Get the handle to the current module if one isn't specified
if (hModule = 0)
hModule = GetModuleHandleA(null);

if (hModule != 0)
{
// Find the string block in the module resource table
hResInfo = FindResourceA(hModule, nStringBlock, RT_STRING);

if (hResInfo != 0)
hResData = LoadResource(hModule, hResInfo);

// If the string table was found, then lock the resource
// and begin walking through the strings; the strings are
// prefixed by the length, and in Unicode (UTF-16) format
if (hResData != 0)
{
pointer lpData;
lpData = LockResource(hResData);

if (lpData != null)
{
for (unsigned int nIndex = 0; nIndex <= nStringIndex; nIndex++)
{
nLength = *(unsigned word)lpData;
lpData += (nLength + 1) * len(word);
}
}
}
}

// Allocate memory for the string and zero it out
pBuffer = AllocHeap(nLength + 1);
RtlZeroMemory(pBuffer, nLength + 1);

// Load the string from the string table
if (nLength > 0)
LoadStringA(hModule, nStringId, pBuffer, nLength + 1);

return *(string)pBuffer;
}


Then, in your program you can declare it like this:


declare extern LoadString(unsigned int nStringId, opt unsigned int hModule = 0), heap;


Typically, the string resources are assigned constants in your program, prefixed with IDS_. For example, using the string table that we created above, we could define them as:


#define IDS_NOLOAD         1
#define IDS_UNAVAILABLE    2
#define IDS_CREATED        3


Then, in your program, you could use LoadString in conjunction with MessageBox:


if (fooBar.CreateZoppleDorp())
{
    MessageBox(this, LoadString(IDS_CREATED), g_strAppTitle);
}


The use of string resources makes it a lot easier to localize (translate) the messages your program generates, it keeps everything in one place and even makes it easier to do things like run a spell checker to make sure there's no typos.
Mike Stefanik
www.catalyst.com
Catalyst Development Corporation

Zen

Ohh well. i should of read this one before. Nevermind at least someone has shown how to do it. When im back though there will be a dynamic DLL class which will support easy functions for loading strings.

Lewis