# VBForums CodeBank > CodeBank - Visual Basic 6 and earlier >  Persistent Debug Print Window

## Elroy

Ok, there's a fairly good Windows "built-in" option for this, discussed here.  

(Don't forget the Add-In companion for this program, found here.)

However, I wanted a more pure VB6 solution.  So, I came up with the following.  Basically, it just gives you a *DebugPrint* procedure in any program you're developing, with the output being persistent across exits and re-loads of the VB6 IDE.  Also, if you're developing something that's crashing your IDE, it will be persistent for that as well.  Also, it can show you substantially more volume of information than the IDE's Immediate window.  Also, it will work from a compiled program as well as from the IDE.

It preserves the ability to use the comma (but not the semicolon) when printing debug information.  For instance, the following are valid:

DebugPrint "Test Point #1"
DebugPrint "Var1:", Var1, "Var2:", Var2

But this won't work:

DebugPrint "Var1: "*;* Var1

With respect to the actual PersistentDebugPrint program, you can either compile it and execute it, or you can just execute it from the IDE.  It'll work either way.

To use it, you just throw the *DebugPrint.bas* module into the program you're developing, and start making calls to the DebugPrint procedure.  

*Warning*:  In the PersistentDebugPrint program, I store a handful of settings in the registry.  If you look, it's obvious what I'm storing, but I know that this concerns some people, so this is just a heads-up that I'm doing this.

Basically, here's all it is (from the Test program):



Enjoy,
Elroy

*Update #1*:  The class name for the DebugPrint window is a bit different when compiled versus running in the IDE, and I forgot to check for that.  That's now fixed.  I also added a DoDebugPrint constant to the DebugPrint.bas module so you can turn it all on/off without needing to comment out your DebugPrint lines.

*Update #2*:  I've actually tweaked on this thing quite a bit since I last updated it.  Changes (I can remember):

There's now a "CodeForDebugPrint--->ToClipboard" menu option which allows you to quickly grab the new module code (modDebugPrint) for throwing into any project and use this thing.There are tabs, so you can have different programs output debug info to separate tabs.  To use them, there's a new DebugPrintToTab procedure in the modDebugPrint module.  Here's the declaration:


```
Public Sub DebugPrintToTab(iTabNumber As Long, ParamArray vArgs() As Variant)
```

Just specify the tab you wish to DebugPrint to as iTabNumber, starting with 1, and it'll do that.  If the tab doesn't exist, tabs are opened in the PersistentDebugPrint program such that your tab does exist.  An iTabNumber=0 prints out to whatever tab currently has the focus.  Any negative value in iTabNumber prints out to the largest tab number currently open in PersistentDebugPrint.  Thanks to Eduardo for the custom tab control.  It's a modified and cut-down version of what he has here.The actual output textbox has a context menu with most of the same stuff that's in the main menu.  The "Clear" and "Separate" are only for the tab with the focus.  Font, BackColor, & ForeColor are global.  Making those per-tab might be a future update.And just as an FYI, the older DebugPrint procedure (if you've got it in existing projects) will still work with this new program.  It'll just output to whatever tab has the focus (as if iTabNumber=0).
Here's a new screenshot:



*Update #3*:  Fixed a bug in the modDebugPrint stub.  The bug was that it only printed one line, now fixed.

If you download it, it's best to just compile it immediately.  I just place the EXE on my desktop as I use it frequently.  And then, just click the new "CodeForDebugPrint--->ToClipboard" option, and paste that code into a new module of whatever project you like.  To use it, just replace your Debug.Print statements with DebugPrint (no period, with caveats noted above).  Or, alternatively, use DebugPrintToTab.

Enjoy!

----------


## yokesee

very good work, as additional data only works if the program works as an administrator.

I could also put the path or name of the executable that sent the message in case there is more to differentiate

a greeting

sorry for using translator

----------


## yereverluvinuncleber

Elroy, I love this addition to VB6 and it adding it to my Rocketdock Enhanced Settings utility was a real boon to debugging certain runtime issues.

Do you have a VB.NET version? If not then I'll try and convert it myself.

----------


## Elroy

> Elroy, I love this addition to VB6 and it adding it to my Rocketdock Enhanced Settings utility was a real boon to debugging certain runtime issues.
> 
> Do you have a VB.NET version? If not then I'll try and convert it myself.



Nope, sorry.  I'm not a netter.  Good luck with the translation.

----------


## dz32

I really like this thanks for sharing. couple possible ideas

- when the exe runs have it savesetting its current path. The client can then auto start it on demand if not running yet
- a <CLEAR> command so you can include a dbg.Clear to start fresh display from the code under debugging.
- printf handler for easy formatting
- topmost setting

----------


## Elroy

Hi dz32,

Thanks for the compliment.  However, truth be told, I doubt I'll find the motivation to do much more to this thing.  But you've got the source code, and you're more than welcome to add the enhancements you'd like.  Heck, I don't even mind if you post an enhanced version into this thread, or just fork it and make your own CodeBank thread.

Take Care,
Elroy

----------


## dz32

Sure happy to contribute.

Mods:
 - Sub debugPrintf(ByVal msg As String, ParamArray values() As Variant)
 - Sub debugClear()
 - Sub debugDiv()  'divider
 - Sub debugPrint(ParamArray vArgs() As Variant) (pre-existing)
 - Settings -> TopMost
 - Settings -> Timestamp
 - debug client module can autostart the debug window server (exe) if its not running already
 (must have been started once though so it can save path to registry)

The printf handler covers the basics:


```
'implements:
'    \t -> tab
'    \n -> vbcrlf
'    %% -> %
'    %x = hex
'    %X = UCase(Hex(var))
'    %s = string
'    %S = UCase string
'    %c = Chr(var)
'    %d = numeric

some printf examples from an old blog post:
    List1.AddItem printf("number 0x%x", 16)
    List1.AddItem printf("number %08x", &HCC)
    List1.AddItem printf("number %8X", &HCC)
    List1.AddItem printf("this is %s and can also be %S", "my string", "upper case")
    List1.AddItem printf("%s", "string with no prefix")
    List1.AddItem printf("0xCC in decimal = %d", &HCC)
    List1.AddItem printf("chr(&h41) = %c", &H41)
    List1.AddItem printf("chr(&h41) = %c it is my %s", &H41, "favorite letter")

output:
    number 0x10
    number 000000CC
    number       CC
    this is my string and can also be UPPER CASE
    string with no prefix
    0xCC in decimal = 204
    chr(&h41) = A
    chr(&h41) = A it is my favorite letter
```

----------


## dz32

one more addition, just in case the vb process is running as admin 



```
Private Declare Function ChangeWindowMessageFilter Lib "user32" (ByVal msg As Long, ByVal flag As Long) As Long 'Vista+
Const WM_COPYDATA = &H4A
Const WM_COPYGLOBALDATA = &H49

Public Function AllowCopyDataAcrossUIPI()
    Dim a, b, c
    Const MSGFLT_ADD = 1
    'a = ChangeWindowMessageFilter(WM_DROPFILES, MSGFLT_ADD)
    b = ChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD) 
    c = ChangeWindowMessageFilter(WM_COPYGLOBALDATA, MSGFLT_ADD)
    'MsgBox a & " " & b & " " & c
End Function
```

and a sample C client



```
#include <Windows.h>
#include <stdio.h>
#include <conio.h>

void msg(char);
void msgf(const char*, ...);

bool Warned=false;
HWND hServer=0;

HWND regFindWindow(void){

         // SaveSetting "dbgWindow", "settings", "hwnd", Me.hWnd
	 char* baseKey = "Software\\VB and VBA Program Settings\\dbgWindow\\settings";
	 char tmp[20] = {0};
     unsigned long l = sizeof(tmp);
	 HWND ret=0;
	 HKEY h;
	 
	 printf("regFindWindow triggered\n");

	 RegOpenKeyExA(HKEY_CURRENT_USER, baseKey, 0, KEY_READ, &h);
	 RegQueryValueExA(h, "hwnd", 0,0, (unsigned char*)tmp, &l);
	 RegCloseKey(h);
	
	 ret = (HWND)atoi(tmp);
	 if(!IsWindow(ret)) ret = 0;
	 return ret;
}

void FindVBWindow(){
	char *vbIDEClassName = "ThunderFormDC" ;
	char *vbEXEClassName = "ThunderRT6FormDC" ;
	char *vbEXEClassName2 = "ThunderRT6Form" ;
	char *vbWindowCaption = "Persistent Debug Print Window" ;

	hServer = FindWindowA( vbIDEClassName, vbWindowCaption );
	if(hServer==0) hServer = FindWindowA( vbEXEClassName, vbWindowCaption );
	if(hServer==0) hServer = FindWindowA( vbEXEClassName2, vbWindowCaption );
	if(hServer==0) hServer = regFindWindow(); //if ide is running as admin

	if(hServer==0){
		if(!Warned){
			//MessageBox(0,"Could not find msg window","",0);
			printf("Could not find msg window\n");
			Warned=true;
		}
	}
	else{
		if(!Warned){
			//first time we are being called we could do stuff here...
			printf("hServer = %x\n", hServer);
			Warned=true;

		}
	}	

} 

int msg(char *Buffer){
  
  if(!IsWindow(hServer)) hServer=0;
  if(hServer==0) FindVBWindow();
  
  COPYDATASTRUCT cpStructData;
  memset(&cpStructData,0, sizeof(struct tagCOPYDATASTRUCT )) ;
  
  //_snprintf(msgbuf, 0x1000, "%x,%x,%s", myPID, GetCurrentThreadId(), Buffer);

  cpStructData.dwData = 3;
  cpStructData.cbData = strlen(Buffer) ;
  cpStructData.lpData = (void*)Buffer;
  int ret = SendMessage(hServer, WM_COPYDATA, 0,(LPARAM)&cpStructData);
  return ret; //log ui can send us a response msg to trigger special reaction in ret

} 

void msgf(const char *format, ...)
{
	DWORD dwErr = GetLastError();
		
	if(format){
		char buf[1024]; 
		va_list args; 
		va_start(args,format); 
		try{
 			 _vsnprintf(buf,1024,format,args);
			 msg(buf);
		}
		catch(...){}
	}

	SetLastError(dwErr);
}

void main(void){

	msg("this is my test");
	msgf("this is test %d", 2);
	getch();

}
```

----------


## Elroy

I had posted an update here, but please see the OP for the latest.

----------


## dz32

had an idea on this today, I tend to use this for the worst debugging situations
sometimes when you are dumping a lot of info to the debug window info can get interspersed
like high level outer loop then a bunch of inner loop messages making it hard to digest the outerloop

Similar to the concept of log levels (info messages, warnings, critical etc.)

I am thinking of being able to add tabs on demand from the debugee for different message levels
or maybe just log levels is fine with a filter capability on the main textbox. humm...I guess a filter textbox
control will do

in other news I merged the tricks replace debug.print with a copy of this in a single class so you can 
enable/disable the redirection on demand. could still use a couple cleanups but working

https://github.com/dzzie/libs/blob/m...rintSample.cls

----------


## Elroy

Ahhh, I like the idea of tabs on the PersistentDebug window.  I'll make that improvement today.

I looked into The Trick's work on redirecting Debug.Print.  What he did works, but it still has two problems to be useful for this project: 1) It just sends it to an object that can handle the .Print method (and you still can't capture the text), and 2) When compiled, Debug.Print goes away, so it's not useful for compiled programs which is where I often find PersistentDebug particularly useful.  I'm just going to work it out another way.

