How to detect if an OutputDebugString viewer is running

Have you ever noticed that your program is running slowly because you have waaaaaay too many calls to OutputDebugString (ODS) ?

What’s worse, is when it’s a complete waste, because there is no program running to benefit from ODS calls.

Here’s a programmatic way to detect if a viewer is running
P.S. my favorite ODS viewer is DebugView++ https://github.com/djeedjay/DebugViewPP

The following code is based on VB code written by Carl Barnes.
Carl based his work on info found here: http://www.unixwiz.net/techtips/outputdebugstring.html

 PROGRAM

  INCLUDE('Windows.inc'),ONCE
  MAP
      OutputDebugStringNotReady(),LONG 
      MODULE('Standard Windows APIs')
         OpenEvent    (DWORD InDesiredAccess, BOOL InInheritHandle, |
                            *CSTRING InName),HANDLE,RAW,PASCAL,NAME('OpenEventA')
      !already declared in Windows.inc!  GetLastError (),DWORD,PASCAL
      !already declared in Windows.inc!  CloseHandle  (HANDLE InObject),BOOL,PASCAL
      END
  END

  CODE
  MESSAGE('An OutputDebugString View is ['& |
         CHOOSE( OutputDebugStringNotReady()=0, |
                 'Running', 'NOT Running') &']')

OutputDebugStringNotReady  PROCEDURE()!,LONG 
!Ported from VB6 Code written by *Carl Barnes*
!PSS: OutputDebugString is expensive requiring a switch to kernel mode to access event, mutex and MMF. See
!     http://www.unixwiz.net/techtips/outputdebugstring.html

hObject            HANDLE,AUTO
SYNCHRONIZE        DWORD(100000h) ! 10 0000
szEventName        CSTRING('DBWIN_BUFFER_READY') !case sensitive
RetError           LONG,AUTO
   CODE 	                                                        
   hObject = OpenEvent(SYNCHRONIZE, FALSE, szEventName)    
   IF hObject <> 0
      CloseHandle(hObject)
      RetError = 0                !We are ready
   ELSE
      RetError = GetLastError() 
                                          ! probably 2 = File Not Found,
                                          !  i.e. the Event name does not exist because there is no debugger
      IF RetError = 0 
         RetError = -1            ! no handle and no error, then say -1
      END          
   END
   RETURN RetError
4 Likes

Mark, would it make sense to add this as a function call in centralised caller. That is, I have a procedure called DebugView that does the call to outputdebugstring. Is there an overhead to call this every time? That way I send when the user opens and not if he closes it.

Hi Chris,

I check to see if there is an OutputDebugString Viewer running at program launch
When a viewer is not present, then I suppress the calls to OutputDebugString as they have some performance impact.

Note: I also add a menu pick to the frame’s menu, to check again for the viewer, which allows you to launch a viewer after the program is running, and have the program start sending messages.

I did some performance testing on this back in January. I found that 100,000 ODS calls added slightly less than 5 seconds to a LOOP that contained nothing else but the ODS call and longvar += 1. Interestingly, the delay we see in debugview is not the delay your program experiences. IE: your code may be done making 100000 calls to ODS in 5 seconds, but it could take 10-15-20 seconds for all 100000 lines to be displayed in debugview.

I think the performance issues are based on whether something is listening at all. Not that you’re waiting for the listener to finish listening.

I havent seen that it matters that a viewer is active. I mentioned the delay in DBV because during high volume logging, it appears that things are much slower. It turns out that the app has been done with those calls for a long while. Filtering them out of DBV eliminates much of the delay since it isnt doing the work to format / display / scroll them.

I can’t figure out how to put the INCLUDE and MAP statements into my generated ABC application (LCLesson.exe). Can anyone talk me through it? I’m sure it will be obvious once I get it done

(post deleted by author)

1 Like

Donn,

Open the Embeditor (right click to see the menu in the image)

Then find an appropriate spot in the code and type in INLCUDE('yourfile.inc'),ONCE

As to the MAP
You can add one – or add to an existing one.
Note: you can have multiple MAP/END right next to each other

For the API work, I would move all of this code into a Class
then you write:

  INCLUDE('ctTheClass.inc'),ONCE
TheClass   ctTheClass

The CLW for the class has it’s own map/end where the API calls are nicely tucked away. You just need to call TheClass.SomeMethod() and let the class worry about the details

1 Like

I refactored to original code to remove Windows types (DWord, Handle).
Removed Include Windows.INC. Prefixed API calls with “ODNR_” to make unique. This can easily be undone should you want to use Windows.Inc.
Added comments !FYI APP: for tips on adding to an APP.

This would be best as a Class or Module (like CwUtil). That encapsulates any duplicate symbols so it can easily be “plugged in”.

  PROGRAM
! INCLUDE('Windows.inc'),ONCE - FYI APP: would go in Global Embed "Before Global Includes"

  MAP
!FYI APP: You add a Procedure for OutputDebugStringNotReady with the (),LONG prototype
      OutputDebugStringNotReady(),LONG 

