[RMI-like-lib] is there any interest in such?

I've been thinking of a library that would bring RMI-like capabilities to C++ (RMI stands for Remote Method Invocation). Primary goal, and differentiating feature, of this library is not to rely on IDL-like compiler, hence no cross-language support either -- if you need that, check out CORBA (www.omg.org) or ICE (http://www.zeroc.com). As an example, I'm going to "implement" client and server sides of a trivial calculator using proposed library. Step 1. Define calculator interface ----------------------------------- // // Operations: add, subtract, multiply and divide // struct add { typedef function2<double, double, double> function_type; }; struct subtract { typedef function2<double, double, double> function_type; }; struct multiply { typedef function2<double, double, double> function_type; }; struct divide { typedef function2<double, double, double> function_type; }; // Calculator interface // typedef interface< operation<sum>, operation<subtract>, operation<multiply>, operation<divide> > calculator_i; Step 2. Client invocation ------------------------- // Construct a proxy to the remote calculator // instance. Arguments to the constructor // describe remote object's location (not shown). // proxy<calculator_i> calculator(...); // Call `subtract' on remote object (call // doesn't block). Returned object implements // Asynchronous Completion Token (or Future) // concept. // act<double> res = call<subtract>(calculator, 12, 43); // Retrieve actual result, or get an exception... // double result = res(); Step 3. Server implementation ----------------------------- // Implement actual calculator. // struct calculator_servant { double sum(double a, double b) { return a + b; } ... // other operations here }; // Instantiate calculator servant // calculator_servant servant; // Create a TCP listener. // tcp_listener listener(...) // Create a stub object that acts as an // adapter between transport and a servant. // stub<calculator_i> calculator(listener); // Install operation handlers. // calculator.register_function<add>(bind(&calculator_servant::sum, ref(servant), _1, _2)); ... // register remaining handlers // Start accepting calls. // calculator.activate(); // Block until exiting. // calculator.wait_for_shutdown(); ... -- end of example] I'm fairly satisfied with the client side, but I'm not so enthusiastic about the server side though. For one, it feels less safe. For example, what if a user forgets to register a "handler" for a particular operation? Any way to enforce this at compile time? Typically, say with CORBA, IDL compiler generates operations as pure virtual functions, so at compile time, the user knows what's left out in her servant. On the other side, it's quite powerful too (being able to "bind" functors to operations, that is). Would that be a problem for you or you'd see it as a good feature? Let me know what you think. Cheers, Slawomir Lisznianski

Slawomir Lisznianski wrote:
I've been thinking of a library that would bring RMI-like capabilities to C++ (RMI stands for Remote Method Invocation).
no cross-language support
That's bad, in my opinion. This excludes most of the useful applications of RMI in distributed systems, where one of the reasons to have a distributed system is to postpone the implementation choices (language in particular) or even to *enable* programs in different languages to communicate.
As an example, I'm going to "implement" client and server sides of a trivial calculator using proposed library.
struct add { typedef function2<double, double, double> function_type; };
How do you plan to solve the problem of double (and any other type for that matter) having implementation-defined size (and even representation)? Apart from that, how do you plan to encode *names* of messages, if in the code they exist only as compile-time names of types? The server has to somehow map incoming message to one of the registered operations. How? -- Maciej Sobczak : http://www.msobczak.com/ Programming : http://www.msobczak.com/prog/

Maciej Sobczak wrote:
no cross-language support
That's bad, in my opinion. This excludes most of the useful applications of RMI in distributed systems,
As already mentioned, there are already libraries (or I should say, platforms, as they come with misc tools such as interface language compilers) that do just that. Having cross-language support requires external interfacing language and use of a subset of native-language features. Here, the goal is to support virtually any C++ type, including user types, (say, compatible with Boost Serialization) to be marshallable without having to map it to some common "wire" type.
How do you plan to solve the problem of double (and any other type for that matter) having implementation-defined size (and even representation)?
If we were to use Boost Serialization, the problem of type sizes and endianess would imply use of text serialization, at the cost of performance. Unless of course BSL extends its archiving support for XDR-like format.
Apart from that, how do you plan to encode *names* of messages, if in the code they exist only as compile-time names of types? The server has to somehow map incoming message to one of the registered operations. How?
Each operation is part of an interface type, and has a unique id (enum) within that interface. The id sent over the wire which is then used on the server side to dispatch the operation to the bound functor. The client and server sides are assumed to share the same interface def. Cheers, Slawomir

