5.5 Client development


  • 5.5.5 C++ client

    The C++ client supports the static mode, and the Variant mode. The script mode is integrated with the Lua library to support the Lua, which will be separately introduced.

    The C++ client uses the C++ 11 standard to implement, and furthest are consistent with the Java version and the C# version. The C++ library provides the support in all common platforms (g++, ndk, clang). The example bases on the VS2013.

    • The static mode

      1. The C++ client supports the static mode, and the Variant mode. The script mode is integrated with the Lua library to support the Lua, which will be separately introduced.

      2.

      java -jar <path to limax.jar> xmlgen –c++ –noServiceXML –outputPath sdir example.client.xml
      

      3. There are two new created directories xmlgeninc and xmlgensrc in the source code directory. The xmlgeninc does not need to submit to the version control system.

      4. Create the same directory structure according to the generated directory structure, then manually add all generated .cpp files by using "add the existing item" option, and the future new generated .cpp files should also be manually added. For the convenience of checking files through editor, we suggest to add the .h files.

      5. Add the Limax project of the C++ version into the solution.

      6. Add the reference for the application project, and refer to the Limax project.

      7. Edit the project attribute, and add the include directory of the Limax project as the "Additional Include Directories".

      The source code of the main funtion:


      #include "stdafx.h"
      #include <iostream>
      #include "limax.h"
      #include "xmlgeninc/xmlgen.h"
      using namespace limax;
      
      class MyApp : public EndpointListener
      {
          const int providerId = 100;
      public:
          MyApp()
          {
              Endpoint::openEngine();
              auto config = Endpoint::createEndpointConfigBuilder(
                      "127.0.0.1", 10000, LoginConfig::plainLogin("testabc", "123456", "test"))
                  ->staticViewCreatorManagers({ example::getShareViewCreatorManager(providerId) })
                  ->endpointState({ example::getExampleClientStateClient(providerId) })
                  ->build();
              Endpoint::start(config, this);
          }
          ~MyApp(){
              std::mutex mutex;
              std::condition_variable_any cond;
              std::lock_guard<std::mutex> l(mutex);
              Endpoint::closeEngine([&](){
                  std::lock_guard<std::mutex> l(mutex);
                  cond.notify_one();
              });
              cond.wait(mutex);
          }
          void run()  { Sleep(2000); }
          void onManagerInitialized(EndpointManager*,EndpointConfig*) { std::cout << "onManagerInitialized" << std::endl; }
          void onManagerUninitialized(EndpointManager*) { std::cout << "onManagerUninitialized" << std::endl; }
          void onTransportAdded(Transport*) {
              std::cout << "onTransportAdded" << std::endl;
              View* mysessionview = example::ExampleClient::share::MySessionView::getInstance();
              mysessionview->registerListener([](const ViewChangedEvent &e){std::cout << e.toString() << std::endl; });
          }
          void onTransportRemoved(Transport*){ std::cout << "onTransportRemoved" << std::endl; }
          void onAbort(Transport*) { std::cout << "onAbort" << std::endl; }
          void onSocketConnected() { std::cout << "onSocketConnected" << std::endl; }
          void onKeyExchangeDone() { std::cout << "onKeyExchangeDone" << std::endl; }
          void onKeepAlived(int ping) { std::cout << "onKeepAlived " << ping << std::endl; }
          void onErrorOccured(int errorsource, int errorvalue, const std::string& info) { std::cout << "onErrorOccured " << errorsource << " " << errorvalue << " " << info << std::endl; }
          void destroy() {}
      };
      
      int _tmain(int argc, _TCHAR* argv[])
      {
          MyApp().run();
          return 0;
      }    
      

      Constrasted to the Java version and the C# version, execept for the language feature, there is no much difference. There is a little difference for the onErrorOccured. When the source is SOURCE_ENDPOINT, the code corresponds to the SYSTEM type error, and the info reports the detailed error content. The definition of the source/code refers to the endpoint.h header file. In addition, there is an extra destroy method in the MyApp class. And the detailed reason will be explained in the following.

      share.MyTemporaryView.cpp:


      #include "share.MyTemporaryView.h"
      #include "../xmlgeninc/views/share.MySessionView.h"
      
      #include <iostream>
      using namespace limax;
      namespace example { 
      namespace ExampleClient { 
      namespace share { 
      
          void MyTemporaryView::onOpen(const std::vector<int64_t>& sessionids) {
              std::cout << toString() << " onOpen";
              for (auto &l : sessionids)
                  std::cout << " " << l;
              std::cout << std::endl;
              registerListener([](const ViewChangedEvent &e){std::cout << e.toString() << std::endl; });
              MySessionView::getInstance()->control(99999);
          }
          void MyTemporaryView::onAttach(int64_t sessionid) {}
          void MyTemporaryView::onDetach(int64_t sessionid, int reason)
          {
              if (reason >= 0)
              {
                  //Application Reason
              }
              else
              {
                  //Connection abort Reason
              }
          }
          void MyTemporaryView::onClose() {
              std::cout <<  toString() << " onClose " << std::endl;
          }
      } 
      } 
      }
      

      It seems no much difference. It is noted that the required header file share.MySessionView.h must be manually included due to using the MySessionView. In addition, the generated header file share.MyTemporaryView.h in the same directory could add the necessarey member variables and member functions.


      The execution result:

      onManagerInitialized

      onSocketConnected

      onKeyExchangeDone

      onTransportAdded

      [class = example.ExampleClient.share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 78] onOpen 61440

      [class = example.ExampleClient.share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 78] 61440 _var0 00A3BCBC NEW

      onKeepAlived 15

      [class = example.ExampleClient.share.MySessionView ProviderId = 100 classindex = 1] 61440 var0 0088585C NEW

      [class = example.ExampleClient.share.MySessionView ProviderId = 100 classindex = 1] 61440 var0 0088585C REPLACE

      [class = example.ExampleClient.share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 78] 61440 _var0 00A3BCBC REPLACE

      onTransportRemoved

      [class = example.ExampleClient.share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 78] onClose

      onManagerUninitialized

      This result seems to be consistent with the previous versions, and only the value of the View is displayed as an address. That is because the type of the value should only be the void* in the definition of the ViewChangeEvent message notification.


    • The Variant mode

      Modify the constructor function, and create the configuration in the Variant mode.


      auto config = Endpoint::createEndpointConfigBuilder(
              "127.0.0.1", 10000, LoginConfig::plainLogin("testabc", "123456", "test"))
          ->variantProviderIds({ providerId })
          ->build();    
      

      Modify the onTransportAdded method:


      class MyTemporaryViewHandler : public TemporaryViewHandler
      {
              VariantView* mySessionView;
          public:
              MyTemporaryViewHandler(VariantView* v) : mySessionView(v) {}
              virtual void onOpen(VariantView* view, const std::vector<int64_t>& sessionids){
                  std::cout << view->toString() << " onOpen";
                  for (auto &l : sessionids)
                      std::cout << " " << l;
                  std::cout << std::endl;
                  view->registerListener([](const VariantViewChangedEvent &e){std::cout << e.toString() << std::endl; });
                  Variant param = Variant::createStruct();
                  param.setValue("var0", 99999);
                  mySessionView->sendControl("control", param);
              }
              virtual void onClose(VariantView* view){
                  std::cout << view->toString() << " onClose " << std::endl;
              }
              virtual void onAttach(VariantView* view, int64_t sessionid) {}
              virtual void onDetach(VariantView* view, int64_t sessionid, int reason) {}
              virtual void destroy() { delete this; }
      };
      
      void onTransportAdded(Transport*transport) {
          std::cout << "onTransportAdded" << std::endl;
          VariantManager* manager = VariantManager::getInstance(transport->getManager(), providerId);
          VariantView* mySessionView = manager->getSessionOrGlobalView("share.MySessionView");
          mySessionView->registerListener([](const VariantViewChangedEvent &e){std::cout << e.toString() << std::endl; });
          manager->setTemporaryViewHandler("share.MyTemporaryView", new MyTemporaryViewHandler(mySessionView));
      } 
      

      This source code looks like the C# version, except for the languge feature. In addition, there is an extra destroy method in the MyTemporaryViewHandler class. And the detailed reason is in the following.

      The execution result:

      onManagerInitialized

      onSocketConnected

      onKeyExchangeDone

      onTransportAdded

      [view = share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 79] onOpen 61440

      [view = share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 79] 61440 _var0 Hello 61440 NEW

      onKeepAlived 16

      [view = share.MySessionView ProviderId = 100 classindex = 1] 61440 var0 Hello 61440 NEW

      [view = share.MySessionView ProviderId = 100 classindex = 1] 61440 var0 99999 REPLACE

      [view = share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 79] 61440 _var0 99999 REPLACE

      onTransportRemoved

      [view = share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 79] onClose

      onManagerUninitialized

      This result reflects the actual value because that the Variant knowleges some type information.


    • Matters need attention

      1. The main function must include the limax.h header file, and extra include the generated xmlgeninc/xmlgen.h header file when using the static mode. When modifying the generated source code which uses the other generated source code, which is used should be included. Refer to the previous modification to the share.MyTemporaryView.cpp.

      2. For the convenience, the Limax library source code only uses the Limax namespace and has no further division. So use "using namespace limax".

      3. When creating the configuration, the parameters of the staticViewCreatorManagers, endpointState, and variantProviderIds use the specific initial list of the C++11 and could support multiple. Just like the C#, the runOnUiThread and uiThreadSchedule two assistant methods are provided for the convenient usage, ->executor([](Runnable r){ runOnUiThread(r); }).

      4. All kinds of pointers EndpointManager*, Transport* provided by the library and all types of View* could be directly used and the delete is forbidden. The life time of the EndpointManager is till the onManagerUninitialized; the life time of the Transport, the global View, and the session View are till the onTransportRemoved; the life time of the temporary View is till the temporary view onClose itself. If holding these pointers is necessary, the life time issue must be strictly concerned, especially when reference to the closure of the lamba expression.

      5. When the system need the application to provide the created object, the object should be created by the user iteself and provide the pointer used by the library, and the destroy method of itself. The previous MyApp::destroy and MyTemporaryViewHandler::destroy are the typical examples. It is because that the new/delete used in the internal library and the new/delete of the application itself might be not consistent.

      6. If using Objective-C++, uses -oc parameter to replace the -c++ parameter when generating the source code for static mode to guarantee to generate the required .mm file.

Prev Next