Menu

Strings_in_OWLNext

Strings in OWLNext

OWL and OWLNext originally only used brittle C-style string handling. To make the API safer and easier to use with modern C++, OWLNext 6.32 introduced comprehensive use of owl::tstring; an alias for std::string or std::wstring, depending on the character set build mode. These changes comprise both API changes and changes in the internal implementation of OWLNext.



API changes

Generally, functions that previously only accepted LPCTSTR arguments (or const char* arguments in the original OWL) will now also accept owl::tstring. Some function parameters of LPCTSTR type, for functions that did not allow a null-pointer, or did not properly deal with a null-pointer, have been replaced by owl::tstring.

Early on in the development of 6.32, we experimented with detecting an invalid null-pointer argument, such as in the call SetText(0), as a compilation error, but we found that it was too unreliable to be useful. The user can pass the null-pointer through a variable, or use other non-int representations of the null-pointer literal, and this is impossible to detect at compile-time. (Interestingly, prohibiting the construction of a standard string from null-pointer has since been proposed and accepted for C++23. See P2166R1.)

The policy we ended up with is to provide side-by-side overloads where a null-pointer had meaning, and to replace LPCTSTR by owl::tstring where it did not, i.e. where a null-pointer was not properly handled by the code anyway (which proved to be the case in numerous places). For these latter cases, a null-pointer will now cause a predictable run-time error in the owl::tstring constructor. This should be no worse than previous behaviour.

However, we made an exception to this policy in our handling of virtual functions. To retain backwards compatibility and not silently break existing client code, for virtual functions with LPCTSTR parameters, we have in most cases provided side-by-side overloads, instead of replacing LPCTSTR by owl::tstring, even when a null-pointer makes no sense. For example, see TCommandEnabler::SetText.


Intended breaking changes

The following API changes may cause run-time errors in legacy code, and affected client code will need to be adapted. Note that these are intended API changes, so you need to update your code.

