У меня есть двухуровневое приложение Delphi для Win32 с большой бизнес-логикой, реализованной в объекте God, который я хочу передать в отдельный сервис.Эта отдельная служба должна быть доступна нескольким клиентам через протокол TCP / IP в стиле telnet.
Как мне сделать переход максимально простым?
Точно, я бы хотел сохранить этопростота: я хочу определить каждую функцию только один раз.Например, если я хочу добавить в приложение функцию входа с помощью пин-кода, мне просто нужно определить функцию
function Login(Username: string; PinCode: integer): boolean;
в каком-либо объекте на сервере, тогда я могу использовать ее с клиентов без каких-либодополнительная работа.
В худшем случае мне нужно реализовать три функции вместо одной.Во-первых, собственно тело функции на сервере, во-вторых, unmarshaller, который получает текстовую строку из сети, распаковывает ее и проверяет правильность:
procedure HandleCommand(Cmd: string; Params: array of string);
begin
...
if SameText(Cmd, 'Login') then begin
CheckParamCount(Params, 2);
ServerObject.Login(
Params[0],
StrToInt(Params[1])
);
end;
end;
В-третьих, маршаллер, который при вызове клиентом упаковываетпараметры и отправляет их на сервер:
function TServerConnection.Login(Username: string; PinCode: integer): boolean;
begin
Result := StrToBool(ServerCall('Login '+Escape(Username)+' '+IntToStr(PinCode)));
end;
Очевидно, я этого не хочу.
Пока мне удалось избавиться от демаршаллера.Работая с Delphi RTTI, я написал общий unmarshaller, который ищет опубликованный метод по имени, проверяет параметры и вызывает его.
Так что теперь я могу просто добавить опубликованный метод к объекту сервера, и я могучтобы позвонить по телнету:
function Login(Username: string; PinCode: integer): boolean;
> login john_locke
Missing parameter 2 (PinCode: integer)!
Но что мне делать с написанием маршаллера?Я не могу динамически получить список функций сервера и добавить функции к клиентскому объекту.Я, вероятно, могу сохранить какой-то динамический набор псевдофункций, но это сделало бы мой клиент безобразным:
ServerConnection.Call('Login', [Username, Password]);
Плюс, это отрицательно сказывается на безопасности типов, поскольку каждый параметр передается как вариант.Если возможно, я бы хотел сохранить безопасность типов во время компиляции.
Может быть, автогенерация кода клиента?Я, вероятно, могу написать «GetFunctionList ()» и «GetFunctionPrototype (Name: string)» на моем сервере:
> GetFunctionList
Login
Logout
IsLoggedIn
> GetFunctionPrototype Login
function Login(Username: string; PinCode: integer): boolean;
Так что каждый раз, когда мне нужно обновить клиента, я просто повторно запрашиваю все функциипрототипы с сервера и автоматически генерировать маршаллерский код для них.Но это смешало бы компиляцию с выполнением: мне нужно было сначала скомпилировать сервер, затем запустить его и запросить его функции, затем собрать клиентский маршаллер и перекомпилировать клиент только после этого.Сложно!
Другой вариант - написать универсальную функцию маршаллера и затем вызвать ее из всех функций-прототипов клиента:
procedure TServerConnection.GenericMarshaller(); assembler;
asm
//finds the RTTI for the caller function, unwinds stack, pops out caller params,
//packs them according to RTTI and sends to the server.
//receives the result, pushes it to stack according to RTTI, quits
//oh god
end;
function TServerConnection.Login(Username: string; PinCode: integer): boolean; assembler;
asm
call GenericMarshaller
end;
Это избавляет меня от написания ручной упаковки каждый раз (меньше шансов на ошибки), но все равно требует, чтобы я вручную скопировал прототипы серверных функций в объект client.Кроме того, написание этого универсального маршаллера, вероятно, станет настоящим адом.
Тогда есть возможность использовать RPC, но мне это не нравится, потому что мне нужно переопределить все функции в IDL,И редактор Delphi IDL отстой.А для интерфейсов OLE Delphi принудительно генерирует «безопасные» функции, которые тоже отстой.И нет никакого способа реализовать функции автоматического обнаружения-разъединения и автоматического восстановления-соединения, кроме проверки вызова функции EVERY SINGLE в классе RPC:
function TServerWrapper.Login(Username: string; PinCode: integer): boolean;
begin
try
RealObject.Login(Username, Pincode);
except
on E: EOleException do
if IndicatesDisconnect(E) then
Disconnect;
Reconnect;
RealObject.Login(Username, Pincode);
end;
end;
Мы вернулись к нескольким функциям вместо одной.
Итак, что бы вы предложили, ребята?Какие другие варианты я пропускаю?Существуют ли общие шаблоны или готовые решения для удаленного взаимодействия в Delphi?