The mapping of Slice interfaces revolves around the idea that, to invoke a remote operation, you call a member function on a local class instance that is a proxy for the remote object. This makes the mapping easy and intuitive to use because making a remote procedure call is no different from making a local procedure call (apart from error semantics).
On this page:
Proxy Classes and Proxy Handles
On the client side, a Slice interface maps to a class with member functions that correspond to the operations on that interface. Consider the following simple interface:
The Slice compiler generates the following definitions for use by the client:
As you can see, the compiler generates a proxy class
Simple in the
IceProxy::M namespace, as well as a proxy handle
M::SimplePrx. In general, for a module
M, the generated names are
In the client's address space, an instance of
IceProxy::M::Simple is the local ambassador for a remote instance of the
Simple interface in a server and is known as a proxy class instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.
Simple inherits from
IceProxy::Ice::Object, reflecting the fact that all Ice interfaces implicitly inherit from
Ice::Object. For each operation in the interface, the proxy class has two overloaded member functions of the same name. For the preceding example, we find that the operation
op has been mapped to two member functions
One of the overloaded member functions has a trailing parameter of type
Ice::Context. This parameter is for use by the Ice run time to store information about how to deliver a request; normally, you do not need to supply a value here and can pretend that the trailing parameter does not exist. (The parameter is also used by IceStorm.)
Client-side application code never manipulates proxy class instances directly. In fact, you are not allowed to instantiate a proxy class directly. The following code will not compile because the proxy class does not provide a public default constructor:
Proxy instances are always instantiated on behalf of the client by the Ice run time, so client code never has any need to instantiate a proxy directly. When the client receives a proxy from the run time, it is given a proxy handle to the proxy, of type
SimplePrx for the preceding example). The client accesses the proxy via its proxy handle; the handle takes care of forwarding operation invocations to its underlying proxy, as well as reference-counting the proxy. This means that no memory-management issues can arise: deallocation of a proxy is automatic and happens once the last handle to the proxy disappears (goes out of scope).
Because the application code always uses proxy handles and never touches the proxy class directly, we usually use the term proxy to denote both proxy handle and proxy class. This reflects the fact that, in actual use, the proxy handle looks and feels like the underlying proxy class instance. If the distinction is important, we use the terms proxy class, proxy class instance, and proxy handle.
Methods on Proxy Handles
As we saw for the preceding example, the handle is actually a template of type
IceInternal::ProxyHandle that takes the proxy class as the template parameter. This template has the usual default constructor, copy constructor, and assignment operator.
You can default-construct a proxy handle. The default constructor creates a proxy that points nowhere (that is, points at no object at all). If you invoke an operation on such a null proxy, you get an
The copy constructor ensures that you can construct a proxy handle from another proxy handle. Internally, this increments a reference count on the proxy; the destructor decrements the reference count again and, once the count drops to zero, deallocates the underlying proxy class instance. That way, memory leaks are avoided:
Note the assertion in this example: proxy handles support comparison.
You can freely assign proxy handles to each other. The handle implementation ensures that the appropriate memory-management activities take place. Self-assignment is safe and you do not have to guard against it:
Widening assignments work implicitly. For example, if we have two interfaces,
Derived, we can widen a
DerivedPrx to a
Implicit narrowing conversions result in a compile error, so the usual C++ semantics are preserved: you can always assign a derived type to a base type, but not vice versa.
Proxy handles provide a
A checked cast has the same function for proxies as a C++
dynamic_cast has for pointers: it allows you to assign a base proxy to a derived proxy. If the type of the base proxy's target object is compatible with the derived proxy's static type, the assignment succeeds and, after the assignment, the derived proxy denotes the same object as the base proxy. Otherwise, if the type of the base proxy's target object is incompatible with the derived proxy's static type, the derived proxy is set to null. Here is an example to illustrate this:
DerivedPrx::checkedCast(base) tests whether
base points at an object of type
Derived (or an object with a type that is derived from
Derived). If so, the cast succeeds and
derived is set to point at the same object as
base. Otherwise, the cast fails and
derived is set to the null proxy.
checkedCast is a static member function so, to do a down-cast, you always use the syntax
Also note that you can use proxies in boolean contexts. For example,
if (proxy) returns true if the proxy is not null.
checkedCast typically results in a remote message to the server.The message effectively asks the server "is the object denoted by this reference of type Derived?"
checkedCast on a proxy that is already of the desired proxy type returns immediately that proxy. Otherwise,
checkedCast always calls
ice_isA on the target object, and upon success, creates a new instance of the desired proxy class.
The reply from the server is communicated to the application code in form of a successful (non-null) or unsuccessful (null) result. Sending a remote message is necessary because, as a rule, there is no way for the client to find out what the actual run-time type of the target object is without confirmation from the server. (For example, the server may replace the implementation of the object for an existing proxy with a more derived one.) This means that you have to be prepared for a
checkedCast to fail. For example, if the server is not running, you will receive a
ConnectFailedException; if the server is running, but the object denoted by the proxy no longer exists, you will receive an
In some cases, it is known that an object supports a more derived interface than the static type of its proxy. For such cases, you can use an unchecked down-cast:
uncheckedCast provides a down-cast without consulting the server as to the actual run-time type of the object, for example:
You should use an
uncheckedCast only if you are certain that the target object indeed supports the more derived type: an
uncheckedCast, as the name implies, is not checked in any way; it does not contact the object in the server and, if it fails, it does not return null. If you use the proxy resulting from an incorrect
uncheckedCast to invoke an operation, the behavior is undefined. Most likely, you will receive an
OperationNotExistException, but, depending on the circumstances, the Ice run time may also report an exception indicating that unmarshaling has failed, or even silently return garbage results.
heckedCast on a proxy that is already of the desired proxy type returns immediately that proxy. Otherwise, unc
heckedCast creates a new instance of the desired proxy class.
Despite its dangers,
uncheckedCast is still useful because it avoids the cost of sending a message to the server. And, particularly during initialization, it is common to receive a proxy of static type
Ice::Object, but with a known run-time type. In such cases, an
uncheckedCast saves the overhead of sending a remote message.
Stream insertion and stringification
For convenience, proxy handles also support insertion of a proxy into a stream, for example:
This code is equivalent to writing:
Either code prints the stringified proxy. You could also achieve the same thing by writing:
The advantage of using the
ice_toString member function instead of
proxyToString is that you do not need to have the communicator available at the point of call.
ice_staticId method returns the type ID of the proxy's Slice interface:
Here's an example that shows how to use it:
Applications normally use
checkedCast instead of calling
Using Proxy Methods in C++
The base proxy class
ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these "factory methods" returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second timeout as shown below:
A factory method returns a new proxy object if the requested modification differs from the current proxy, otherwise it returns the current proxy. With few exceptions, factory methods return a proxy of the same type as the current proxy, therefore it is generally not necessary to repeat a down-cast after using a factory method. The example below demonstrates these semantics:
The only exceptions are the factory methods
ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, therefore they return a base proxy that you must subsequently down-cast to an appropriate type.
Object Identity and Proxy Comparison in C++
Proxy handles support comparison using the following operators:
Proxies have a conversion operator to
bool. The operator returns true if a proxy is not null, and false otherwise. This allows you to write:
These operators permit you to compare proxies for equality and inequality. It's also legal to compare a proxy against the literal
0to test whether a proxy is null, but we recommend using the
Proxies support comparison. This allows you to place proxies into STL containers such as maps or sorted lists.
Note that proxy comparison uses all of the information in a proxy for the comparison. This means that not only the object identity must match for a comparison to succeed, but other details inside the proxy, such as the protocol and endpoint information, must be the same. In other words, comparison with
!= tests for proxy identity, not object identity. A common mistake is to write code along the following lines:
p2 differ, they may denote the same Ice object. This can happen because, for example, both
p2 embed the same object identity, but each use a different protocol to contact the target object. Similarly, the protocols may be the same, but denote different endpoints (because a single Ice object can be contacted via several different transport endpoints). In other words, if two proxies compare equal with
==, we know that the two proxies denote the same object (because they are identical in all respects); however, if two proxies compare unequal with
==, we know absolutely nothing: the proxies may or may not denote the same object.
To compare the object identities of two proxies, you can use helper functions in the
proxyIdentityEqual function returns true if the object identities embedded in two proxies are the same and ignores other information in the proxies, such as facet and transport information. To include the facet name in the comparison, use
proxyIdentityLess function establishes a total ordering on proxies. It is provided mainly so you can use object identity comparison with STL sorted containers. (The function uses
name as the major ordering criterion, and
category as the minor ordering criterion.) The
proxyIdentityAndFacetLess function behaves similarly to
proxyIdentityLess, except that it also compares the facet names of the proxies when their identities are equal.
proxyIdentityAndFacetLess allow you to correctly compare proxies for object identity. The example below demonstrates how to use