Warning

qilang is still under active development and its API may not be backward-compatible in the future.

Using qilang to generate strongly-typed proxies

qilang allows the usage of strongly-typed proxies in client code. This means that instead of writing:

std::vector<int> ret = myservice.call<std::vector<int> >("workIt", "blah", 12);

and maybe trigger a run-time error because the argument types are wrong, using qilang allows you to write:

std::vector<int> ret = myservice->workIt("blah", 12);

and get a compile-time error in case you got the argument types incorrect.

The code generated by qilang can then be put in a library so that it can be exposed to clients who may compile against it.

If you need details about the qilang syntax, see qilang IDL syntax.

Creating a simple service with qilang

A typical project using qilang would look like this:

myproject
 ├── mylibrary
 │   ├── api.hpp
 │   └── myservice.idl.qi
 ├── src
 │   ├── myserviceimpl.hpp
 │   └── mylibrary_module.cpp
 ├── qiproject.xml
 └── CMakeLists.txt

You can find a template in <repo>/templates/service and create your project with this single command-line:

$ qisrc create -i <repo>/templates/service-cpp myservice

The project, library and service names will be identical in this precise case.

qilang takes advantage of libqi to generate the specialized proxy boilerplate out of the provided .idl.qi file, hence it needs to be compiled for your machine, which will execute the generator, rather than for your target device: this is called host dependency.

Note

When cross-compiling, a host dependency will still be compiled for your system, instead of being compiled for the target system.

Host and regular dependencies will appear as following in your qiproject.xml:

<project name="myproject" version="3">
  <depends buildtime="true" runtime="true" names="libqi"/>
  <depends host="true" names="libqilang"/>
</project>

The IDL file is where you will write the actual API of your service. The file myservice.idl.qi could look like that:

package mylibrary

interface MyService
  fn emitPing(value: int) // Function
  fn workIt(name: str, value: int) -> Vec<int> // Function with return value
  sig ping(value: int) // Signal
  prop pingEnabled(value: bool) // Property
end

qilang will be able to generate the code from this IDL file as soon as the following CMake code calls for it. Your CMakeLists.txt should look like this:

cmake_minimum_required(VERSION 2.8)
project(mymodule)

find_package(qibuild) # Always required
qi_sanitize_compile_flags(HIDDEN_SYMBOLS) # To behave the same way on every platform
include_directories(".") # Exposes the headers locally

# Generate the specialized proxies. You can put several IDL files if you want.
find_package(qilang-tools) # Required for qi_gen_idl
set(mylibrary_idl "mylibrary/myservice.idl.qi")
qi_gen_idl(mylibrary_generated CPP "mylibrary" "${CMAKE_CURRENT_BINARY_DIR}" ${mylibrary_idl})
include_directories(${CMAKE_CURRENT_BINARY_DIR}) # Exposes the generated headers locally

# Install the headers so that people can use your proxies
qi_install_header(
  ${mylibrary_generated_INTERFACE}
  "mylibrary/api.hpp"
  KEEP_RELATIVE_PATHS)

# Create a lib with the proxies only
qi_create_lib(mylibrary
  ${mylibrary_generated}
  ${mylibrary_idl}
  DEPENDS
  qi)

qi_stage_lib(mylibrary)

# Create a module with your implementation of the MyService interface
# It is the easiest way to expose your service in NAOqi
find_package(qimodule) # Required only if you create a module
qi_create_module(
  mylibrary_module
  SRC
  "src/myserviceimpl.hpp"
  "mylibrary_module.cpp"
  DEPENDS qi mylibrary
)

The api.hpp file is still needed and must contain:

#ifndef MYLIBRARY_API_HPP
#define MYLIBRARY_API_HPP

#include <qi/macro.hpp>

#define MYLIBRARY_API_HPP QI_LIB_API(mylibrary)

#endif

The argument of QI_LIB_API must be the same as the name of your CMake target, i.e. the library exposing the specialized proxies in this example (“mylibrary”).

You can now focus on the actual implementation of your service, let us have a look at myserviceimpl.hpp:

#include <qi/session.hpp>
#include <src/myservice_p.hpp>

// Same name as your package in qilang
namespace mylibrary {
  class MyServiceImpl {
  public:
    MyServiceImpl(const qi::SessionPtr& session) : _session(session) { }

    void emitPing(int value) {
      if (pingEnabled.get())
        QI_EMIT ping(value);
    }

    qi::Future<std::vector<int> > workIt(std::string, int) {
      return std::vector<int>{}; // Immediate or asynchronous tasks are advised
    }

    qi::Signal<int> ping;
    qi::Property<bool> pingEnabled;

  private: // This session is useless here, but most people will need it
    qi::SessionPtr _session;
  };
} // mylibrary

// Declare your implementation of MyService
QI_REGISTER_IMPLEMENTATION_H(mylibrary::MyService, mylibrary::MyServiceImpl)

QI_REGISTER_IMPLEMENTATION_H will automatically enable the conversion from boost::shared_ptr<MyServiceImpl> to qi::Object<MyService>. It is only needed for the interfaces declared in the IDL, and is not needed for structs, for example.

The last thing to implement is the module associated with your library. It will serve as a cross-language and unified entry point for your service to be started. The code consists in registering types and functions on the module.

#include <qi/anymodule.hpp>
#include "myserviceimpl.hpp"

REGISTER_MYSERVICE(mylibrary::MyServiceImpl);

void registerMe(qi::ModuleBuilder* mb) {
  mb->advertiseFactory<mylibrary::MyService, const qi::SessionPtr&>("MyService");
}

QI_REGISTER_MODULE("mylibrary_module", &registerMe);

Good job, but now it is time to face your compiler =) It is strongly advised to add tests to your project, to do so you can refer to Using strongly typed proxies in another project.

Building a qilang project

  1. Set your host config, here if your have the linux64 toolchain:
$ qibuild set-host-config linux64

This has to be done only once ever, usually.

  1. Build the host tools:
$ qibuild make-host-tools

Do not forget to call it again if you update qilang, for example when syncing a worktree including it.

  1. Build your project
$ qibuild configure
$ qibuild make

Using strongly typed proxies in another project

In another project, to benefit from the proxies you generated in myproject, follow these steps:

  1. Add myproject as a dependency in your qiproject.xml.
  2. Add mylibrary as a dependency of your binary:
qi_create_bin(main "main.cpp" DEPENDS qi mylibrary)
  1. Use the proxy in your code:
#include <qi/applicationsession.hpp>
#include <mylibrary/myservice.hpp>

qiLogCategory("myclient");

int main(int argc, char* argv[])
{
  qi::ApplicationSession app(argc, argv);
  app.start();

  qi::SessionPtr session = app.session();
  mylibrary::MyServicePtr myserv = session->service("MyService");

  for (const auto& value : myserv->workIt("blah", 42))
    qiLogInfo() << "value: " << value;

  return 0;
}

You can also make asynchronous calls like this:

auto future = myserv->async().workIt("blah", 42);
future.connect(mycallback);

Subpackages and subfolder in includes

qilang also supports subpackages in the IDL files, and it has an impact in how the headers will be exposed. Say you have the following IDL file mylibrary/subpackage/mydata.idl.qi:

package mylibrary.subpackage

struct MyData
  number: int
  text: str
end

In the CMake, you can refer to this IDL file in addition to the other ones. The generated interface header will be found in <mylibrary/subpackage/mydata.hpp>.

Note

IDL files must always be stored in a directories matching the package in which they provide definitions. Here the package is mylibrary.subpackage, so the IDL file must be stored in mylibrary/subpackage.