!FYI APP: In Global Embed "Inside the Global Map" add the below MODULE() ... END
      MODULE('Windows')
         ODNR_OpenEvent(LONG InDesiredAccess, BOOL InInheritHandle,*CSTRING InName),LONG,RAW,PASCAL,NAME('OpenEventA'),DLL(1)
         ODNR_GetLastError(),LONG,PASCAL,NAME('GetLastError'),DLL(1)            !In Windows.inc so prefix ODNR_
         ODNR_CloseHandle(LONG Hnd),BOOL,PROC,PASCAL,NAME('CloseHandle'),DLL(1) !In Windows.inc so prefix ODNR_
      END !Module Windows

  END !Map

  CODE  !Simple test of calling
  MESSAGE('An OutputDebugString View is ['& |
         CHOOSE( OutputDebugStringNotReady()=0, |
                 'RUNNING', 'NOT Running') &']','Test OutputDebugStringNotReady')

!------------------------------------------------------------------------------------------------------------
!FYI APP: - You add a Source Procedure with the below code for OutputDebugStringNotReady with the (),LONG prototype
OutputDebugStringNotReady  PROCEDURE()!,LONG 
!Ported from VB6 Code written by *Carl Barnes*
!PSS: OutputDebugString is expensive requiring a switch to kernel mode to access event, mutex and MMF. See
!     http://www.unixwiz.net/techtips/outputdebugstring.html

hObject            LONG,AUTO
SYNCHRONIZE        LONG(100000h) ! 10 0000
szEventName        CSTRING('DBWIN_BUFFER_READY') !case sensitive
RetError           LONG,AUTO
   CODE 	                                                        
   hObject = ODNR_OpenEvent(SYNCHRONIZE, FALSE, szEventName)    
   IF hObject <> 0
      ODNR_CloseHandle(hObject)
      RetError = 0                !We are ready
   ELSE
      RetError = ODNR_GetLastError()    ! probably 2 = File Not Found,
                                        !  i.e. the Event name does not exist because there is no debugger
      IF RetError = 0 
         RetError = -1            ! no handle and no error, then say -1
      END          
   END
   RETURN RetError

DbgNotReady.zip (1.6 KB)


Add to APP …

  1. In Global Embed "Inside the Global Map

  2. Add Source Procedure with Code
    image

2 Likes

@MarkGoldberg suggested this should be a Class. I made it a module like CWUtil.

All you need to do is in the Inside Global Map embed add INCLUDE('DbgViewNotReadyUTIL.INC'),ONCE then the OutputDebugStringNotReady() function will be available to call.

DbgNotReady_IncUtilModule.zip (2.1 KB)


The attached example program to test the function is this code:

  PROGRAM
  MAP
    INCLUDE('DbgViewNotReadyUTIL.INC'),ONCE
  END
  CODE
  MESSAGE('An OutputDebugString View is ['& |
           CHOOSE( OutputDebugStringNotReady()=0, |
                 'RUNNING', 'NOT Running') &']' , |
           'Test DbgViewNotReadyUTIL.INC')

The DbgViewNotReadyUTIL INC file contains this code:

  MODULE('DbgViewNotReadyUTIL.clw')
    OutputDebugStringNotReady(),LONG
  END
  PRAGMA('compile(DbgViewNotReadyUTIL.CLW)')

The DbgViewNotReadyUTIL CLW file with the function contains this code:

  MEMBER
  MAP
    INCLUDE('DbgViewNotReadyUTIL.INC'),ONCE
    MODULE('Windows')
         OpenEvent(LONG InDesiredAccess, BOOL InInheritHandle,*CSTRING InName),LONG,RAW,PASCAL,NAME('OpenEventA'),DLL(1)
         GetLastError(),LONG,PASCAL,DLL(1)  
         CloseHandle(LONG Hnd),BOOL,PROC,PASCAL,DLL(1)
    END
  END
!------------------------------------------------------------------------------------------------------------
OutputDebugStringNotReady  PROCEDURE()!,LONG 
!Ported from VB6 Code written by *Carl Barnes*
!PSS: OutputDebugString is expensive requiring a switch to kernel mode to access event, mutex and MMF. See
!     http://www.unixwiz.net/techtips/outputdebugstring.html

hObject            LONG,AUTO
SYNCHRONIZE        LONG(100000h) ! 10 0000
szEventName        CSTRING('DBWIN_BUFFER_READY') !case sensitive
RetError           LONG,AUTO
   CODE 	                                                        
   hObject = OpenEvent(SYNCHRONIZE, FALSE, szEventName)    
   IF hObject <> 0
      CloseHandle(hObject)
      RetError = 0                !We are ready
   ELSE
      RetError = GetLastError()    ! probably 2 = File Not Found,
                                        !  i.e. the Event name does not exist because there is no debugger
      IF RetError = 0 
         RetError = -1            ! no handle and no error, then say -1
      END          
   END
   RETURN RetError

This encapsulates the declarations so they would not cause errors should your program Include('Windows.Inc'). You can test that in the attached project.

2 Likes