www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - RPC and Dynamic function call

reply "Denis Koroskin" <2korden gmail.com> writes:
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
next sibling parent reply grauzone <none example.net> writes:
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 why 
Yes, 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
parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Sat, 21 Nov 2009 06:54:11 +0300, grauzone <none example.net> wrote:

 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 why
Yes, 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.
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.
 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.
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.00
Nov 21 2009
parent grauzone <none example.net> writes:
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.
 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.
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).
 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.00
Segfaulted on x86 32 bit / Tango / Linux.
Nov 21 2009
prev sibling parent reply Rory McGuire <rjmcguire gmail.com> writes:
"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
parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Sat, 21 Nov 2009 12:42:35 +0300, Rory McGuire <rjmcguire gmail.com>  
wrote:

 "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
You missed the point. I only used asm to invoke function on remote side. Serialization is done purely with templates.
Nov 21 2009
parent Rory McGuire <rjmcguire gmail.com> writes:
"Denis Koroskin" <2korden gmail.com> wrote:
 
 On Sat, 21 Nov 2009 12:42:35 +0300, Rory McGuire <rjmcguire gmail.com>  
 wrote:
 
 "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
You missed the point. I only used asm to invoke function on remote side. Serialization is done purely with templates.
:) 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 ~")"; }
Nov 22 2009