Seite 1 von 1

ENM SimpleRPC (Remote Procedure Call)

Verfasst: 29.06.2011 00:28
von cxAlex
Endlich mal ein sinnvolles Beispiel für ENM.

Ich habe einen kleinen RPC - Server und Client auf Basis von ENM implementiert. Der Source wird mit dem nächsten Release mitgeliefert, da das aber noch etwas dauern wird poste ich den Code jetzt schonmal damit ihr damit spielen könnt ;).

Aufgrund eines kleinen Bugs im Ping-Modul von ENM funktioniert der Code derzeit NICHT unter Unicode, das wird im nächsten Release behoben. Einfach als Ascii kompilieren oder das Ping-Modul in den Settings ausschalten.

Die Verwendung ist simpelst und sollte selbsterklärend sein. Es wurde bewusst auf komplexere Funktionen verzichtet (Speicher Blöcke zurückgeben, ...).
Die Remote-Proceduren dürfen jeden beliebigen PB - Standard Typen zurückgeben (Ascii/Byte/Character/Word/Long/Quad/Float/Double/String) außer Integer damit es keine Verwirrungen zwischen x86/x64 Client/Server gibt. Die Proceduren bekommen eine beliebige Anzahl von Parametern (auch jedes Typs) Als Variant - Liste übergeben.

Server-Beispielcode:

Code: Alles auswählen

XIncludeFile "ENM_SimpleRPC.pbi"

Procedure.d AddAll(*p.ENM_RPC_Parameter)
  Protected All.d
  
  ForEach *p\p()
    All + *p\p()\d
  Next
  
  ProcedureReturn All
EndProcedure

Procedure.s ReverseStringR(*p.ENM_RPC_Parameter)
  ProcedureReturn ReverseString(*p\p()\s)
EndProcedure

InitNetwork()

; Server erstellen
Define RPC.ENM_RPC_Server = ENM_RPC_NewServer(1024)

