Lazarus

Programming => Operating Systems => Windows => Topic started by: Aidex on February 12, 2019, 08:08:12 am

Title: How to use Named Pipes?
Post by: Aidex on February 12, 2019, 08:08:12 am
Hello!
I want to use Named Pipes to communicate between two programs.
With the Windows function I manage to create a named pipe, but unfortunately I fail to write into the pipe.
What am I doing wrong?

uses Windows;

function Server_createPipe(): THandle;
var
   SD: SECURITY_DESCRIPTOR;
   SA: SECURITY_ATTRIBUTES;
const
   PIPE_UNLIMITED_INSTANCES = 255;
begin
   InitializeSecurityDescriptor(@SD, SECURITY_DESCRIPTOR_REVISION);
   SetSecurityDescriptorDacl(@SD, true, nil{ACL}, false);
   SA.lpSecurityDescriptor := @SD;
   SA.nLength := SizeOf(SA);
   SA.bInheritHandle := true;

   Result := CreateNamedPipe(PChar('\\.\pipe\Test'),
                             PIPE_ACCESS_DUPLEX or FILE_FLAG_WRITE_THROUGH,
                             PIPE_TYPE_BYTE or PIPE_NOWAIT, // others: PIPE_TYPE_MESSAGE or PIPE_READMODE_MESSAGE
                             PIPE_UNLIMITED_INSTANCES,
                             1024,
                             1024,
                             50,
                            @SA);
end; 

function writeToPipe(Pipe: THandle; const s: String): Boolean;
var written: DWord;
begin
  if s='' then Exit(true);
  Result := WriteFile(Pipe, s[1], Length(s), {out}written, nil);
end;

The handle seems to be correct (value 252), but WriteFile always returns false.
Title: Re: How to use Named Pipes?
Post by: Aidex on February 12, 2019, 08:23:02 am
By the way, is there a cross-operating system solution for Named Pipes?
Unfortunately, the Lazarus unit Pipes only provides functions for Anonymous Pipes.
Title: Re: How to use Named Pipes?
Post by: Remy Lebeau on February 12, 2019, 07:52:52 pm
Code: [Select]
SA.bInheritHandle := true;

Why are you creating the server pipe as inheritable?

Code: [Select]
Result := CreateNamedPipe(PChar('\\.\pipe\Test'),
                          PIPE_ACCESS_DUPLEX or FILE_FLAG_WRITE_THROUGH,
                          PIPE_TYPE_BYTE or PIPE_NOWAIT, // others: PIPE_TYPE_MESSAGE or PIPE_READMODE_MESSAGE
                          PIPE_UNLIMITED_INSTANCES,
                          1024,
                          1024,
                          50,
                          @SA);

Did you check to make sure that CreateNamedPipe() is not returning INVALID_HANDLE_VALUE on failure?

Code: [Select]
Result := WriteFile(Pipe, s[1], Length(s), {out}written, nil);

Note that this code assumes that String is AnsiString.  I would suggest encoding the String to a byte array first, using whatever charset you want to use (like UTF-8) and then send the bytes.

The handle seems to be correct (value 252), but WriteFile always returns false.

Did you check with GetLastError() to find out WHY WriteFile() is returning false?

Note that you are creating the pipe with the PIPE_NOWAIT flag:

Quote
PIPE_NOWAIT
Nonblocking mode is enabled. In this mode, ReadFile, WriteFile, and ConnectNamedPipe always return immediately.

When WriteFile() is used asynchronously, it always returns false, so you MUST check with GetLastError() afterwards to see if it returns ERROR_IO_PENDING indicating the write operation has actually been started in the background and did not really fail.

Note, however:

Quote
Note that nonblocking mode is supported for compatibility with Microsoft LAN Manager version 2.0 and should not be used to achieve asynchronous I/O with named pipes. if you want asynchronous I/O - use FILE_FLAG_OVERLAPPED

You should replace the PIPE_NOWAIT flag with FILE_FLAG_OVERLAPPED when calling CreateNamedPipe(), and then pass OVERLAPPED structs to ConnectNamedPipe(), ReadFile(), and WriteFile() so you can track the status of I/O operations that are running in the background.

See the following articles on MSDN:

Title: Re: How to use Named Pipes?
Post by: Aidex on February 13, 2019, 10:33:25 pm
Thank you very much for your detailed answer.
The idea with GetLastError() really could have come to me.

After WriteFile() GetLastError() returns the value 536 (ERROR_PIPE_LISTENING) = "Waiting for a process to open the other end of the pipe."

After the other process has also opened the pipe, WriteFile() works now!  :)
Thank you very much!
Title: Re: How to use Named Pipes?
Post by: Remy Lebeau on February 13, 2019, 11:21:47 pm
The idea with GetLastError() really could have come to me.

Next time, please read the documentation more carefully.

After WriteFile() GetLastError() returns the value 536 (ERROR_PIPE_LISTENING) = "Waiting for a process to open the other end of the pipe."

That means you are calling WriteFile() too soon, you are not waiting for a client to connect to the pipe first.  Call ConnectNamedPipe() and wait for it to report a successfully connected client before trying to read/write to it:

Quote
Enables a named pipe server process to wait for a client process to connect to an instance of a named pipe. A client process connects by calling either the CreateFile or CallNamedPipe function.

And don't forget to disconnect the pipe when done.
TinyPortal © 2005-2018