Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Implementing Windows Anonymous Pipe Into Class

windows api object oriented programming api wrapper anonymous pipe

  • Please log in to reply
3 replies to this topic

#1 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 05 December 2012 - 10:28 AM

Overview

In previous tutorial (Execute a Console Program and Capture Its Output) we were using anonymous pipes to read output from console program. The pipes were created using direct Windows API routines. Therefore we were using procedural programming approach in that specific area. On the other hand, Delphi is well known as object oriented programming language. So why don't we implement OOP in manipulating anonymous pipes and reap OOP advantages, such as reusability.

In this tutorial we will try to encapsulate windows API calls in creating and managing anonymous pipe into a class. This way we can reuse and maintain the codes easily. We will name this class TAnonymPipe.


TAnonymPipe Class, the Design

These are the features of TAnonymPipe.
  • Create the pipe
  • Read data from the pipe
  • Write data from the pipe
  • Destroy/finalize the pipe
Create the Pipe

Let's not confuse the "create" pipe with usual delphi class constructor name, Create. When creating the class we don't always want the pipe to be created immediately. In order to mimic more of the real world pipe, let's use Open for the method that responsible to actually create the pipe. Imagine that in the real world, a pipe can not be used unless at least one of its ends is open.

Now we have an Open method to implement in TAnonymPipe.


Get Data from the Pipe

Of course we need to get data from our communication pipe. It is obvious we need a method for this. And like previously, let's use simple but intuitive name. This time I believe Get would be a good name. To make it easier the use of our TAnonymPipe class, I would like to have several "versions" of this Get method. They are:
  • function Get(var ABuffer; const ABufferSize: Cardinal): Cardinal;.
    This will get data from the pipe and put it into a memory buffer. ABufferSize defines both the buffer size (in bytes) and the number of bytes the caller tries to get.
  • function Get(ADest: TStream; const ASize: Cardinal=0): Cardinal;
    This will get data from the pipe and put it into the given instance of TStream. ASize simply specifies the number of bytes the caller wishes to get. When ASize equals 0, this method simply gets all available data.
  • function GetAnsiStr: AnsiString;
    This will read the whole current content of the pipe and return it to the caller as an AnsiString.
Put Data to the Pipe

Another mandatory thing we need to have in our communication data is way(s) to put/write data to it. As usually, we will use simple and intuitive name for method(s) related with this operation. This time we will use Put. Like with getting data, we also want to make it easier for users of TAnonymPipe by providing several "Put" methods to fit most popular approaches in transferring data.
  • function Put(const ABuffer; const ADataSize: Cardinal): Cardinal;
    This method put data that's being held in a memory buffer into the pipe. ABuffer is the memory location holding the data, and ADataSize specifies the number of bytes should be read from ABuffer and put to the pipe.
  • function Put(ASource: TStream; const ADataSize: Cardinal=0): Cardinal;
    This method puts data currently stored in the given TStream instance to the pipe. ADataSize specifies how many bytes should be read from the stream, from the current position, and put into the pipe. If ADataSize equals 0, the method will try to put the whole content of the stream.
  • procedure PutAnsiStr(const AStr: AnsiString);
    This method writes the content of an AnsiString variable into the pipe.
Finalize the Pipe

To avoid memory or resource leak, we need to "close" the pipe when we don't need it anymore. Like with the Open method, we also will use method name that mimic the real world pipe, i.e. Close.


Could We Peek, Please?

At the last moment, it occured to me that sometimes we need to know the content of the pipe without actually removing the content. In real world, it's like peeking into the inside of the pipe to see whether it has something. Le't have several Peek methods.
  • function Peek: Boolean;
    This method checks the pipe if it has some data. It returns true if there is data in the pipe, otherwise it returned false.
  • function Peek(var AContentSize: Cardinal): Boolean;
    This method operates like the above Peek, but it also returns the size of data available inside the pipe.
  • function Peek(var ABuffer; const ABufferSize: Cardinal; var AContentSize: Cardinal): Boolean;
    This method works like the previous Peek methods, but it also read the content into the given memory location.
  • function Peek(var AStr: AnsiString): Boolean;
    This method also works similarly like the previous Peek methods, but it would read current content of the pipe into the passed AnsiString variable.
Skeleton of TAnonymPipe

TAnonymPipe=class
public
  procedure Open;
  procedure Close;

  // read methods
  function Get(var ABuffer; const ABufSize: Cardinal): Cardinal; overload;
  function Get(ADest: TStream; const AToReadCount: Cardinal=0): Cardinal; overload;
  function GetAnsiStr: AnsiString;

  // write methods
  function Put(const ABuffer; const ADataSize: Cardinal): Cardinal; overload;
  function Put(ASrc: TStream; const AToWriteCount: Cardinal=0): Cardinal; overload;
  procedure PutAnsiStr(const AStr: AnsiString);

  // Peek methods
  function Peek: Boolean; overload;
  function Peek(var AContentSize: Cardinal): Boolean; overload;
  function Peek(var ABuffer; const ABufferSize: Cardinal; var AContentSize: Cardinal): Boolean; overload;
  function Peek(var AStr: AnsiString): Boolean; overload;

  // data to go into the pipe should get through here
  property Input : THandle read FInput;
  // data from the pipe should be read from here
  property Output : THandle read FOutput;
  // indicates if the pipe is active and ready for communication
  property IsOpened: Boolean read GetIsOpened write SetIsOpened;
  // returns true if there is no data in the pipe's output buffer
  property IsEmpty : Boolean read GetIsEmpty;
