Мое исследование возможных реализаций RPC. Для начала рассмотрим реализацию RPC через создание потока в удаленном процессе.
Одной из основных проблем при проектировании сложных систем является обеспечение взаимодействия между различными комплексами. Трудно представить, какое количество велосипедов было изобретено и ежеминутно куется в программных мануфактурах. Сетевое взаимодействие так возбуждает системных архитекторов, что они просто не могут остановиться и не написать протокол информационно-логического взаимодействия страниц так на 150. В таких протоколах обычно расписывается куча квитанций, которые обеспечивают надежность передачи данных. Самый эпичный велосипед, который я видел, реализовывал функциональность TCP поверх UDP.
Интересно, что самый предпочтительный с точки зрения прозрачности и дальнейшей поддержки подход – RPC – считается сложным и ресурсоемким. И, если с последним можно отчасти согласиться, то вот насчет сложности можно поспорить. Для того чтобы RPC стал простым и понятным, я решил для себя собрать возможные реализации RPC под различные платформы. Для начала попробую сделать вызов функции в другом исполняемом модуле под ОС Windows.
Исходные данные:
Есть два исполняемых модуля A.exe и B.exe
В модуле B.exe есть функция foo
Задача
Выполнить из модуля A.exe функцию foo
Решение
Для того, чтобы вызвать функцию, нужно знать ее адрес. Не все знают, но любой исполняемый модуль (exe и dll) может экспортировать функции. При импорте функции мы узнаем RVA функции в модуле. Для того чтобы получить полный адрес, нам нужно узнать базовый адрес загрузки модуля. Итак, в модуле B.exe объявляем функцию void foo() и экспортируем ее с помощью def файла.
Итак, в модуле B.exe определена функция foo, которая при вызове выставит флаг working в значение false. После чего цикл в функции WinMain завершится после очередной итерации. Событие ev создается для того, чтобы модуль B.exe успел проинициализироваться прежде, чем мы начнем вызывать функцию foo.
Для начала получаем путь к B.exe. Этот танец с бубном вокруг path нужен для запуска по относительному пути в случае, если текущий путь отличается от папки где лежат A.exe и B.exe.
Класс удаленного вызова процедур (rpc_caller.hpp)download
Надеюсь, отсутствие комментариев не смущает, потому что код достаточно красноречив.
Итак, для чего нужен RPC_caller?
Функция call_in_ipc, собственно, вызывает удаленную функцию.
После того как мы найдем искомый модуль, необходимо определить адрес загрузки модуля. Собственно, после этого нам остается только сложить базовый адрес модуля и RVA функции - в результате получаем реальный адрес в удаленном модуле.
Теперь дело осталось за малым – запустить функцию в адресном пространстве другого процесса. Для этого в Windows существует функция CreateRemoteThread. Эта функция создает поток в целевом процессе, который выполняет заданную функцию. Собственно, поэтому в B.exe я использовал std::atomic_flag.
В результате мы получили вызов функции в удаленном процессе. Однако для полноценного RPC не хватает передачи в функцию параметров и приема результата выполнения. Об этом подробно расскажу во второй части.