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:
Code:
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!
Last edited by Elroy; Dec 24th, 2022 at 03:06 PM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
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.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
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
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
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
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:
Code:
'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
one more addition, just in case the vb process is running as admin
Code:
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
Code:
#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();
}
I had posted an update here, but please see the OP for the latest.
Last edited by Elroy; Dec 23rd, 2022 at 07:11 PM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
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
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.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
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
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.
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.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
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
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
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.
Last edited by Elroy; Dec 22nd, 2022 at 04:17 PM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
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.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Suggestions for a future enhancement: Support for unicode.
hahaha, it's still the same me whether it's posted here or in the other thread.
You've now got a clipboard option which isn't too bad, and it's also persistent.
I'll eventually add Unicode support. It's just a bit involved (as outlined in the other thread), but I'll get to it someday.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
kind of a niche case but, with some small changes the debuggee sending messages to the debug print window can actually request config values to determine on the fly what behaviors to enable.
use case, debugging complex dll where you might want to enable debug messages or certain features on the fly.
dll sends message to print window to load a certain profile (ini of config settings)
dll requests the config values
acts differently without having to recompile the dll everytime.
basic change just use sendmessage vrs sendmessagetimeout and return a long value. Some special debug print messages are taken as commands.
I suppose the dll could just load the ini on its own, but then I would have had to program that part in C in a big complex project ;-\ Im lazy.