Replacement of TApplication::SetWinMainParams (HINSTANCE, HINSTANCE hPrevInstance, LPCTSTR cmdLine, int cmdShow) [r793]
Replaced by SetWinMainParams (HINSTANCE, HINSTANCE hPrevInstance, const tstring& cmdLine, int cmdShow). Passing a null-pointer argument for the cmdLine parameter will now crash. This should ideally never occur since application command lines are never empty; a command line at least includes the program name. Resolution: Pass the correct command line.
Replacement of virtual functions TButtonTextGadget::PaintText (TDC&, TRect&, LPCTSTR) and TCheckList::PaintText (TDC&, const TRect&, LPCTSTR) [r793]
Replaced by signatures using owl::tstring instead of LPCTSTR. This change will make client code that overrides the old signatures inoperative. Resolution: Client code that overrides the old functions must be updated to use the new signatures. Tip: Use the C++11 keyword override on your overriding functions. This will detect any signature mismatches.
Replacement of virtual functions TDC::TextOut, ExtTextOut, TabbedTextOut, DrawText, DrawTextEx and GrayString [r793]
Replaced by signatures using owl::tstring instead of LPCTSTR. These changes will make client code that overrides the old signatures inoperative. Resolution: Client code that overrides the old functions must be updated to use the new signatures. Tip: Use the C++11 keyword override on your overriding functions. This will detect any signature mismatches.
Replacement of virtual functions TFile::Open, TStreamFile::Open and TDrawMenuItemProxy::CreateItem [r1122]
Replaced by signatures using owl::tstring instead of LPCTSTR. These changes will make client code that overrides the old signatures inoperative. Resolution: Client code that overrides the old functions must be updated to use the new signatures. Tip: Use the C++11 keyword override on your overriding functions. This will detect any signature mismatches.
TLvItem now has additional members [r968]
An additional member was added to TLvItem to cache the string passed to the constructor or SetText. Client code that assumes TLvItem is just an empty wrapper around LVITEM, and illegally down-cast from LVITEM to TLvItem, may now fail. Note that such down-casting is not standard compliant. Resolution: Do not down-cast an LVITEM. Instead wrap a TLvItem around it.
TDC::ExtTextOut no longer accepts a null-pointer for the string parameter
TDC::ExtTextOut previously accepted a null-pointer for the string parameter. This feature was sometimes used to draw rectangles with no text. This use is no longer supported. Instead use TDC::TextRect. This function is just a wrapper around ExtTextOut, passing in ETO_OPAQUE for the options parameter and a null-pointer for the string parameter for you.
Some functions that previously did so, no longer support non-null-terminated strings
Functions such as TDC::ExtTextOut previously had a pointer and a count as parameters, and hence supported non-null-terminated strings. Now, to pass contents of a non-null-terminated buffer, you have to create a temporary string object. For example, see [bugs:#401].


Regressions

As part of the string-related overhaul, we may have unintentionally broken the API in places. To review any outstanding regressions browse the bug tracker for tickets with the tag "Strings". The "Milestone" field of the bug tickets indicate the OWLNext version in which the problem was fixed (if closed) or will be fixed (if open/pending). If you find more regressions, let us know.



Internal implementation changes

Much of the internal string handling in OWLNext has been replaced by the use of standard strings, streams and containers. Our aim is to completely replace all C-style string handling in the OWLNext implementation. This should increase reliability, while decreasing the maintenance burden. This work is ongoing.


Unicode support

While OWL could only deal with narrow strings, OWLNext has added support for wide strings. In the UNICODE character set build mode, owl::tchar is defined as wchar_t, and owl::tstring is defined as std::wstring, encoded as Unicode UTF-16 in accordance with the traditional Unicode support in Windows (see "Working with Strings" and "Unicode in Microsoft Windows" for an introduction). Since Windows 10 version 1903 (May 2019 Update), the operating system now also supports an ANSI code page for UTF-8, allowing you to use narrow Unicode strings encoded in UTF-8 within std::string. Since owl::tstring is an alias for std::string in the ANSI character set build mode, this means you can now use the good old ANSI build mode for your OWLNext applications and still have full support for Unicode. See "Use the UTF-8 code page".

For more information about using Unicode in your OWLNext applications, see OWLNext and Unicode.

Converting between narrow and wide strings

The OWLNext string conversion macros can be used to convert between narrow and wide strings in a simple way. However, beware of [bugs:#486] — these macros may fail in corner cases for character sets outside ASCII.

auto ConvertToNarrow(const std::wstring& s) -> std::string
{
  USES_CONVERSION;
  return {W2A(s.c_str())};
}

auto ConvertToWide(const std::string& s) -> std::wstring
{
  USES_CONVERSION;
  return {A2W(s.c_str())};
}

Also, owl::to_tstring can be used for simple conversion from either narrow or wide string to owl::tstring. The latter is an alias for std::string or std::wstring, depending on the build mode.

auto Concat(const std::string& narrow, const std::wstring& wide) -> owl::tstring
{
  return to_tstring(narrow) + to_tstring(wide);
}

To go the other way, from owl::tstring to either narrow or wide string, the conditional conversion macros are helpful. These have a leading underscore, and are defined to do a conversion or not, depending on the character set build mode. The ones with the suffix "_A" will only do a conversion if in ANSI build mode. The ones without the suffix will only do a conversion if in UNICODE build mode. Otherwise, they do nothing.

auto ConvertToNarrow(const owl::tstring& s) -> std::string
{
  _USES_CONVERSION;
  return {_W2A(s.c_str())};
}

auto ConvertToWide(const owl::tstring& s) -> std::wstring
{
  _USES_CONVERSION_A;
  return {_A2W_A(s.c_str())};
}

In OWLNext 8, these utility functions will be readily available within OWLNext, so that you do not have to deal with the macros. See [feature-requests:#174].


Related

Bugs: #355
Bugs: #360
Bugs: #401
Bugs: #486
Bugs: #571
Discussion: Problems with migrating to OWLNext 6.35
Discussion: [6.44] Problem with TDC::ExtTextOut
Discussion: SetTextColor doesn't change the color of TRadioButton and TCheckBox
Discussion: Some Patches against abandoned owl::string class
Discussion: TLvItem and ANSI build mode issue
Feature Requests: #127
Feature Requests: #174
Wiki: Frequently_Asked_Questions
Wiki: Knowledge_Base
Wiki: Making_an_application_Unicode-ready
Wiki: Upgrading_from_OWL
Wiki: Vidar_Hasfjord

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.