Application-relevant changes 2025:
Changes of C++ standard, libraries and classes
Update to C++ 20
With firmware release 2025.0, the C++ language standard used in the SDK is updated to C++ 20 to enable the usage of new features provided by the language. Since features of C++ 20 are used in the SDK’s header files, all compilation units including headers from the PLCnext Technology SDK must be recompiled using at least C++ 20.
The new features of C++ 20 which are now available with PLCnext Technology can be found in the cppreference.com for C++ 20.
The main improvements regarding Arp
code are described in the following subsections.
Simplified namespace syntax
The syntax of namespace declaration was simplified as shown in the following example.
Before C++ 20:
namespace Arp { namespace System { namespace Commons
{
// code here
}}} // end of namespace Arp::System::Commons
From C++ 20:
namespace Arp::System::Commons
{
// code here
} // end of namespace Arp::System::Commons
The new namespace syntax should be preferred in future development.
Library Arp.Base.Core
(formerly Arp.System.Core)
The entire code of the Arp.System.Core
library is re-implemented in the Arp.Base.Core
library. The purpose of this change is to provide a source-compatible migration path to ensure binary compatibility in future releases. Arp.Base.Core
implements some techniques to further separate the interface from the implementation to enable new features and easier fixes. Some inconsistencies in naming and behavior have also been fixed.
Even though the location of the library in the SDK has changed, it does not cause any adjustments of the include directives nor any type names:
- All include directives of
Arp.System.Core
files are delegated to the corresponding header file inArp.Base.Core
- All types of the
Arp.Base.Core
library are defined in namespaceArp::Base::Core
, but are imported into theArp
namespace, as was in theArp.System.Core
library.
All (non-template) classes in the Arp.Base.Core
library implement the PImpl
pattern now. This is to avoid any binary incompatibilities in future when extensions or modifications become necessary.
The Arp.System.Core
library was built as archive and linked statically, which causes some trouble with the AppDomain
singleton pattern, so that the AppDomain
instance had to be injected into any shared library. This issue is now fixed, since the Arp.Base.Core
library is built as a shared library and linked dynamically. Therefore, dependencies do not propagate to the client's link process anymore.
There are only a few minor changes regarding the usage of Arp.Base.Core
, which are mostly indicated through deprecated warnings during compilation, while the warning message gives a hint how to fix it. All deprecated operations will be removed in future (but not yet in firmware release 2025.0).
Mapping Arp::byte to std::byte
C++ 17 introduced a new datatype called std::byte
. This type might be seen as a real binary type of size 'one byte', which was lacking in C++ for the time being.
The characteristics of this datatype are:
- It's not an integral datatype.
- It's not an arithmetic datatype.
- It only supports logical bit operations.
- Initialization with integral datatypes is not possible; therefore, in namespace
Arp
two string literal operators were defined:_b
and_B
(see examples in the code block below).
PLCnext Technology firmware release 2021.6 switched to the C++ 17 standard already. At that time, the C++ compiler may have regarded the datatype byte
as ambiguous, so that our Change Notes stated:
C++ 17 introduces the datatype
std::byte
which is unfortunately not compatible with Arp::byte
. Thus, if the namespaces std
and Arp
are both active, the compilation results in an error. In this case existing C++ sources have to be adjusted, so that they explicitly use Arp::byte
(e.g. by adding using byte = Arp::byte;
).
With the 2025.0 firmware, the Arp::byte
datatype is switched from std::uint8_t
to std::byte
. Up to now it was just an alias of Arp::uint8
which maps to unsigned char
. Thus, if user code generates compile errors related to the new Arp::byte
datatype, it is necessary to just switch to Arp::uint8
to avoid them.
Another approach would be to adapt the code to the new datatype if it should represent a plain byte buffer. To get familiar with the new byte
datatype, examine the following code examples, which demonstrate the usage of it:
String literal operator for byte datatype:
byte b = 0; // distinct type => compile error!
byte b1 = 0_b;
byte b2 = 123_B;
byte b3 = 0xff_B;
byte b4 = 0xFE_b;
byte b5 = 333_B;
Zero initialization:
byte b = static_cast<byte>(0); // static_cast operator
byte b0 = (byte)0; // explicit C cast
byte b1 = byte(0); // explicit C++ cast
byte b2 = 0_b; // use of string literal operator _b
byte b3{ 0 }; // {}-Initialization with implicit cast
byte b4{ 0_b }; // {}-Initialization (initialize first field by 0,
// all others are default initialized)
byte b5{ }; // Default {}-Initialization (value is zero'ed)
byte b6[6]{ }; // Default array {}-Initialization (array is zero'ed)
byte b7[7]{ 0_b }; // Explicit array {}-Initialization with zero
byte b8[]{ 0_b, 0_b, 0_b }; // Initialization through initializer list (size==3)
byte b9[9] { 0 }; // distinct type => compile error!
Number initialization:
byte b = 123; // distinct type => compile error!
byte b = static_cast<byte>(1); // static_cast operator
byte b0 = (byte)123; // explicit C cast
byte b1 = byte(0x9A); // explicit C++ cast
byte b2 = 111_b; // use of string literal operator _b
byte b3{ 255 }; // {}-Initialization with implicit cast
byte b4{ 0xA4_b }; // {}-Initialization
byte b5[5]{ 5_b }; // Partial array Initialization through
// initializer list => {5, 0, 0, 0, 0}
byte b6[6]{ 1_b, 2_b, 3_b }; // Partial array Initialization through
// initializer list => {1, 2, 3, 0, 0}
byte b7[]{ 1_b, 2_b, 3_b }; // Initialization through
// initializer list (size==3)
byte b8[8] { 8 }; // distinct type => compile error!
Byte format operations defined by Arp:
byte b0{};
byte b1 = 1_b;
byte b2 = 43_B; // ==0x2B
byte b3 = 0xff_B;
Assert::AreEqual("00", String::Format("{}", b0));
Assert::AreEqual("01", String::Format("{}", b1));
Assert::AreEqual("2b", String::Format("{}", b2));
Assert::AreEqual("ff", String::Format("{}", b3));
Bit operations:
byte b11111110 = 254_b;
byte b00001111 = 1_b | 2_b | 4_b | 8_b; // bitwise | operator
byte b00001110 = b00001111 & b11111110; // bitwise & operator
byte b01111111 = static_cast<byte>(~0x80_b); // bitwise ~ operator
Assert::AreEqual(0x0F_b, b00001111, "Bitwise | operator.");
Assert::AreEqual(0x0E_b, b00001110, "Bitwise & operator.");
Assert::AreEqual(0x7F_b, b01111111, "Bitwise ~ operator.");
Shift operation:
constexpr byte b1 = 0x01_b;
uint16 u16 = uint16(b1 << 7);
static_assert(u16 == 128, "Shift operation of byte in range.");
If shift operation fails:
byte b1 = 0x01_b;
uint16 u16 = uint16(b1 << 8); // fails, correct code would be:
uint16 u16 = uint16(b1) << 8 // ok!
Assert::AreEqual(u16, 256, "Shift operation of byte as uint16.");
// The failing shift operation would be okay
// for any 1 byte integral data type like uint8,
// but not for std::byte,
// because std::byte is not cast implicitly to int
Converting string from and to bytes:
String actual("abc");
std::vector<byte> bytes = actual.ToBytes();
String expected(bytes);
Assert::AreEqual(expected, actual);
class AppDomain
The following operations are deprecated:
AppDomain::GetCurrent()
→ useAppDomain::GetInstance()
instead.AppDomain::Assign(AppDomain&)
→ it's not required any more.
class AppDomainSingleton
The following operation is deprecated:
AppDomainSingleton::GetInstancePtr()
→ useAppDomainSingleton::IsCreated()
to check if it was created,
and use&AppDomainSingleton::GetInstance()
to get access to the instance pointer.
The following operation changes its behavior:
AppDomainSingleton::CreateInstance(...)
→ throws an exception now if the singleton was already created.
The AppDomainSingleton
class is deprecated.
It should not be used any more. The main goal of this class was to fix the issue, that the Arp::Singleton<T>
base class has to be implemented as template, so that it only works within shared-library scope, but not within process-wide scope.
The GlobalSingleton
pattern should be used instead of the AppDomainSingleton
implementation. The following demo code might be seen as a template to be copied into custom projects, with replacing the class name by the desired custom class name.
Header file of the GlobalSingleton pattern:
© Phoenix Contact
#pragma once
#include "Arp/Base/Core/Arp.hpp"
#include <memory>
namespace Apps::Demo::Extension::Internal
{
class GlobalSingleton
{
public: // usings
using Instance = GlobalSingleton;
using InstancePtr = std::unique_ptr<Instance>;
public: // construction
// If the constructor shall get parameters
// then the CreateInstance(..) operation has to be adjusted accordingly
GlobalSingleton(void) = default;
public: // copy/move/assign/destruction
GlobalSingleton(const GlobalSingleton& arg) = delete;
GlobalSingleton(GlobalSingleton&& arg) = delete;
GlobalSingleton& operator=(const GlobalSingleton& arg) = delete;
GlobalSingleton& operator=(GlobalSingleton&& arg) = delete;
~GlobalSingleton(void) = default;
public: // static singleton operations
static Instance& CreateInstance(void);
static bool IsCreated(void);
static void DisposeInstance(void);
static Instance& GetInstance(void);
private: // static fields
static InstancePtr instancePtr;
};
///////////////////////////////////////////////////////////////////////////////
// inline methods of class GlobalSingleton
} // end of namespace Apps::Demo::Extension::Internal
Source file of GlobalSingleton pattern:
© Phoenix Contact
#include "Apps/Demo/Extension/Internal/GlobalSingleton.hpp"
#include "Arp/Base/Core/TypeName.hxx"
#include "Arp/Base/Core/Exception.hpp"
namespace Apps::Demo::Extension::Internal
{
using namespace Arp;
// initializing of static fields
GlobalSingleton::InstancePtr GlobalSingleton::instancePtr;
GlobalSingleton::Instance& GlobalSingleton::CreateInstance()
{
if (IsCreated())
{
throw Exception("Singleton instance of type '{}' was created yet!", TypeName<Instance>());
}
instancePtr = std::make_unique<Instance>();
return *instancePtr;
}
bool GlobalSingleton::IsCreated()
{
return instancePtr != nullptr;
}
GlobalSingleton::Instance& GlobalSingleton::GetInstance()
{
if (!instancePtr)
{
throw Exception("Singleton instance of type '{}' was not created yet!", TypeName<Instance>());
}
return *instancePtr;
}
void GlobalSingleton::DisposeInstance()
{
instancePtr.reset();
}
} // end of namespace Apps::Demo::Extension::Internal
If the constructor of the GlobalSingleton
class requires some arguments then the CreateInstance(..)
operation must be adjusted accordingly.
Note: The GlobalSingleton
pattern does not work from within static libraries.
class Singleton<T>
The Singleton<T>
class implemented some operations only intended to be used by the AppDomain
and AppDomainSingleton
classes. Since they got refactored, these functions are not needed any more.
The following operation is deprecated:
Singleton<T>::GetInstancePtr(void)
→ useSingleton<T>::IsCreated()
to check if it was created; use&Singleton<T>::GetInstance()
to get access to the instance pointer.
The following protected operation is deprecated:
Singleton<T>::SetInstance(T* pInstance)
→ not required anymore.
The following operation changes its behavior:
Singleton<T>::CreateInstance(...)
→ throws exception now if singleton was already created.
class ArpVersion
The ArpVersion
combined multiple functionalities in a single class: a version number and additional information like state and name. To simplify the usage of ArpVersion
, this class was divided into two classes in the Arp.Base.Core
library:
Version
→ a simple version class consisting of four integer properties:Major
,Minor
,Patch
, andBuild
ArpVersion
→ providing the build version, the build name and the build state
The following operations and properties of class ArpVersion
are therefore deprecated:
ArpVersion::Current
→ useArpVersion::GetCurrent()
insteadArpVersion::GetMajor()
→ useArpVersion::GetBuildVersion().GetMajor()
insteadArpVersion::GetMinor()
→ useArpVersion::GetBuildVersion().GetMinor()
insteadArpVersion::GetPatch()
→ useArpVersion::GetBuildVersion().GetPatch()
insteadArpVersion::GetBuild()
→ useArpVersion::GetBuildVersion().GetBuildNumber()
insteadArpVersion::operator<
→ useArpVersion::GetBuildVersion()
for comparison insteadArpVersion::operator>
→ useArpVersion::GetBuildVersion()
for comparison insteadArpVersion::operator<=
→ useArpVersion::GetBuildVersion()
for comparison insteadArpVersion::operator>=
→ useArpVersion::GetBuildVersion()
for comparison instead
The following constructors and operations of the Version
class are deprecated and were only added for code compliance:
Version(Value major, Value minor, Value patch, Value build, const String& state, const String& name)
→ useArpVersion
class insteadVersion::GetName()
→ useArpVersion
class insteadVersion::GetState()
→ useArpVersion
class instead
class Exception
The following protected operations are deprecated:
Exception::Exception(String&&, const Exception::Ptr&)
→ use Exception(ExceptionTypeId, String&&, const Exception&)insteadvoid Exception::Format(int indentLevel, bool withInnerException)const
→ usevoid Exception::Format(bool withInnerException)const
insteaduint32 Exception::GetTypeCodeInternal(void)const
→ not required anymore- The return value of
Exception::GetInnerException()
changed fromException::Ptr
toconst Exception&
→ not a deprecation warning but a compile error occurs when using this operation.
class TypeName<T> and CommonTypeName<T>
The classes TypeName<T>
and CommonTypeName<T>
provided a public member variable Value
. This can cause issues regarding compatibility in the future. TypeName<T>
contained a function to generate the CommonTypeName<T>
. This violates the Single Responsibility principle.
class TypeName<T>
The following operations and properties are deprecated:
TypeName<T>::Value
→ use operationTypeName<T>::GetFullName(void)
insteadTypeName<T>::GetCommonName(void)
→ use classCommonTypeName<T>
instead
class CommonTypeName<T>
The following properties are deprecated:
CommonTypeName<T>::Value
→ use operationCommonTypeName<T>::GetFullName()
instead
class DateTime
The class DateTime
provided public member variables. This can cause compatibility issues in the future.
Currently, DateTime
only implements UTC-based timestamps. When support for local time is added in the future, DateTime
objects with an unspecified time zone will cause problems. Therefore, these are not tolerated anymore.
The following properties are deprecated:
DateTime::MinTicks
→ useDateTime::GetMinTicks()
insteadDateTime::MaxTicks
→ useDateTime::GetMaxTicks()
instead
The following operation changes its behaviour:
DateTime
constructor throws an exception now if theDateTimeKind
parameter is not set toDateTimeKind::Utc
class String
Some member functions of String
were renamed for more clarity and for conformance to the C++ standard library.
The following properties are deprecated:
String::GetBaseString()
→ useString::GetStdString()
insteadString::StartWith(..)
→ useString::StartsWith(..)
instead (C++ 20 introducedstd::basic_string<…>::starts_with()
).String::EndWith(..)
→ useString::EndsWith(..)
instead (C++ 20 introducedstd::basic_string<…>::ends_with()
).
Since C++ 20, the C++ standard defines the std::span
type to determine the size of a static array automatically by the compiler. Thus, the explicit specified parameter delimitersCount
is not required any more, and shall be omitted.
Therefore, the following operation change its signature and causes compile errors if not altered:
String::Split(const CharType delimiters[], size_t delimitersCount, bool trimTokens, bool removeEmptyTokens)
→ the new signature is:
String::Split(std::span<const CharType> delimiters, bool trimTokens, bool removeEmptyTokens)const;
The following operations change their behavior and would cause compile errors, which is caused by the upgrade of libfmt
library:
- When using
String::Format(..)
the formatting of some primitive types changes:Arp::int8
is formatted as a number now (formerly as a character)Arp::uint8
is formatted as a number now (formerly as a character)Arp::byte
is formatted as a 2-digit hex number number now (formerly as a character)
String::Format(..)
does not accept following types any more:- raw enums
→ cast the values toint
instead - enum classes which do not implement the
Arp
enum pattern
→ cast the values toint
instead - Variables of type
std::atomic<T>
→ format the raw value by callingstd::atomic<T>::load()
instead std::thread::id
→ no suggestions so far- smart pointers like
std::shared_ptr<T>
→ obtain the raw pointer by callingstd::shared_ptr<T>::get()
and cast it tovoid*
- raw enums
- The requirements on custom types to be formatted using
String::Format
have changed.- Instead of providing an
operator<<(std::ostream&, T)
function, afmt::formatter<T>
specialization has to be provided. - If the
ostream
operatoroperator<<
is defined already, this is just a matter of adding an appropriateusing
declaration on global namespace level:template<> struct fmt::formatter<T> : public fmt::ostream_formatter {};
whereT
is the fully qualified name of the custom type (see example below).
- Instead of providing an
Template specialization of String formatter:
template<> struct fmt::formatter<Arp::Base::Core::String> : public fmt::ostream_formatter {};
Library Arp.Base.Acf.Commons
As mentioned above, the public code of the Arp.System.Acf
library has moved to the new library Arp.Base.Acf.Commons
. This was caused by the fact, that the Acf
did not separate the interfaces from the implementation code, which violates the Inversion of Dependencies principle.
Even though the location of the library in the SDK has changed, it does not cause any adjustments of the include
directives nor any type names:
- All include directives of
Arp.System.Acf
files are delegated to the corresponding header file inArp.Base.Acf.Commons
- All types of the
Arp.Base.Acf.Commons
library are defined in namespaceArp::Base::Acf::Commons
, but are imported also into the former namespaceArp::System::Acf
to avoid code adaptation
But in contrast to the Arp.Base.Core
library, the revised Acf
code causes some major code changes:
- All user code based on the
Acf
classesComponentBase
andLibraryBase
must be adjusted to fit to the new interfaces.
The library implementation of a custom project
The library declaration
The project's library class is derived by LibraryBase
from Acf
and implements a default constructor as well as an operation to obtain the singleton.
Header file:
© Phoenix Contact
#pragma once
#include "Arp/Base/Core/Arp.hpp"
#include "Arp/Base/Acf/Commons/LibraryBase.hpp"
namespace Apps::Demo::Sdk
{
using Arp::Base::Acf::Commons::ILibrary;
using Arp::Base::Acf::Commons::LibraryBase;
class SdkLibrary : public LibraryBase
{
public: // construction/destruction
SdkLibrary(void);
public: // static singleton operations
static ILibrary& GetInstance(void);
};
} // end of namespace Apps::Demo::Sdk
Changes:
- The former code also derives the library class by
Singleton<SdkLibrary>
→ remove this - The signature of the constructor was formerly
SdkLibrary(AppDomain& appDomain)
→ remove the parameter
The library definition
The implementation of the project's library shall look like this:
© Phoenix Contact
#include "Apps/Demo/Sdk/SdkLibrary.hpp"
#include "Apps/Demo/Sdk/SdkComponent.hpp"
#include "Arp/Base/Core/TypeName.hxx"
namespace Apps::Demo::Sdk
{
SdkLibrary::SdkLibrary()
: LibraryBase(/* User defined version: */ ArpVersion(1,2,3))
{
this->AddComponentType<SdkComponent>();
// Add more component types here if required
}
ILibrary& SdkLibrary::GetInstance()
{
static SdkLibrary instance;
return instance;
}
extern "C" ARP_EXPORT ILibrary& Apps_Demo_Sdk_MainEntry()
{
return SdkLibrary::GetInstance();
}
} // end of namespace Apps::Demo::Sdk
Changes:
- The former code passed the
ARP_VERSION_CURRENT
macro to the constructor ofLibraryBase
. This is no longer necessary. The version of the SDK is passed to the library using another mechanism. - The version parameter to the constructor of
LibraryBase
is a version number intended for the user. It can be used to track a version of a library. The newArp.System.Acf.Services.ISystemInfoService
provides methods to query information about the loaded components. For each component theArp
SDK version and the user version is provided by the service. - The former implementation of the library constructor registers the provided components through accessing the protected
componentFactory
member variable directly. This is now replaced by the call ofLibraryBase::AddComponentType<T>
operation, whereT
is the component type to register. - The singleton is implemented by the project's library itself through the
GetInstance()
operation, using a local static instance (see Scott Meyers (1996), More Effective C++, Addison-Wesley, pp. 146 ff.). - The name of the library's entry function was static so far and always called
ArpDynamicLibraryMain
. From now on it depends on the project's name (or library's name, respectively). It consists of the safe name of the shared library omitting the extension .so as well as the default Linux lib prefix and appends a static suffix called_MainEntry
. The safe name of the library replaces all special characters by underscores, e.g. if the project/library is called libArp.Plc.Gds.so, the main entry function shall be called:Arp_Plc_Gds_MainEntry
. - In the ACF configuration files (*.acf.config) the new optional attribute
mainEntry
of theLibrary
element may be used to specify an alternative name for themainEntry
function.
The component implementation of a custom project
The component declaration
The project's component class is derived by ComponentBase
from Acf
.
Header file:
© Phoenix Contact
#pragma once
#include "Arp/Base/Core/Arp.hpp"
#include "Arp/Base/Acf/Commons/ComponentBase.hpp"
namespace Apps::Demo::Sdk
{
using namespace Arp;
using namespace Arp::Base::Acf::Commons;
class SdkComponent : public ComponentBase
{
public: // construction/destruction
SdkComponent(ILibrary& library, const String& name);
public: // IComponent operations
void Initialize(void)override;
void SubscribeServices(void)override;
void LoadSettings(const String& settingsPath)override;
void SetupSettings(void)override;
void PublishServices(void)override;
void LoadConfig(void)override;
void SetupConfig(void)override;
void ResetConfig(void)override;
void Dispose(void)override;
void PowerDown(void)override;
public: // properties
public: // operations
private: // methods
private: // fields
private: // static fields
};
} // end of namespace Apps::Demo::Sdk
Changes:
- The signature of the constructor changed from
SdkComponent(IApplication& application, const String& name)
toSdkComponent(ILibrary& library, const String& name)
- The static
SdkComponent::Create(...)
operation having the same signature as the former constructor might be removed. - All special member functions (copy and move operations, destructor) can be removed, if not required by the implementation of the derived class.
- All canonical constructors and assignment operators can be removed.
- The function
IComponent::GetVersion()
has been removed, since it is now ambiguous. It can be replaced usingGetLibrary().GetBuildVersion()
. Note the new functionILibrary::GetLibraryVersion()
.
The component definition
The implementation of the project's component shall look like this:
© Phoenix Contact
#include "Apps/Demo/Sdk/SdkComponent.hpp"
#include "Apps/Demo/Sdk/SdkLibrary.hpp"
namespace Apps::Demo::Sdk
{
SdkComponent::SdkComponent(ILibrary& library, const String& name)
: ComponentBase(library, name, ComponentCategory::Custom, GetDefaultStartOrder())
{
}
void SdkComponent::Initialize()
{
// register components, initialize singletons
// subscribe notifications or events here
}
void SdkComponent::SubscribeServices()
{
// subscribe the services used by this component here
}
void SdkComponent::LoadSettings(const String& settingsPath)
{
// load firmware settings here
}
void SdkComponent::SetupSettings()
{
// setup firmware settings here
}
void SdkComponent::PublishServices()
{
// publish the services of this component here
}
void SdkComponent::LoadConfig()
{
// load project config here
}
void SdkComponent::SetupConfig()
{
// setup project config here
}
void SdkComponent::ResetConfig()
{
// implement this inverse to SetupConfig() and LoadConfig()
}
void SdkComponent::Dispose()
{
// implement this inverse to SetupSettings(), LoadSettings() and Initialize()
}
void SdkComponent::PowerDown()
{
// implement this only if data must be retained even on power down event
}
} // end of namespace Apps::Demo::Sdk
Changes:
- The signature of the constructor of class
ComponentBase
changed
fromComponentBase(IApplication&, ILibrary&, const String&, ComponentCategory, size_t, bool)
toComponentBase(ILibrary&, const String&, ComponentCategory, size_t)
Thus, the first parameter of typeIApplication
was removed. Pass the component's constructor arguments accordingly toComponentBase
class as shown in the example above. The start order of components must now be passed explicitly. - Use
GetDefaultStartOrder()
for a sensible default value for the start order. The value shall only be changed to resolve dependencies of multiple custom components.
TraceController from Arp.System.Commons
The class Arp::System::Commons::Diagnostics::TraceController
is removed from the SDK. The functionality is offered by the Arp.Services.TraceController
component and its ITraceControllerServiceRSC
service.
Library Arp.Base.Rsc.Commons
(formerly Arp.System.Rsc.Services)
Changes:
- When calling RSC services in C++ with a parameter of type
RscVariant<N>
, then compile errors might occur because the interface ofRscVariant
has changed. In this case, please ask for direct developer support. - Related to template class
RscVariant<N>
and template classRscString<N>
:
The template parameterN
specifies the maximal length of the string. Formerly the effective length wasN-1
, due to theNUL
terminator; now it isN
. This issue was claimed frequently, so that it was fixed with this Application-relevant Changes release. - If any RSC services were implemented (non-public feature), the service implementation code has to be re-generated by the
RscGenerator
. In some rare cases, the code of the service implementation has to be adjusted. - The class
RscStreamAdapter
in C++ was renamed toRscStream
. Thus, if the compiler claims a non-overriddenService-Impl
operation, which usesRscStreamAdapter
, just rename it toRscStream
. - RscStructReader.hxx and RscStructWriter.hxx have been replaced with RscStructReader.hpp and RscStructWriter.hpp (respectively). The template classes
RscStructReader<MaxStringSize>
andRscStructWriter<MaxStringSize>
have been replaced with the non-template classesRscStructReader
andRscStructWriter
(respectively). The latter classes implement template operations which deduce the template arguments automatically.