; Proceduren hinzufügen ...
RPC\AddProcedure("AddAll", #PB_Double, @AddAll())
RPC\AddProcedure("ReverseString", #PB_String, @ReverseStringR())

; Fensterschleife oder sonstwas
OpenConsole()
Input()
CloseConsole()

; Server beenden
RPC\Free()
Client:

Code: Alles auswählen

XIncludeFile "ENM_SimpleRPC.pbi"

InitNetwork()

; Client Verbinden:
Define RPCClient.ENM_RPC_Client = ENM_RPC_NewClient("127.0.0.1", 1024)

; Proceduren verbinden:
Define RemoteAddAll.ENM_RPC_Procedure_d = RPCClient\ConnectProcedure("AddAll", #PB_Double)
Define RemoteReverseString.ENM_RPC_Procedure_s = RPCClient\ConnectProcedure("ReverseString", #PB_String)

; Procedure 1 aufrufen:
; Das Variant - Einstellen kann man dann in Macros usw. einpacken, dann 
; fällt das aiuch nicht mehr auf.
Define p.ENM_RPC_Parameter
AddElement(p\p()) : p\p()\Type = #PB_Double : p\p()\d = 1
AddElement(p\p()) : p\p()\Type = #PB_Double : p\p()\d = 2
AddElement(p\p()) : p\p()\Type = #PB_Double : p\p()\d = 3.3
AddElement(p\p()) : p\p()\Type = #PB_Double : p\p()\d = 4

; Remote Procedure aufrufen
Debug RemoteAddAll\Call(p)

; Kleiner Test mit String-Parameter
Define p2.ENM_RPC_Parameter
AddElement(p2\p()) : p2\p()\Type = #PB_String : p2\p()\s = "Hallo Welt"

; Remote aufrufen
Debug RemoteReverseString\Call(p2)

; Client freigeben
RPCClient\Free()
; Proceduren freigeben
RemoteAddAll\Free()
RemoteReverseString\Free()
ENM_SimpleRPC.pbi:

Code: Alles auswählen

; ------------------------------------------------------------------------------------
; Easy Network Manager - RPC Server
; PB 4.5+ OS: Windows/Linux/MacOS Architecture: x86/x64
; Author: Alexander Aigner, cxAlex @ purebasic.fr/german
; ------------------------------------------------------------------------------------

; Include-Pfad anpassen!!
IncludePath "..\src\"
XIncludeFile "ENM.pbi"

EnableExplicit

; ENM Initialisieren
ENM_Init()

; Globale Defaults
Global _ENM_RPC_CB.ENM_Callbacks
Global _ENM_RPC_BluePrint.ENM_Protocol_Blueprint

; RPC - Variant Parameter
Structure _ENM_RPC_Variant
  Type.a
  StructureUnion
    a.a
    b.b
    c.c
    w.w
    l.l
    q.q
    f.f
    d.d
  EndStructureUnion
  s.s
EndStructure

; RPC-Parameter Structure
Structure ENM_RPC_Parameter
  List p._ENM_RPC_Variant()
EndStructure

; Flag - Konstanten
Enumeration 1
  #_ENM_RPC_REQ_FIND
  #_ENM_RPC_REQ_EXEC
EndEnumeration

Enumeration 1
  #_ENM_RPC_ANS_FIND
  #_ENM_RPC_ANS_EXEC
EndEnumeration

; Prototypen für jeden Rückgabewert
Prototype.a _ENM_RPC_Server_Procedure_a(*p.ENM_RPC_Parameter)
Prototype.b _ENM_RPC_Server_Procedure_b(*p.ENM_RPC_Parameter)
Prototype.c _ENM_RPC_Server_Procedure_c(*p.ENM_RPC_Parameter)
Prototype.w _ENM_RPC_Server_Procedure_w(*p.ENM_RPC_Parameter)
Prototype.l _ENM_RPC_Server_Procedure_l(*p.ENM_RPC_Parameter)
Prototype.q _ENM_RPC_Server_Procedure_q(*p.ENM_RPC_Parameter)
Prototype.f _ENM_RPC_Server_Procedure_f(*p.ENM_RPC_Parameter)
Prototype.d _ENM_RPC_Server_Procedure_d(*p.ENM_RPC_Parameter)
Prototype.s _ENM_RPC_Server_Procedure_s(*p.ENM_RPC_Parameter)

; Variant Prototype für die Rückgabewerte
Structure _ENM_RPC_Server_Procedure_Variant
  Type.a
  StructureUnion
    a._ENM_RPC_Server_Procedure_a
    b._ENM_RPC_Server_Procedure_b
    c._ENM_RPC_Server_Procedure_c
    w._ENM_RPC_Server_Procedure_w
    l._ENM_RPC_Server_Procedure_l
    q._ENM_RPC_Server_Procedure_q
    f._ENM_RPC_Server_Procedure_f
    d._ENM_RPC_Server_Procedure_d
    s._ENM_RPC_Server_Procedure_s
  EndStructureUnion
EndStructure

; Daten für den Client:
Structure _ENM_RPC_Client_Client_Data
  *VTable
  kill.a
  ConID.i
  PT.ENM_Protocol_Transceiver ; Transceiver
  ANSSignal.i
  ANSType.i
  ANS._ENM_RPC_Variant
EndStructure

; Daten für den Server
Structure _ENM_RPC_Server_Data
  *VTable
  Server.i
  Map Functions._ENM_RPC_Server_Procedure_Variant()
  GMutex.i
EndStructure

; Client-Data
Structure _ENM_RPC_Server_Client_Data
  PT.ENM_Protocol_Transceiver ; Transceiver
EndStructure

; Daten für eine Procedure
Structure _ENM_RPC_Procedure_Data
  *VTable
  ProcName$
  *Client._ENM_RPC_Client_Client_Data
EndStructure

; Steuer-Interface für den Server
Interface ENM_RPC_Server
  AddProcedure(ProcedureName$, ReturnType, *Proc)
  Free()
EndInterface

; Client - Interface
Interface ENM_RPC_Client
  ConnectProcedure(ProcedureName$, ReturnType)
  Free()
EndInterface

; RPC - Procedurecall
Macro _ENM_RPC_ProcedureX(_RtType)
  Interface ENM_RPC_Procedure_#_RtType
  Call._RtType(*p.ENM_RPC_Parameter)
  Free()
EndInterface
EndMacro

; Interfaces für jeden RückgabeTyp
_ENM_RPC_ProcedureX(a)
_ENM_RPC_ProcedureX(b)
_ENM_RPC_ProcedureX(c)
_ENM_RPC_ProcedureX(w)
_ENM_RPC_ProcedureX(l)
_ENM_RPC_ProcedureX(q)
_ENM_RPC_ProcedureX(f)
_ENM_RPC_ProcedureX(d)
_ENM_RPC_ProcedureX(s)

; Params wandeln
Procedure.s _ENM_RPC_Params2Base64(List Params._ENM_RPC_Variant())
  Protected gMem = GMem_New(1024) ; wachsender Speicher
  Protected *Mem, MemSize, *BaseMem, BaseMemSize
  Protected Base64$
  
  ForEach Params()
    Select Params()\Type
      Case #PB_String
        ; länge des Strings schreiben
        Params()\l = StringByteLength(Params()\s)
        GMem_WriteData(gMem, @Params(), SizeOf(_ENM_RPC_Variant))
        ; und string hinten dran
        GMem_WriteData(gMem, @Params()\s, StringByteLength(Params()\s) + SizeOf(Character))
      Default ; einfach nur Daten schreiben
        GMem_WriteData(gMem, @Params(), SizeOf(_ENM_RPC_Variant))  
    EndSelect
  Next
  
  ; Base 64-Codedierung, alles in einen String packen...
  MemSize = GMem_GetMemSize(gMem)
  *Mem = GMem_GetMem(gMem)
  If MemSize*2>=64
    BaseMemSize = MemSize*2
  Else
    BaseMemSize = 64
  EndIf
  *BaseMem = AllocateMemory(BaseMemSize)
  BaseMemSize = Base64Encoder(*Mem, MemSize, *BaseMem, BaseMemSize)
  GMem_Free(gMem)
  ; Basedaten lesen
  Base64$ = PeekS(*BaseMem, BaseMemSize)
  FreeMemory(*BaseMem)
  
  ProcedureReturn Base64$
EndProcedure

Procedure _ENM_RPC_Base642Params(Base64$, List Params._ENM_RPC_Variant())
  Protected BaseMemSize, MemSize, *Mem, *BMem
  ClearList(Params()) ; Alle Daten mal löschen...
  
  ; Base 64 decodieren
  BaseMemSize = StringByteLength(Base64$)
  If BaseMemSize*2>=64
    MemSize = BaseMemSize*2
  Else
    MemSize = 64
  EndIf
  *Mem = AllocateMemory(MemSize)
  *BMem = *Mem
  MemSize = Base64Decoder(@Base64$, StringByteLength(Base64$), *Mem, MemSize)
  
  ; Speicher durchgehen, Einträge auslesen
  While MemSize
    AddElement(Params())
    CopyMemory(*Mem, @Params(), SizeOf(_ENM_RPC_Variant))
    MemSize - SizeOf(_ENM_RPC_Variant)
    *Mem + SizeOf(_ENM_RPC_Variant)
    If Params()\Type = #PB_String ; Sonderfall String...
      CopyMemory(*Mem, @Params()\s, Params()\l + SizeOf(Character))
      *Mem + (Params()\l + SizeOf(Character))
      MemSize - (Params()\l + SizeOf(Character))
      Params()\l = #Null
    EndIf
  Wend
  ; speicher freigebn
  FreeMemory(*BMem)
EndProcedure

; Procedure ausführen und Rückgabewert speichern
Procedure _ENM_RPC_Server_ProcExecutor(PT.ENM_Protocol_Transceiver, *Proc._ENM_RPC_Server_Procedure_Variant, *p.ENM_RPC_Parameter)
  PT\T_SetByte("rt_type", *Proc\Type) ; Typ speichern
  ; procedure aufrufen und rückgabewert speichern
  Select *Proc\Type
    Case #PB_Ascii     : PT\T_SetByte("rt_a", *Proc\a(*p))
    Case #PB_Byte      : PT\T_SetByte("rt_a", *Proc\b(*p))
    Case #PB_Character : PT\T_SetLong("rt_l", *Proc\c(*p))
    Case #PB_Word      : PT\T_SetLong("rt_l", *Proc\w(*p))
    Case #PB_Long      : PT\T_SetLong("rt_l", *Proc\l(*p))
    Case #PB_Quad      : PT\T_SetQuad("rt_q", *Proc\q(*p))
    Case #PB_Float     : PT\T_SetFloat("rt_f", *Proc\f(*p))
    Case #PB_Double    : PT\T_SetDouble("rt_d", *Proc\d(*p))
    Case #PB_String    : PT\T_SetString("rt_s", *Proc\s(*p))
  EndSelect
EndProcedure

; Protocol-Handler für den Client...
Procedure _ENM_RPC_Client_ProtocolHandler(Client, PT.ENM_Protocol_Transceiver)
  Protected flag, proc$, para$
  Protected *cClient._ENM_RPC_Client_Client_Data
  
  With *ENMRPCS
    ; Protokoll auslesen
    flag = PT\R_GetByte("flag") ; Flag
    proc$ = PT\R_GetString("proc") ; um welche Procedure geht es
    ; Client laden
    *cClient = ENM\GetConnectionData(Client)
    
    Select flag
      Case #_ENM_RPC_ANS_FIND
        *cClient\ANSType = #_ENM_RPC_ANS_FIND
        *cClient\ANS\a = PT\R_GetByte("rt_a")
        SignalSemaphore(*cClient\ANSSignal)
        
      Case #_ENM_RPC_ANS_EXEC
        *cClient\ANSType = #_ENM_RPC_ANS_EXEC
        *cClient\ANS\Type = PT\R_GetByte("rt_type")
        Select *cClient\ANS\Type
          Case #PB_Ascii     : *cClient\ANS\a = PT\R_GetByte("rt_a")
          Case #PB_Byte      : *cClient\ANS\b = PT\R_GetByte("rt_a")
          Case #PB_Character : *cClient\ANS\c = PT\R_GetLong("rt_l")
          Case #PB_Word      : *cClient\ANS\w = PT\R_GetLong("rt_l")
          Case #PB_Long      : *cClient\ANS\l = PT\R_GetLong("rt_l")
          Case #PB_Quad      : *cClient\ANS\q = PT\R_GetQuad("rt_q")
          Case #PB_Float     : *cClient\ANS\f = PT\R_GetFloat("rt_f")
          Case #PB_Double    : *cClient\ANS\d = PT\R_GetDouble("rt_d")
          Case #PB_String    : *cClient\ANS\s = PT\R_GetString("rt_s")
        EndSelect
        SignalSemaphore(*cClient\ANSSignal)
        
    EndSelect
  EndWith
EndProcedure

; Protocol-Handler für den Server...
Procedure _ENM_RPC_Server_ProtocolHandler(Client, PT.ENM_Protocol_Transceiver, *ENMRPCS._ENM_RPC_Server_Data)
  Protected flag, proc$, para$
  Protected *mem, memsize
  Protected *Proc._ENM_RPC_Server_Procedure_Variant
  Protected p.ENM_RPC_Parameter
  
  With *ENMRPCS
    ; Protokoll auslesen
    flag = PT\R_GetByte("flag") ; Flag
    proc$ = PT\R_GetString("proc") ; um welche Procedure geht es
    
    Select flag
      Case #_ENM_RPC_REQ_FIND
        PT\T_SetByte("flag", #_ENM_RPC_ANS_FIND) ; Rückgabe: gefundene Procedure
        LockMutex(\GMutex)
        PT\T_SetByte("rt_a", (FindMapElement(\Functions(), LCase(proc$)) Or #Null))
        UnlockMutex(\GMutex)
        
        *mem = PT\T_Export(@memsize)
        ; Rückgabe Senden
        ENM\SendPaket(Client, *mem, memsize)
        FreeMemory(*mem)
        
      Case #_ENM_RPC_REQ_EXEC
        PT\T_SetByte("flag", #_ENM_RPC_ANS_EXEC) ; Rückgabe: ausgeführte Procedure
        para$ = PT\R_GetString("para")
        If para$ ; Parameter umwandeln
          _ENM_RPC_Base642Params(para$, p\p())
        EndIf
        
        LockMutex(\GMutex)
        *Proc = FindMapElement(\Functions(), LCase(proc$)) ; Procedure laden
        UnlockMutex(\GMutex)
        
        If *Proc ; Ausführen
          _ENM_RPC_Server_ProcExecutor(PT, *Proc, @p) ; Ausführen und Rückgabewerte direkt speichern
        EndIf
        *mem = PT\T_Export(@memsize)
        ; Rückgabe Senden
        ENM\SendPaket(Client, *mem, memsize)
        FreeMemory(*mem)
        
        
    EndSelect
  EndWith
EndProcedure

; Netzwerk - Events
Procedure _ENM_RPC_NetEvent_Connect(Server, Client)
  Protected *CData._ENM_RPC_Server_Client_Data
  Protected *cCData._ENM_RPC_Client_Client_Data
  
  If Server ; Server - Procedure
    ; Client anlegen
    *CData = AllocateMemory(SizeOf(_ENM_RPC_Server_Client_Data))
    *CData\PT = ENM\OpenProtocolTransceiver(_ENM_RPC_BluePrint)
    ENM\SetConnectionData(Client, *CData)
  Else ; Client - Procedure
     ; Initialisierung in Init...
  EndIf
EndProcedure

Procedure _ENM_RPC_NetEvent_Disconnect(Server, Client)
  Protected *CData._ENM_RPC_Server_Client_Data
  Protected *cCData._ENM_RPC_Client_Client_Data
  
  If Server ; Server - Procedure
    ; Alles freigeben
    *CData = ENM\GetConnectionData(Client)
    *CData\PT\Free()
    FreeMemory(*CData)
  Else ; Client - Procedure
    ; Connection beendet
    *cCData = ENM\GetConnectionData(Client)
    If *cCData\kill ; Killflag, Deinitialisieren
      *cCData\PT\Free()
      FreeSemaphore(*cCData\ANSSignal)
      FreeMemory(*cCData)
    Else
      *cCData\ANSType = #Null
      SignalSemaphore(*cCData\ANSSignal)
    EndIf
  EndIf
EndProcedure

Procedure _ENM_RPC_NetEvent_RecData(Server, Client)
  Protected *CData._ENM_RPC_Server_Client_Data
  Protected *cCData._ENM_RPC_Client_Client_Data
  
  If Server ; Server - Procedure
    Protected Pakets.ENM_Pakets = ENM\ReceivePakets(Client)
    If Pakets ; Pakete emfangen
      *CData = ENM\GetConnectionData(Client)
      While Pakets\GetNext() ; Pakete durchgehen
        If *CData\PT\R_Import(Pakets\GetMem(), Pakets\GetMemSize()) ; Protokoll lesen
          _ENM_RPC_Server_ProtocolHandler(Client, *CData\PT, ENM\GetConnectionData(Server)) ; Protokoll abarbeiten
          *CData\PT\R_Reset() ; zurücksetzen
        EndIf
      Wend
    EndIf
    
  Else ; Client - Procedure
    Pakets.ENM_Pakets = ENM\ReceivePakets(Client)
    If Pakets ; Pakete emfangen
      *cCData = ENM\GetConnectionData(Client)
      While Pakets\GetNext() ; Pakete durchgehen
        If *cCData\PT\R_Import(Pakets\GetMem(), Pakets\GetMemSize()) ; Protokoll lesen
          _ENM_RPC_Client_ProtocolHandler(Client, *cCData\PT) ; Protokoll abarbeiten
          *cCData\PT\R_Reset() ; zurücksetzen
        EndIf
      Wend
    EndIf
    
  EndIf
EndProcedure

Procedure _ENM_RPC_NetEvent_RecFile(Server, Client)
  ; Obsolete....
EndProcedure

; Initialisierung
Procedure _ENM_RPC_Init()
  Shared _ENM_RPC_CB.ENM_Callbacks
  Shared _ENM_RPC_BluePrint.ENM_Protocol_Blueprint
  
  Protected ENMRPCProtoBP$ ; RPC - Protokoll
  ENMRPCProtoBP$ = "PN: ENM_RPC" + #LFCR$
  ENMRPCProtoBP$ + "PV: 1" + #LFCR$
  ENMRPCProtoBP$ + "flag I: 0 T: BYTE D: 0" + #LFCR$
  ENMRPCProtoBP$ + "proc I: 1 T: STRING D: " + #LFCR$
  ENMRPCProtoBP$ + "para I: 2 T: STRING D: " + #LFCR$
  ENMRPCProtoBP$ + "rt_type I: 3 T: BYTE D: 0" + #LFCR$
  ENMRPCProtoBP$ + "rt_a I: 4 T: BYTE D: 0" + #LFCR$
  ENMRPCProtoBP$ + "rt_l I: 5 T: LONG D: 0" + #LFCR$
  ENMRPCProtoBP$ + "rt_q I: 6 T: QUAD D: 0" + #LFCR$
  ENMRPCProtoBP$ + "rt_f I: 7 T: FLOAT D: 0.0000000000" + #LFCR$
  ENMRPCProtoBP$ + "rt_d I: 8 T: DOUBLE D: 0.0000000000" + #LFCR$
  ENMRPCProtoBP$ + "rt_s I: 9 T: STRING D: "
  
  ; Blueprint erstellen
  _ENM_RPC_BluePrint = ENM\OpenProtocolBlueprint(ENMRPCProtoBP$)
  
  With _ENM_RPC_CB
    \Connect = @_ENM_RPC_NetEvent_Connect()
    \Disconnect = @_ENM_RPC_NetEvent_Disconnect()
    \Receive = @_ENM_RPC_NetEvent_RecData()
    \ReceiveFile = @_ENM_RPC_NetEvent_RecFile()
  EndWith
EndProcedure : _ENM_RPC_Init()

; Neue Verbindung:
Procedure ENM_RPC_NewClient(ServerHost$, ServerPort)
  Protected *ENMRPCC._ENM_RPC_Client_Client_Data = AllocateMemory(SizeOf(_ENM_RPC_Client_Client_Data))
  
  With *ENMRPCC
    \ConID = ENM\OpenConnection(ServerHost$, ServerPort)
    If \ConID ; Connection konnte erstellt werden
      ENM\SetCallbacks(\ConID, _ENM_RPC_CB) ; Default - Callbacks
      \VTable = ?_ENM_RPC_Client_VTable
      \PT = ENM\OpenProtocolTransceiver(_ENM_RPC_BluePrint)
      \ANSSignal = CreateSemaphore()
      ENM\SetConnectionData(\ConID, *ENMRPCC)
    Else
      FreeMemory(*ENMRPCC)
      *ENMRPCC = #Null
    EndIf
  EndWith
  
  ProcedureReturn *ENMRPCC
EndProcedure

Procedure _ENM_RPC_Client_ConnectProcedure(*ENMRPCC._ENM_RPC_Client_Client_Data, ProcedureName$, ReturnType)
  Protected *Mem, MemSize
  Protected *ENMRPCP._ENM_RPC_Procedure_Data
  
  With *ENMRPCC
    ; Protokol-Anfrage senden
    \PT\T_SetByte("flag", #_ENM_RPC_REQ_FIND)
    \PT\T_SetString("proc", ProcedureName$)
    *Mem = \PT\T_Export(@MemSize)
    \PT\T_Reset() ; Transmitter-Reset
    ; Senden
    ENM\SendPaket(\ConID, *Mem, MemSize)
    FreeMemory(*Mem)
    ; Auf Antwort warten
    WaitSemaphore(\ANSSignal)
    Select \ANSType
      Case #_ENM_RPC_REQ_FIND
        ; Element erstellen
        *ENMRPCP = AllocateMemory(SizeOf(_ENM_RPC_Procedure_Data))
        InitializeStructure(*ENMRPCP, _ENM_RPC_Procedure_Data)
        ; Client und ProcedureName speichern
        *ENMRPCP\Client = *ENMRPCC
        *ENMRPCP\ProcName$ = ProcedureName$
        Select ReturnType ; VTable suchen
          Case #PB_Ascii     :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_a
          Case #PB_Byte      :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_b
          Case #PB_Character :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_c
          Case #PB_Word      :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_w
          Case #PB_Long      :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_l
          Case #PB_Quad      :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_q
          Case #PB_Float     :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_f
          Case #PB_Double    :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_d
          Case #PB_String    :  *ENMRPCP\VTable = ?_ENM_RPC_Procedure_VTable_s
        EndSelect
    EndSelect
  EndWith
  
  ProcedureReturn *ENMRPCP
EndProcedure

; Client freigeben
Procedure _ENM_RPC_Client_Free(*ENMRPCC._ENM_RPC_Client_Client_Data)
  Protected *Mem, MemSize
  Protected *ENMRPCP._ENM_RPC_Procedure_Data
  
  With *ENMRPCC
    ; Kill-Flag setzen
    \kill = #True
    ; DeInit ist im Close-Event
    ENM\CloseConnection(\ConID)
  EndWith

EndProcedure

; Procedure freigeben
Procedure _ENM_RPC_Procedure_Free(*ENMRPCP._ENM_RPC_Procedure_Data)
  With *ENMRPCP
    ClearStructure(*ENMRPCP, _ENM_RPC_Procedure_Data)
    FreeMemory(*ENMRPCP)
  EndWith
EndProcedure

Macro _ENM_RPC_Procedure_CallX(_Name, _Type)
  Procedure._Name _ENM_RPC_Procedure_Call_#_Name(*ENMRPCP._ENM_RPC_Procedure_Data, *p.ENM_RPC_Parameter)
    Protected *Mem, MemSize
    ; Anfrage schreiben
    *ENMRPCP\Client\PT\T_SetByte("flag", #_ENM_RPC_REQ_EXEC)
    *ENMRPCP\Client\PT\T_SetString("proc", *ENMRPCP\ProcName$)
    *ENMRPCP\Client\PT\T_SetString("para", _ENM_RPC_Params2Base64(*p\p()))
    *Mem = *ENMRPCP\Client\PT\T_Export(@MemSize)
    *ENMRPCP\Client\PT\T_Reset()
    ; Anfrage senden
    ENM\SendPaket(*ENMRPCP\Client\ConID, *Mem, MemSize)
    FreeMemory(*Mem)
    ; Auf Antwort warten
    WaitSemaphore(*ENMRPCP\Client\ANSSignal)
    
    Select *ENMRPCP\Client\ANSType
      Case #_ENM_RPC_REQ_EXEC ; Richtiger Return Type?
        If *ENMRPCP\Client\ANS\Type = _Type
          ProcedureReturn *ENMRPCP\Client\ANS\_Name
        EndIf
    EndSelect
    
  EndProcedure
EndMacro

; Caller-Proceduren
_ENM_RPC_Procedure_CallX(a, #PB_Ascii)
_ENM_RPC_Procedure_CallX(b, #PB_Byte)
_ENM_RPC_Procedure_CallX(c, #PB_Character)
_ENM_RPC_Procedure_CallX(w, #PB_Word)
_ENM_RPC_Procedure_CallX(l, #PB_Long)
_ENM_RPC_Procedure_CallX(q, #PB_Quad)
_ENM_RPC_Procedure_CallX(f, #PB_Float)
_ENM_RPC_Procedure_CallX(d, #PB_Double)
_ENM_RPC_Procedure_CallX(s, #PB_String)

; Neuen Server
Procedure ENM_RPC_NewServer(ServerPort)
  Protected *ENMRPCS._ENM_RPC_Server_Data = AllocateMemory(SizeOf(_ENM_RPC_Server_Data))
  InitializeStructure(*ENMRPCS, _ENM_RPC_Server_Data)
  
  With *ENMRPCS
    \VTable = ?_ENM_RPC_Server_VTable ; VTable
    \Server = ENM\OpenServer(ServerPort) ; Server öffnen
    If \Server
      ENM\SetCallbacks(\Server, _ENM_RPC_CB) ; Default - Callbacks
      \GMutex = CreateMutex() ; Schutzmutex
      ENM\SetConnectionData(\Server, *ENMRPCS) ; Daten im Server-Handle speichern
    Else ; Alles Freigeben, Port nicht frei
      ClearStructure(*ENMRPCS, _ENM_RPC_Server_Data)
      FreeMemory(*ENMRPCS)
      *ENMRPCS = #Null
    EndIf
  EndWith
  
  ; Zurückgeben
  ProcedureReturn *ENMRPCS
EndProcedure

; Procedure Hinzufügen
Procedure _ENM_RPC_Server_AddProcedure(*ENMRPCS._ENM_RPC_Server_Data, ProcedureName$, ReturnType, *Proc)
  With *ENMRPCS
    LockMutex(\GMutex)
    AddMapElement(\Functions(), LCase(ProcedureName$))
    \Functions()\Type = ReturnType
    \Functions()\a = *Proc
    UnlockMutex(\GMutex)
  EndWith
EndProcedure

; Server Beenden
Procedure _ENM_RPC_Server_Free(*ENMRPCS._ENM_RPC_Server_Data, ProcedureName$, ReturnType, *Proc)
  With *ENMRPCS
    ENM\CloseServer(\Server) ; Server Beenden
    ; Structur freigeben
    ClearStructure(*ENMRPCS, _ENM_RPC_Server_Data)
    FreeMemory(*ENMRPCS)
  EndWith
EndProcedure

; VTables für Procedure-Typen
Macro _ENM_RPC_Procedure_VTableX(_Type)
  _ENM_RPC_Procedure_VTable_#_Type:
  Data.i @_ENM_RPC_Procedure_Call_#_Type()
  Data.i @_ENM_RPC_Procedure_Free()
EndMacro

; DataSection
DataSection
  _ENM_RPC_Server_VTable:
  Data.i @_ENM_RPC_Server_AddProcedure()
  Data.i @_ENM_RPC_Server_Free()
  _ENM_RPC_Client_VTable:
  Data.i @_ENM_RPC_Client_ConnectProcedure()
  Data.i @_ENM_RPC_Client_Free()
  ; VTables für Procedure-Typen
  _ENM_RPC_Procedure_VTableX(a)
  _ENM_RPC_Procedure_VTableX(b)
  _ENM_RPC_Procedure_VTableX(c)
  _ENM_RPC_Procedure_VTableX(w)
  _ENM_RPC_Procedure_VTableX(l)
  _ENM_RPC_Procedure_VTableX(q)
  _ENM_RPC_Procedure_VTableX(f)
  _ENM_RPC_Procedure_VTableX(d)
  _ENM_RPC_Procedure_VTableX(s)
EndDataSection
Der Source ist noch sehr frisch, über gefundene Bugs freue ich mich immer ;)

Gruß, Alex