bl.OGware

infrequent grumblings of a software engineer and then some… (also some Delphi programming)

Interface unit for sending debug messages to SysInternals ProcessMonitor

Posted by tier777 on 2010-04-15

The latest version of SysInternals‘ excellent ProcessMonitor is now able to receive custom debug log messages and display them right in between the I/O logs.

Even though I still don’t quite get the rationale for the need to create a new API for this (in contrast to simply merging in the functionality of DebugView) I went straight ahead and converted the API to Delphi.

This should work with Delphi 6 and later. I successfully tested with Delphi 2007, 2009 and 2010.

(Update: the originally posted version did actually not work with Delphi 2007 or earlier because I had inexplicably overlooked “that Unicode thing”… Thanks to GunSmoker for pointing that out! Using $ifdef UNICODE and WideString is a cheap cop-out, I know – but it works)

Enjoy:

unit ProcMonDebugOutput;

interface

uses
  Windows;

type
  TProcessMonitorLogger = class
  private
    const
//      FILE_WRITE_ACCESS           = $00000002;
//      METHOD_BUFFERED             = $00000000;
//      FILE_DEVICE_PROCMON_LOG     = $00009535;
//      IOCTL_EXTERNAL_LOG_DEBUGOUT = CTL_CODE(FILE_DEVICE_PROCMON_LOG,
//                                             $81,
//                                             METHOD_BUFFERED,
//                                             FILE_WRITE_ACCESS);
      IOCTL_EXTERNAL_LOG_DEBUGOUT = $95358204;
    class var
      FDevice: THandle; // = INVALID_HANDLE_VALUE
    class function Open(): THandle;
    class procedure Close();
    {$IF CompilerVersion >= 21}
    class constructor Create;
    class destructor Destroy;
    {$IFEND}
  public
    class function Output(const AOutputString: {$ifdef UNICODE}String{$else}WideString{$endif}): Boolean;
  end;
  PML = TProcessMonitorLogger;

implementation

{ TProcessMonitorLogger }

//function CTL_CODE(const ADevType, AFunc, AMethod, AAccess: Cardinal): Cardinal; inline;
//begin
//  Result := (ADevType shl 16) or (AAccess shl 14) or (AFunc shl 2) or AMethod;
//end;

{$IF CompilerVersion >= 21}
class constructor TProcessMonitorLogger.Create;
begin
  FDevice := INVALID_HANDLE_VALUE;
end;

class destructor TProcessMonitorLogger.Destroy;
begin
  Close();
end;
{$IFEND}

class procedure TProcessMonitorLogger.Close;
begin
  if INVALID_HANDLE_VALUE <> FDevice then
    begin
      CloseHandle(FDevice);
      FDevice := INVALID_HANDLE_VALUE;
    end;
end;

class function TProcessMonitorLogger.Open(): THandle;
begin
  if INVALID_HANDLE_VALUE = FDevice then
    FDevice := CreateFile('\\.\Global\ProcmonDebugLogger',
                            GENERIC_READ or GENERIC_WRITE,
                            FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
                            nil,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL,
                            0);
  Result := FDevice;
end;

class function TProcessMonitorLogger.Output(const AOutputString: {$ifdef UNICODE}String{$else}WideString{$endif}): Boolean;
var
  lProcMonHwnd: THandle;
  lInputLength: Cardinal;
  lOutputLength: Cardinal;
  lLastError: Cardinal;
begin
  Result := False;
  if AOutputString = '' then
    SetLastError(ERROR_INVALID_PARAMETER)
  else
    begin
      lProcMonHwnd := Open();
      if lProcMonHwnd <> INVALID_HANDLE_VALUE then
        begin
          lInputLength := Length(AOutputString) * SizeOf(WideChar);
          lOutputLength := 0;
          Result := DeviceIoControl(lProcMonHwnd,
                                    IOCTL_EXTERNAL_LOG_DEBUGOUT,
                                    PWideChar(AOutputString),
                                    lInputLength,
                                    nil,
                                    0,
                                    lOutputLength,
                                    nil);
          if not Result then
            begin
              lLastError := GetLastError();
              if lLastError = ERROR_INVALID_PARAMETER then
                SetLastError(ERROR_WRITE_FAULT);
            end;
        end
      else
        SetLastError(ERROR_BAD_DRIVER);
    end;
end;

{$IF CompilerVersion < 21}
initialization
  TProcessMonitorLogger.FDevice := INVALID_HANDLE_VALUE;
finalization
  TProcessMonitorLogger.Close();
{$IFEND}
end.

Usage:

uses
  ProcMonDebugOutput;
begin
  PML.Output('How hard could it be?');
end;
About these ads

10 Responses to “Interface unit for sending debug messages to SysInternals ProcessMonitor”

  1. Hi!

    It will not work in Delphi 6 without modifications. Reasons:
    1) no constants allowed in classes declaration. so IOCTL_EXTERNAL_LOG_DEBUGOUT should be moved to outside for Delphi 6 compatibility
    2) Delphi 6 does not allow to use class var in class declaration. After I moved FDevice: THandle; from class to implementation, everything worked fine.

    Thanks!

  2. LDS said

    From John Robbins’ Blog:
    “Mark and I bounced a couple of emails back and forth discussing how the trace statements should get into Process Monitor. Originally, I thought merging the functionality of Debug View and Process Monitor would be the way to go but Mark thought adding the OutputDebugString capturing to Process Monitor would add lots of extraneous lines of output that would get in most people’s way. We settled on the idea that getting your tracing into Process Monitor should be something a developer specifically opts into by calling an API.”

    • tier777 said

      Luigi: yes, I read that too (and commented on it but the comment is apparently still in moderation) but I don’t really think it makes a lot of sense. ProcessMonitor already has excellent filtering capabilities and IMHO would actually be pretty much unusable without them. So I don’t really buy the “adding lots of extraneous lines of output that would get in most people’s way” argument.

      That’s exactly the part of the article that I was talking about when I wrote that I didn’t get the rationale behind the new API.

      • Keep in mind it was more than just the OutputDebugString flood we were avoiding. First, the majority of Process Monitor users are *definitely not* software developers, but network administrators. Adding more stuff to the log that they probably don’t care about is not helpful to them. Second it was much easier to add the custom interface than to essentially add all the Debug View code to Process Monitor. Like all development there are trade offs. :)

        Hope that clarifies!
        John Robbins

        • tier777 said

          John,

          > Hope that clarifies!

          it does… a bit at least… ;)

          Anyway, as easy as it was to implement the new API I’m not really complaining (anymore).

          However, the proprietary API still means that existing installations (which are already using OutputDebugString) can’t benefit from this. I will first have to recompile and deploy a new release of our products to our customers.

  3. GunSmoker said

    Are you sure this will work on D6? Delphi 6 is ANSI, not unicode.

    • tier777 said

      Indeed. Sorry for that. :(
      I was only thinking about the {$IF CompilerVersion >=21} when I wrote that…
      I’ll post a revised version shortly.

      • GunSmoker said

        BTW, you can use

        .Output(const AOutputString: UnicodeString);

        In order to work, add:

        type
        {$IFNDEF UNICODE}
        UnicodeString = WideString;
        {$ENDIF}

        TProcessMonitorLogger = class
        ...

        That way you’ll get both nice declarations and compatibility with the old Delphi.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: