Jack Tackett, Jr. is a software engineer with Wandel and Goltermann Technologies, Inc. in Research Triangle Park, NC, and a principle with Tristar Systems. Mr. Tackett holds a BSCS from UNC-Asheville. He can be reached on CompuServe at 70312,132, or on the Internet at 70312.132@compuserve.com.
It is naive to assume the entire world communicates in one language, such as English. Today's software has potential application in a wide variety of cultures, something partly accounted for in the design of MS-Windows. While Microsoft makes Windows available in many different languages, it provides for language-specific behavior via the Control Panel, and via values written to the WIN.INI initialization file, located in the \windows subdirectory. The ability to modify this file (a text file) helps programmers develop internationally aware applications.
This article provides a brief outline of the international [intl] section of WIN.INI, and the functions provided by the Windows API. The code disk contains a sample Microsoft Foundation Class (MFC) application which queries and edits the WIN.INI file and responds to changes to the file made by other applications. The functions that actually query the .INI file are SDK functions that any Windows development system can use.
The WIN.INI File
Windows reads and understands two initialization files specific to the system, SYSTEM.INI and WIN.INI. Windows can also use .INI files specific to an application, but in this article I deal with WIN.INI. All .INI files are broken into named sections identified by brackets, e.g.:
[section name]then within each section items are defined via a key and a value, e.g.:
key = valueThe various keys and most of their values are listed in Table 1. The following example from the WIN.INI file shows the character used to set off the thousands digit for numbers having more than three digits;
[intl] sThousands = .This entry will cause a number such as 1234 to displayed as 1.234. The user controls the keys' values in WIN.INI via the control panel and via the international icon (the control applet represented by the globe icon).The sShortDate and sLongDate keys replace the older iDate and sDate values. Table 2 illustrates the formatting character values used by these keys. By combining the various formatting characters you can create different date displays. For example, the format m/d/yy displays 3/15/94 and the format dddd, mmmmm d, yyyy displays Tuesday, March 15, 1994.
While Microsoft recommends that your application be flexible with regards to different languages, they do recommend storing the section and key names in English (ENU). Thus, all applications, regardless of their primary language, can access the same information using the above keys and their stored values.
Accessing .INI Files
The Windows API provides the developer with two functions, GetProfileString and GetProfileInt, to query the WIN.INI file for information. You use GetProfileString for keys such as sCountry and sLanguage in fact, for any key with a leading 's'. You use GetProfileInt for keys that start with 'i'. These entries contain numeric values. Both functions take a default value which is returned in case the desired section and key cannot be found. Neither function is case-sensitive, so both work with mixed upper- and lower-case strings.
The prototype for GetProfileString is shown below:
int GetProfileString( LPSTR SectionName, LPSTR KeyName, LPSTR Default, LPSTR ReturnString, int strsize);The SectionName parameter is a string containing the section name in the WIN.INI file. The SectionName for internationalization is "intl." Note that it does not contain the enclosing brackets when used as a parameter. The KeyName parameter is a string containing the key whose value we wish to retrieve. The Default parameter is a string which GetProfileString will return if the desired key is not found in the file. ReturnString is a buffer in which KeyName's corresponding value will be returned, and strsize is the size of this buffer in bytes. The function returns the number of characters copied to ReturnString. The following code snippet returns the string value for the sShortDate key in the intl section.
#define strsize 32 CString SectionName("intl";) CString KeyName("sShortDate"); FAR char sShortDate[ strsize ]; CString Default("Not Found"); result = ::GetProfileString( SectionName, KeyName, Default, sShortDate, strsize);Note the use of the global scope operator (::) in the SDK API function call. This operator instructs the C++ compiler to use the SDK function call rather than a local version of the function ( e.g. CWinApp's GetProfileString member function). While the code snippets are written with MFC in mind, any Windows development system supporting calls to SDK functions can be used.Each Windows installation is unique and does not require every key in the intl section to have a corresponding value. So it is important to provide an appropriate default value when calling either function. To see what keys are defined you can set the KeyName parameter to NULL and the function then returns all the keynames found in the indicated section, in ReturnString. Each key name returned is null-terminated and the final key name is terminated with two nulls. If the supplied buffer is too small then Windows returns as many key names as will fit the last one returned is truncated and terminated with two null characters.
The GetProfileInt function is a little less cumbersome to use since it only deals with an integer value rather than strings. The prototype for GetProfileInt is:
WORD GetProfileInt( LPSTR SectionName, LPSTR KeyName, int DefaultValue );The function returns the numeric equivalent of value, as found in
[SectionName] KeyName = valueThe function returns DefaultValue if the section/key combination is not found. The following is a code snippet that returns the time format deemed in the intl section:
CString SectionName("intl"); CString KeyName("iTime"); int Default = -1; WORD result; result = ::GetProfileInt( SectionName, KeyName, Default); switch( result ) { case 0 : AfxMessageBox("Use a 12 hour clock "); break; case 1: AfxMessageBox("Use a 24 hour clock "); break; case Default: default: AfxMessageBox(" iTime key not found! "); // set a default value for clock format, break; } // end switchRun-Time Changes to WIN.INI
The developer should be aware that the user can, via the Control Panel, change WIN.INI settings while an application is running. Fortunately, Windows broadcasts a message, WM_WININICHANGE, indicating a change to the file. The LPARAM parameter contains a pointer to a string indicating the section modified, or a null pointer if an application changed multiple sections. Thus, internationally-aware applications should check LPARAM for "intl" and then scan for the keys used, checking to see which have been modified. One tip Microsoft offers is to beware when the sCurrency key is changed since the application may not want to update the symbols used for items already entered by the user.
While the user can change WIN.INI via the Control Panel, applications can also change settings with the Windows API WriteProfileString function. (The Windows API does not provide a function to write integer values to WIN.INI so you must format a string with sprintf or wsprintf and then write the resultant string to the file.) The prototype for the function is:
BOOL WriteProfileString( LPSTR Section, LPSTR Key, LPSTR Value);This function sets Key to the passed Value in the Section section of WIN.INI and returns TRUE if successful or FALSE if an error occurs. If your application modifies any section of WIN.INI then you should broadcast a message telling all other tasks the file has changed. The following code snippet illustrates this technique.
char Value[16]; CString Section; CString Key; BOOL retv; int iTimeValue = 1; // use the 24 hour clock sprintf(Value,"%d", iTimeValue); retv = ::WriteProfileString( Section, Key, Value); if ( retv ) { ::SendMessage( HWND_BROADCAST, // -1, send to all // windows WM_WININICHANGE, // message 0L, // not required Section); // section updated }If the application modifies several sections of the WIN.INI file then set the last parameter to SendMessage to a null.
Finding More Detailed Information
If you are preparing your application for foreign markets, go to your stash of SDK manuals and pull out the Programmer's Reference Volume 1: Overview, and mark chapter 18 "International Applications."[1] This chapter covers the topic of creating an internationally aware application (except for newer topics such as using the Registry). Chapter 18 explains each of the items found in the [intl] section of WIN.INI. The chapter also provides some tips and tricks for preparing your application for foreign markets, including the use of the WIN.INI file.
Conclusion
Writing internationally aware Windows application is not a difficult task, especially when following the tips offered in the Programmer's Reference. By manipulating the WIN.INI file, developers can greatly increase the usefulness of their applications to users around the world.
Reference
Programmer's Reference Volume 1: Overview, (Microsoft corporation, Copyright 1987-1992), Chapter 18 "International Applications."
Table 1 Key values for the [intl] section of WIN.INI.
Table 2 Date format strings
Character(s) Value Range ------------------------------- m 1-12 mm 01-12 mmm Jan-Dec mmmm January-December d 1-31 dd 01-13 ddd Mon-Sun dddd Monday-Sunday yy 00-99 yyyy 1900-2040