Skip to main content
Skip table of contents

Writing a Greeter Client in C++

This page presents a step-by-step guide to writing the client-side of our C++ Greeter application.

This client creates a proxy to a remote object that implements the Greeter interface and invokes the greet operation on this object.

You can find the complete source code for this example in the ice-demos repository.

Compile Slice File with Slice Compiler

The first step when writing a C++ application with Ice is to compile the Slice definitions for this application with the Slice to C++ compiler (slice2cpp).

Here, we compile the Greeter.ice Slice file we wrote earlier. We recommend that you include this Slice compilation step in your build project, like we demonstrate for the C++ demo programs.

You can easily integrate Slice compilation with most build projects. The C++ demo programs use cmake.

The Slice compiler produces two files from Greeter.ice: a header file, Greeter.h, and a C++ source file, Greeter.cpp. The header file provides the GreeterPrx class we instantiate in the code below.

Client Implementation

The structure of our client is going to look like:

CPP
#include "Greeter.h"

#include <Ice/Ice.h>
#include <future>
#include <iostream>

using namespace std;

int
main(int argc, char* argv[])
{
    // ...
}

Before anything else, we need to include a few header files:

  • Greeter.h: The header file that the Slice compiler generated from Greeter.ice.

  • Ice.h: This header provides definitions that are necessary for accessing the Ice runtime.

  • future, iostream: Standard library headers we use in this client.

Then we get to the interesting part: the main function which will run the client logic. This logic can be broken down into four pieces:

1. Create a Communicator

First, we create a Communicator with Ice::initialize:

CPP
Ice::CommunicatorPtr communicator = Ice::initialize(argc, argv);

The Ptr in the Ice for C++ API are all aliases for shared_ptr. For example:

CODE
using CommunicatorPtr = std::shared_ptr<Communicator>;

The communicator is the main entry point into the Ice runtime. Its responsibilities include establishing connections to servers, caching these connections, and managing configuration properties. We also need a communicator to create a proxy (see next step).

Our client, like most Ice applications, creates a single communicator.

When we no longer need a communicator, we must call destroy on this communicator. This destruction closes network connections and performs other important cleanups. An easy way to do this is by placing the communicator in a CommunicatorHolder. When the holder goes out of scope, its destructor calls destroyon the communicator:

CPP
Ice::CommunicatorHolder communicatorHolder{communicator};

2. Create a Greeter Proxy

Next, we need a way to call on a remote Greeter object. In Ice, this is done with proxies. Proxies are local constructs that represent remote Ice objects and provide functions to call operations on those objects.

We create a Greeter proxy by constructing an instance of the GreeterPrx class generated by the Slice compiler:

CPP
VisitorCenter::GreeterPrx greeter{communicator, "greeter:tcp -h localhost -p 4061"};

The constructor accepts our communicator and a “stringified proxy” with the address of the remote Ice object. Here, our stringified proxy says that the target Ice object is named “greeter” and can be reached via tcp on localhost on port 4061.

The name of the interface (Greeter) and the identity of the Ice object (greeter) are independent. The Ice objects hosted in the server could just as easily have identities like santa, bugsBunny, etc.

3. Make an Invocation

The third step is to call greet on the remote Ice object using our proxy and to print the greeting:

CPP
string greeting = greeter.greet("alice");
cout << greeting << endl;

The greet function does all the heavy lifting for us: the proxy creates a request with the username string, the communicator establishes a connection to localhost:4061, and the request is sent over it. When a response is received, the proxy will unmarshal its payload and finally return a string (the greeting).

Here, we called the synchronous version of greet, which means this function call will block until the response is received. And don’t let the simplicity of the syntax fool you: this is a remote call which will be much slower than a local call!

You can instead call greet asynchronously with greetAsync on the generated GreeterPrx class:

CPP
promise<void> promise;
greeter.greetAsync(
    "bob",
    [&promise](string_view greeting) // response callback
    {
        cout << greeting << endl;
        promise.set_value();
    },
    [&promise](std::exception_ptr exceptionPtr) // exception callback
    { promise.set_exception(exceptionPtr); });

// Wait for the response/exception callback to be called.
promise.get_future().get();

With the asynchronous version, the function call doesn’t block. Instead, we provide callback functions to greetAsync that the communicator calls when it receives the response or when it delivers an exception.

Asynchronous invocations are more semantically correct for remote calls, and they alert readers to the potential delays inherent to these calls. But in C++, they’re significantly more complicated to write… as a result, the best invocation syntax depends on your situation.

GreeterPrx provides another (simpler) greetAsync overload that returns a future instead of the accepting callback functions. This is less complicated but also less useful since we can’t attach continuations to the future - we need to poll for completion, as shown here:

CPP
future<string> futureGreeting = greeter.greetAsync("carol"); // Send the request.

// Wait for the response.
greeting = futureGreeting.get();
cout << greeting << endl;

4. Cleanup

The final step is the end of our main function. At this point, the CommunicatorHolder calls destroy on the communicator, and then our application exits.

CPP
return 0;

Running the Client

After building the client (see the demo’s README for instructions), running it is as simple as running any other executable:

BASH
./build/client

This client won’t work unless you’ve also launched a Greeter server.
Writing and running a Greeter server is covered on the next page.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.