Conversion of Delphi string types (template, specialization)

additional text shouldn't be visible

 

 

We all know the problem that the Delphi string types (UnicodeString or short String, AnsiString) do not necessarily fit to C++, and we have to use the method c_str() again and again. This is not only annoying, it can also lead to errors. In addition, we often want to have the content of a string in a different data type, or vice versa convert it into a Delphi string, for example to display it in an edit field of a form.

In addition, thanks to Embarcadero's decision to continue using a 16 bit character type under iOS and Android, instead of the system-specific wchar_t type, which is a 32 bit data type in the unixoid operating systems, this is not necessarily understandable. So it can happen that in a C++ program under Android we suddenly have it with 3 not necessarily compatible datatypes. In the business part of your application you should only have the choice between the national character set (8 bit, char, string) or the international character set (wchar_t, wstring). Furthermore, the indices for the Delphi string types are based on the desktop operating systems 1, but on the mobile operating systems they are based on C and C++ oriented 0. This can also lead to problems and necessary adjustments. All this would be simply impossible under C++, since an industry standard protects the developer's investment.

I would like to create a header file for further use in this blog, and this problem with the help of C++- templates and the specialization of these for special data types.


Maybe this one in front. I was once, so not only fairy tales begin. Often we as business consultants also start with such sentences. I was once at a customer's for an in-house training. Here several developers have been working on a program from the energy sector for several years. During the training, they also discussed the fact that for the Embarcadero string types, the constructors for the standard C++ data types are missing, or the conversion operators for the reverse direction are not implemented. Unfortunately, the operators in particular cannot be added via argument dependent name lookup (ADL). In this case, the powerful C++ compiler could solve this problem for us, and that without an error. Now this is certainly not interesting for a Delphi programmer, he might not even understand why I criticize this at all. And this might also explain why it is missing. Now one of the developers ran a grep- search over the current source code, and over the years 2.5 million calls of the method c_str() had accumulated in the source code. Should changes become necessary now, for example when migrating to a unixoid operating system, everyone can imagine the extent of the necessary work.

I would like to solve this problem for the future examples in this blog with the templates __ToVCL and __FromVCL. Again I use the predefined conditions for VCL or FMX, because only when using one of these frameworks the use of Delphi types seems to make sense. After that follow the two header files for Delphi string types <system.hpp> and C++ string types <string>.

For most data types you don't need international characters, so I use only a national variant for the implementation of the template and use the automatic conversion from UnicodeString to AnsiString in the Delphi class library. For the necessary type conversion I will use the template boost::lexical_cast, which can convert a string into any C++ type. This also works for data types defined by yourself, if you have implemented the stream operators.



#ifndef MyDelphiHelperH
#define MyDelphiHelperH

#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
   #error A framework variant must be selected for this application
#endif

#include <system.hpp>
#include <string>

#include <sstream>
#include <boost/lexical_cast.hpp>

template <typename ty>
inline String  __ToVCL(ty const& val) {
   std::ostringstream os;
   os << val;
   return os.str().c_str();
   }

template <typename ty>
inline ty __FromVCL(String const& text) {
   return boost::lexical_cast<ty>(AnsiString(text).c_str());
   }


#endif

Since I always deal with Unicode strings on the surface of the Delphi frameworks, I have implemented this only for this type. But an extension to the Delphi type Ansistring should be very easy for everyone. I am not interested in a complete and working library, but in explaining techniques, problems and solutions. 

Now the effort to carry the boost library on a mobile system like Android can be higher than the benefit. Or a compiler does not offer me the support of boost. Maybe there is an easy way, even boost must have found an approach. Therefore I change the above example as follows, and use the conditional translation again, if the target environment is Android.


#ifndef MyDelphiHelperH
#define MyDelphiHelperH

#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
   #error A framework variant must be selected for this application
#endif

#include <system.hpp>
#include <string>

#include <sstream>

#ifndef __ANDROID__
   #include <boost/lexical_cast.hpp>
#endif

template <typename ty>
inline String  __ToVCL(ty const& val) {
   std::ostringstream os;
   os << val;
   return os.str().c_str();
   }

template <typename ty>
inline ty __FromVCL(String const& text) {
   #ifndef __ANDROID__
      return boost::lexical_cast<ty>(AnsiString(text).c_str());
   #else
      std::istringstream ins(AnsiString(text).c_str());
      ty val;
      ins >> val;
      return val;
   #endif
   }

