Всем привет! Пишу службу/сервис для Win7, и юм-софт для управления им. Вроде всё ок, CreateService() создаёт, а ControlService() отправляет коды управления службе. Только непонятно, какими средствами можно организовать обратный канал связи от службы к моему приложению, чтобы я мог получить запрошенную инфу. Прежние IPC типа Pipe/MailSlot/Socket вроде не работаю начиная с Vista, а что тогда осталось? Где-то советуют RDP, но это слишком громоздко получается, т.к. почти на всех машинах этот протокол отключён, и нужно включать его глобально через реестр с последующим ребутом. Так-ли всё плохо на самом деле?
Тимур, а почему не работают? Все примеры в Межпроцессные взаимодействия были написаны под Win8 и Win10
Код легко конвертируется в Си в любом даже самом всратом gpt. Имхо, лучше чем сокеты. Код (Text): //--- const str_len = 16384; var Terminated: bool; hProcess, hThread: THandle; var hPipeInputRead, hPipeInputWrite: THandle; hPipeOutputRead, hPipeOutputWrite: THandle; //--- function IsPipeOpen(hPipe: THandle): Boolean; begin Result := (GetFileType(hPipe) = FILE_TYPE_PIPE); end; //IsPipeOpen procedure NotifyThread; begin Terminated := false; WaitForSingleObject(hProcess, INFINITE); Writeln('Process stoped.'); Terminated := true; CloseHandle(hThread); end; //NotifyThread procedure StartNotifyThread; var ThreadId: Cardinal; begin hThread := CreateThread(nil, 0, @NotifyThread, nil, 0, ThreadId); end; //StartNotifyThread //--- function StartProcess(current_file: string): bool; var s: string; StartupInfo: TStartupInfo; ProcessInformation: TProcessInformation; SecurityAttributes: TSecurityAttributes; begin Result := true; Writeln('Process started.'); ZeroMemory(@StartupInfo, SizeOf(TStartupInfo)); ZeroMemory(@ProcessInformation, SizeOf(TProcessInformation)); ZeroMemory(@SecurityAttributes, SizeOf(TSecurityAttributes)); SecurityAttributes.nLength := SizeOf(TSecurityAttributes); SecurityAttributes.lpSecurityDescriptor := nil; SecurityAttributes.bInheritHandle := True; CreatePipe(hPipeInputRead, hPipeInputWrite, @SecurityAttributes, 0); CreatePipe(hPipeOutputRead, hPipeOutputWrite, @SecurityAttributes, 0); StartupInfo.cb := SizeOf(TStartupInfo); StartupInfo.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; StartupInfo.wShowWindow := SW_HIDE; StartupInfo.hStdInput := hPipeInputRead; StartupInfo.hStdOutput := hPipeOutputWrite; StartupInfo.hStdError := hPipeOutputWrite; s := format('python "%s"', [current_file]); if CreateProcess(nil, PChar(s), nil, nil, true, CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcessInformation) then begin hProcess := ProcessInformation.hProcess; StartNotifyThread; end else begin Result := false; end; end; //StartProcess function GetPipeInfo: string; var BytesCount: Cardinal; Buffer: array [0..str_len] of Char; begin Result := ''; if IsPipeOpen(hPipeOutputRead) then begin if ReadFile(hPipeOutputRead, Buffer, SizeOf(Buffer), BytesCount, nil) then begin if BytesCount > 0 then begin OemToAnsiBuff(Buffer, Buffer, BytesCount); Result := Copy(Buffer, 1, BytesCount); end; end; end; end; //GetPipeInfo procedure SetPipeInfo(s: string); const br = #13#10; var BytesCount: Cardinal; Buffer: array [0..str_len] of Char; begin if Length(s) + 2 < str_len then begin StrPCopy(Buffer, s + br); if IsPipeOpen(hPipeInputWrite) then begin WriteFile(hPipeInputWrite, Buffer, Length(s) + 2, BytesCount, nil); end; end; end; //SetPipeInfo procedure StopThread; // begin TerminateProcess(hProcess, 255); WaitForSingleObject(hProcess, INFINITE); CloseHandle(hProcess); CloseHandle(hPipeInputWrite); CloseHandle(hPipeInputRead); CloseHandle(hPipeOutputWrite); CloseHandle(hPipeOutputRead); end; //StopThread //--- Код (Text): procedure ReadThreadProc; var s: string; begin while not Terminated do begin s := GetPipeInfo; if s <> '' then begin WritelnRus(s); //FormMain.MemoConsole.Text := FormMain.MemoConsole.Text + s; //Application.ProcessMessages; end; Sleep(100); end; end; //ReadThreadProc procedure StartPipeRead; var ThreadId: Cardinal; begin CreateThread(nil, 0, @ReadThreadProc, nil, 0, ThreadId); end; //StartPipeRead if StartProcess('!___Run.py') then begin StartPipeRead; end; //SetPipeInfo('html_code'); Readln(s); if s <> '' then begin SetPipeInfo(s); end; Код (Text): # start.py print("test str") run = True while run: # Получение данных от main.py data = input('start.py -> Enter data: ') # Обработка данных и генерация ответа response = f"start.py -> Hi, you enter: {data}" if data == "111": run = False print("Exit") else: # Передача ответа обратно в main.py print(response)
Mikl___, это внутри одной пользовательской сессии(1+), а я говорю про обмен со службой в закрытой сессии(0).
делай объект в пространстве имён Global и или подключайся из той 0 сессии или устанавливай соответствующий ACL. Всё работает - и общая память и пайпы и что угодно другое
Спасибо всем за советы, но у меня что-то не воркает функция StartService(), если вызывать её с аргументами. Эти аргументы нужны мне для передачи службе дескрипторов Read/Write анонимного пайпа. Крэш AV получаю до GetLastError(), где-то внутри либы RPCRT4.dll. Если-же вызывать StartService() без аргументов, то всё ОК, но тогда сервис остаётся без хэндлов пайпа. Пробовал собирать под х32/64, и всегда одинаковая проблема. Может начиная с Vista эти аргументы уже не действительны - хз. Вот фрагмент кода приложения: Код (ASM): .data hScm dd 0 hServ dd 0 hReadPipe dd 0 hWritePipe dd 0 align 16 szSrvName db 'WiFiService',0 ; lpServiceArgVectors szRpipe db 16 dup(0) szWpipe db 16 dup(0) ;//----------------- .code start: invoke OpenSCManager,0,0,SC_MANAGER_ALL_ACCESS or eax,eax jnz @f invoke MessageBox,0,<'Ошибка! Не удалось открыть SCM.',0>,0,30h jmp @close @@: mov [hScm],eax invoke GetFullPathName,<'WiFiService.exe',0>,256,buff,esp invoke CreateService,[hScm],\ <'WiFiService',0>,\ <'Частная служба процесса WiFiTest',0>,\ SERVICE_ALL_ACCESS,\ SERVICE_WIN32_OWN_PROCESS,\ ; + SERVICE_INTERACTIVE_PROCESS,\ SERVICE_DEMAND_START,\ SERVICE_ERROR_NORMAL,\ buff,0,0,0,0,0 or eax,eax jne @f invoke MessageBox,0,<'Ошибка! Не удалось создать службу.',0>,0,0 jmp @close @@: mov [hServ],eax ;// Создаю анонимный пайп, и перевожу 2 его хэндла в строку, ;// ...чтобы передать как аргументы в StartService() invoke CreatePipe,hReadPipe,\ hWritePipe,0,0 cinvoke _ltoa,[hReadPipe], szRpipe,10 cinvoke _ltoa,[hWritePipe],szWpipe,10 cinvoke strlen,szRpipe inc eax push eax cinvoke strlen,szWpipe inc eax ;// Формирую массив нуль-терминальных строк, ;// как требует агрумент "lpServiceArgVectors" сл.функции ;// https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-startservicea xchg ecx,eax pop ebx mov edi,szRpipe add edi,ebx mov esi,szWpipe rep movsb invoke StartService,[hServ],0,0 ;<----- Так всё ОК! ; invoke StartService,[hServ],3,szSrvName ;<----- а так крэш :( invoke GetLastError cinvoke _ltoa,eax,buff,16 invoke MessageBox,0,buff,0,0 invoke ControlService,[hServ],SERVICE_CONTROL_STOP,sStatus invoke DeleteService, [hServ] invoke CloseServiceHandle,[hServ] invoke CloseServiceHandle,[hScm]
Marylin, вроде там надо массив строк, а не просто строку. Попробуй типа такого: Код (ASM): szSrvName db 'WiFiService',0 argsArray dd offset szSrvName invoke StartService.... , offset argsArray
Marylin, хендлы пайпов будут невалидны в контексте службы, создавай по имени. Ну и M0rg0t, верно написал, нужно передавать массив строк что есть массив указателей на данные строк. Ну и вызов A функций - это не есть хорошо.
Исправил некоторые грубые ошибки. Скопилировал в виде .ехе 42 кб для тех кто любит реверсить делфи код.
Наконец разобрался я со-своей службой, и скажу гемор ещё тот, т.к. имеются известные проблемы с отладкой на этапе тестирования. Некоторую инфу о сервисе можно получить в "ProcessHacker". Учитывая, что все службы запускаются процессом services.exe, то достаточно в списке его детей найти свой, и заглянуть в свойства по Enter. Аналогично и на вкладке "Сервисы", где можно узнать состояние Run/Stop и уже немного другие свойства. Как правильно заметил M0rg0t, аргументы StartService() нужно оформлять в виде массива указателей на строки, но поскольку я заменил анонимный пайп на именованный по совету Thetrik, то эти аргументы уже мне не понадобились, ведь клиент открывает пайп по его имени. Кстати само имя не обязательно должно быть с префиксом "Global" - норм.работает и без него (в утилите WinObj видно, что линк всё равно прописывается в папке Global). В общем теперь моя служба работает от имени System, что собственно мне и требовалось для вызова CryptUnprotectData() из crypt32.dll. Всем спасибо за дельные советы! Код (ASM): ;...... invoke CreateNamedPipe,<'\\.\pipe\WiFiPipe',0>,\ ;<-- '\\.\pipe\Global\WiFiPipe' PIPE_ACCESS_DUPLEX,\ PIPE_TYPE_MESSAGE,1,1024,1024,0,0 mov [hPipe],eax invoke StartService,[hServ],0,0 invoke ControlService,[hServ],\ SERVICE_CONTROL_INTERROGATE, sStatus ;// Ждать подключения клиента к каналу.. invoke ConnectNamedPipe,[hPipe],0 invoke Sleep,1000 ;// Прочитать приветствие сервиса! invoke ReadFile,[hPipe],buff,128,byteRetn,0 invoke SetDlgItemText,[hwnddlg],ID_File,buff jmp @next