This is an automated email from the ASF dual-hosted git repository. dmagda pushed a commit to branch IGNITE-7595 in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/IGNITE-7595 by this push: new fd5725f ported C++ specific classes fd5725f is described below commit fd5725f9f193521b59629184f4187c842e55e5ee Author: Denis Magda <dma...@gridgain.com> AuthorDate: Thu Oct 1 16:39:58 2020 -0700 ported C++ specific classes --- docs/_data/toc.yaml | 28 ++- docs/_docs/cpp-specific/cpp-objects-lifetime.adoc | 78 +++++++ .../cpp-platform-interoperability.adoc | 236 +++++++++++++++++++ docs/_docs/cpp-specific/cpp-serialization.adoc | 253 +++++++++++++++++++++ ...options.adoc => net-configuration-options.adoc} | 0 ...upport.adoc => net-cross-platform-support.adoc} | 0 ...nt-options.adoc => net-deployment-options.adoc} | 0 ...-cache.adoc => net-entity-framework-cache.adoc} | 0 ...ution.adoc => net-java-services-execution.adoc} | 0 .../net-specific/{linq.adoc => net-linq.adoc} | 0 ...ity.adoc => net-platform-interoperability.adoc} | 2 +- ...ading.adoc => net-remote-assembly-loading.adoc} | 0 ...dalone-nodes.adoc => net-standalone-nodes.adoc} | 0 13 files changed, 586 insertions(+), 11 deletions(-) diff --git a/docs/_data/toc.yaml b/docs/_data/toc.yaml index 6bd41f0..4e7e349 100644 --- a/docs/_data/toc.yaml +++ b/docs/_data/toc.yaml @@ -431,30 +431,30 @@ url: extensions-and-integrations/cassandra/usage-examples - title: DDL Generator url: extensions-and-integrations/cassandra/ddl-generator -- title: C# and .NET Specific +- title: .NET Specific items: - title: Configuration Options - url: net-specific/configuration-options + url: net-specific/net-configuration-options - title: Deployment Options - url: net-specific/deployment-options + url: net-specific/net-deployment-options - title: Standalone Nodes - url: net-specific/standalone-nodes + url: net-specific/net-standalone-nodes - title: Logging url: net-specific/net-logging - title: LINQ - url: net-specific/linq + url: net-specific/net-linq - title: Java Services Execution - url: net-specific/java-services-execution + url: net-specific/net-java-services-execution - title: Plugins url: net-specific/net-plugins - title: Serialization url: net-specific/net-serialization - title: Cross-Platform Support - url: net-specific/cross-platform-support + url: net-specific/net-cross-platform-support - title: Platform Interoperability - url: net-specific/platform-interoperability + url: net-specific/net-platform-interoperability - title: Remote Assembly Loading - url: net-specific/remote-assembly-loading + url: net-specific/net-remote-assembly-loading - title: Troubleshooting url: net-specific/net-troubleshooting - title: Integrations @@ -464,7 +464,15 @@ - title: ASP.NET Session State Caching url: net-specific/asp-net-session-state-caching - title: Entity Framework 2nd Level Cache - url: net-specific/entity-framework-cache + url: net-specific/net-entity-framework-cache +- title: C++ Specific + items: + - title: Serialization + url: cpp-specific/cpp-serialization + - title: Platform Interoperability + url: cpp-specific/cpp-platform-interoperability + - title: Objects Lifetime + url: cpp-specific/cpp-objects-lifetime - title: Plugins url: plugins - title: SQL Reference diff --git a/docs/_docs/cpp-specific/cpp-objects-lifetime.adoc b/docs/_docs/cpp-specific/cpp-objects-lifetime.adoc new file mode 100644 index 0000000..b779cb2 --- /dev/null +++ b/docs/_docs/cpp-specific/cpp-objects-lifetime.adoc @@ -0,0 +1,78 @@ += Objects Lifetime in Ignite.C++ + +== Ignite Objects + +Apache Ignite objects, such as `Ignite` or `Cache`, that are created using Ignite public APIs are implemented as a thin +handler of an internal/underlying object and can be safely and quickly copied or passed to functions by value. It is also +a recommended way for passing Ignite objects from one function to another because an underlying object lives as long as +there is at least one handler object alive. + +[tabs] +-- +tab:C++[] +[source,cpp] +---- +// Fast and safe passing of the ignite::Ignite instance to the function. +// Here 'val' points to the same underlying node instance even though +// Ignite object gets copied on call. +// It's guarateed that the underlying object will live as long as 'val' +// object is alive. +void Foo(ignite::Ignite val) +{ + ... +} +---- +-- + +== Custom Objects +Your application can put in Ignite custom objects which lifetime can not be easily +determined during the compilation. For example, when a `ContinuousQuery` instance is created, you are required to +provide the continuous query with an instance of the local listener - `CacheEntryEventListener`. In such a case, it is +unclear whether it is the responsibility of Apache Ignite or the application to manage the local listener's lifetime and +release it once it is no longer needed. + +Apache Ignite C{pp} is pretty flexible in this part. It uses `ignite::Reference` class to address custom objects ownership +problem. Refer to the code below to see how this class can be used in practice. + +[tabs] +-- +tab:C++[] +[source,cpp] +---- +// Ignite function that takes a value of 'SomeType'. +void Foo(ignite::Reference<SomeType> val); + +//... + +// Defining an object. +SomeType obj1; + +// Passing a simple reference to the function. +// Ignite will not get ownership over the instance. +// The application is responsible for keeping instance alive while +// it's used by Ignite and for releasing it once it is no longer needed. +Foo(ignite::MakeReference(obj1); + +// Passing the object by copy. +// Ignite gets a copy of the object instance and manages +// its lifetime by itself. +// 'SomeType' is required to have a copy constructor. +foo(ignite::MakeReferenceFromCopy(obj1); + +// Defining another object. +SomeType* obj2 = new SomeType; + +// Passing object's ownership to the function. +// Ignite will release the object once it's no longer needed. +// The applicaiton must not use the pointer once it have been passed +// to Ignite as it might be released at any point of time. +foo(ignite::MakeReferenceFromOwningPointer(obj2); + +std::shared_ptr<SomeType> obj3 = std::make_shared<SomeType>(); + +// Passing the object by smart pointer. +// In this case, Reference class behaves just like an underlying +// smart pointer type. +foo(ignite::MakeReferenceFromSmartPointer(obj3); +---- +-- diff --git a/docs/_docs/cpp-specific/cpp-platform-interoperability.adoc b/docs/_docs/cpp-specific/cpp-platform-interoperability.adoc new file mode 100644 index 0000000..f24e070 --- /dev/null +++ b/docs/_docs/cpp-specific/cpp-platform-interoperability.adoc @@ -0,0 +1,236 @@ += Ignite.C++ and Platform Interoperability + +== Overview + +When using Apache Ignite C++, it is quite common to have several C++ and Java nodes running in a single cluster. To seamlessly +interoperate between C++ and Java nodes, you need to take several aspects into consideration. Let's review them. + +== Binary Marshaller Configuration + +Ignite uses its binary marshaller for data, logic, messages serialization and deserialization. Due to architectural specificities, +Java and C++ nodes are started with different default settings of the binary marshaller that can lead to exceptions like + the one below during a node startup if you try to set up a heterogeneous cluster: + +[tabs] +-- +tab:Java[] +[source,java] +---- +class org.apache.ignite.spi.IgniteSpiException: Local node's +binary configuration is not equal to remote node's binary configuration +[locNodeId=b3f0367d-3c2b-47b4-865f-a62c656b5d3f, +rmtNodeId=556a3f41-eab1-4d9f-b67c-d94d77ddd89d, +locBinaryCfg={globIdMapper=org.apache.ignite.binary.BinaryBasicIdMapper, +compactFooter=false, globSerializer=null}, rmtBinaryCfg=null] +---- +-- + +To avoid the exception and to make sure Java and C++ nodes can co-exist in a single cluster, add the following binary +marshaller's settings to the Java configuration: + +[tabs] +-- +tab:XML[] +[source,xml] +---- +<?xml version="1.0" encoding="UTF-8"?> + +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> + + <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"> + ... + <property name="binaryConfiguration"> + <bean class="org.apache.ignite.configuration.BinaryConfiguration"> + <property name="compactFooter" value="false"/> + + <property name="idMapper"> + <bean class="org.apache.ignite.binary.BinaryBasicIdMapper"> + <property name="lowerCase" value="true"/> + </bean> + </property> + </bean> + </property> + ... + </bean> +</beans> +---- +-- + +== Basic Types Compatibility + +Your C++ application can put a value into the cluster and another Java application can read it back. The table below +shows how the types are matched between Java and C++: + +[opts="header"] +|=== +|Java Type | C++ Type + +| `boolean`, `java.lang.Boolean`| `bool` +| `byte`, `java.lang.Byte`| `int8_t` +| `short`, `java.lang.Short`| `int16_t` +| `int`, `java.lang.Integer`| `int32_t` +| `long`, `java.lang.Long`| `int64_t` +| `float`, `java.lang.Float`| `float` +| `double`, `java.lang.Double`| `double` +| `char`, `java.lang.Character`| `uint16_t` +| `java.lang.String`| `std::string`, `char[]` +| `java.util.Date`| `ignite::Date` +| `java.sql.Time`| `ignite::Time` +| `java.sql.Timestamp`| `ignite::Timestamp` +| `java.util.UUID`| `ignite::Guid` +|=== + +== Custom Types Compatibility + +To get access to the same application-specific object on both Java and C++ nodes, you need to describe it similarly in +both the languages. This includes the same type name, type id, fields id, hash code algorithm, as well as read/write functions +for the type. + +To do this on the C++ end, you need to use the `ignite::binary::BinaryType` class template. + +Let's consider the following example that defines a Java class that will be later read by a C++ application: + +[tabs] +-- +tab:Java[] +[source,java] +---- +package org.apache.ignite.examples; + +public class CrossClass implements Binarylizable { + private long id; + + private int idPart; + + public void readBinary(BinaryReader reader) throws BinaryObjectException { + id = reader.readLong("id"); + idPart = reader.readInt("idPart"); + } + + public void writeBinary(BinaryWriter writer) throws BinaryObjectException { + writer.writeLong("id", id); + writer.writeInt("idPart", idPart); + } +} +---- +-- + +Next, you create a counter-part on the C++ end: + +[tabs] +-- +tab:C++[] +[source,cpp] +---- +namespace ignite +{ + namespace binary + { + template<> + struct BinaryType<CrossClass> + { + static int32_t GetTypeId() + { + return GetBinaryStringHashCode("CrossClass"); + } + + static void GetTypeName(std::string& name) + { + name = "CrossClass"; + } + + static int32_t GetFieldId(const char* name) + { + return GetBinaryStringHashCode(name); + } + + static bool IsNull(const CrossClass& obj) + { + return false; + } + + static void GetNull(CrossClass& dst) + { + dst = CrossClass(); + } + + static void Read(BinaryReader& reader, CrossClass& dst) + { + dst.id = reader.ReadInt64("id"); + dst.idPart = reader.ReadInt32("idPart"); + } + + static void Write(BinaryWriter& writer, const CrossClass& obj) + { + writer.WriteInt64("id", obj.id); + writer.WriteInt32("idPart", obj.idPart); + } + }; + } +} +---- +-- + +Finally, you need to use the following `BinaryConfiguration` for **both** Java and C++ nodes: + +[tabs] +-- +tab:XML[] +[source,xml] +---- +<?xml version="1.0" encoding="UTF-8"?> + +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> + + <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"> + ... + <property name="binaryConfiguration"> + <bean class="org.apache.ignite.configuration.BinaryConfiguration"> + <property name="compactFooter" value="false"/> + + <property name="idMapper"> + <bean class="org.apache.ignite.binary.BinaryBasicIdMapper"> + <property name="lowerCase" value="true"/> + </bean> + </property> + + <property name="nameMapper"> + <bean class="org.apache.ignite.binary.BinaryBasicNameMapper"> + <property name="simpleName" value="true"/> + </bean> + </property> + + <property name="classNames"> + <list> + <value>org.apache.ignite.examples.CrossClass</value> + </list> + </property> + </bean> + </property> + ... + </bean> +</beans> +---- +-- + +[CAUTION] +==== +[discrete] +It is especially important to implement `GetTypeName()` and `GetTypeId()` methods in the right manner for the types that +are used for the keys. +==== + +[CAUTION] +==== +[discrete] +C++ function `GetBinaryStringHashCode()` always calculates hash as `BinaryBasicIdMapper` when its property `lowerCase` is set +to `true`. So make sure you have the correct configuration for the `BinaryBasicIdMapper` if you are going to use this +function to calculate the type id in C++. +==== + diff --git a/docs/_docs/cpp-specific/cpp-serialization.adoc b/docs/_docs/cpp-specific/cpp-serialization.adoc new file mode 100644 index 0000000..8d7fe2a45 --- /dev/null +++ b/docs/_docs/cpp-specific/cpp-serialization.adoc @@ -0,0 +1,253 @@ += Serialization in Ignite.C++ + + +== BinaryType Templates + +Most user-defined classes going through Ignite C{pp} API will be passed over the wire to other cluster nodes. These classes +include your data records, compute tasks, and other objects. + +Passing objects of these classes over wire requires serialization. For Ignite C{pp} it can be achieved by providing +a `BinaryType` class template for your object type: + +[tabs] +-- +tab:C++[] +[source,cpp] +---- +class Address +{ + friend struct ignite::binary::BinaryType<Address>; +public: + Address() { } + + Address(const std::string& street, int32_t zip) : + street(street), zip(zip) { } + + const std::string& GetStreet() const + { + return street; + } + + int32_t GetZip() const + { + return zip; + } + +private: + std::string street; + int32_t zip; +}; + +template<> +struct ignite::binary::BinaryType<Address> +{ + static int32_t GetTypeId() + { + return GetBinaryStringHashCode("Address"); + } + + static void GetTypeName(std::string& name) + { + name = "Address"; + } + + static int32_t GetFieldId(const char* name) + { + return GetBinaryStringHashCode(name); + } + + static bool IsNull(const Address& obj) + { + return obj.GetZip() && !obj.GetStreet().empty(); + } + + static void GetNull(Address& dst) + { + dst = Address(); + } + + static void Write(BinaryWriter& writer, const Address& obj) + { + writer.WriteString("street", obj.GetStreet()); + writer.WriteInt32("zip", obj.GetZip()); + } + + static void Read(BinaryReader& reader, Address& dst) + { + dst.street = reader.ReadString("street"); + dst.zip = reader.ReadInt32("zip"); + } +}; +---- +-- + +Also, you can use the raw serialization mode without storing names of the object's fields in the serialized form. This +mode is more compact and performs faster, but disables SQL queries that require to keep the names of the fields in the serialized form: + +[tabs] +-- +tab:C++[] +[source,cpp] +---- +template<> +struct ignite::binary::BinaryType<Address> +{ + static int32_t GetTypeId() + { + return GetBinaryStringHashCode("Address"); + } + + static void GetTypeName(std::string& name) + { + name = "Address"; + } + + static int32_t GetFieldId(const char* name) + { + return GetBinaryStringHashCode(name); + } + + static bool IsNull(const Address& obj) + { + return false; + } + + static void GetNull(Address& dst) + { + dst = Address(); + } + + static void Write(BinaryWriter& writer, const Address& obj) + { + BinaryRawWriter rawWriter = writer.RawWriter(); + + rawWriter.WriteString(obj.GetStreet()); + rawWriter.WriteInt32(obj.GetZip()); + } + + static void Read(BinaryReader& reader, Address& dst) + { + BinaryRawReader rawReader = reader.RawReader(); + + dst.street = rawReader.ReadString(); + dst.zip = rawReader.ReadInt32(); + } +}; +---- +-- + +== Serialization Macros + +Ignite C{pp} defines a set of utility macros that could be used to simplify the `BinaryType` specialization. Here is a list of such macros with description: + +* `IGNITE_BINARY_TYPE_START(T)` - Start the binary type's specialization. +* `IGNITE_BINARY_TYPE_END` - End the binary type's specialization. +* `IGNITE_BINARY_GET_TYPE_ID_AS_CONST(id)` - Implementation of `GetTypeId()` which returns predefined constant `id`. +* `IGNITE_BINARY_GET_TYPE_ID_AS_HASH(T)` - Implementation of `GetTypeId()` which returns hash of passed type name. +* `IGNITE_BINARY_GET_TYPE_NAME_AS_IS(T)` - Implementation of `GetTypeName()` which returns type name as is. +* `IGNITE_BINARY_GET_FIELD_ID_AS_HASH` - Default implementation of `GetFieldId()` function which returns Java-way hash code of the string. +* `IGNITE_BINARY_IS_NULL_FALSE(T)` - Implementation of `IsNull()` function which always returns `false`. +* `IGNITE_BINARY_IS_NULL_IF_NULLPTR(T)` - Implementation of `IsNull()` function which return `true` if passed object is null pointer. +* `IGNITE_BINARY_GET_NULL_DEFAULT_CTOR(T)` - Implementation of `GetNull()` function which returns an instance created with default constructor. +* `IGNITE_BINARY_GET_NULL_NULLPTR(T)` - Implementation of GetNull() function which returns `NULL` pointer. + +You can describe the `Address` class declared earlier using these macros: + +[tabs] +-- +tab:C++[] +[source,cpp] +---- +namespace ignite +{ + namespace binary + { + IGNITE_BINARY_TYPE_START(Address) + IGNITE_BINARY_GET_TYPE_ID_AS_HASH(Address) + IGNITE_BINARY_GET_TYPE_NAME_AS_IS(Address) + IGNITE_BINARY_GET_NULL_DEFAULT_CTOR(Address) + IGNITE_BINARY_GET_FIELD_ID_AS_HASH + + static bool IsNull(const Address& obj) + { + return obj.GetZip() == 0 && !obj.GetStreet().empty(); + } + + static void Write(BinaryWriter& writer, const Address& obj) + { + writer.WriteString("street", obj.GetStreet()); + writer.WriteInt32("zip", obj.GetZip()); + } + + static void Read(BinaryReader& reader, Address& dst) + { + dst.street = reader.ReadString("street"); + dst.zip = reader.ReadInt32("zip"); + } + + IGNITE_BINARY_TYPE_END + } +} +---- +-- + +== Reading and Writing Values + +There are several ways for writing and reading data. The first way is to use an object's value directly: + + +[tabs] +-- +tab:Writing[] +[source,cpp] +---- +CustomType val; + +// some application code here +// ... + +writer.WriteObject<CustomType>("field_name", val); +---- +tab:Reading[] +[source,cpp] +---- +CustomType val = reader.ReadObject<CustomType>("field_name"); +---- +-- + +The second approach does the same but uses a pointer to the object: + +[tabs] +-- +tab:Writing[] +[source,cpp] +---- +// Writing null to as a value for integer field. +writer.WriteObject<int32_t*>("int_field_name", nullptr); + +// Writing a value of the custom type by pointer. +CustomType *val; + +// some application code here +// ... + +writer.WriteObject<CustomType*>("field_name", val); +---- +tab:Reading[] +[source,cpp] +---- +// Reading value which can be null. +CustomType* nullableVal = reader.ReadObject<CustomType*>("field_name"); +if (nullableVal) { + // ... +} + +// You can use a smart pointer as well. +std::unique_ptr<CustomType> nullablePtr = reader.ReadObject<CustomType*>(); +if (nullablePtr) { + // ... +} +---- +-- + +An advantage of the pointer-based technique is that it allows writing or reading `null` values. diff --git a/docs/_docs/net-specific/configuration-options.adoc b/docs/_docs/net-specific/net-configuration-options.adoc similarity index 100% rename from docs/_docs/net-specific/configuration-options.adoc rename to docs/_docs/net-specific/net-configuration-options.adoc diff --git a/docs/_docs/net-specific/cross-platform-support.adoc b/docs/_docs/net-specific/net-cross-platform-support.adoc similarity index 100% rename from docs/_docs/net-specific/cross-platform-support.adoc rename to docs/_docs/net-specific/net-cross-platform-support.adoc diff --git a/docs/_docs/net-specific/deployment-options.adoc b/docs/_docs/net-specific/net-deployment-options.adoc similarity index 100% rename from docs/_docs/net-specific/deployment-options.adoc rename to docs/_docs/net-specific/net-deployment-options.adoc diff --git a/docs/_docs/net-specific/entity-framework-cache.adoc b/docs/_docs/net-specific/net-entity-framework-cache.adoc similarity index 100% rename from docs/_docs/net-specific/entity-framework-cache.adoc rename to docs/_docs/net-specific/net-entity-framework-cache.adoc diff --git a/docs/_docs/net-specific/java-services-execution.adoc b/docs/_docs/net-specific/net-java-services-execution.adoc similarity index 100% rename from docs/_docs/net-specific/java-services-execution.adoc rename to docs/_docs/net-specific/net-java-services-execution.adoc diff --git a/docs/_docs/net-specific/linq.adoc b/docs/_docs/net-specific/net-linq.adoc similarity index 100% rename from docs/_docs/net-specific/linq.adoc rename to docs/_docs/net-specific/net-linq.adoc diff --git a/docs/_docs/net-specific/platform-interoperability.adoc b/docs/_docs/net-specific/net-platform-interoperability.adoc similarity index 99% rename from docs/_docs/net-specific/platform-interoperability.adoc rename to docs/_docs/net-specific/net-platform-interoperability.adoc index 3c2b4db..ac54a6d 100644 --- a/docs/_docs/net-specific/platform-interoperability.adoc +++ b/docs/_docs/net-specific/net-platform-interoperability.adoc @@ -1,4 +1,4 @@ -= Platform Interoperability += Ignite.NET and Platform Interoperability Ignite allows different platforms, such as .NET, Java and C{pp}, to interoperate with each other. Classes and objects defined and written to Ignite by one platform can be read and used by another platform. diff --git a/docs/_docs/net-specific/remote-assembly-loading.adoc b/docs/_docs/net-specific/net-remote-assembly-loading.adoc similarity index 100% rename from docs/_docs/net-specific/remote-assembly-loading.adoc rename to docs/_docs/net-specific/net-remote-assembly-loading.adoc diff --git a/docs/_docs/net-specific/standalone-nodes.adoc b/docs/_docs/net-specific/net-standalone-nodes.adoc similarity index 100% rename from docs/_docs/net-specific/standalone-nodes.adoc rename to docs/_docs/net-specific/net-standalone-nodes.adoc