Конференция "WinAPI" » Обмен данными между программой и сервисом через named pipes [D7, WinXP]
 
  • kotyara12 (15.03.13 20:36) [0]
    Доброго времени суток!

    Возникла проблема при работе с named pipes под win7, не могу понять в чем дело. Ситуация такова: на компе есть программа, которая запускается от имени текущего пользователя и сервис, запускаемый от SYSTEM. Между собой эти два компонента общаются по Named Pipe.
    Все прекрасно работает на Windows 2000 и XP.
    Но под Windows 7 сервис стартует без ошибок, но клиент к нему подключиться не может - CreateFile возвращает "Файл не существует"... не пойму в чем может быть дело...????
    Хотя бы укажите направление "куда копать" :-)

    Сервер:

    procedure InitializeSecurity(var SA: SECURITY_ATTRIBUTES);
    var
     SCD: PSECURITY_DESCRIPTOR;
    begin
     SCD := AllocMem(SECURITY_DESCRIPTOR_MIN_LENGTH);
     try
       Win32Check(InitializeSecurityDescriptor(SCD, SECURITY_DESCRIPTOR_REVISION));
       Win32Check(SetSecurityDescriptorDacl(SCD, True, nil, False));
       with SA do
       begin
         nLength := SizeOf(SECURITY_ATTRIBUTES);
         lpSecurityDescriptor := SCD;
         bInheritHandle := True;
       end;
     except
       FreeMem(SCD);
       raise;
     end;
    end;

    procedure FinalizeSecurity(var SA: SECURITY_ATTRIBUTES);
    begin
     if Assigned(SA.lpSecurityDescriptor) then
     begin
       FreeMem(SA.lpSecurityDescriptor);
       SA.lpSecurityDescriptor := nil;
     end;
    end;

    function TAuScriptsService.CreatePipeInstance(out NewPipeHandle: THandle; Value: TOverlapped): Boolean;
    var
     fSA: TSecurityAttributes;
    begin
     Result := False;
     try
     try
       InitializeSecurity(fSA);
       // Создаем новый экземпляр канала
       NewPipeHandle := CreateNamedPipe(
           SPipeName_AuScriptsService,
           PIPE_ACCESS_DUPLEX or FILE_FLAG_OVERLAPPED,
           PIPE_TYPE_MESSAGE or
           PIPE_READMODE_MESSAGE or
           PIPE_WAIT,
           PIPE_UNLIMITED_INSTANCES,
           SizeOf(TRPipeTaskEvent),
           SizeOf(TRPipeTaskCommand),
           NMPWAIT_USE_DEFAULT_WAIT,
           @fSA);
       if NewPipeHandle = INVALID_HANDLE_VALUE then
         RaiseSystemError;
       // Ждем подключения клиента
       Result := ConnectNamedPipe(NewPipeHandle, @Value);
       if not Result then
       begin
         case GetLastError of
           ERROR_IO_PENDING:
             Result := True;
           ERROR_PIPE_CONNECTED:
             SetEvent(Value.hEvent);
         else
           RaiseSystemError;
         end;
       end;
     except
       on E: Exception do
       begin
         if NewPipeHandle <> INVALID_HANDLE_VALUE then
         begin
           CloseHandle(NewPipeHandle);
           NewPipeHandle := INVALID_HANDLE_VALUE;
         end;
         LockHandleExcept(E, Self, SErrCreatePipeInstance);
       end;
     end;
     finally
       FinalizeSecurity(fSA);
     end;
    end;



    Клиент:

       // Открываем канал
       fPipe := CreateFile(SPipeName_AuScriptsService,
         GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0);
       if fPipe = INVALID_HANDLE_VALUE then
         RaiseSystemErrorFmt(SErrOpenPipe);
       // Под Win 7 всегда fPipe = INVALID_HANDLE_VALUE!
       // а RaiseSystemError выдает что-то типа "файл не существет"

  • MBo © (15.03.13 21:30) [1]
    Частые причины:
    1. Атрибуты безопасности (наличие, недочеты в инициализации, совместимость)
    2. Имена (например, префикс Global)
  • kotyara12 (15.03.13 21:43) [2]

    MBo ©   (15.03.13 21:30) [1]
    Частые причины:
    1. Атрибуты безопасности

    я тоже подозреваю, что что-то не так в InitializeSecurity... я ее "выдрал" откуда-то, сам не помню... Не подскажете, где в ней может быть проблема?
  • MBo © (15.03.13 22:03) [3]
    На мой взгляд выглядит нормально, но я не имею дела с вистой/семеркой, и тонкостей всех не знаю.

    Пример отсюда на семерке работает? http://www.delphimaster.ru/articles/named_pipes/index.html
  • p © (16.03.13 06:31) [4]
    Начиная с Windows Vista

    const
     StringSecurityDescriptor = 'D:' + '(A;ID;GRGW;;;WD)' + 'S:' + '(ML;;;;;LW)';

    type
     TConvertStringSecurityDescriptorToSecurityDescriptorW = function(StringSecurityDescriptor: LPCWSTR;
       StringSDRevision: DWORD; var SecurityDescriptor: Pointer;
       SecurityDescriptorSize: PULONG): BOOL; stdcall;

    var
     SecurityDescriptor: PSECURITY_DESCRIPTOR;
     SecurityAttributes: TSecurityAttributes;
     
    FillChar(SecurityDescriptor, SizeOf(PSECURITY_DESCRIPTOR), 0);
    if ConvertStringSecurityDescriptorToSecurityDescriptorW(StringSecurityDescriptor, 1, SecurityDescriptor, nil) then
    begin
     SecurityAttributes.nLength := SizeOf(TSecurityAttributes);
     SecurityAttributes.lpSecurityDescriptor := SecurityDescriptor;
     SecurityAttributes.bInheritHandle := True;
    end;

  • p © (16.03.13 08:59) [5]
    Или попробуй IPC на основе MailSlot http://smalldonkey.net/delphi/vcl/ipc.zip
  • kotyara12 (17.03.13 21:59) [6]

    MBo ©
    Пример отсюда на семерке работает?

    нет, не работает... мой вариант на основе него и был сделан. на ХР - на ура, но на 7-ке "не катит"... :-(


    > p ©

    спасибо, завтра попробую.

    Еще вопрос: SECURITY_DESCRIPTOR + SecurityAttributes нужны только для вызова CreateNamedPipe и после вызова CreateNamedPipe их сразу же можно грохнуть из памяти (как в моем примере) или нужно это делать только по завершении канала?
  • Игорь Шевченко © (17.03.13 22:25) [7]
    Как-то так:

    function CreateFullAccessSA(var SA: TSecurityAttributes): Boolean;
    type
     TAceHeader = packed record
       AceType: Byte;
       AceFlags: Byte;
       AceSize: Word;
     end;
     TAccessAllowedAce = packed record
       Header: TAceHeader;
       Mask: ACCESS_MASK;
       SidStart: DWORD;
     end;
    const
     FILE_READ_DATA = $0001; // file & pipe
     FILE_LIST_DIRECTORY = $0001; // directory
     FILE_WRITE_DATA = $0002; // file & pipe
     FILE_ADD_FILE = $0002; // directory
     FILE_APPEND_DATA = $0004; // file
     FILE_ADD_SUBDIRECTORY = $0004; // directory
     FILE_CREATE_PIPE_INSTANCE = $0004; // named pipe
     FILE_READ_EA = $0008; // file & directory
     FILE_WRITE_EA = $0010; // file & directory
     FILE_EXECUTE = $0020; // file
     FILE_TRAVERSE = $0020; // directory
     FILE_DELETE_CHILD = $0040; // directory
     FILE_READ_ATTRIBUTES = $0080; // all
     FILE_WRITE_ATTRIBUTES = $0100; // all
     FILE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED or Windows.SYNCHRONIZE or $1FF);
     FILE_GENERIC_READ = (STANDARD_RIGHTS_READ or FILE_READ_DATA or
       FILE_READ_ATTRIBUTES or FILE_READ_EA or Windows.SYNCHRONIZE);
     FILE_GENERIC_WRITE = (STANDARD_RIGHTS_WRITE or FILE_WRITE_DATA or
       FILE_WRITE_ATTRIBUTES or FILE_WRITE_EA or FILE_APPEND_DATA or
         Windows.SYNCHRONIZE);
     FILE_GENERIC_EXECUTE = (STANDARD_RIGHTS_EXECUTE or FILE_READ_ATTRIBUTES or
       FILE_EXECUTE or Windows.SYNCHRONIZE);

     HEAP_ZERO_MEMORY = $00000008;
     ACL_REVISION = 2;
     SECURITY_WORLD_RID = $00000000;
     SECURITY_WORLD_SID_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0,
       0, 1));
    var
     pSD: PSecurityDescriptor;
     psidEveryone: PSID;
     sidAuth: TSidIdentifierAuthority;
     lSDSize, lACLSize: Cardinal;
     lpACL: PACL;
    begin
     Result := False;
     pSD := nil;
     psidEveryone := nil;
     try
       SA.nLength := SizeOf(TSecurityAttributes);
       SA.lpSecurityDescriptor := nil;
       SA.bInheritHandle := False;
       sidAuth := SECURITY_WORLD_SID_AUTHORITY;
       if not AllocateAndInitializeSid(sidAuth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0,
         0, 0, 0, psidEveryone) then
          RaiseLastOSError;
       lSDSize := SizeOf(TSecurityDescriptor);
       lACLSize := GetLengthSID(psidEveryone) + SizeOf(TAccessAllowedACE) +
         SizeOf(TACL);
       pSD := HeapAlloc(GetProcessHeap, HEAP_ZERO_MEMORY, lSDSize + lACLSize);
       if pSD = nil then
          RaiseLastOSError;
       if not InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION) then
          RaiseLastOSError;
       lpACL := PACL(PRawByte(pSD) + lSDSize);
       if not InitializeAcl(lpACL^, lACLSize, ACL_REVISION) then
          RaiseLastOSError;
       if not AddAccessAllowedAce(lpACL^, ACL_REVISION, FILE_ALL_ACCESS,
         psidEveryone) then
          RaiseLastOSError;
       if not SetSecurityDescriptorDacl(pSD, True, lpACL, False) then
          RaiseLastOSError;
       SA.lpSecurityDescriptor := pSD;
       Result := True;
     finally
       if Assigned(psidEveryone) then
         FreeSID(psidEveryone);
       if not Result and Assigned(pSD) then
         HeapFree(GetProcessHeap, 0, pSD);
     end;
    end;

  • kotyara12 (18.03.13 14:25) [8]

    > Игорь Шевченко ©   (17.03.13 22:25) [7]

    PRawByte - ???
  • kotyara12 (18.03.13 14:55) [9]
    Спасибо за помощь!

    Ну в принципе все заработало. Только я немного изменил инициализацию SA, "скрестив" метод, предложенный Игорем Шевченко с реализацией, которую я откопал в FWIOCompletionPipes.pas (автор: Александр (Rouse_) Багель).
    Остался один вопрос - что делать с памятью, выделенной под DACL?
    Вот что у меня получилось:

    function TAuScriptsService.CreatePipeInstance(out NewPipeHandle: THandle; Value: TOverlapped): Boolean;
    const
     FILE_READ_DATA = $0001; // file & pipe
     FILE_WRITE_DATA = $0002; // file & pipe
     FILE_CREATE_PIPE_INSTANCE = $0004; // named pipe
     FILE_READ_EA = $0008; // file & directory
     FILE_WRITE_EA = $0010; // file & directory
     FILE_READ_ATTRIBUTES = $0080; // all
     FILE_WRITE_ATTRIBUTES = $0100; // all
     FILE_GENERIC_READ = STANDARD_RIGHTS_READ or FILE_READ_DATA or FILE_READ_ATTRIBUTES or FILE_READ_EA or SYNCHRONIZE;
     FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE or FILE_WRITE_DATA or FILE_WRITE_ATTRIBUTES or FILE_WRITE_EA or SYNCHRONIZE;
     PIPE_ALL_ACCESS = FILE_CREATE_PIPE_INSTANCE or FILE_GENERIC_READ or FILE_GENERIC_WRITE;

     SECURITY_WORLD_SID_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 1));
     SECURITY_WORLD_RID = $00000000;
     ACL_REVISION = 2;
    var
     SecurityAttributes: TSecurityAttributes;
     SecurityDescriptor: TSecurityDescriptor;
     SIDIdentifierAuthority: TSIDIdentifierAuthority;
     SID: PSID;
     DACL: PACL;
     DaclSize: Cardinal;
    begin
     Result := False;
     // Инициализация параметров безопасности
     SID := nil;
     try
       SIDIdentifierAuthority := SECURITY_WORLD_SID_AUTHORITY;
       if not AllocateAndInitializeSid(SIDIdentifierAuthority, 1,
         SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, SID) then
           RaiseSystemError;
       try
         DaclSize := SizeOf(TACL) + SizeOf(ACCESS_ALLOWED_ACE) + GetLengthSid(SID);

         
         DACL := AllocMem(DaclSize);

         if not InitializeAcl(DACL^, DaclSize, ACL_REVISION) then
           RaiseSystemError;
         if not AddAccessAllowedAce(DACL^, ACL_REVISION, PIPE_ALL_ACCESS, SID) then
           RaiseSystemError;
         if not InitializeSecurityDescriptor(@SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION) then
           RaiseSystemError;
         if not SetSecurityDescriptorDacl(@SecurityDescriptor, True, DACL, False) then
           RaiseSystemError;
         SecurityAttributes.nLength := SizeOf(TSecurityAttributes);
         SecurityAttributes.lpSecurityDescriptor := @SecurityDescriptor;
         SecurityAttributes.bInheritHandle := False;
         // Создаем новый экземпляр канала
         NewPipeHandle := CreateNamedPipe(
             SPipeName_AuScriptsService,
             PIPE_ACCESS_DUPLEX or FILE_FLAG_OVERLAPPED,
             PIPE_TYPE_MESSAGE or
             PIPE_READMODE_MESSAGE or
             PIPE_WAIT,
             PIPE_UNLIMITED_INSTANCES,
             SizeOf(TRPipeTaskEvent),
             SizeOf(TRPipeTaskCommand),
             NMPWAIT_USE_DEFAULT_WAIT,
             @SecurityAttributes);
         if NewPipeHandle = INVALID_HANDLE_VALUE then
           RaiseSystemError;
         // Ждем подключения клиента
         Result := ConnectNamedPipe(NewPipeHandle, @Value);
         if not Result then
         begin
           case GetLastError of
             ERROR_IO_PENDING:
               Result := True;
             ERROR_PIPE_CONNECTED:
               SetEvent(Value.hEvent);
           else
             RaiseSystemError;
           end;
         end;
       finally
         if SID <> nil then
           FreeSID(SID);
       end;
     except
       on E: Exception do
       begin
         if NewPipeHandle <> INVALID_HANDLE_VALUE then
         begin
           CloseHandle(NewPipeHandle);
           NewPipeHandle := INVALID_HANDLE_VALUE;
         end;
         LockHandleExcept(E, Self, SErrCreatePipeInstance);
       end;
     end;
    end;



    Этот код работает и на XP и на 7-ке.

    Одно но!!!!!

    Меня смущает

    DACL := AllocMem(DaclSize);


    В этом коде память из-под DACL никак не освобождается. При этом любая попытка ее освободить, даже при обработке ошибок

    DACL := AllocMem(DaclSize);
    try
    ....
    except
     FreeMem(DACL);
     ....
    end;



    приводит к странному результату: первое подключение к каналу не удается, зато все последющие проходят успешно (хотя исключение не генерируется).
    Если все оставить все как есть (т.е. не освобожать память, выделенную под DACL, все работает и память, занимаемая сервисом не растет при последующих попдыках доступа к каналу).
    Может все так и оставить?
  • Rouse_ © (18.03.13 16:44) [10]

    > kotyara12   (18.03.13 14:55) [9]

    Если речь идет от этом примере: http://rouse.drkb.ru/network.php#fwiocompletionpipe
    ...то, в моем варианте ничего подправлять не нужно, он сразу готов к работе можно сказать "из коробки" :)
    Собственно именно этот класс и используется в большей части времени как раз для обеспечения связи между сервисом и утилитой конфигурирования.

    А память выделенную конечно нужно освобождать.
  • kotyara12 (18.03.13 19:32) [11]

    > Rouse_ ©   (18.03.13 16:44) [10]
    Attributes: SECURITY_ATTRIBUTES;

    Именно об этом. К сожалению не прокатило. Я вначале так и сделал - просто тупо скопировал кусок кода, который отвечает за инициализацию Attributes: SECURITY_ATTRIBUTES. Сервис заработал, но с одной особенностью - на ХР все по прежнему работало без проблем, а на семерке - только со второй попытки. Т.е. первый раз клиент получал отказ "нет такого файла", а во 2-ой, 3-ий и т.д. раз все уже работало нормально. Причем если закомментарить FreeMem(ACL) то все работает с первого раза.
    Сам удивлен :-(


    > А память выделенную конечно нужно освобождать.

    вот и я про то же...

    может я еще где накосячил :-( чем так принципиально 7-ка от ХР отличается ?
  • Rouse_ © (18.03.13 19:39) [12]
    Попробуй без экспериментов целиком мой модуль использовать...
  • kotyara12 (18.03.13 19:41) [13]
    Кажется я нашел, в чем собственно проблема...

    Спасибо за помощь!
  • kotyara12 (18.03.13 20:20) [14]

    > Rouse_ ©   (18.03.13 19:39) [12]
    > Попробуй без экспериментов целиком мой модуль использовать.
    > ..

    Я может быть что-то не понял, но я не нашел в TFWPipeClient генерации события  OnRead (FRead: TOnClientReadEvent); дело в том, что мне нужно отправить "серверу" запись -команду и в ответ клиент получает не один ответ, а некоторое множество... Одним SendData не обойдешся.
    Поэтому и не понимаю пока как его использовать. Поясните плиз.
  • Rouse_ © (18.03.13 20:26) [15]
    Это безсобытийный компонент. Грубо сервер всегда висит (в прямом смысле) на чтении, а клиент шлет ему SendData на каждый из которых сразу получает ответ. Собственно это именно то поведение которое обычно реализует сервис - ждет команд извне.
  • kotyara12 (18.03.13 20:46) [16]

    > Rouse_ ©   (18.03.13 20:26) [15]

    У меня это работает так: сервер висит на чтении (ждет команду), в idle обрабатывает сообщения главного потока сервиса. При подключении клиента создается новый поток и ему передается handle канала. Далее вся работа с каналом происходит в уже новом потоке. Поток читает из канала команду, компилит ее и начинает ее выполнять. В процессе выполнения он в "одностороннем порядке" отправляет клиенту результаты выполнения - данные о позициях индикаторов, тексты логов и сообщения, выводимые на экран и т.д.
    Поэтому двухсторонний режим обмена категорически не подходит :-(

    Конечно же мне было бы проще использовать готовый модуль. Самое интересное, этот код уже проработал больше двух лет больше чем на 300 компах без проблем. До тех пор пока на 7-ку переходить не начали.
  • Rouse_ © (18.03.13 21:00) [17]
    Тут тебе тогда обычные сокеты подошли бы :) Пайпы уже излишество...
  • kotyara12 (18.03.13 21:11) [18]
    Сокеты клиент использует для связи с сервером приложений, котором они все и управляются . Вот такой многосвязный клиент получился :-)

    И все-таки. Я так и не понял главного.
    SECURITY_ATTRIBUTES нужны только при СОЗДАНИИ канала или на протяжении ВСЕГО ВРЕМЕНИ его существования?
    Во втором случае тогда мне нужно их сохранить в новом потоке, а уже при завершении потока удалять из памяти? Тогда все логично получается.
  • Rouse_ © (18.03.13 21:15) [19]

    > SECURITY_ATTRIBUTES нужны только при СОЗДАНИИ канала или
    > на протяжении ВСЕГО ВРЕМЕНИ его существования?

    Первое.
  • kotyara12 (19.03.13 10:02) [20]
    УРА! ВСЕ ЗАРАБОТАЛО НА 100%!
    Проблему с освобождением памяти из-под DACL все же удалось решить. И причина оказалась весьма необычной.
    Для тех, кому интересно:

    Во всех примерах на Delphi, что я шашел, размер DACL вычисляется следующим образом:
    SizeOf(ACL) + SizeOf(ACCESS_ALLOWED_ACE) + GetLengthSid(SID);


     DaclSize := SizeOf(ACL) + SizeOf(ACCESS_ALLOWED_ACE) + GetLengthSid(SID);
     ACL := AllocMem(DaclSize);
     try
       Win32Check(InitializeAcl(ACL^, DaclSize, ACL_REVISION));
       ...
     finally
       FreeMem(ACL);
     end



    Между тем, в Win32 Developer's Reference по функции InitializeAcl сказано:

    When calculating the size of an ACL, note that each ACE in an ACL gets the SID specified by its SidStart member copied to the ACE structure, starting at the ACE's SidStart member. Thus, each ACE added to the ACL requires room for the ACE plus room for its SID minus the size of the SidStart member (a DWORD).
    For example, the size of an ACL buffer large enough to contain a single ACCESS_ALLOWED_ACE is :
    cbAcl = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pSid) - sizeof(DWORD); !!!!

    Я попробовал сегодня код исправить в соответствии с Help-ом и ВСЕ ЗАРАБОТАЛО!!! Без глюков!!!.

     DaclSize := SizeOf(ACL) + SizeOf(ACCESS_ALLOWED_ACE) + GetLengthSid(SID) - SizeOf(DWORD);
     ACL := AllocMem(DaclSize);
     try
       Win32Check(InitializeAcl(ACL^, DaclSize, ACL_REVISION));
       ...
     finally
       FreeMem(ACL);
     end



    И еще мелочь... нашел сегодня в FWIOCompletionPipes:


    const
     SECURITY_WORLD_SID_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 1));
     ...
    var
     SIA: SID_IDENTIFIER_AUTHORITY;
     ...
    begin
     Result := False;
     try
       SIA := SECURITY_WORLD_SID_AUTHORITY;
       try
         Win32Check(InitializeSid(SID, SECURITY_WORLD_SID_AUTHORITY, 1)); // <-- SIA никак не используется
     ...



    SIA никак не используется. Может SIA нужен для чего-то, но я у себя убрал и все работает. Мелочь конечно. :-)

    Еще раз огромное спасибо всем откликнувшимся.
 
Конференция "WinAPI" » Обмен данными между программой и сервисом через named pipes [D7, WinXP]
Есть новые Нет новых   [134430   +4][b:0][p:0.008]