But again, I'll add tabs today.

----------


## yokesee

I use it every day.
And I add some simple tabs.
I send PropertyBag and in property I send the name of the group.
Thank you very much Elroy for this utility

----------


## dz32

so I think the most natural place for that single class debug.print redirector to live is actually with it hosted in an addin.

I have a couple addins always loaded, so I just added an extra checkbox to one of the forms to enable/disable it on demand. (always off by default for stability)

this way it doesnt have to be added to every project you might want to use it from and then add code to turn it on and off
its just always ready to use with a simple toggle.

added it to my fastbuild addin

https://github.com/dzzie/addins/tree/master/FastBuild

----------


## Elroy

Yeah, I've thought about doing it as an add-in, but it doesn't quite work as one.  For one, which piece are we talking about as an add-in:  The "send" piece or the "receive/report" piece?  If we put the "send" piece there, then it won't work when the program is compiled ... and I often use it that way.  If we put the "receive/report" piece there, if the IDE crashes (which is a big part of this), it won't be persistent.

I've just about got my "tabs" version finished and hope to post it later today.

I've pretty much gotten it all worked out, but I'm certainly open to suggestions as to how tabs should work.  Here's the way I've got it:

* The tabs just have numbers for the tab-captions, starting with 1.
* If zero-tab is specified by the "sending" program, the tab with the focus will be used.
* If a tab number larger than 1 is specified, tabs will be added by the receiving/printing program up to the specified tab-number.
* If -1 (or any negative tab#) is specified, the largest (already opened) tab will be used.
* You can delete tabs from the reporting program, but only the last one.  The first tab (#1) can never be deleted.
* There's already a "Clear" menu option, which will clear the tab with the focus.  There will also be a new "Clear All" option which will clear and delete all tabs (leaving only a blank #1 tab).

Again, I should have it out here for y'all to examine later today.

----------


## dz32

I added tricks debug.print redirector into the addin
Which has been enhanced with your remote debug out sender 

So whatever project your working on you just use the normal 
Debug.print as always, no extra classes to include.

If you want to redirect all normal debug.print messages to the remote Persistent debug window, just go to the addin config form and click enable  And the rest works magically behind the scenes

The remote debug receiver window is still a standalone process. I added an extra button to easily launch it from the ide as well

----------


## dz32

I didnt fully flesh out the idea of tabs. I was thinking of sending a control message like 

Inittabs:tab1:tab2

Which the receiver would then parse and configure itself for. 
Then any message that started with tab1: would be redirected to the proper tab with the control part of the message stripped

But that just lead me to the filter text box idea since i am basically just filtering based on message text then I could search for anything plus see all the messages in order still if I wanted and it would be very simple to code.

A copy of it with the filter concept is in the dbgwindow sub folder of above project

----------


## Elroy

> I didnt fully flesh out the idea of tabs. I was thinking of sending a control message like 
> 
> Inittabs:tab1:tab2


Ohh, I've got the LPARAM I can use to send a number through, and I'm just using a new DebugPrintToTab(TheTab As Long, ParamArray ...) to specify which tab it's to go to.  I'll just stuff that TheTab arg into LPARAM.

EDIT:  It's actually wParam.  lParam is used for a pointer to the incoming structure which holds the text message.

----------


## dz32

Thats a good solution

----------


## Elroy

Updated, see post #1.

----------


## Elroy

*Notice*:  There was a bug in the modDebugPrint piece that I posted yesterday.  So, if you grabbed it yesterday, you'll need to re-grab it.

----------