end;
Note that some new public properties were added to the class. The first three were mandatory, while IsEmpty was a nice helper. I considered IsEmpty as just a nice helper, because actually we could use Peek here. But I believe in some situation code like "if Pipe.IsEmpty then ..." would be easier to understand than "if Pipe.Peek then ...".


Key Methods Implementation

Procedure Open;

Here we want to create the anonymous pipe and keep the read and write handle to respective variabel. The implementation is as the following. Note that if you had read the Execute a Console Program and Capture Its Output tutorial, you will familiar with the codes.
 
procedure TAnonymPipe.Open;
var
  vSecurityDesc: TSecurityDescriptor;
  vSecurityAttributes: TSecurityAttributes;
begin
  if IsOpened then Exit;

  if (Win32Platform = VER_PLATFORM_WIN32_NT) then
  begin
    //initialize security descriptor (in NT based Windows)
    InitializeSecurityDescriptor(@vSecurityDesc, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(@vSecurityDesc, True, nil, false);
    vSecurityAttributes.lpSecurityDescriptor := @vSecurityDesc;
  end
  else
    vSecurityAttributes.lpSecurityDescriptor := nil;

  if not CreatePipe(FOutput, FInput, @vSecurityAttributes, 0) then
  begin
    FInput := 0;
    FOutput := 0;
    raise EAnonymousePipeCreationerror.Create('Can not create anonymous pipe. System error message: '+ SysErrorMessage(GetLastError));
  end;
end;
  • TSecurityAttributes' or SECURITY_ATTRIBUTES' official information can be found in this msdn page.
  • Procedure Close;

    Here we want to finalize and clean up the created pipe.

    procedure TAnonymPipe.Close;
    begin
      if not GetIsOpened then Exit;
      CloseHandle(FInput);
      CloseHandle(FOutput);
      FInput := 0;
      FOutput := 0;
    end;
    
    Function Get

    In this case we talk about Get method that read the content of the pipe and put it into the given memory location. And the implementation was:

    function TAnonymPipe.Get(var ABuffer; const ABufferSize: Cardinal): Cardinal;
    begin
      Result := 0;
      if not GetIsOpened then
        raise EClosedPipe.Create('Can not read from closed pipe');
    
      if not ReadFile(FOutput, ABuffer, ABufferSize, Result, nil) then
        raise EReadPipeFailure.Create('Failed reading pipe. System error: ' + SysErrorMessage(GetLastError));
    end;
    
    Function Put

    Here we only discuss the Put version that read the content of a given memory location and put it into the pipe. And the implementation was:

    function TAnonymPipe.Put(const ABuffer; const ADataSize: Cardinal): Cardinal;
    begin
      if not GetIsOpened then
        raise EClosedPipe.Create('Can not write to a closed pipe');
    
      if not WriteFile(FInput, ABuffer, ADataSize, Result, nil) then
        raise EWritePipeFailure.Create('Failed writing to pipe. System error: ' + SysErrorMessage(GetLastError));
    end;
    
    Function Peek

    Here we only discuss the most complex Peek version out of the four, i.e. the one that read the current content of the pipe into AnsiString variable.

    function TAnonymPipe.Peek(var AStr: AnsiString): Boolean;
    var
      vReadSize: Cardinal;
      vBytesLeft: Cardinal;
      vAvailBytes: Cardinal;
    begin
      Result := PeekNamedPipe(FOutput, nil, 0, nil, @vAvailBytes, nil)
                and (vAvailBytes > 0);
    
      if not Result then Exit;
    
      SetLength(AStr, vAvailBytes);
      PeekNamedPipe(FOutput, @AStr[1], Length(AStr), @vReadSize, @vAvailBytes, @vBytesLeft);
    end;
    
    In this case (actually for all Peek versions), you might be interested to look into Windows API PeekNamedPipe. Visit this msdn page to read official information about it.


    Demo Project

    For this writing, I created a DUnit project for testing the functionalities of TAnonymPipe. With DUnit unit tests, if in the future we do some change to TAnonymPipe code, we can make sure that the change does not break any functionality by running this DUnit test project.


    Full source code of TAnonymPipe along with the DUnit demo project is contained in the following zip file, Attached File  Demo.zip   746.74KB   806 downloads. Feel free to use or improve any codes.

Edited by LuthfiHakim, 28 April 2013 - 06:22 AM.

  • 0

#2 Roland

Roland

    CC Lurker

  • Just Joined
  • Pip
  • 1 posts

Posted 19 February 2016 - 07:32 AM

Hello! "Unit1.pas" and "Unit1.dfm" (from the Delphi 7 demo) are missing.


  • 0

#3 hantt163

hantt163

    CC Lurker

  • New Member
  • Pip
  • 3 posts

Posted 13 May 2016 - 02:15 AM

woa! it's helpful


  • 0

#4 YannickWiggins

YannickWiggins

    CC Lurker

  • Just Joined
  • Pip
  • 1 posts

Posted 10 August 2016 - 11:36 AM

Hi, I have actually running a program that is executing cmd.exe /c "some commands".  It works but as you said in your other tutorial, you have to wait for the process to end before displaying the console.  I'd like to know if there would be a way to make it real time.  I run a ping command but it would be great to have it in real time.

 

Regards,

Yannick.


  • 0





Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download