digitalmars.D - RPC and Dynamic function call
- Denis Koroskin (483/483) Nov 20 2009 I am working on a prototype of PRC library and it involves a wide range ...
- grauzone (55/64) Nov 20 2009 Yes, it is possible. You'll have to pass the method as alias template
- Denis Koroskin (63/127) Nov 21 2009 I thought about that, too. And then I thought:
- grauzone (18/40) Nov 21 2009 And you have to special case this in very awkward ways. What if a struct...
- Rory McGuire (18/21) Nov 21 2009 cut
- Denis Koroskin (4/24) Nov 21 2009 You missed the point. I only used asm to invoke function on remote side....
- Rory McGuire (126/166) Nov 22 2009 :) I thought I had missed the point thats why I asked :D.
I am working on a prototype of PRC library and it involves a wide range of
techniques that I also implement. I'd like to share my code,
implementation details and issues I come across. I will split my report
into a few posts. This one is mostly an introduction.
RPC is a concept that allows to execute some code in a remote application.
To make it work, you must know function name, a set of arguments it
accepts, type it returns etc.
This is usually is not a problem if local and remote applications are same
or based on shared code.
The two applications may be ran in different environments (different
Operation Systems, different memory modes, x86/x64 etc).
In order to make a call a few components are required:
Function (one that we want to call), context (remote object reference; not
needed for static member functions and global/free functions), input/ouput
arguments.
A RPC requested is created locally. It contains everything in a list
above. Input arguments might be reference type, in which case they might
have references to other objects which in turn have references to other
objects etc. In general, everything accessible through a set of input
arguments might be accessed on remote side. This is why the whole object
hierarchy accessible through input arguments need to be saved, sent over
network and fully restored on remote side.
This is called serialization. The fastest and most compact form of
serialization is binary serialization. I'll talk about serialization in
one of the next posts.
Next thing we need to do is to somehow encode the function we are gonna
call in a way that remote application will understand and get a proper
function pointer out of this information. This has something to do with
reflection (although pretty simple mechanism is required).
Okay, now we received a RPC request that contains function pointer and a
set of input arguments. We also have some knowledge on ouput arguments.
All we need to do is to call that function and return back result.
At this point, it's worth to note that all we have at remote side is some
void[] array. No types. Once we step out of type system, there is no way
back. It means that you can't really call a function in a type-safe manner
anymore (well, one could create a set of trampolines for each of set of
types involved in a call, but I don't think it's reasonable or even
possible; I'll look into it, too, though). That's why you need to make a
dynamic call: iterate over some kind of input arguments' type information,
push values to stack, call function via funcptr, store the result etc.
I was the last piece to make RPC work, that's what I was implementing
today and that's what I will share in this post. The resulting code is
quite small and simple.
Note that all the asm it uses is just 3 constructs: naked, ret and call!
Everything else is implemented with a help of compiler (i.e. pushing
arguments, grabbing result, stack alignment etc). My code is most probably
not very portable (I didn't test it on anything other that Windows), it
doesn't account x64 specifics etc, but it shouldn't be hard to fix.
When could it be useful? A few applications include RPC, reflection
(possibly), binding to other languages (including scripting ones), code
injection/hijacking, run-time code generation etc.
Any suggestions, critics, comments are highly appreciated.
Full listing below (tested with DMD2.036, Windows XP / Windows 7):
module DynamicCall;
import std.traits;
import std.stdio;
enum ParamType
{
// No return value
Void,
// ST0
Float,
Double,
Real,
// EAX
Byte,
Word,
DWord,
Pointer,
// AArray, // isn't tested (merge with Pointer?)
//EDX,EAX
QWord,
DArray, // isn't tested
// Hidden pointer
Hidden_Pointer, // for returning large (>8 bytes) structs, not tested yet
}
struct Param
{
ParamType type; // type of value
void* ptr; // pointer to value
}
// This struct describes everything needed to make a call
struct Call
{
Param[] input; // set of input arguments
Param output; // result info
void* funcptr; // function pointer
}
// makes a call, stores result in call.ouput
void makeDynamicCall(Call* call)
{
switch (call.output.type) {
case ParamType.Void:
_makeCall!(void)(call);
break;
case ParamType.Pointer:
makeCall!(void*)(call);
break;
case ParamType.Byte:
makeCall!(byte)(call);
break;
case ParamType.Word:
makeCall!(short)(call);
break;
case ParamType.DWord:
makeCall!(int)(call);
break;
case ParamType.QWord:
makeCall!(long)(call);
break;
case ParamType.Float:
makeCall!(float)(call);
break;
case ParamType.Double:
makeCall!(double)(call);
break;
case ParamType.Real:
makeCall!(real)(call);
break;
}
}
// helper function to save some typing
void makeCall(T)(Call* call)
{
*cast(T*)call.output.ptr = _makeCall!(T)(call);
}
T _makeCall(T)(Call* call)
{
void* funcptr = call.funcptr;
void* argptr;
int numArgs = call.input.length;
if (numArgs != 0) { // this check is needed because last parameter is
passed in EAX (if possible)
Param* param = call.input.ptr;
// iterate over first numArgs-1 arguments
for ( ; --numArgs; ++param) {
/*
// the following doesn't work for some reason (compiles but lead to
wrong result in run-time)
// would be so much more elegant!
push!(arg)(param);
/*/
argptr = param.ptr;
switch (param.type) {
case ParamType.Byte: // push byte
arg(*cast(byte*)argptr);
break;
case ParamType.Word: // push word
arg(*cast(short*)argptr);
break;
case ParamType.Pointer:
case ParamType.DWord: // push dword
arg(*cast(int*)argptr);
break;
case ParamType.QWord: // push qword
arg(*cast(long*)argptr);
break;
case ParamType.Float: // push float
arg(*cast(float*)argptr);
break;
case ParamType.Double: // push double
arg(*cast(double*)argptr);
break;
case ParamType.Real: // push real
arg(*cast(real*)argptr);
break;
}
//*/
}
// same as above but passes in EAX if possible
/*
push!(lastArg)(param);
/*/
argptr = param.ptr;
switch (param.type) {
case ParamType.Byte:
lastArg(*cast(byte*)argptr);
break;
case ParamType.Word:
lastArg(*cast(short*)argptr);
break;
case ParamType.Pointer:
case ParamType.DWord:
lastArg(*cast(int*)argptr);
break;
case ParamType.QWord:
lastArg(*cast(long*)argptr);
break;
case ParamType.Float:
lastArg(*cast(float*)argptr);
break;
case ParamType.Double:
lastArg(*cast(double*)argptr);
case ParamType.Real:
lastArg(*cast(real*)argptr);
}
//*/
}
asm {
// call it!
call funcptr;
}
}
// A helper function that pushes an argument to stack in a type-safe manner
// extern (System) is used so that argument isn't passed via EAX
// does it work the same way in Linux? Or Linux uses __cdecl?
// There must be other way to pass all the arguments on stack, but this
one works well so far
// Beautiful, isn't it?
extern (System) void arg(T)(T arg)
{
asm {
naked;
ret;
}
}
// A helper function that pushes an argument to stack in a type-safe manner
// Allowed to pass argumet via EAX (that's why it's extern (D))
void lastArg(T)(T arg)
{
asm {
naked;
ret;
}
}
// Compare it to my older implementation:
/+
T _makeCall(T)(Call* call)
{
void* funcptr = call.funcptr;
void* argptr;
int i = call.input.length;
int eax = -1;
foreach (ref param; call.input) {
--i;
argptr = param.ptr;
switch (param.type) {
case ParamType.Byte:
// passing word
asm {
mov EDX, argptr;
mov AL, byte ptr[EDX];
}
if (i != 0) {
asm {
push EAX;
}
} else {
asm {
mov eax, EAX;
}
}
break;
case ParamType.Word:
// passing word
asm {
mov EDX, argptr;
mov AX, word ptr[EDX];
}
if (i != 0) {
asm {
push EAX;
}
} else {
asm {
mov eax, EAX;
}
}
break;
case ParamType.Pointer:
case ParamType.DWord:
// passing word
asm {
mov EDX, argptr;
mov EAX, dword ptr[EDX];
}
if (i != 0) {
asm {
push EAX;
}
} else {
asm {
mov eax, EAX;
}
}
break;
case ParamType.QWord:
// pushing word
asm {
mov EDX, argptr;
mov EAX, dword ptr[EDX+4];
push EAX;
mov EAX, dword ptr[EDX];
push EAX;
}
break;
case ParamType.Float:
// pushing float
asm {
sub ESP, 4;
mov EAX, dword ptr[argptr];
fld dword ptr[EAX];
fstp dword ptr[ESP];
}
break;
case ParamType.Double:
// pushing double
asm {
sub ESP, 8;
mov EAX, qword ptr[argptr];
fld qword ptr[EAX];
fstp qword ptr[ESP];
}
break;
}
}
asm {
mov EAX, eax;
call funcptr;
}
}
+/
// I was trying to move out common code to a separate function, but
failed. It doesn't work for reasons unknown to me
/+
void push(alias fun)(Param* param)
{
switch (param.type) {
case ParamType.Byte:
fun(*cast(byte*)param.ptr);
break;
case ParamType.Word:
fun(*cast(short*)param.ptr);
break;
case ParamType.Pointer:
case ParamType.DWord:
fun(*cast(int*)param.ptr);
break;
case ParamType.QWord:
fun(*cast(long*)param.ptr);
break;
case ParamType.Float:
fun(*cast(float*)param.ptr);
break;
case ParamType.Double:
fun(*cast(double*)param.ptr);
break;
case ParamType.Real:
fun(*cast(real*)param.ptr);
break;
}
}
+/
// Convenient templates to map from type T to corresponding ParamType enum
element
template isStructSize(T, int size)
{
enum isStructSize = is (T == struct) && T.sizeof == size;
}
template ParamTypeFromT(T) if (is (T == byte) || is (T == ubyte) || is (T
== char) || isStructSize!(T, 1))
{
alias ParamType.Byte ParamTypeFromT;
}
template ParamTypeFromT(T) if (is (T == short) || is (T == ushort) || is
(T == wchar) || isStructSize!(T, 2))
{
alias ParamType.Word ParamTypeFromT;
}
template ParamTypeFromT(T) if (is (T == int) || is (T == uint) || is (T ==
dchar) || isStructSize!(T, 4))
{
alias ParamType.DWord ParamTypeFromT;
}
template ParamTypeFromT(T) if (is (T == long) || is (T == ulong) ||
isStructSize!(T, 8) || is (T == delegate))
{
alias ParamType.QWord ParamTypeFromT;
}
template ParamTypeFromT(T) if (is (T == float))
{
alias ParamType.Float ParamTypeFromT;
}
template ParamTypeFromT(T) if (is (T == double))
{
alias ParamType.Double ParamTypeFromT;
}
template ParamTypeFromT(T) if (is (T == real))
{
alias ParamType.Real ParamTypeFromT;
}
template ParamTypeFromT(T) if (is (T == void))
{
alias ParamType.Void ParamTypeFromT;
}
template ParamTypeFromT(T) if (isPointer!(T))
{
alias ParamType.Pointer ParamTypeFromT;
}
Test case:
import DynamicCall;
import std.stdio;
Param createParam(T)(T value)
{
//T* ptr = new T; // BUG! Doesn't work for delegate type
T* ptr = cast(T*)(new void[T.sizeof]).ptr;
*ptr = value;
Param param;
param.type = ParamTypeFromT!(T);
param.ptr = cast(void*)ptr;
return param;
}
struct b1
{
char c;
}
struct b2
{
wchar w;
}
struct b4
{
dchar d;
}
real foo(byte b, short s, int i, long l, float f, double d, real r, b1 c,
b2 w, b4 d2, int function() func, int delegate() dg, int* ptr)
{
writeln(b + s + i + l + f + d + r + c.c + w.w + d2.d + func() + dg() +
*ptr);
return 13;
}
int func()
{
return 42;
}
void main()
{
int dg()
{
return 14;
}
Call call;
call.funcptr = &foo;
int* i = new int;
*i = 128;
call.input ~= createParam!(byte)(cast(byte)1); // BUG! cast is mandatory
to compile
call.input ~= createParam!(short)(200);
call.input ~= createParam!(int)(4);
call.input ~= createParam!(long)(cast(long)8);
call.input ~= createParam!(float)(16.0f);
call.input ~= createParam!(double)(32.0);
call.input ~= createParam!(real)(64.0);
call.input ~= createParam!(b1)(b1(1));
call.input ~= createParam!(b2)(b2(2));
call.input ~= createParam!(b4)(b4(4));
call.input ~= createParam!(int function())(&func);
call.input ~= createParam!(int delegate())(&dg);
call.input ~= createParam!(int*)(i);
call.output.type = ParamType.Real;
call.output.ptr = new real;
makeDynamicCall(&call);
writeln(*cast(real*)call.output.ptr);
}
Nov 20 2009
Denis Koroskin wrote:type-safe manner anymore (well, one could create a set of trampolines for each of set of types involved in a call, but I don't think it's reasonable or even possible; I'll look into it, too, though). That's whyYes, it is possible. You'll have to pass the method as alias template parameter. Then you get a tuple of the parameter types. You can foreach over that tuple and serialize/deserialize the actual values and read/write them from the tuple. You also can declare a nested function that does the actual call to the server's function. That delegate can have a type independent from the method, and thus can be stored in the non-template world. Basically like this (pseudo code): //client function to invoke a specific RPC //the parameters can be passed normally thanks to tuples void makeDynamicCall(Params...)(Stream stream, char[] method, Params p) { stream.write(method); //serialize the parameters foreach (int index, _; p) { auto val = p[index]; stream.write!(typeof(val))(val); } } alias void delegate(Stream) Stub; //registry of server functions Stub[char[]] stubs; //the server network code calls this on incomming RPC requests void receiveDynamicCall(Stream stream) { auto method = stream.read!(char[])(); stubs[method].call(stream); } //the server calls this on program initialization //he passes an alias to the server function, and its name void registerStub(alias Function)(char[] name) { //generate code to deserialize a RPC and to call the server void stub(Stream stream) { //you can get the param tuple of a function //Phobos2 and Tango should have something similar alias ParamTupleOfFunction!(Function) Params; //deserialize the arguments Params p; foreach (int index, _; p) { alias typeof(p[index]) PT; p[index] = stream.read!(PT)(); } //actually call the function Function(p); } stubs[name] = &stub; } It all can be typesafe and non-compiler specific. The question is; how much template bloat will this emit into the application binary? You have to consider the instantiations of Stream.read and Stream.write, too.Note that all the asm it uses is just 3 constructs: naked, ret and call! Everything else is implemented with a help of compiler (i.e. pushing arguments, grabbing result, stack alignment etc). My code is most probably not very portable (I didn't test it on anything other that Windows), it doesn't account x64 specifics etc, but it shouldn't be hard to fix.Wow, I'm surprised that this works. Actually, I'd *like* to do it this way, but the code is completely compiler and platform specific. Maybe it also depends from the compiler code generator's mood if it works. And if you want to deal with user-define types (structs), you're completely out of luck.
Nov 20 2009
On Sat, 21 Nov 2009 06:54:11 +0300, grauzone <none example.net> wrote:Denis Koroskin wrote:I thought about that, too. And then I thought: 1) you can move deserialization out of this method (my serializer doesn't need type hints to deserialize data). 2) you can reuse the trampoline for those functions that have same type of input argument (less bloat) 3) you can create a trampoline for each type, not for each set of types (N trampolines instead of N! in worst case) One more step and you don't even need for those trampolines (just carry some type information and translate call to trampoline into a switch). That's how I ended up with my implementation.type-safe manner anymore (well, one could create a set of trampolines for each of set of types involved in a call, but I don't think it's reasonable or even possible; I'll look into it, too, though). That's whyYes, it is possible. You'll have to pass the method as alias template parameter. Then you get a tuple of the parameter types. You can foreach over that tuple and serialize/deserialize the actual values and read/write them from the tuple. You also can declare a nested function that does the actual call to the server's function. That delegate can have a type independent from the method, and thus can be stored in the non-template world. Basically like this (pseudo code): //client function to invoke a specific RPC //the parameters can be passed normally thanks to tuples void makeDynamicCall(Params...)(Stream stream, char[] method, Params p) { stream.write(method); //serialize the parameters foreach (int index, _; p) { auto val = p[index]; stream.write!(typeof(val))(val); } } alias void delegate(Stream) Stub; //registry of server functions Stub[char[]] stubs; //the server network code calls this on incomming RPC requests void receiveDynamicCall(Stream stream) { auto method = stream.read!(char[])(); stubs[method].call(stream); } //the server calls this on program initialization //he passes an alias to the server function, and its name void registerStub(alias Function)(char[] name) { //generate code to deserialize a RPC and to call the server void stub(Stream stream) { //you can get the param tuple of a function //Phobos2 and Tango should have something similar alias ParamTupleOfFunction!(Function) Params; //deserialize the arguments Params p; foreach (int index, _; p) { alias typeof(p[index]) PT; p[index] = stream.read!(PT)(); } //actually call the function Function(p); } stubs[name] = &stub; } It all can be typesafe and non-compiler specific. The question is; how much template bloat will this emit into the application binary? You have to consider the instantiations of Stream.read and Stream.write, too.Why? It works well with custom structs, too. There is a test case attached, try to run in, it should work. One of the reasons I posted the code is because I'm not sure it is correct and that it will always work (on a given platform). I'm not an ASM guru, so comments would be invaluable. For example, I use the following trick to push arguments to stack: // A helper function that pushes an argument to stack in a type-safe manner // extern (System) is used so that argument isn't passed via EAX // does it work the same way in Linux? Or Linux uses __cdecl? // There must be other way to pass all the arguments on stack, but this one works well so far // Beautiful, isn't it? extern (System) void arg(T)(T arg) { asm { naked; ret; } } // A helper function that pushes an argument to stack in a type-safe manner // Allowed to pass argumet via EAX (that's why it's extern (D)) void lastArg(T)(T arg) { asm { naked; ret; } } Explanation: when one of these functions are called, arguments to them are pushed to stack and/or target registers properly (compiler does it for me automatically). By convention, extern (D) and extern (Windows) aka __stdcall function pop their arguments from the stack on their own. Naked function (asm naked) lack prolog and epilog (i.e. they don't pop arguments and restore stack). As a result, these function push their arguments to stack the way compiler wants them while still not being compiler specific. lastArg uses extern (D) calling convention that allows passing last argument in EAX (this is specified in http://www.digitalmars.com/d/2.0/abi.html) How safe it is? How portable it is? As far as I know all major compilers support naked functions (Microsoft C++ compiler: _declspec( naked ), GCC: __attribute__(naked)) This is 2 out of 3 uses of asm in my code. The third one is: void* functptr = ...; asm { call funcptr; } I think this is also implementable on other platforms. I updated code to work with Tango, could anybody please try to run it on Linux x86/x86_64 (ldc)? An output should be: 516.00 13.00Note that all the asm it uses is just 3 constructs: naked, ret and call! Everything else is implemented with a help of compiler (i.e. pushing arguments, grabbing result, stack alignment etc). My code is most probably not very portable (I didn't test it on anything other that Windows), it doesn't account x64 specifics etc, but it shouldn't be hard to fix.Wow, I'm surprised that this works. Actually, I'd *like* to do it this way, but the code is completely compiler and platform specific. Maybe it also depends from the compiler code generator's mood if it works. And if you want to deal with user-define types (structs), you're completely out of luck.
Nov 21 2009
Denis Koroskin wrote:I thought about that, too. And then I thought: 1) you can move deserialization out of this method (my serializer doesn't need type hints to deserialize data). 2) you can reuse the trampoline for those functions that have same type of input argument (less bloat) 3) you can create a trampoline for each type, not for each set of types (N trampolines instead of N! in worst case)That would be interesting.And you have to special case this in very awkward ways. What if a struct is larger than 8 bytes? This whole approach is so shaky, I don't trust it one bit. It would be another thing if all this would be part of the runtime core (and the compiler implementer makes sure it works).Wow, I'm surprised that this works. Actually, I'd *like* to do it this way, but the code is completely compiler and platform specific. Maybe it also depends from the compiler code generator's mood if it works. And if you want to deal with user-define types (structs), you're completely out of luck.Why? It works well with custom structs, too. There is a test case attached, try to run in, it should work.One of the reasons I posted the code is because I'm not sure it is correct and that it will always work (on a given platform). I'm not an ASM guru, so comments would be invaluable.Neither am I, but I guess it will be very hard to emulate calling conventions on non-x86 32 bit platforms. E.g. on amd64, several arguments are passed in registers (AFAIK). In any case, you trust the compiler not to insert additional code and not to touch the stack. Maybe it works with dmd, but what about ldc or gdc? Well, let's see if anyone can test it... Now, this doesn't really matter. You can always rewrite it as asm code. And in asm, you definitely can do such dynamic calls. One issue is that you need the static signature of the RPC target for type checking. What are you going to do about this?I updated code to work with Tango, could anybody please try to run it on Linux x86/x86_64 (ldc)? An output should be: 516.00 13.00Segfaulted on x86 32 bit / Tango / Linux.
Nov 21 2009
"Denis Koroskin" <2korden gmail.com> wrote:"Denis Koroskin" <2korden gmail.com> wrote: ...cut...Why did you use asm etc...? I made a de/serializer that just uses templates and static ifs. it doesn't do serialization of delegates etc... but I don't know that moving executable code across would be safe (I think java can do it, but then it has a vm). mine does: int, long, double, string, object, struct. I can post the code if you want. So far I have only used it to present the methods of an object as a kind of server, with a client being able to connect to it. Was inspired by hessiand on dsource.org. Can post code if you would like to see. I posted the automatic object wrapping code a couple of days ago as a simple example of template usage Thread: "Metaprogramming in D: Some real world examples". -Rory
Nov 21 2009
On Sat, 21 Nov 2009 12:42:35 +0300, Rory McGuire <rjmcguire gmail.com> wrote:"Denis Koroskin" <2korden gmail.com> wrote:You missed the point. I only used asm to invoke function on remote side. Serialization is done purely with templates."Denis Koroskin" <2korden gmail.com> wrote: ...cut...Why did you use asm etc...? I made a de/serializer that just uses templates and static ifs. it doesn't do serialization of delegates etc... but I don't know that moving executable code across would be safe (I think java can do it, but then it has a vm). mine does: int, long, double, string, object, struct. I can post the code if you want. So far I have only used it to present the methods of an object as a kind of server, with a client being able to connect to it. Was inspired by hessiand on dsource.org. Can post code if you would like to see. I posted the automatic object wrapping code a couple of days ago as a simple example of template usage Thread: "Metaprogramming in D: Some real world examples". -Rory
Nov 21 2009
"Denis Koroskin" <2korden gmail.com> wrote:On Sat, 21 Nov 2009 12:42:35 +0300, Rory McGuire <rjmcguire gmail.com> wrote::) I thought I had missed the point thats why I asked :D. On the Server side I used a similar method that the guy who wrote hessiand used for calling the objects methods, my code: module servers.ObjectSocketServer; //import serialize.ISerializer; import transports.ITransport; import transports.SocketTransport; import tango.core.Exception; version (Tango) { alias char[] string; import tango.net.Socket; import tango.io.Stdout; import tango.core.Traits; // alias ParameterTupleOf ParameterTypeTuple; } class ObjectSocketServer(T,C) { Socket request_conn; T serializer; ITransport transport; C instance; IMethod[string] methods; this(Socket request_conn) { instance = new C(); transport = new SocketTransport(request_conn); serializer = new T(transport); this.request_conn = request_conn; } ~this() { request_conn = null; serializer = null; transport = null; foreach (key, method; methods) { methods.remove(key); } instance = null; } void close() { try { if (request_conn !is null) { request_conn.shutdown(SocketShutdown.BOTH); request_conn.detach(); } } catch (Exception e) {} } /+ void expose(char[] method)() { pragma(msg, mixin(MethodType_mixin!(method)())); } +/ void expose(alias M)() { const string name = NameOfFunc!(M); //pragma(msg, "expose: "~ name); pragma(msg, "expose:\n"~ GenMethod!(C, name)); methods[name] = mixin(GenMethod!(C, name)); } void run() { scope(exit) close(); try { Stdout("run").newline; int i; while (true) { //Stdout("again").newline; auto method = serializer.read!(string)(); //Stdout("method = "~ method).newline; try { if (!(method in methods)) throw new Exception("Not Found"); methods[method](serializer, instance); } catch (Exception e) { e.writeOut((char[] str) { Stdout(str).newline;}); Stdout("Caught exception must serialize: ")(e.line)(":") (e.file)(" ")(e.toString).newline; } Stdout(i)("\r")(); i++; } Stdout().newline; } catch (Exception e) { Stdout("Caught exception: ")(e.toString).newline; } } interface IMethod { string getName(); void opCall(T, C); } } // this would be inside the class but cant be string GenMethod(alias C, string name)() { string ret = "new class IMethod {\n"; ret ~= "\tstring getName() { return \""~ name ~"\"; }\n"; ret ~= "\tvoid opCall(T serializer, C instance) {\n"; ret ~= "\t\tserializer.write(instance."~name~"(serializer.read! ("~ParameterTupleOf!(mixin(MethodType_mixin!(name)()))[0].stringof~")()));\n"; foreach (i,p; ParameterTupleOf!(mixin(MethodType_mixin!(name)()))[1..$]) { ret ~= "\t\tserializer.write(instance."~name~"(serializer.read! ("~p.stringof~")()));\n"; } ret ~= "\t\tserializer.getTransport().flush();\n"; ret ~= "\t}\n"; ret ~= "}\n"; return ret; } public template NameOfFunc(alias f) { version (LDC) { const char[] NameOfFunc = (&f).stringof[1 .. $]; } else { const char[] NameOfFunc = (&f).stringof[2 .. $]; } } string MethodType_mixin(string method)() { return "typeof(&C.init."~ method ~")"; }"Denis Koroskin" <2korden gmail.com> wrote:You missed the point. I only used asm to invoke function on remote side. Serialization is done purely with templates."Denis Koroskin" <2korden gmail.com> wrote: ...cut...Why did you use asm etc...? I made a de/serializer that just uses templates and static ifs. it doesn't do serialization of delegates etc... but I don't know that moving executable code across would be safe (I think java can do it, but then it has a vm). mine does: int, long, double, string, object, struct. I can post the code if you want. So far I have only used it to present the methods of an object as a kind of server, with a client being able to connect to it. Was inspired by hessiand on dsource.org. Can post code if you would like to see. I posted the automatic object wrapping code a couple of days ago as a simple example of template usage Thread: "Metaprogramming in D: Some real world examples". -Rory
Nov 22 2009









grauzone <none example.net> 