Volání služby serveru pomocí zprávy z klienta má obvykle charakter volání procedury, a tak se kód pro manipulaci se zprávami na klientovi a serveru odděluje a automaticky generuje, nápad zhruba kolem roku 1984.
Když se volá normální procedura, uloží se na stack parametry, procedura si je vyzvedne a něco udělá, vrátí výsledky. Když se volá služba na serveru, parametry se uloží do zprávy, server ji přijme a něco udělá, vrátí výsledky. RPC udělá lokální proceduru, která vezme parametry ze stacku, uloží je do zprávy, zavolá server, přijme výsledky a vrátí je volajícímu. A aby i programátoři serveru měli pohodu, udělá se to samé také na druhé straně - říká se tomu client a server stub, případně client stub a server skeleton.
Uložení parametrů do zprávy se říká marshalling, opačně zase unmarshalling. Závisí na typu parametrů, které se předávají.
Passed by value. Jediným problémem může být nekompatibilita reprezentací parametru. Ta se řeší buď stanovením společného formátu (+ krátké zprávy, - někdy oba zbytečně převádí), nebo uváděním formátu ve zprávě (+ flexibilní, - složité stuby a delší zprávy).
Passed by reference. Nejtěžší varianta. U reference na dobře typovaná malá data se dá převést na obousměrné by value (+ jednoduché a efektivní, - nemá přesně tutéž sémantiku při existenci cyklů referencí), u velkých dat je vhodnější když server žádá dodatečně klienta o data (+ flexibilnější, - složitější protokoly a neefektivní dereference). Některé reference se prakticky nedají přenést, typickým příkladem je předání funkce jako parametru.
S předáváním jsou ještě další záludnosti, které nejsou na první pohled zřejmé. Mezi ně patří:
Global variables. Pochopitelně nejsou u serveru dostupné, ale ze sémantiky procedure callu to není zjevné, tak se na to zapomíná. Hlavně to vadí u takových věcí jako jsou globální error resulty. Ručně vytvořené stuby to umí dodělat, automaticky generované už ne.
System identifiers. Pokud se předává nějaká hodnota, která má význam pro kernel klienta, nemusí už znamenat totéž u serveru. Typicky handlery souborů, čísla portů a podobně. Zmínit konverzi při posílání zpráv u Machu.
Další problém je error handling. S tím moc chytristiky udělat nejde. Možné varianty selhání jsou známé, je prostě nutné počítat s tím, že RPC může selhat ještě pár jinými způsoby než normální call a ošetřit to v programu.
Při implementaci RPC je důležitá efektivita, stojí na ní celý systém. Kritická cesta při RPC - call stub, prepare msg buffer, marshall params, fill headers, call kernel send, context switch, copy stub buffer to kernel space, fill headers, set up interface - receive interrupt, check packet, find addressee, copy buffer to addressee, wake up addressee, context switch, unmarshall params, prepare stack, call server.
Co trvá dlouho - marshalling, buffer copying (při špatné implementaci header filling). Řeší se obvykle mapováním a scatter and gather network hardware (efektivní jen pro delší zprávy).
Stuby a skeletony je potřeba automaticky generovat. Jako vstup generátoru slouží definice hlaviček procedur, ty jazykové ale nejsou zpravidla dostatečně informativní, takže se definuje nějaký jazyk pro popis hlaviček procedur (IDL), podle kterého se pak jednak generují stuby a skeletony a jednak hlavičky procedur v nějakém programovacím jazyce.
Na právě popsaném principu běží například Spring, kde se procesy volají skrz doors. Při volání door se předává buffer, který může obsahovat data, identifikátor door, out of line data. Předávání je buď consume nebo copy, s jasnou sémantikou. Thread na straně klienta se pozastaví, na straně serveru se vybere thread z thread pool příslušejícího k door, který vykoná kód spojený s door. Interfaces jsou popsané v IDL, překládá se do client a server stubů, pod nimi jsou ještě subcontracts, ignore.
Pro marshalling Spring původně používal buffer fixní velikosti spojený s každým threadem, to se ale ukázalo špatné ze dvou důvodů. Za prvé, většina volání přenášela méně než 128 bajtů dat (90% pod 48 bajtů), a několikakilobajtový buffer byl pak zbytečně velký. Za druhé, buffery se rezervovaly staticky, čímž spotřebovávaly paměť. Jako řešení se udělal stream interface s metodami put_int, put_short, put_char, put_bulk, put_door_identifier, put_aligned (a odpovídajícími get metodami). Stream si by default alokuje buffer 128 bajtů, do kterého od konce ukládá structured data (door identifiers a out of line data) a od začátku unstructured data (všechno ostatní). Structured data se překládají, unstructured kopírují, při zaplnění se alokuje extra overflow buffer.