Slawomir Lisznianski wrote:
Having cross-language support requires external interfacing language
Why? For example, in CORBA the use of IDL is optional and all communicating parties can rely on dynamic interface operations. I guess that other standards allow this as well (which is actually good if you take into account applications like generic bridges or languages with extensive support for reflection).
and use of a subset of native-language features.
Why?
Here, the goal is to support virtually any C++ type, including user types, (say, compatible with Boost Serialization)
OK.
Apart from that, how do you plan to encode *names* of messages, if in the code they exist only as compile-time names of types? The server has to somehow map incoming message to one of the registered operations. How?
Each operation is part of an interface type, and has a unique id (enum) within that interface.
1. How is this id supposed to be allocated? 2. Who has the responsibility to assign unique ids to operations? The above issues are not clear from the code examples you have shown. I guess that it is the *order* of types that appear as the parameters to the interface template, right? What about "objects" and their lifetime? It would be very limiting to have only one "set" of operations in the server. The library has to support many "objects" (even having the same interface) that come and go during the work of the server. How to identify and address those "objects"? (I use quotation marks above to stress that "objects" in a distributed system are of logical rather than C++ nature. In the most straightforward implementation, each such "object" is implemented by a separate C++ object on the server side - and their lifetimes match - but that need not be the case in general.) How about clients addressing the servers? -- Maciej Sobczak : http://www.msobczak.com/ Programming : http://www.msobczak.com/prog/

Maciej Sobczak wrote:
Having cross-language support requires external interfacing language
Why? For example, in CORBA the use of IDL is optional and all communicating parties can rely on dynamic interface operations.
Well, at the cost of type safety. DII is also considerably slower and relies on Interface Repository.
... or languages with extensive support for reflection).
C++ doesn't fit in this category, does it? ;-)
and use of a subset of native-language features.
Why?
Because you need to narrow down to what's common between languages for which binding is to be provided. As an example, cannot use `unsigned' type qualifiers or enums as they may not exist (Java comes to mind -- although they got enums in 1.5).
1. How is this id supposed to be allocated? 2. Who has the responsibility to assign unique ids to operations?
Template magic. Users don't know about "ids" -- they're inner workings.
The above issues are not clear from the code examples you have shown.
Code examples were high-level (read, naive) and aimed to show user interactions. Once I have you all wormed up I'll demonstrate code ;-)
guess that it is the *order* of types that appear as the parameters to the interface template, right?
bingo, order is of quite significance!
What about "objects" and their lifetime?
Good question. I have it partially answered in a parallel thread (boost-users). Snippet: "...CORBA-like object semantics with support for "transient" instances. For example, when you construct a client-proxy, out of a portable stringified reference, currently URI, you are pointing at a concrete stub/servant instance. If that stub/servant was brought down, your reference is no longer valid and "transient" exception is thrown on any operations against it. In other words, say TCP address and port are not pointing at a servant yet. It takes "instance id", think of it as `this' pointer in C++, to reach a particular servant. Why this is considered important? Because it allows for stateful remote objects. You can construct N-number of instances of the same class of servant, and each one will have a distinguishing reference that the client can call."
It would be very limiting to have only one "set" of operations in the server.
Couldn't agree more.
How about clients addressing the servers?
Once servant is up, it can be queried for a "reference to self". This reference can be stringified (say, as URI). Client-side proxies use references to refer to particular servant instances. Cheers, Slawomir
participants (2)
-
Maciej Sobczak
-
Slawomir Lisznianski