#endif

Now this conversion is not effective, can also lead to errors if string types are used themselves. For these types I want to implement specializations. Normally the compiler creates a copy of the templates for each combination of type parameters and replaces the type parameters with the concrete types. This results in very robust and directly linked code that is faster than runtime decisions. Templates can also be specialized (define a different implementation for selected type parameters), which I want to take advantage of here.

There is also an inconsistency in the Delphi string types, because the indices here follow the standard behaviour on mobile operating systems and start with 0. Furthermore, the conversion from the 16 bit character set to the system type wchar_t, which uses 32 bit for all unixoidal ones, must be done here.


#ifndef MyDelphiHelperH
#define MyDelphiHelperH

#if !defined BUILD_WITH_VCL && !defined BUILD_WITH_FMX
   #error A framework variant must be selected for this application
#endif

#include <system.hpp>
#include <string>

#include <sstream>

#ifndef __ANDROID__
   #include <boost/lexical_cast.hpp>
#endif

template <typename ty>
inline String  __ToVCL(ty const& val) {
   std::ostringstream os;
   os << val;
   return os.str().c_str();
   }

template <>
inline String  __ToVCL<std::string>(std::string const& val) {
   return val.c_str();
   }

template <>
inline String  __ToVCL<std::wstring>(std::wstring const& val) {
   return val.c_str();
   }

template <typename ty>
inline ty __FromVCL(String const& text) {
   #ifndef __ANDROID__
      return boost::lexical_cast<ty>(AnsiString(text).c_str());
   #else
      std::istringstream ins(AnsiString(text).c_str());
      ty val;
      ins >> val;
      return val;
   #endif
   }

template <>
inline std::wstring __FromVCL<std::wstring>(String const& text) {
   #ifdef __ANDROID__
      // String indexes are 1-based in desktop platforms and 0-based in mobile platforms.
      std::wstring strTemp2 = L"";
      for(int i = 0; i < text.Length(); ++i) strTemp2.push_back(static_cast(text[i]));
      return strTemp2. c_str();
   #else
      return text.c_str();
   #endif
   }

template <>
inline std::string __FromVCL<std::string>(String const& text) {
   return AnsiString(text).c_str();
   }

#endif
  

Now there may be problems with zero-terminated strings, since they are stored as const char*, unlike the international character fields with wchar_t. So I will implement a method with the same name __ToVCL, but it is not a template. For later use this is not important. And this approach frees me to keep the variants apart so that I can concentrate on content problems.


inline String  __ToVCL(const char* val) {
   return val;
   }

With this I implemented the differences between the mobile operating systems and possible conversions from national to international character sets at a central location. Should there be adjustments, there will be no impact on the parts of my business application. I have the decisions in my hands again. This gives me the opportunity to create a test application that is the same for FMX and the VCL and also works with the mobile operating system Android as target platform.


#include "MainFormVCL.h"
#include "MyDelphiHelper.h"

#include <exception>
using namespace std;

void __fastcall TForm1::Button1Click(TObject *Sender) {
   try {
      int iWert   = __FromVCL<int>(Edit1->Text);
      iWert *= 5;
      Edit2->Text = __ToVCL(iWert);
      }
   catch(exception& ex) {
      ShowMessage(__ToVCL(ex.what()));
      }
   return;
   }

Now I already hear the criticism that I have a lot of function calls here, moreover this source code creates unnecessary copies and therefore cannot be effective. But this is not true for modern C++ compilers anymore. I will explain this in a later blog post and also show you a concrete example. And the function calls just give me as a responsible programmer the possibility to make decisions, resolve inconsistencies and react to future changes. And this not distributed over the complete source code of my application, but at a central location. Should I have to rework them, it is much easier to search for __FromVCL or ::ToVCL in the source code.

 

Kommentar verfassen

* These fields are required

Kommentare

Keine Kommentare

About the Author

 


Über den Autor

Volker Hillmann

CEO, Developer, Germany, Berlin
Volker Hillmann was born in 1965 and holds a degree in mathematics with a focus on databases and data security. He has been programming in C since 1988. After first touches on a Unix machine with Turbo C 1.5 on PCs. That's how he got to know C++ in 1991 and since then he is programming in different areas with C++. After some experience in the insurance and banking industry, he founded adecc Systemhaus GmbH in 2000, of which he is still the CEO. He is also MVP at embarcadero Germany.

More posts by this author