# VBForums CodeBank > CodeBank - Visual Basic 6 and earlier >  [vb6] Project Scanner

## LaVolpe

Might be useful to some to help clean up their code before they post it out to the world? A project that started with an experiment in parsing VB files, but evolved to what we have here.

Posts related to this version start after post #215, page 6.

This project has two parts

1) Scan any VB6 project and quickly display a summary of declarations, procedures, controls used, and more. This can be used for a quick review of your own projects or those you download from sites like this one.

2) A deeper analysis of the code content within a project. It currently offers several validation checks. These checks can help tidy up your code and maybe even pinpoint a problem or two. Here is a brief description of the various scans. 

- Code files (forms, classes, etc) that do not use Option Explicit
- Procedures with no executable code
- Procedures that contain active End or Stop statements
- Zombie declarations and procedures. Items that are created/declared but not referenced within your code
- VarType checks. Items not declared with a variable type and default to Variant
- Strings that are duplicated within your code. These can be consolidated to constants
- Declarations that are duplicated
- Variant functions used in place of string functions, i.e., Trim() vs. Trim$()
- OPC (other people's code) checks to highlight code that could potentially be used to modify your registry. Also, shows you what, within your code, would be flagged if someone used this tool to scan your project.

Many of the checks could produce false positives. So, consider these checks as informational only. The help menu in the project offers more details on these checks along with known false positives. One thing this project does not do is to look for undeclared variables. Adding Option Explicit statements to your code files will help you in that case. One thing Option Explicit can't help with that this project can, is the dreaded dead declaration that results in a typo with the ReDim statement. Following code creates 2 declared & arrayed variables. In this example, the second one (highlighted in blue) is a typo, but valid. Chances are that one of these would not be used within the routine, due to the typo. In that case, one of them would be reported as a zombie declaration.


```
Dim TempVal() As Long
ReDim TmpVal(25) As Long ' << letter e missing in variable name
```





```
Major Changes
 3 May 2020: Minor patches. Included option to drop vbp files onto compiled exe for activation/processing.
18 Apr 2020: Fix extended control events being flagged as zombies (posts 218/219)
 4 Apr 2020: Version 2, major rewrite
-- umm, Validation & View have same accelerator key? Future update will change it to Validation
X-mas 2018: Attempt to make DBCS-compatible for non-US versions of VB
```

----------


## LaVolpe

I will use this section to explain items in more detail or offer tips/tricks that can be used with this project

1. Once you choose to validate the project, you cannot go back and re-validate it using different options. To do that, re-scan the project and then re-validate. If you choose menu: File|Rescan, then the previous validation options are cached and auto-selected when you choose to validate again. If you made any changes to the project between validations, ensure you saved the changes. To save or view any validation reports: Ctrl+S

2. Clarification. I use the word "items" in code comments quite often. An item is anything that is declared within a code file, the code file itself, or any external references associated with the project. See the public enumeration ItemTypeConstants in modMain for a listing. 

3. FYI. Compiler directives, i.e, #If, #ElseIf and #Else statements are processed in the code via the Scripting Control. Any code that exists in one of those blocks where the block tested false is not processed (skipped) by this project.

----------


## Elroy

WOW Keith ... it's GREAT to see you back.  I'm guessing you took a hiatus to write this code and to just take a breather from things.  This project looks SUPER interesting.

Welcome Back!,
Elroy

----------


## Elroy

A suggestion if you don't mind ... some kind of label that reports what it's doing when it's opening/scanning the project (maybe similar to the way the validation does).  My main project takes several minutes to open, and I just start wondering if it's hung.  Just something to let me know it's not would be super.   :Smilie: 

Take Care,
Elroy

----------


## wqweto

Typelib versions are in hex (e.g. "a.0" is a version) in pvAddReference had to tweak this

thinBasic Code:
' Ln 1504        iMajor = CInt("&H" & Left$(sParams(1), lValue - 1))        iMinor = (CInt("&H" & Mid$(sParams(1), lValue + 1)))
When typelib is missing bombs in frmMain w/ invalid use of null (field constFldAttr2 is null)

thinBasic Code:
' Ln 881                    tvItems(0).Nodes.Add .Index, tvwChild, , rsLocal.Fields(constFldAttr2).Value
Constants can be used as default values to optional params like this (misses LOCALE_USER_DEFAULT usage)

thinBasic Code:
Private Function pvGetUserLocaleInfo(ByVal dwLCType As Long, Optional ByVal LocaleID As Long = LOCALE_USER_DEFAULT) As String
Named params seem to not be parsed (misses sCriteria usage)

thinBasic Code:
If DispInvoke(rs, "Find", Args:=sCriteria) Then
Module public properties assigned with module prefix(misses property let ErrClientName usage)

thinBasic Code:
mdApp.ErrClientName = "test"

This turned out to be a super useful linter! I've been using MZ-Tools Review Code feature but it misses lots of checks.

Probably this is on par with aivosto's Project Analyzer functionality-wise.

cheers,
</wqw>

----------


## LaVolpe

wqweto, I'll look into those & thanks for the examples. 
Edited: Regarding the module property being missed? If that's a standard (bas) module, then that's on me. If it's a public property in a non-standard module (class, form, uc, etc), then public properties aren't tested for zombie state; explained in the documentation. Based on the prefix you gave in your example, that looks like it may be a standard module. In either case, will re-examine the logic I used.

Elroy, I'll look at a popup form to display some sort of status. There is feedback while the parsing is happening, but I'm assuming you are talking about the delay from when that finishes and the treeview is ultimately populated & displayed. I can see that being an issue for very large projects: dozens upon dozens of code files containing potentially 100s of procedures overall, and even more so when validation finishes, if there are 1000's of validation entries. The largest project I tested was about 50K statements; which was really a group project of many projects.

----------


## wqweto

Yes, mdApp in sample above is a standard .bas module.

Also, just hit a limitation of the tree-view control -- 32k nodes but the project I'm scanning has more than 100k rows in rsItems. Had to guard each tvItems(0).Nodes.Add w/ If tvItems(0).Nodes.Count >= 0 Then as on overflow Count becomes -32768 (negative:-))

cheers,
</wqw>

----------


## LaVolpe

:: the tree-view control -- 32k. I'll have to rework some things similar to "paging" for such heavy results. Maybe delay loading branches, removing collapsed branch's child nodes for example.  I can't imagine the item listing treeview could reach that limit. But I could see where the validation listing treeview could.

In the mean time, may just look at a quick fix and use some sort of "rule of thumb" for displaying portions of validation results. There is no hard & fast ratio of recordset rows to treeview nodes, but I think I can figure out a good guesstimate. For example, a single parsed item, say a Dim statement, could generate several nodes in the validation listing because the variable in that statement could fail multiple checks, i.e., vartype and zombie checks. In that treeview, displaying the validation also results in including additional nodes for headers and grouping. I honestly didn't plan on any collections of +100K records and +32K nodes.

----------


## Elroy

> There is feedback while the parsing is happening, but I'm assuming you are talking about the delay from when that finishes and the treeview is ultimately populated & displayed. I can see that being an issue for very large projects: dozens upon dozens of code files containing potentially 100s of procedures overall, and even more so when validation finishes, if there are 1000's of validation entries. The largest project I tested was about 50K statements; which was really a group project of many projects.


Hi LaVolpe,

Yes, my primary project has a VBG as well, but I seldom use it as the DLLs are quite stable and seldom re-compiled.  However, here's what happens when I load the primary VBP with your program: 1) it loads the modules fairly quickly, then 2) there's a long pause while it's somewhat hung on that completed list, and then 3) that list is cleared after which another long pause occurs.  I didn't study your source to see exactly what's happening in those two pauses, but I suppose that's where I'm thinking some feedback would be nice.  If it just happened directly in the TreeView, that would be fine, or a "popup" would be fine.

Here's my primary project (the VBP of the main piece):




Again, THANKS! for the magnificent work.  I was rather amazed at how many occurrences of default dimensioned (i.e., Variant) variables I had.  Those are all fixed now. 

Elroy

----------


## LaVolpe

> I didn't study your source to see exactly what's happening in those two pauses, but I suppose that's where I'm thinking some feedback would be nice.  If it just happened directly in the TreeView, that would be fine, or a "popup" would be fine.
> 
> Again, THANKS! for the magnificent work.  I was rather amazed at how many occurrences of default dimensioned (i.e., Variant) variables I had.  Those are all fixed now. 
> 
> Elroy


At least one of those pauses I have no control over. The treeview, using common ctrls v5, seems to have a delay  between me adding the last node and ultimate display of the nodes. Short of subclassing to try to figure out what's going on inside the control at that time, thinking a self-closing pop-up to show which code files are being processed (added to the tree), then a final indication that the treeview is preparing to show the results. Adding the state to the treeview itself would only make the issue worse I think.

Regarding the variants -- me too. I ran this on several of my projects. Found a few functions, and some properties, where I forgot to include the return vartype. Typically, I think those are cases where we convert a Sub to a Function & forget to finish it off completely. In one of my projects, the ReDim zombie (noted in 1st posting above) was found; that was unexpected. Of all the ones I tested, I only found one variable where I forgot to declare the type -- patting myself on the back  :Wink: 

Edited: 502 code files for one project - wow, that's a biggie

----------


## wqweto

DefObj A-Z for the win. . . Zero untyped vars/functions :-)) Though I found out couple of (small) modules w/ no DefObj A-Z

I also have a policy for zero warnings by the MZ-Tools linter so no unused private consts/vars/routines/types but LaVolpe's linter detects dead *public* functions too which took me most of today to clean up.

cheers,
</wqw>

----------


## Elroy

> At least one of those pauses I have no control over. The treeview, using common ctrls v5, seems to have a delay between me adding the last node and ultimate display of the nodes. Short of subclassing to try to figure out what's going on inside the control at that time, thinking a self-closing pop-up to show which code files are being processed (added to the tree), then a final indication that the treeview is preparing to show the results.


Hey, if you do it as a popup, maybe feedback on what you can, and just a message something like "Loading TreeView, there may be a pause between loading last node and final loading ... please wait." or some such.  I wouldn't do any subclassing just for that.




> Typically, I think those are cases where we convert a Sub to a Function & forget to finish it off completely.


Yeah, I had about a dozen of those.  I think mine were about 50/50 what you're saying, with the other 50% being the opposite.  I found several cases where I had a Function with no type declaration, but also no return value set.  I checked the calls, and, in all cases, they were called like a Sub, so I just changed them.  Who knows why I did that.  

I was rather proud of myself for not having any naked Redims.  I often mistakenly code those.  However, the way I tend to code, they typically self-correct themselves with runtime errors during alpha-testing.  They only one I found was in your latest Common Dialog Class, which I'm using for one case.   :Stick Out Tongue:   Actually, the Redim'med variable wasn't being used, so I just deleted the line of code.

It's truly FANTASTIC to have you back!  I was actually worried about you.

I'd still like to know if Bonnie West is okay.  She just dropped out without a peep, and I think she'd get a smile out of some of the recent Collections work that's happened.

Best Regards,
Elroy

EDIT1:




> Edited: 502 code files for one project - wow, that's a biggie


Yeah, that's why I sort of cringe when I ask a question about something and somebody says, "Well, why don't you just add another form to get that done?"   :Smilie:   In _many_ cases, I'm using "general purpose" forms to get various things done.  For instance, I let the users maintain, I'd say, at least 50 lists of things in a maintenance area (physicians, procedures, prosthetics, orthoses, forefoot deformities, hindfoot deformities, etc, etc, etc), and in well over half of these, it's a common form that pops up to maintain these lists.

----------


## couttsj

Excellent program! I often cobble together parts of programs to make other programs, and often end up with unused and unreferenced code that takes a long time to clean up. This will help a lot.

J.A. Coutts

----------


## Elroy

This thing is truly great!

Just a list of a couple of "niceties" I wouldn't mind seeing:

A "recently used file" list under the "File" option on the menu.If opening the entire module (Show Entire Code File), have an option to actually edit and save modules from this Scanner program.  I know this'd be a bit dangerous, but it'd still be nice.From the TreeView, have a "Copy" option on the context menu.  I find I have my project open in the IDE, but I have to search for the procedure and/or variable I'm wanting to fix.  I have to re-type it to do the search.  A right-click/copy of just the procedure name or variable name would be quite nice for bouncing back and forth.In addition to the XML export, have an indented ASCII export of the Validation List. 
Obviously, all just ideas, but things I thought about.

Best Regards,
Elroy

----------


## LaVolpe

Elroy, thinking about your "nice to haves"

- CSV: not sure that'll be doable. CSV's are not friendly to hierarchical data. There is no consistency how each header's branch structure is defined. Nothing there where I can say that the CSV will always have fields A, B, C etc. Some of the nodes are dependent on nodes created before them, dynamically assigned. I chose XML because it does support hierarchy easily and it's easy to view in any web browser and other apps like Notepad++

- MRU listing would need to be cached to file or registry. I'm not a big fan of writing to the registry; though a config file of sorts could be a consideration. Will think about that.

- Regarding the display of the file and or procedures/declarations section, etc... Plan on doing away with that viewer I've included in the project. I'd prefer to send the info to NotePad/WordPad which would enable the user to do whatever they want with the text/data, including find and search & replace support, saving, etc. I'm not going to add anything that would update a project's code file. But the NotePad/WordPad option would give the user choices.

For today and next couple of days -- focusing on the 'bug' report wqw gave us.

----------


## Elroy

> For today and next couple of days -- focusing on the 'bug' report wqw gave us.


No problem at all.  I guess my biggest issue for me was bouncing back and forth between the Project Scanner and my project loaded into the IDE, and being able to quickly get to the areas in my project within the IDE that need work.  On my first big "fix", I exported the TreeView to XML, edited all the XML tags out of it, and then used that file as a ToDo list for my project.  That also gave me copy-paste capabilities for searching my project in the IDE (using the saved edited XML file).  It also saved a record of my work and how much was done so that I didn't have to Re-Validate the project when I started working on it again (such as I did last night and then this morning).  

On a slightly different note, opening modules in Notepad or Wordpad would be super.  Preferably, you'd let the user decide the editor, as NP++ is my preferred ANSI/Unicode editor.  

Best of luck with it, and it seems to be working perfectly on my projects.  Apparently I don't use the situations outlined by Wqweto.  I'm not sure why but that "Named Argument" syntax for calling a procedure has always driven me a bit nuts.  

Take Care,
Elroy

----------


## LaVolpe

The biggest annoyance I have at this time is the treeview delay between last node added and when it shows all the nodes. Even subclassing the treeview via Spy++, nothing unique is sent when the treeview finally repaints. So, at this point in the game, I have no way of knowing when the treeview will respond to mouse input after I've finished appending nodes. I implemented a popup form to show progress while parsing and while populating the treeview, but it closes (the load project routine finishes) several seconds before the treeview becomes responsive. For large projects that could be more significant than just a few seconds. And I'm no where close to adding a project with 500 code files. In my example it was about 100 (group with 6 projects) and the delay felt like 10 seconds. SendMessageTimeOut might be an answer to knowing when it becomes responsive, but the other issue of the 32K node limit still applies.

Gotta start wondering whether the treeview is the best control for displaying these results; don't want to use the MS hierarchical flex grid -- ugly. Probably gonna look at delay loading nodes when they are expanded. That will speed things up quite a bit. However, that in itself lends to other issues, i.e., the XML report expects all nodes to be there. Delay loading guarantees they won't be. Probably take a day to brain dump and come at it from a different angle. One idea is to create the nodes in a recordset and then simply upload them to the treeview on demand. All the nodes will be created, but exist outside the treeview. I'd think this might be a speed bump, but offers a positive side effect -- we could remove items from the validations & persist them, so that if the results are saved as a recordset and reloaded, the "hidden/removed" items will remain hidden. That could be a nice way of knowing where one left off when using the list to fix their project. Just thinking out loud.

Regarding the bugs that wqw provided, all but one are already fixed. The named params issue ended up being just 2 additional IF statements since I was already looking for them - just forgot to include them in the pvProcessWords routine.

----------


## dreammanor

Very nice. It's great to see you come back.

----------


## Thierry69

I am trying it on one of my projects (quite big).
After 10 min, I had to kill VB6  :Frown: 

Also, I had invalid use of null here 
Private Sub pvLstAdd_References(rs As ADODB.Recordset, pNode As Node)
tvItems(0).Nodes.Add .Index, tvwChild, , rsLocal.Fields(constFldAttr2).Value 

and I had to do this continue
tvItems(0).Nodes.Add .Index, tvwChild, , rsLocal.Fields(constFldAttr2).Value & ""


Otherwise, VBIDEUtils also is doing similar analysis.
You can grab sources on github

----------


## Elroy

Here's another one I'd sure like to see included in the validation:



```

Option Explicit

Private Sub Form_Load()
    ReDim abc(1 To 5) As Long
    Debug.Print abc(1)
End Sub

```


That code validates just fine, but I personally don't think it should:



In fact, when you right-click the abc variable and then click "Definition" in the IDE, you get the following:



So, in a sense, not even the IDE thinks it's very good code, but it will compile and run.  Personally, I think it's a "leak" or "circumvention" of Option Explicit.

Personally, I just don't think a Redim should ever be used on variables that aren't previously declared.

Best Regards,
Elroy

----------


## LaVolpe

> Also, I had invalid use of null here 
> Private Sub pvLstAdd_References(rs As ADODB.Recordset, pNode As Node)
> tvItems(0).Nodes.Add .Index, tvwChild, , rsLocal.Fields(constFldAttr2).Value


Yepper, already reported earlier. The preferred fix is to test for Null and not create the sub-node that follows. As for the lengthy delays for "quite big" projects, working on that.




> Personally, I just don't think a Redim should ever be used on variables that aren't previously declared.
> 
> Elroy


I will consider flagging it for FYI. Though it is 100% legitimate and even some promote its use as faster than the alternative: Dim a() then ReDim a(5).

FYI: If you look at this short example, the ExitRoutine label gets the same message.


```
Public Sub Tester()

    MsgBox "Hello World"
    
ExitRoutine:
    
End Sub
```

----------


## Elroy

RE: My ReDim check outlined in post #20:

Just as any FYI for you, I'm pretty good-to-go.  It's my habit to never explicitly declare the vartype when I use Redim.  Therefore, when I "Validate", it catches them in the "VarType Check" section if/when they're not previously declared.

But that also tells me that, when you see a ReDim, you're jumping through some hoops to see if it's been previously declared.  So, it would seem that you're pretty close to doing that check anyway.

Take Care, 
Elroy


EDIT1:  Just more praise, LaVolpe.  I'm truly excited about your project.  In a very real sense, it feels like the Microsoft VB6 project is finally being completed.  Most (if not all) of this stuff should have been included in the IDE, imho.  I could easily see this expanded to start looking for some of the known compiler bugs.  The one that immediately comes to mind is the UDT array bug, where the UDT-vartype received by a procedure is NOT validated against the UDT-vartype sent by the caller.  Another is where trying to negate a UDT causes major problems.  Yet another is where, under certain circumstances, assigning dynamic arrays (of any type) can bring down the IDE (as outlined here).  Not saying these should be immediately included in this validation project.  Just suggesting that they could. 

Oh, and hey, as a fun poke at your "Though it is 100% legitimate..." comment, so is not using Option Explicit or just letting variables default to Variant.   :Stick Out Tongue:   But, yeah, I suppose I do get the point that some say a naked ReDim is faster.  It's just not to my liking.    :Smilie:

----------


## LaVolpe

Yes, it is known whether the ReDim is applied to a previously declared variable when procedures are parsed. Flagging it as a separate validation check is just a matter of creating a separate validation flag, i.e., vnReDimNoDim, & applying that flag at that time. The vartype check is performed in the routine that tests ReDims during procedures parsing. The zombie check is performed after the procedure is parsed. That specific case could trigger 3 different validation failures: vnZombieChk Or vnVarTypeChk Or vnReDimNoDim




> But, yeah, I suppose I do get the point that some say a naked ReDim is faster. It's just not to my liking.


Nor mine. If it occurs in any code I write, it is unintentional and likely an error in waiting

----------


## Elroy

Ok, I found a bug.

Here's the little Form1 test code I'm using:



```

Option Explicit

Private Sub Form_Load()
    Dim s As String
    s = Left(s, 2)
End Sub


```


Now, if I validate for nothing but "Identify functions used as Variant vs. String", it doesn't find that Left(s) function...



However, if I also check the "Identify zombie items", it works just fine:



Take Care,
Elroy

EDIT1:  It seems that a great deal "pivots" off the zombie scan, and that probably (internally) that one needs to be mandatory.  But I'm almost certainly telling you things that you already well know.  I was just staring at it a bit.

----------


## DllHell

getting error -2147221231 from CoCreateInstance in pvInitStructure when trying to open a project.

----------


## LaVolpe

> getting error -2147221231 from CoCreateInstance in pvInitStructure when trying to open a project.


That's from the browser class. Can you tell me what O/S you are running? If it isn't something simple, may ask you to bump your question over to the thread related to that class...
http://www.vbforums.com/showthread.p...t-Another-One)

----------


## LaVolpe

At Elroy; thanx for the bug report. I'll have to look at some of the filters I created. Didn't want to perform unnecessary checks based off of the options form. I probably have a conflict where I only compared for zombie checks vs zombie + combination of other options. Easy enough patch - thanx.

----------


## DllHell

> That's from the browser class. Can you tell me what O/S you are running? If it isn't something simple, may ask you to bump your question over to the thread related to that class...
> http://www.vbforums.com/showthread.p...t-Another-One)


win7 (6.1.7601)

----------


## LaVolpe

> win7 (6.1.7601)


Do you have themes turned off? I found some posts that describe that exact error on Win7 when themes are off. If that is the case, if you look in the CmDialogEx class Initialize event, you'll see a comment regarding faking pre-vista. Un-rem that comment and the problem will go away, but you will be using pre-Vista dialog.




> It seems that if Visual Themes feature is turned off for whatever reason, Windows would hide the COM interface and lead to the crash. However, this answer does not provide any specific workaround.


Edited. Another possibility is the usage of the SetThemeAppProperties API. If it is called without passing the STAP_ALLOW_CONTROLS flag, it could cause the same error you are seeing, per some threads on StackOverflow 

If any of these apply to your situation, please let me know so I can update the thread related to that browser class and also update this project. Thank you.

----------


## DllHell

> Do you have themes turned off? I found some posts that describe that exact error on Win7 when themes are off. If that is the case, if you look in the CmDialogEx class Initialize event, you'll see a comment regarding faking pre-vista. Un-rem that comment and the problem will go away, but you will be using pre-Vista dialog.


Themes have not been turned off but adding that line to the init did fix it.

----------


## jpbro

Hi LaVolpe,

I'm looking forwarding to trying out your tool, but unfortunately I've been unable to open my projects - the Project Scanner just crashes completely (taking down the IDE without warning) after trying to open them. I had some spare time to step through the code to see if I could find the error, and what I've found is that the _pvAddReference_ function is getting an unhandled Type Mismatch error at the following line:



```
iMajor = CInt(Left$(sParams(1), lValue - 1))
```

An example problematic _sRef_ parameter value is:



```
{B53264D5-3EF2-4C25-AD09-EAC54E74AD4F}#1fe.1#0#..\..\..\bin\common\SL5ImageFactory.dll#FIVE Image Processing Library
```

You'll notice that the version is quite high and is represented in hex, which explains the type mismatch error. 

I don't know if version numbers are always represented in hex in VBP files, or if they change to hex once they reach a certain value, but this will need to be accounted for. I think it would also be ideal if the error was trapped so it didn't nuke the whole program.

If version numbers are always in hex, then the fix should be easy:



```
iMajor = CInt("&H" & Left$(sParams(1), lValue - 1))
```

If version numbers only become hex after a certain value, then you'd have to perform a test I guess...not sure if the VBP file format is documented anywhere?

%

----------


## Elroy

WOW, that is a large version number.  He's also got to worry about the sub-version.

I did a Google search for: "Reference" "vbp" "Form" "Module" "MaxNumberOfThreads" (with the quotes, looking for posted VBP files).

It was difficult to find references with versions over 9, and I looked at at least 20 or 30 VBP files.  Eventually I did find this one.  It was particularly interesting because it appeared to have a hex sub-version:



```
Reference=*\G{0E3B7611-7892-11D2-84D9-0040C71340BC}#7.a#0#..\..\..\..\..\WINDOWS\system32\Trax.dll#
```


I'm starting to think that they're just _all_ hex, and we've seldom seen it because we never go over 9.

----------


## LaVolpe

@jbpro. wqweto mentioned that in post #5. I've fixed it on my end, but haven't uploaded the update since I'm still working on the delay issue.

FYI: All parts of the TLB version and LCID are hex. They are also hex in the registry. The patched pvAddReference routine will convert the hex to decimal before it is added to the recordset. When the main form displays the references in the treeview, no hex conversion needed because it is getting the value from the recordset.

@Elroy. It is possible to find 409 in the references as the final part of the VB reference string: 409 is hex for 1033 (US English LCID). When I first designed this thing, I knew LCIDs could be hex, but didn't know that the versions were, for the same reason... didn't see any examples using a-f in the version tokens.

@DllHell: "Themes have not been turned off but adding that line to the init did fix it."
Still would love to know why CoCreateInstance isn't working for you when trying to initialize a file dialog. If I knew for sure I could modify the dialog/browser class to check and fix the problem. In the mean time, I'll tweak the code to test for an error and revert to pre-Vista if it errors.

----------


## jpbro

Ahh, missed that one. Sorry for the noise and thanks for clarification re: the TLB version & LCID.

----------


## DllHell

getting a "subscript out of range" error when scanning this line of code:



```
If TypeOf .Controls(t) Is Frame Then
```

----------


## Elroy

I checked, and it is throwing an error.  Here's a complete piece of code that will produce the error.  I just threw a single Frame on the Form1:



```

Option Explicit

Private Sub Form_Load()
    With Me
        If TypeOf .Controls(0) Is Frame Then
            Debug.Print "Found frame"
        End If
    End With
End Sub

```

----------


## LaVolpe

Thanx for that bug report. The logic failed to expect "Me" by itself, my bad. As a quick fix, you can make these small changes in pvParseWordList with clsZombie

1. Find the lines in red and replace with the ones in blue


```
                If sToken = "Me" Then       ' use a set path of current code page
                    lPath = m_CodePage
                ElseIf sToken <> sName Then ' else recordsets already filtered on the project

                If sToken = "Me" Then       ' use a set path of current code page
                    lPath = m_CodePage: rsIndex = 2
                ElseIf sToken = sName Then ' recordsets already filtered on the project
                    lPath = m_PrjRef: rsIndex = 2
                Else
```

2. In the same routine, towards bottom...


```
      Else       ' object/item found, flag as non-zombie

      ElseIf rsIndex < 2 Then ' object/item found, flag as non-zombie
```

----------


## Eduardo-

Hello. Thank you LaVolpe for the great tool.

I found a possible bug, and also there are some features I would like it to have if possible.

1) The possible bug:
In my component I have a class module named cFontAttributes, it is a private class and it has only a few public variables (as un-validated properties) and nothing else, the actual code is:



```
Option Explicit

Public Name As String
Public Size As Single
Public Bold As Boolean
Public Italic As Boolean
Public Underline As Boolean
Public Strikethrough As Boolean
Public Width As Long

Public ForeColor As Long
Public BackColor As Long
Public Tag As String
```

The tool detects all the variables as not used.
They are not used in the class module itself but they are used elsewhere in the project because they are public.

Some features I would like it to to have:

2) The *Copy* feature already asked by Elroy, I think it is very necessary.
In the time being I've made my own.
I added to frmMain a menu entry named *mnuCopy* and this code:



```
Private Sub mnuCopy_Click()
    ' context menu item selected clicked
    Dim lIndex As Long
    Dim iText As String
    Dim iPos As Long
    Dim iStr As String
    
    If tvItems(1).Visible = True Then lIndex = 1
    If tvItems(lIndex).Nodes.Count = 0 Then Exit Sub
    
    Clipboard.Clear
    iText = Trim$(tvItems(lIndex).SelectedItem.Text)
    iPos = InStr(iText, " ")
    If iPos > 1 Then
        If Mid(iText, iPos - 1, 1) <> ":" Then
            If Mid(iText, iPos + 1, 5) = "{Fnc}" Then
                iStr = "Function "
            ElseIf Mid(iText, iPos + 1, 5) = "{Sub}" Then
                iStr = "Sub "
            ElseIf Left$(iText, 6) = "Raised" Then
                iStr = Replace(Mid$(iText, iPos + 1), ":", "")
                iPos = 1
            End If
            iStr = iStr & Left$(iText, iPos - 1)
        End If
    End If
    If iStr = "" Then iStr = iText
   Clipboard.SetText iStr
End Sub
```

3) Bigger font.
The treeview font was a bit small for me to read comfortably, it would be nice to have a preference option to change it within the program. In my test, I changed its size form 8 to 11.

4) Already asked by Elroy, it would be nice to have a recently opened file list. I particularly don't mind it to use the registry (is there any problem using the registry? Anyway most of the programs do it). Or otherwise using and INI file.
This one is not important but still would contribute to the usability.

Thank you.

*Edit:* I found out that the zombie procedures are in fact handled so modified the original message.

----------


## ChenLin

Why do I get the following error when I test? Is it because of Chinese?

Type Mistmatch

----------


## Eduardo-

Another bug: it seems that it has some trouble when the variables or procedures are used only qualified.

For example, in my program I have a [tt]RECT[tt] type variable named [tt]mTabBodyRect_Prev[tt].
The only references to this variable are to its members: mTabBodyRect_Prev.Left, mTabBodyRect_Prev.Top and so on. The scanner flag it as not used.

Another case:
In a bas module named mGUIStrings I have a procedure: Public Property Let GUILanguage
It is only referenced in a GlobalMultiuse class module of the component in this way:



```
Public Property Get ExFnGUILanguage() As vbExGUILanguageConstants
    ExFnGUILanguage = mGUIStrings.GUILanguage
End Property
```

Then it is in fact used, but the scanner reports it as not used.

*Edit:* another thing:
After the validation utility has ran, if I want to run it again but with other options checked (or unchecked), I'm not able, the message instruct to re-scan the project.

Also, I would like the program to remember the options as they were selected the last time (but it would also require to write to the registry or somewhere else).
Thanks.

*Edit2:*
I suggest to use the bottom label to show the progress when loading the project instead of the treeview itself.
I attached a modified version of the project that does that.

Another bug: Private Sub/Function/Property when they are part of an Implementation.
These procedures are flagged as zombies.
They are not used elsewhere in the program but even when they are declared as Private, they are needed because are accessible publicly.

----------


## 2kaud

[contents removed]

[TEST POST FROM EDUARDO USING FIREFOX - THIS POST CAN BE REMOVED BY A MOD AS IT POSTED OK USING FIREFOX]

----------


## LaVolpe

@All, completely rewrote the tool but have not reposted it yet. This is just a follow up. Plan on reposting by this weekend at the latest.

@ChenLin. That is a bug in the program. For now, just test for a null field, i.e., If IsNull(rsLocal.Fields(constFldAttr2)), and then skip that line.

@Eduardo. Thank you for the bug reports -- will be looking at them. Some of the bugs you are reporting, I'm coding for, so I'm interested in why they are failing for you? For example, whenever a Implements is used, the code should recognize the Implemented class events (those that VB forces you to include even if not used) and not report those as zombies. This tells me that in your case, those events are not being recognized as events. Is there any way you can give me a some short examples in a sample project? P.S. I like the idea of allowing user to increase/decrease font size.

^^ Edited. Regarding class public variables -- good one. I'll have to treat them as public properties. In reality, that's what they are. 

^^ Also I did find another unique bug in pvProcessWordList that could be part of the problems you are seeing? That word list is a sorted list. In my case, I had "words" in that list alphabetically as *rs.Clone* and also *rsItems.EOF*. That routine tries to speed up things by comparing the next word against the previous word. If they are the same, then skip it. For example, let's say in that word list we had "tRect.Left" and then "tRect.Right". Well, when tRect.Left is looked at, tRect is flagged as "used". The next "word" in the list is tRect.Right. Well tRect not searched for again because it was just checked. However, in my case with rs & rsItems, rsItems wasn't checked because the bug only looked at the length of the previous word (rs is 2 characters) and *rs*Items begins with those 2 characters. Since rs matched because of the bug, rsItems was never searched for and flagged as zombie/unused. This bug can occur when variables referenced in same routine begin with the same letters. In your case, let's say you also have a variable named mTab and that variable is referenced in the same procedure where you use mTabBodyRect. In that case mTab is flagged as used but mTabBodyRect will not be.

The major changes I've done (not yet posted) are:

1. The progress is displayed at the bottom of the treeview and the treeview nodes are dynamically expanded/created. This results in near zero delay between end of parsing the project and the treeview being populated initially.

2. The project allows you to hide/unhide nodes in the validation listing and caches that state inside the recordset if the recordset is saved. Intent is for you to fix items in the validation, then hide them, removing the clutter. If at any time you want to unhide them, you can. Each time you modify the hidden state, you should resave the recordset to persist that state.

3. All known bugs, except the latest by Eduardo, have been addressed and fixed. Even took some of Elroys suggestions: any code displayed by the project is sent to a txt or rtf file and displayed within their default viewers. The project no longer displays any code within itself. Instead of offering a context menu for copying a node caption, another Elroy suggestion, I allow the node to be set to "edit" mode and the user can copy from that what they want; but not allowed to actually modify the caption.

4. Several other enhancements but will talk about those when I repost later this week.

----------


## LaVolpe

@Eduardo, wanted to address this one separately. Regarding choosing some validation options and then going back and choosing other ones later, without having to re-scan the project. I agree that would be a good thing, but will have to rework several things. Why?

During initial scan, only the declarations section of each file is fully processed so it can be displayed in the tree. During that processing, critical information is stored with each declaration item so that it doesn't have to be re-parsed during future validations. Procedures are not fully parsed, they are skimmed, and the only information that is stored is the procedure location and size within the code file and whether the procedure header/signature is a multi-line statement, i.e., has underscore continuation characters. Now, when validations are performed, information gathered and written to the recordset are relative to the options selected. The code was written for a one-time pass through the procedures, processing each "word" in the procedure. Most information written to the recordset was intended for one-time use only. For example, parameters are parsed only if needed. The routines expect them not to exist in the recordset at the time they are parsed. If the project were run again, they would be there and foul up the logic. That's just one example of many cases.

What you are asking can be done, and am thinking about it. Just requires additional tracking during procedure parsing to ensure pre-existing flags, attributes, items don't mess up future runs of similar parsing. Also don't want to re-parse something that doesn't need to be

----------


## Elroy

> @Eduardo, wanted to address this one separately. Regarding choosing some validation options and then going back and choosing other ones later, without having to re-scan the project. I agree that would be a good thing, but will have to rework several things. Why?
> 
> During initial scan, only the declarations section of each file is fully processed so it can be displayed in the tree. During that processing, critical information is stored with each declaration item so that it doesn't have to be re-parsed during future validations. Procedures are not fully parsed, they are skimmed, and the only information that is stored is the procedure location and size within the code file and whether the procedure header/signature is a multi-line statement, i.e., has underscore continuation characters. Now, when validations are performed, information gathered and written to the recordset are relative to the options selected. The code was written for a one-time pass through the procedures, processing each "word" in the procedure. Most information written to the recordset was intended for one-time use only. For example, parameters are parsed only if needed. The routines expect them not to exist in the recordset at the time they are parsed. If the project were run again, they would be there and foul up the logic. That's just one example of many cases.
> 
> What you are asking can be done, and am thinking about it. Just requires additional tracking during procedure parsing to ensure pre-existing flags, attributes, items don't mess up future runs of similar parsing. Also don't want to re-parse something that doesn't need to be


Personally, that made sense to me from the beginning.  I just knew you wouldn't deny a re-validate without re-scan if there weren't sound reasons.  However, maybe a solution is this ... when they/we attempt to re-validate (after initial validation), You're prompted with something like the following:  "The project must be re-scanned to do this.  Do you wish to re-scan and then proceed to re-validation? (Yes/No)."  Just a thought.  (And I'm fine if you leave that part like it is.  Thanks for the copy-paste considerations.)

----------


## Eduardo-

Hello LaVolpe,




> For example, whenever a Implements is used, the code should recognize the Implemented class events (those that VB forces you to include even if not used) and not report those as zombies. This tells me that in your case, those events are not being recognized as events. Is there any way you can give me a some short examples in a sample project?


I see that most of the Implements that I have are in fact recognized, but only one is not.
This one is declared as:



```
Implements VB.Printer
```

The "VB." must be the cause.

The others bugs also have to be with dots.
For example I have 1) an UDT and 2) a Class module declared As New.
In both cases the variables are never referenced alone, but they are referenced by their members.

UDT example:


```
Private Type RECT
    Left as Long
    Right As Long
    Top As Long
    Bottom As Long
End Type

Private Sub Test()
    Dim MyRect as RECT
    
    MyRect:Left = 100
    MyRect.Top = 100
[...]
End Sub
```

Class module example:


```
Private Sub Test()
    Dim MyClass1 As New Class1
    
    MyClass1.DoSomething
End Sub
```

And there is another case that also has to do with a dot.
I have a Public procedure let's say named MyProc in a bas module named Module1.
The procedure is only used referenced as Module1.MyProc

Used in that way it is flagged as zombie.

I'll make a sample project, but later because now I need to leave.

----------


## LaVolpe

That VB.Printer was interesting. I coded the project to use a class' default interface methods when there is no default events interface. However, VB.Printer does have a default events interface, but that interface has no methods/events! So, I tweaked my copy of the code to use the default interface when there is no default events interface or that interface has no methods - problem solved.

Regarding your latest examples. I cannot reproduce the problem here, but I know my code & yours are no longer exactly the same. Here is something you can try to narrow down the problem. In the pvProcessWordList routine, immediately after the FOR statement, add a line like this:
If m_Words(m_WordLstIdx).List(n) = "MyClass1.DoSomething" Then Stopnow you can walk the routine a bit to see what happens. If the For/Next loop exits with rsIndex having a value > 1 then the item wasn't found - which should be the problem. 

I've posted my current code below. But I'll take it down later -- it's not ready yet & still has some test code in it. If you can reproduce the problem with the latest code, please let me know. Thank you in advance

** attachment removed **

----------


## ahenry

My code with collections of collections, i.e.



```
Item(1)(1).b
```

Causes an "invalid procedure call or argument" error in pvProcessWordList; it's generating an empty token.

----------


## LaVolpe

> ```
> Item(1)(1).b
> ```
> 
> Causes an "invalid procedure call or argument" error in pvProcessWordList; it's generating an empty token.


Thanx, will fix that. I knew doing this project, I'd run up against coding practices I wouldn't anticipate. That is one of them  :Wink: 

Edited: patched in my copy -- still expecting to repost no later than this weekend.

----------


## Eduardo-

> Regarding your latest examples. I cannot reproduce the problem here


I made some wrong assumtions.
The cause of the problem was actually when there was another variable that was the same name but shorter. It had nothing to do with the dots.
(That bug is now already fixed in your latest code)

Only one "bug" remains, it is in the sample project that is attached.

----------


## LaVolpe

Version 2 of the scanner is now posted. Here is a list of key changes

1. The lengthy delay many experienced after a large project was parsed and when the treview finally allowed you to mess with it is now gone. All tree nodes are dynamically created, on demand as needed. The 32K limit in nodes will be history. The project will nag you if you are displaying more than 5K nodes. Each time you collapse a tree branch, invisible nodes are removed which keeps the node count low.

2. Completely rewrote how stuff is written to the recordset and when it is accessed during validation. Short story: stuff written the the recordset is parsed just once, any future access is from the recordset, not re-parsed. This enables the project to allow you to choose options to validate, then later validate with other options you haven't chosen. Requested by Eduardo.

3. The treeview captions can be clicked on to set them in edit mode. You can copy anything from the caption, but you are not permitted to change the caption

4. The project allows you to hide validations that you either don't care about, or have fixed. This just serves to reduce clutter and allows you to keep track of what validations you have reviewed or fixed. Once hidden, you can later unhide them if you choose. You should export the scan/validations database any time you make changes that way to persist them.

5. Added new validation as requested by Elroy: Identify whether any variables use ReDim that have not been previously declared.

As you ladies and gents play with this, will consider enhancements. Always welcome bug reports.

----------


## ChenLin

Still getting an error.

----------


## LaVolpe

Hmm, curious if it has to do with unicode? There is only one field in the recordset that allows unicode and if I'm trying to stuff unicode in any of those other fields, an error will occur. In the clsZombie's Validate procedure, comment out the "On Error GoTo ParsingErrors" line and try again. The code should stop on the line causing the error. That will help me troubleshoot it from here. Thank you.

Important: If you comment out that On Error statement. When your code stops on the error, paste these lines in the Immediate window (Ctrl+G) and execute them, before you terminate the running code. The lines will release any existing memory overlays. Not doing that could result in the IDE crashing


```
modMain.CreateBuffer "", m_Buffer(0), Nothing, True, True
pvOverlayToken True
```

P.S. If you could translate that error description that could help too.

----------


## Thierry69

You should add check to find and validate CreateDC, ReleaseDC etc...
helping to find memory leaks

----------


## LaVolpe

> You should add check to find and validate CreateDC, ReleaseDC etc...
> helping to find memory leaks


Oops, you posted this on the wrong thread  :Wink:

----------


## Thierry69

Non, in the project scanner, finding a way to check if all object created are well destroyed.

I have a control, where I know I have somewhere a memory leak, trying to find it for many years without any success  :Frown:

----------


## LaVolpe

Oh, I see. The answer is  no. The scanner does not track variables as they move across code, in or out of procedures, etc. When I was an active member on PlanetSourceCode, myself and others actually attempted to come up with a way of doing that. The result was a class that could be added to your project and it would track handles for you, but you had to pass handles to it for tracking and destroying. If the class terminated and any handles remained, it would destroy them. Don't have a link for it, if it still exists. And it wasn't fool-proof. Typically, you can't delete an object if it is selected into a DC (pre-Vista I believe). Not sure that's still true today. In that case, if the user failed to unselect a selected item (font, pen, bitmap, etc) from a DC, the deletion would fail anyway. But the class warned you because it also looked at the return value of the various Delete[xxx] APIs.




> I have a control, where I know I have somewhere a memory leak, trying to find it for many years without any success


In my signature below, the "Memory Leak FAQ" might be useful?

----------


## Thierry69

Already tried to find with your doc.

----------


## Elroy

hahaha, I'm sorry Thierry69, but I had to chuckle.  That would be a HUGE task to try and examine the flow of source code for all possibilities of a memory leak.  In fact, just because user-input can alter the flow of the executing code, it'd basically be impossible.  It's a good thought though.   :Smilie:

----------


## Thierry69

Sure, I am certain that it is not pssible to find 100% of cases.

I sent you a private message

----------


## ChenLin

> Hmm, curious if it has to do with unicode? There is only one field in the recordset that allows unicode and if I'm trying to stuff unicode in any of those other fields, an error will occur. In the clsZombie's Validate procedure, comment out the "On Error GoTo ParsingErrors" line and try again. The code should stop on the line causing the error. That will help me troubleshoot it from here. Thank you.
> 
> Important: If you comment out that On Error statement. When your code stops on the error, paste these lines in the Immediate window (Ctrl+G) and execute them, before you terminate the running code. The lines will release any existing memory overlays. Not doing that could result in the IDE crashing
> 
> 
> ```
> modMain.CreateBuffer "", m_Buffer(0), Nothing, True, True
> pvOverlayToken True
> ```
> ...


The error prompt is: 
subscript out of range。

----------


## LaVolpe

ChenLin, thank you. But that doesn't help too much. From your screenshot, it looks like you were running a validation on this project? Did you make any changes to the project? I've scanned this project dozens and dozens of times with no errors. 

That error could possibly happen in pvParseWords, but I thought I handled every scenario to prevent that. I'd love to know how you got that error, so that I can fix my logic. If you can reproduce that error again, would you please tell me how so I can try to force the error also?

----------


## Elroy

Just an FYI, I finally found the time to run your latest on my very large primary project.  It all ran through flawlessly.  It took a while to load, and also to validate, but I love the new labels that show the progress.  That's just what I needed to have confidence that it wasn't hung.

I may have missed it last time, but it seems to have found two more cases of "Variant vs. String Functions" than it did before.  

Someday (or not) I'll clean up my "Duplicate Declarations" (571 of them), and my "Duplicate String Literals" (1706 of those).  My "Zombie Check" was also a large number, but that's fine.  I know I've got all kinds of code in that project that's not being used.  That project serves a dual purpose: 1) It's the primary project on which I work and maintain, and 2) It's sort of a repository for code that I may use someday.  As stated in my signature footer, I've also got an overflowing "random code folder", but stuff gets lost in that folder, and I don't trust a great deal that's in there.  Anything that's in my primary project (used or not) has been well alpha-tested.

Again, a HUGE thanks for the effort you've put into this.

Elroy

----------


## Eduardo-

Hello, great work. All bugs that I previously reported seem to be fixed.
Regarding the Stop/End detection, the last version seems to only detect End but not Stop statements.

.................
Some ideas: I didn't study the code of the scanner, then I prefer to ask it to you: do you store detailed information about the discoveries, I mean the line positions, where an issue starts, where it ends?
I'm thinking, perhaps in the future, to make an Add-In, something simple that would have to be able to receive orders from the project scanner and highlight the text of the issue. May be to receive the orders via DDE or by some other means.

Thinking more... after correcting an issue it would also be desirable to have that reflected in the list, could it be possible to re-scan just a module or just a procedure and update it without having to re-scan the whole project?
Just thinking...

----------


## ChenLin

> ChenLin, thank you. But that doesn't help too much. From your screenshot, it looks like you were running a validation on this project? Did you make any changes to the project? I've scanned this project dozens and dozens of times with no errors. 
> 
> That error could possibly happen in pvParseWords, but I thought I handled every scenario to prevent that. I'd love to know how you got that error, so that I can fix my logic. If you can reproduce that error again, would you please tell me how so I can try to force the error also?


I haven't changed any code, and there are also errors in scanning other projects.

My steps: Open a project - > Validation - > Do Validation.

I thought it was the reason why the project directory contained Chinese, but in fact, copying the project to another directory would also cause errors.

----------


## LaVolpe

Looking at your screen shot in post #51, the project seemed to parse fine initially. So the problem I think is in the pvParseWords routine. But I cannot get that error on my machine, Win10. In post #52 above, I mentioned how you can determine exactly what line of code is causing the error. That may help me in trying to narrow down the problem. I'm sorry, but without more information, I do not know what to look for. I am confused as to why it fails on your machine but not mine or anyone else, so far.

----------


## ChenLin

The error occurred in Sub pvShowValidation, see the screenshot.

----------


## LaVolpe

@ChenLin, that is where the msgbox is that displays the error. The error occurred in clsZombie's Validate routine. In that routine, when an error occurs during validation, it sets the recordset field to -1 and saves the error number. Then when the validation form closes, the recordset is checked for a -1 code and displays the error box.

In order to know which line caused the error, you must comment the "On Error GoTo ParsingErrors" line in clsZombie Validate routine. I tried to explain that in post #52.

Edited & for ChenLin... I may have discovered a problem with a recursion scenario that could logically result in an array changing before it is completely processed. I have uploaded the change to post #1 as a new zip.

----------


## ChenLin

Find the error line, prompt the error: subscript out of range。

----------


## LaVolpe

@ChenLin.  That does confuse me. That part of the program is trying to load the VB TLB for forms, classes, usercontrols, etc. This would have been done when the project was initially scanned. An error there can only happen in 2 cases:

1. The project could not load the VB6.OLB type library. If this is the case, and your VB is installed in a path with Chinese characters, then I can only assume that the TLB reference I am using for parsing TLBs is not unicode friendly. You can help verify that this way:

a. In the clsPrjFile class, look at the routine: pvAddReference
b. Find these lines in the routine and add the blue line. Then when you run the project, after loading a project to scan, please check the Immediate window to see if anything was printed out there. If so, failure to load the TLB and that is not good.


```
    On Error Resume Next                        ' attempt to load the TLB
    Set TLIinfo = tli.TypeLibInfoFromRegistry(sParams(0), iMajor, iMinor, lLCID)
    If Err Then
        Debug.Print "Err TLB load: "; sName; " "; sRef
```

2. The recordset contains unexpected values. You can help check this also.

a. In the clsZombie class, look at this routine: pvAddShadowMethods (same place where your error occurred).
b. In that routine, after the "End Select" statement, add this. Then validate the project. If this your code stops on that line, unexpected values in the recordset.


```
If lType < ptModule Or lType > ptUnknown Then Stop
```

Also, could you expand the "References" tree view node after the project is initially scanned? I am interested if there is any indication that the TLB failed to load. Thank you.


Edited: I think it may be the LCID for your version of VB. That could very well be it. Here is an updated class. I hope this will fix the problem

----------


## ChenLin

I tested the error of the screenshots on all three computers, but Do Validation on other computers is normal and no error is returned.

----------


## LaVolpe

I'll try to explain what is happening... ComCtl32 version 1.4 with the LCID shown in that line (not visible from your screen shot) was not found in your registry. This project then tries to find the version with a different LCID. If it cannot be found, then that scanned project should not be able to be loaded into VB IDE without errors. This project assumes any scanned project can be opened with VB and run without errors.

Common controls version is a typical problem with downloaded VB projects. If your computer's installed version of the ocx is not equal to or greater than the version referenced in a project, then VB will display an error when you try to load that project. The trick is to open the vbp and other files, i.e., (.frm, .ctl, etc) and change the version to a lower version like 1.2. Now VB IDE should be able to load the project.

This does give me an idea to be able to identify these problems with the Common Control versions and offer a solution to the user to prevent those errors.

----------


## ChenLin

I'm sure this is a Chinese environment or something else: because not only is ComCtl32 not properly acquired, but not all can be obtained...

----------


## dreammanor

I quickly took a look at the source code, the project does not process Chinese. All "*xxx > 32*" in the project should be changed to "*(xxx > 32 or xxx < 0)*"

----------


## LaVolpe

> I quickly took a look at the source code, the project does not process Chinese. All "*xxx > 32*" in the project should be changed to "*(xxx > 32 or xxx < 0)*"


I doubt that. VB variable names, procedure names, parameters, types, API, etc won't be in anything other than ANSI. It is possible that hard coded literals/strings could be? If that is possible, strings are processed as unicode just in case. In other words, there shouldn't be any non-ANSI stuff to parse, except maybe string values, which are not truly parsed anyway. If my ANSI-assumptions are not true, please someone zip up a sample form/class so that I can review it.

----------


## dreammanor

> I doubt that. VB variable names, procedure names, parameters, types, API, etc won't be in anything other than ANSI. It is possible that hard coded literals/strings could be? If that is possible, strings are processed as unicode just in case. In other words, there shouldn't be any non-ANSI stuff to parse, except maybe string values, which are not truly parsed anyway. If my ANSI-assumptions are not true, please someone zip up a sample form/class so that I can review it.


In the VBP file, ExeName32, Path32, Description, VersionFileDescription and VersionProductName may contain Chinese chars, which will cause the returned value(*sLine*) of "*pvGetStatement_NoParse*(sLine as String) As Boolean" to be a vbNullString.

----------


## LaVolpe

Those will be fine. There are 2 instances where the byte > 32 is tested in that routine. Finding 1st character of a line and right-trimming a line. Since a line in a vbp won't start with a non-ANSI character, the 1st scenario won't cause a problem. And since the values of those vbp properties will be a string enclosed with Chr(34), the 2nd scenario won't apply. 

However, and am glad you brought up that point... The project will not work with a unicode VB project file, vbp, form, etc. When I read the file, it is converted with StrConv to unicode. So a unicode file will become something like double-wide which will fail to load.

Out of curiosity, do you have such project examples? I don't know how VB writes such project files: BOM, no BOM. I'll have to be able to recognize whether the file content is unicode or not.

----------


## DEXWERX

Do the Chinese systems use DBCS? Is there anything special that needs to be considered when handling DBCS vs ANSI. (ANSI APIs inherently handle DBCS)

----------


## Elroy

Ok, I'm confused.  I always thought that all VB6 source files _HAD_ to be saved as ANSI (including the VBP file).  And, in fact, that's one of the reasons we have many of our Unicode limitations like Captions (etc).  And that's why it never really mattered that the Properties Window didn't handle Unicode.  Sure, we have many workarounds, but none of them involve saving Unicode versions of the source files (other than possibly stuffing a binary version of the Unicode into the ??X files).

And, just for grins, I tried saving a VBP file as UCS-2 Big Endian, UCS-2 Little Endian, and also UTF-8 with BOM.  And, in all cases, the VB6 IDE would have nothing to do with them.  I even tried placing some UTF-8 characters in a source file that didn't have the BOM, and the IDE just expanded each UTF-8 character to multiple characters (treating them as ANSI).

Even LaVolpe's statement, "It is possible that hard coded literals/strings could be?", is confusing to me.  _String literal constants_ are just written directly into the source file, so I don't understand how they could be Unicode.  They might be Unicode once compiled.  I'm actually not sure.  But they're certainly not Unicode prior to that.

----------


## LaVolpe

@Elroy. VB can read UTF-8 format with and without BOM. It doesn't seem to want to load UTF-16, BOM included or not. With UTF-8 & BOM included, VB does throw an informational warning, but still loads the project -- incorrectly at times. It doesn't expect the BOM, so it misinterprets the 1st line of the vbp file and makes a best-guess. For other VB file types, probably gonna break them too if a BOM exists. As far as "expanding" the characters, it may very well rely on regional settings.

@All. Unless someone can tell me that VB will load a true unicode file (with or without the BOM), i.e., 2 bytes per character in the file itself, I won't modify this code. Using StrConv() on the file does break this project in that case, but that scanned project can't be loaded in VB anyway. This project throws an error saying it couldn't parse the file. Primary assumption for this project is that any scanned project can be loaded into and run from the IDE without errors. Using StrConv() on a UTF-8 encoded file does not break this project, but may not reflect actual unicode characters in the validation option that lists duplicated strings. However, will that problem exist on the same system that produced those UTF-8 files, i.e., same regional settings?

Edited. It'll take a bit of work, but I am reconsidering handling of UTF8 encoding... Here is a screenshot showing how this project can carryover the unicode characters even though my VB IDE cannot display them correctly.

----------


## DEXWERX

You're correct, VB doesn't handle Unicode (or UTF-8 even, since it's built off the same VC6 compiler). However VB should be able to handle DBCS which is treated like ANSI. From what I understand the VB6 runtimes built in string comparisons functions tend to choke on DBCS. Schmidt might know more on this.

https://raymai97.github.io/myblog/ms...-since-vc6#vc6

*edit:* You could replace your string comparison with ANSI APIs, which can handle DBCS.

----------


## LaVolpe

@Dex. I've some code from another source (will credit him) that checks for UTF8 with/without BOM and converts UTF8 without StrConv(), using APIs.

That should do the trick and should allow strings to show their intended value in the treeview (theoretically), whether or not the user's regional settings match that of the scanned project when written. Again, all this assumes that non-US versions of VB writes UTF8 to allow unicode strings in the vbp, frm, ctl and other project files. If not, no harm, other than a very small speed bump checking for BOM-less UTF8 encoding. Initial tests look like its working just fine.

----------


## DEXWERX

So digging into that link I posted - the blogger is claiming that VC6 works with Unicode string literals (symbols still need to be ANSI I assume) _if_ the regional settings are set to an en-* locale. All other locales are ANSI/DBCS. So yeah - It's pretty odd that you might have to handle all those situations.

*edit:*So basically VC6 expects the opposite of what you're thinking. Handle UTF-8 literals if its an en-* Locale otherwise its ANSI/DBCS.

----------


## Eduardo-

> Again, all this assumes that non-US versions of VB writes UTF8 to allow unicode strings in the vbp, frm, ctl and other project files.


VB6 handling UTF-8 seems strange to me. 
When VB6 was made UTF-8 didn't exist yet (OK, it existed, but wasn't much used).
Unless a VB6 service pack patched it to support UTF-8, I don't think it could be supported.

----------


## Elroy

Ok, just to be clear, I took a VBP file, edited it so that the "Title" line looked like the following:



And then I saved it as UTF8 with BOM.

And then, I loaded it with the VB6 IDE.  It gave me that warning, but I told it to load anyway, which it did.  Then, I went to the debug window to check on some things, as follows:



```
? len(app.Title)
 7 

? app.Title
a†••
```


This tells me that it was _not_ read as UTF8.  But rather, the BOM was discarded and it was read as an ANSI file.  And when it hit those UTF8 characters, it just read each byte as a character (as opposed to correctly reading the UTF8 multi-byte characters).

I'd dearly love to get some Unicode in our source code, but I don't think it's ever happening with the existing VB6 IDE.  Now, I suppose we could test to see what the C2 compiler can handle from a command line directive, but that's somewhat different from what we're discussing.  And it would require that we use a different IDE for our VB6 development, which I don't think I'm willing to do.

Merry Christmas,  :Smilie: 
Elroy

EDIT1:  Also, let's not forget that UTF8 was designed to be backward compatible with ASCII (which is not at all true of either UTF16 or UCS2).  Which means that an ASCII file can be read as UTF8 (but not vice-versa which is what VB6 tried to do in my example above).

----------


## LaVolpe

@Eduardo. A while back, I was looking at this from a different angle. I could hard code string literals with non-ANSI and VB had no issues with it. This is the link for that discussion. Among that discussion, I was able to create and load non-ANSI resource file items, i.e., Set Me.Picture = LoadResPicture("цветок", vbResBitmap). So, VB must be able to handle UTF8? I was also able to use Chinese similarly. However, LoadResPicture didn't support Chinese though a workaround was provided.

----------


## Elroy

> VB6 handling UTF-8 seems strange to me. 
> When VB6 was made UTF-8 didn't exist yet (OK, it existed, but wasn't much used).
> Unless a VB6 service pack patched it to support UTF-8, I don't think it could be supported.


I always think/thought of UTF-16 (and its UCS-2 subset where the characters are always two-bytes) as the way Microsoft did things, and that UTF-8 was the way the rest of the world did things.  In fact, it's my understanding that escaped URLs are basically escaped UTF-8.  And yeah, they were both around prior to VB6.   :Smilie: 

Therefore, since Microsoft has always been on the UTF-16 side of the fence, yeah, the VB6 IDE handling UTF-8 makes no sense to me.

----------


## Elroy

WOW, I can't get that *Set Me.Picture = LoadResPicture("цветок", vbResBitmap)* into my IDE.  Here's what I get when trying to copy-paste:



LaVolpe, if you did, I'd sure like to know how.

EDIT1:  I also tried several ways to make sure that the clipboard did, in fact, contain UTF-8, but I always got the same results when trying to paste into the VB6 IDE.

----------


## LaVolpe

@Elroy. If you really want to play with that, follow the other thread I referenced in post #85. You'll need to set your locale to Russian and your default language also to Russian. Memorize how to set them back to English because after you reboot, you won't be seeing English any longer until then  :Wink:

----------


## Eduardo-

I'm quite confident that when VB6 was designed, they ddidn't care about UTF-8 because even when it already existed, it was not known/used.
It was years later when UTF-8 begun to be used, and even later it became an standard, at least for the web.

If VB6 can handle UTF-8 in some of its features, it must be because it is doing that function through an API. And the OS's API is the one that was updated to handle UTF-8.

Some info (not much): https://www.quora.com/Why-doesnt-Mic...-on-Windows-10

----------


## DrUnicode

> @Dex. I've some code from another source (will credit him) that checks for UTF8 with/without BOM and converts UTF8 without StrConv(), using APIs.


I always thought Vb6 IDE was ANSI and DBCS which depends on setting default LCID for non-Unicode apps.




> You'll need to set your locale to Russian and your default language also to Russian. Memorize how to set them back to English because after you reboot, you won't be seeing English any longer until then


AFAIK you only need to change the default LCID for non-Unicode apps and not the Main language LCID.
That is what I am doing here for testing and it appears to work OK. 

For example, when you set Administrator non-Unicode LCID to 2052, you can type Chinese for captions and comments in Vb6 IDE.
You will need to install a keyboard IME for Chinese if you are using a U.S. English.
It looks like Unicode UTF16 but since the Vb6 IDE is ANSI only(and DBCS) it is actually showing Chinese DBCS.

This was discussed at:
http://www.vbforums.com/showthread.p...network-folder.
Thanks to Olaf for sugesting I switch to StrConv.
See demo code in thread #14 for EncodeMBCS and DecodeMBCS.
Turns out the OP was looking for a VbScript solution but since it was posted in Vb6 forum I originally provided a Vb6 solution.

In the demo at thread #14, Chinese system non-Unicode LCID 2052, it displays "中文" but the same string displayed on U.S. system with Locale 1033 shows ""
Two utilities are provided in the link below, EncodeMBCS and DecodeMBCS so you can convert strings from one LCID to another.
I tried pasting Chinese Unicode "中文" in Vb6 IDE (with LCID 2052) but it just displays "??" (not suprising since we know Vb6 IDE is not Unicode).

----------


## dreammanor

Hi LaVolpe,

The attachment is the VBP used for testing：

After I changed the *company* and *description* of pScanner.VBP to Chinese, this pScanner.VBP cannot be read by pScanner. In addition, I added a line of code to clsPrjFile.ParseFile to display the error message.



```
Public Function ParseFile(ByVal FileName As String, Caller As ICustEvent) As Boolean
'...
'...
'...

ExitRoutine:
    MsgBox "ParseFile Error:" & vbCrLf & vbCrLf & Error         '--- DreamManor Added on 2018-12-22 ---
    modMain.CreateBuffer m_Buffer.FileName, m_Buffer, Nothing, True, True
    Set colFiles = Nothing
    On Error GoTo 0

End Function
```

----------


## LaVolpe

@dreammanor. Sorry, no errors on my end. I did get your "nullstring line" message, but that is expected at end of file (no more data to read).  I did not get any other errors running the project you just posted. Do note that I had to change the ComCtl32 version to 1.3 to open the project because I don't have 1.4 installed as you do.

Also note, that the vbp file is ANSI/UTF8 and those fields look like this in the zip file... Is that expected?
VersionComments="ܰĿɨ"
VersionCompanyName="˾(LaVolpe)"
VersionFileDescription="Ŀɨ"
VersionProductName="Ŀɨ"

----------


## DrUnicode

As mentioned in thread #90, this is LCID 2052 DBCS, not UTF8

What you see on U.S. system
---------------------------
VersionComments="ܰĿɨ"
VersionCompanyName="˾(LaVolpe)"
VersionFileDescription="Ŀɨ"
VersionProductName="Ŀɨ" 

Converted to Chinese (LCID 2052)
--------------------------------
VersionComments = 很棒的项目扫描器
VersionCompanyName = 公司(LaVolpe)
VersionFileDescription = 项目扫描器
VersionComments = 项目扫描器

Translated Chinese to English w/Google
--------------------------------------
VersionComments = Great Project Scanner
VersionCompanyName = Company (LaVolpe)
VersionFileDescription = Project Scanner
VersionProductName = Project Scanner

----------


## dreammanor

> As mentioned in thread #90, this is LCID 2052 DBCS, not UTF8
> 
> What you see on U.S. system
> ---------------------------
> VersionComments="ܰĿɨ"
> VersionCompanyName="˾(LaVolpe)"
> VersionFileDescription="Ŀɨ"
> VersionProductName="Ŀɨ" 
> 
> ...


Hi DrUnicode, 
you are right. Since VB6 IDE is ANSI, the Chinese chars we input in the VB6 IDE are not Unicode, but ANSI. For example: Ascii of "中" is -10544 (Asc("中") = -10544, Ascw("中") = 20013). So in our VB6 source code we need to judge "Asc(sChar) > 32 Or Asc(sChar) < 0". Olaf's cwTextBox can't input Chinese chars is the same reason.

----------


## dreammanor

Hi LaVolpe:
I made some changes to your code, now it can handle VBP files containing Chinese chars. But when I open my actual VB project with pScanner, there are still a lot of errors. I checked it and found that it was mainly caused by *AscW(sChar)* (AscW will generate an error when sChar is vbNullString). In other words, in your project, all AscW(sChar) needs to check whether sChar is empty. For example: pvScanDeclarations, GetToken, etc. This issue may be revised in the new year. 




```

Public Function ParseFile(ByVal FileName As String, Caller As ICustEvent) As Boolean
'...
'...
'...

ExitRoutine:
    if Err then MsgBox "ParseFile Error:" & vbCrLf & vbCrLf & Error         '--- DreamManor Added on 2018-12-22 ---
    modMain.CreateBuffer m_Buffer.FileName, m_Buffer, Nothing, True, True
    Set colFiles = Nothing
    On Error GoTo 0

End Function

Private Function pvGetStatement_NoParse(sLine As String) As Boolean

    ' Purpose: replicate VB's Line Input statement, trimming the line

    Dim iPos As Long            ' current character position
    Dim iAsc As Long
    
    sLine = vbNullString
    If m_Buffer.cIndex > m_Buffer.length Then Exit Function
        
    With m_Buffer
        .iLTrim = 0: .iRTrim = 0
        For iPos = .cIndex To m_Buffer.length
            iAsc = .Bytes(iPos)
            If .iLTrim = 0 Then
                If (iAsc > 32 Or iAsc < 0) Then .iLTrim = iPos          '--- DreamManor added "iAsc < 0" on 2018-12-12 ---
            ElseIf iAsc = 13 Then
                Exit For
            ElseIf iAsc = 10 Then
                Exit For
            End If
        Next
        .cIndex = iPos + 1
        If .iLTrim <> 0 Then
            Do Until .Bytes(iPos - 1) > 32 Or .Bytes(iPos - 1) < 0      '--- DreamManor added ".Bytes(iPos - 1) < 0" on 2018-12-12 ---
                iPos = iPos - 1
            Loop
            .iRTrim = iPos
            sLine = Mid$(.Buffer, .iLTrim, .iRTrim - .iLTrim)
            pvGetStatement_NoParse = True
        End If
    End With
    
    '--- DreamManor Added on 2018-12-22 -------------------------------------------------------------
    If sLine = vbNullString Then
        If m_Buffer.cIndex <= m_Buffer.length Then
            MsgBox "Parse error(pvGetStatement_NoParse): sLine is a vbNullString" & _
                        vbCrLf & vbCrLf & "FileName: " & m_Buffer.FileName & _
                        vbCrLf & vbCrLf & "Error position: " & m_Buffer.cIndex & " / " & m_Buffer.length
        End If
    End If
    '---------------------------------------------------------------------------------------------------
    
End Function
```

----------


## LaVolpe

Looks like I'll have some work to do to try to understand DBCS. But I think I might understand... DBCS can result in ASCII characters preceded with a null character? For example, the word "VersionComments" first character is not Chr(86), but something else like Chr(0), Chr(86)? If I process your file as UTF8 via APIs, I don't get Chinese, but I also do not get the ANSI text where DBCS exists. So, simply processing as UTF8 is not a solution either. I will have to educate myself a bit on DBCS. 

But here's a question.... Can you directly enter Chinese characters in the IDE? If you do, does VB save the file in DBCS like your examples or does it save it in UTF8? Also, if you cannot enter Chinese characters directly in the IDE, you had to manually patch the vbp file for the DBCS characters?

In the meantime, this project cannot be successfully run with DBCS if DBCS can be placed anywhere in any VB code file.

----------


## dreammanor

Yes, I can input Chinese chars directly in the VB6 IDE, and the Ascii of Chinese chars are all negative (for example: -10544). The VBP I sent to you is the VBP that I modified on my computer. I don't need to do any extra operations on Chinese characters. VB6 can handle Chinese chars very well, without any settings and third-party plugins. (Perhaps the Chinese OS has helped us do everything.)

Although all my software is Chinese, Unicode is rarely used in our software. In fact, I know very little about Unicode. I only used Unicode once, and that is English phonetic symbols. I have no idea what DBCS is.

----------


## Elroy

Ahhh, I now understand the problem.  And it's still _NOT_ a Unicode problem.

Within the Windows-Code-Pages, there are Single-Byte-Character-Sets (SBCS) and Multi-Byte-Character-Sets (MBCS).  (Again, nothing to do with any flavor of Unicode.)  I think us Americans are most familiar with the SBCS-Code-Pages.  In fact, I wasn't even aware the VB6 IDE could deal with the MBCS-Code-Pages, but apparently it can.

In fact, here's an MSDN quote:




> *Asc Function* ... The range for returns is 0 – 255 on non-DBCS systems, but –32768 – 32767 on DBCS systems.


Way back, when I first read that, I probably thought they were referring to Unicode, but I now don't think that's the case.  I didn't test, but dreammanor seems to have shown this whereas *Asc("中") = -10544* and *Ascw("中") = 20013*.  And the MSDN is clear that Ascw returns a Unicode (i.e., UCS-2) interpretation.

Also, just as another FYI, apparently Microsoft worked fairly hard to insure that all the initial Code-Pages (including MBCS ones) were translatable to UCS-2 (and vice-versa).  I'm not at all sure that that's still true, and even Microsoft admits that this is a continually evolving effort.

But, regarding VB6, I'd think we'd be fairly safe if we just read these ANSI-Code-Page source code files and just immediately converted them to UCS-2 Unicode before doing anything.

LaVolpe, I didn't study your program enough to know if that's what you're doing.  But it does seem that that's necessary if languages like Chinese are going to be correctly handled.  If you're attempting to treat all source files as single-byte-characters, that's not going to work in the ANSI-MBCS cases.

Merry Christmas,
Elroy

----------


## LaVolpe

The above posts help.

I am assuming you cannot name a variable or object (like a control) using Chinese characters. Is that correct? Reason for this is that VB specifically says that variables (really any vb code item) must begin with ANSI alphabet, i.e., A-Z, a-z. Other stuff might be allowed, like literals and comments.

Edited: If my latest assumptions are correct, then changing the project will not be difficult at all.
And as for the idea of trying to display in the treeview the intended characters (like Chinese) on any system will be abandoned. Though it may be possible (maybe not) to be 100% sure if DBCS is used or not, it isn't worth the effort I think. I doubt Chinese have the only DBCS locale so one would have to know which locale the files originated from to properly display any character as intended. Would have been nice, but oh well. Even NotePad punted on dreammanor's vbp file, defaulting to ANSI format.




> But it does seem that that's necessary if languages like Chinese are going to be correctly handled. If you're attempting to treat all source files as single-byte-characters, that's not going to work in the ANSI-MBCS cases.


Actually, that's the opposite of what I wanted. I better understand DBCS now: kinda like ANSI + "unicode" combined into a single format, from cyberactivex site



> DBCS is actually not the correct terminology for what Windows uses. It is actually MBCS where a character can be 1 or 2 bytes. To illustrate this consider the following code which will take a Unicode string of English and Chinese characters, convert to a byte array of MBCS Chinese, dump the byte array to the immediate window, and finally convert it back to a Unicode string to display in a Unicode aware textbox. The byte array when converted using Chinese(PRC) LCID = 2052 contains single bytes for the english characters and double bytes for the Unicode characters.
> 
> sUni = "2006" & ChrW$(&H6B22) & "9" & ChrW$(&H8FCE) & "12" & ChrW$(&H6B22) & " 8:04"

----------


## Elroy

> kinda like ANSI + "unicode" combined into a single format


I'd be careful with a statement like that.  I don't think ANSI (i.e., Windows Code Pages), even when MBCS, have anything to do with Unicode.  In fact, the ANSI Code Pages are _always_ specific to a specific language.  Unicode (particulary UTF-16 or UCS-2) is something entirely different.  Although, so long as your UTF-16 or UCS-2 usage is confined to a specific language, it can be converted to some Code Page language.

--------------

Now, just to confuse matters a bit more, there's actually a Code Page ID for UTF-8.  In other words, you can treat the entirety of UTF-8 as a _single language_.  The CodePage code is 65001.  This works because ASCII is a subset of UTF-8.  Therefore, from &h80 and above, the CodePage characters are just interpreted as UTF-8.

Because UTF-16 (nor UCS-2) can correctly handle the ASCII characters (as one-byte-each), there is no CodePage for those.  But that's basically what the StrConv() function is for (using vbFromUnicode or vbUnicode).

Noting that there's actually a UTF-8 LCID makes for some interesting thoughts.  For instance, we should be able to directly read a UTF-8 file if we specify the LCID = 65001.  Or, we could use StrConv() to convert any of UCS-2, UTF-8, and/or default CodePage.  However, I haven't tested to see if that actually works.

And lastly, I'm not sure that VB6 has any good way of dealing with the _full_ UTF-16 character set (including the 4-byte characters).

EDIT1: Correction, it's 65001 (not 65501).

EDIT2: Another correction.  Apparently there actually is some ability to treat UTF-16 as a CodePage.  I'm exploring these possibilities.  I'm not at all sure VB6 can exploit these abilities though.

----------


## LaVolpe

@Elroy, UTF isn't the answer here. Text converted as UTF8 (CP 65501) is not the same result as StrConv(text, ..., 2052)

@dreammanor. I'm still struggling to figure out why you are getting an error and I am not.
If I change only the following line in the existing project (not your modified version), I get Chinese characters and no errors at all.

In modMain, routine: CreateBuffer
from .Buffer = StrConv(bData(), vbUnicode) to .Buffer = StrConv(bData(), vbUnicode, 2052)

If you are getting an error due to a null line, then calling StrConv() on your system is not the same as me calling the same function, passing 2052 as the locale. Could you do me a favor and save the array to file and upload it for me to review?

1. For the vbp file only, after this line is called: .Buffer = StrConv(bData(), vbUnicode)
2. Execute this code, changing the file location as needed


```
bData() = .Buffer
Open "C:\blah blah\Lavolpe.dat" For Binary As #1
Put #1, 1, bData()
Close #1
```

3. Zip and upload the dat file and also the vbp file

Once I have those two files, I can compare the results to my system after calling .Buffer = StrConv(bData(), vbUnicode, 2052) on the same vbp file. In any case, I can review the array data and know why the error is occurring.

----------


## wqweto

> Reason for this is that VB specifically says that variables (really any vb code item) must begin with ANSI alphabet, i.e., A-Z, a-z.


Check out 3.3.5 Identifier Tokens for strict definition, particularly simplified-Chinese-identifier might be of interest.

Basicly codepage-identifier there means anything in &H80 to &HFF char range (so called extended ASCII) is treated for the purposes of identifiers as a letter too. E.g. my cyrillic letters are there too although I would have to be insane to use cyrillic var-names :-))

cheers,
</wqw>

----------


## LaVolpe

> Check out 3.3.5 Identifier Tokens for strict definition, particularly simplified-Chinese-identifier might be of interest.
> 
> Basicly codepage-identifier there means anything in &H80 to &HFF char range (so called extended ASCII) is treated for the purposes of identifiers as a letter too. E.g. my cyrillic letters are there too although I would have to be insane to use cyrillic var-names :-))
> 
> cheers,
> </wqw>


Per MSDN



> A variable name:
>     Must begin with a letter.
>     Can't contain an embedded period or embedded type-declaration character.
>     Must not exceed 255 characters.
>     Must be unique within the same scope, which is the range from which the variable can be referenced — a procedure, a form, and so on.


I guess the definition of a "letter" is unclear. Per your link, technically, it can be a codepage-identifier

Edited: Yepper - never would've guessed (latin codepage). Compiles and runs


```
Dim ™ As Long    ' Chr(&H99)
™ = 100
MsgBox ™

Dim ܰĿɨ As String
ܰĿɨ = "ܰĿɨ"
MsgBox ܰĿɨ
```

----------


## dreammanor

> Ahhh, I now understand the problem.  And it's still _NOT_ a Unicode problem.
> 
> Within the Windows-Code-Pages, there are Single-Byte-Character-Sets (SBCS) and Multi-Byte-Character-Sets (MBCS).  (Again, nothing to do with any flavor of Unicode.)  I think us Americans are most familiar with the SBCS-Code-Pages.  In fact, I wasn't even aware the VB6 IDE could deal with the MBCS-Code-Pages, but apparently it can.
> 
> In fact, here's an MSDN quote:
> 
> 
> 
> Way back, when I first read that, I probably thought they were referring to Unicode, but I now don't think that's the case.  I didn't test, but dreammanor seems to have shown this whereas *Asc("中") = -10544* and *Ascw("中") = 20013*.  And the MSDN is clear that Ascw returns a Unicode (i.e., UCS-2) interpretation.
> ...


Yes, what you said is very reasonable. In our VB6 software, all Chinese chars are *single-byte-characters*. In other words, *Len("中") = 1*, but *LenB("中") = 2*

Merry Christmas !

----------


## DrUnicode

> But, regarding VB6, I'd think we'd be fairly safe if we just read these ANSI-Code-Page source code files and just immediately converted them to UCS-2 Unicode before doing anything.


How do you know what LCID to use since the original creators Administrator Locale for non-Unicode apps is NOT stored in the Vbp file.
In dreammanor example, how would you know that you need to use LCID 2052 to convert VersionComments="ܰĿɨ" to VersionComments = "很棒的项目扫描器"?

And...if you do convert to Unicode then you would have to use Unicode controls for ProjectScanner.

----------


## dreammanor

Hi LaVolpe,

My projects is very large, so pScanner has encountered many new problems while scanning. Even if there is no Chinese chars in some bas files, pScanner still generate errors and exit VB6 IDE. 

I'm a little busy these days. When I have free time, I'll fix all the defects in the pScanner and add detailed comments, and then post the modified pScanner source code. But this may be a week later.

In addition, Chinese chars can be used to declare variables, classes, and controls in VB6, although I never do this.

*员工.cls*


```
Option Explicit

Public 编号 As String
Public 姓名  As String
Public 性别 As String
```

*Form1.frm*


```

Option Explicit

Private mEmployee As New 员工      'Class: 员工

Private Sub Form_Load()

    mEmployee.编号 = "0001"
    mEmployee.姓名 = "Tom.Zhang"
    
    MsgBox mEmployee.编号 & "， " & mEmployee.姓名
    
End Sub
```

----------


## dreammanor

Hi LaVolpe and DrUnicode, the attachment is the test file you want:



```
Public Function CreateBuffer(FileName As String, Buffer As CodePageStruct, _
                            Caller As ICustEvent, bNoArrayOverlay As Boolean, _
                            Optional ReleaseOnly As Boolean = False) As Boolean
'...
'...

 With Buffer
        '.Buffer = StrConv(bData(), vbUnicode)   ' get code data
        
        '--- DreamManor Added 2018-12-23 ------------------------------
        .Buffer = StrConv(bData(), vbUnicode, 2052)
        Dim bTestData() As Byte
        bTestData() = .Buffer
        Open "C:\1\Lavolpe.dat" For Binary As #1
        Put #1, 1, bTestData()
        Close #1
        Erase bTestData
        ' *** Note: ***
        ' Buffer.length -1 = 1731
        ' UBound(bTestData) = 3423     '???
        '----------------------------------------------------------------

        Erase bData()
        .tSA.cDims = 1                          ' nr of dimensions
        .tSA.cbElements = 2                     ' integer
        .tSA.rgSABlBound = 1                    ' one-bound same as string char indexing
        .tSA.pvData = StrPtr(.Buffer)           ' memory address
        .tSA.rgSABelements = .length + 1        ' number array items
        .cIndex = 1
        .iLTrim = 0: .iRTrim = 0
        .FileName = FileName
        ' overlay array onto the buffer string for faster parsing
        If bNoArrayOverlay = False Then _
            CopyMemory ByVal VarPtrArray(.Bytes), VarPtr(.tSA), 4&
    End With

'...
, ...
```

Also, hope the following information is useful to you:

Len("中") = 1
LenB("中") = 2
Len("abc中")= 4
LenB("abc中")= 8
LenB("abc") = 6
Asc("中") = -10544 
Ascw("中")= 20013 

The above information is output in the debug(immediate) window.

Merry Christmas and Happy New Year.

----------


## LaVolpe

Thank you for the file dreammanor. Will take a look at it tomorrow.

----------


## Elroy

> Len("中") = 1
> LenB("中") = 2
> Len("abc中")= 4
> LenB("abc中")= 8
> LenB("abc") = 6
> Asc("中") = -10544 
> Ascw("中")= 20013


As I understand it, all of the above is going to be assuming Unicode (UCS-2) except for the *Asc("中") = -10544* statement.  For that one, it's going to go to the MBCS CodePage and fetch the value from that CodePage, which clearly doesn't agree with the UCS-2 value (of 20013).

----------


## LaVolpe

@Dreammanor... Solution has nothing to do with testing for integer values < 0

The problem is that the length of the file is not the same as the length of StrConv() when DBCS is in play. I did not know about, nor account for, reduction of overall bytes after string conversion. This logic flaw is causing your errors.

In modMain.CreateBuffer, after this line: .Buffer = StrConv(bData(), vbUnicode)
add this line: .length = Len(.Buffer)
Without that line added, the .length variable is the file's length in bytes; now > string length in chars. The code assumed that LOF(x) = Len(StrConv(fileBytes)) for ASCII/ANSI files and DBCS breaks that.

That was easy to figure out. However, it also causes another problem in modMain.IsFileDirty(). You won't be able to enter the validation routines until this is also done:
change: If Not (lLen = lSize And lDateHigh = lDtHigh And lDateLow = lDtLow) Then IsFileDirty = True
to: If Not (lDateHigh = lDtHigh And lDateLow = lDtLow) Then IsFileDirty = True

I still need to add/tweak code to account for possible use of DBCS VB identifiers and VB identifiers that have characters in the ASCII range 128-255. But the above changes should allow you to at least play along for now, without any other code changes, as long as you are not using Chinese characters in actual code, including comments and string literals, for now.

P.S. Don't add Chinese characters to the vbp "Title" entry. It will likely cause an error trying to add unicode to a non-unicode recordset field. Lots of little things in the code needs to be changed to support DBCS

And to be completely honest. The reason I was not getting the same errors as you after using StrConv(..., ..., 2052), is that, for different reasons, I already modified my code with those changes. This was done a few days ago when I was playing with the idea of converting VB files to UTF8 which also experienced similar problems, in some cases. That didn't register with me, because I was thrown off when you said that testing for negative integer values solved your problem. What you did, solved a symptom but not the underlying problem.

----------


## LaVolpe

Updated posted today. Merry Christmas to all.

Brief summary of major changes. Version not compatible with previous versions
1. DBCS compatible I hope
- changed 2 recordset fields to accept unicode characters
- had to modify several routines to look for specific unicode characters: line-breaks and spacing
- internally, unicode line-breaks/spacing  are converted to ANSI for downstream parsing, as needed
- if an ANSI system processes a DBCS project file, characters will not be displayed as unicode; displayed as shown in project file
- source used to identify what special characters to look for: https://msdn.microsoft.com/en-us/library/dd361851.aspx
2. Replaced DoEvents calls with GetQueueStatus API calls to see if DoEvents is needed. This should speed up overall processing quite a bit as less DoEvents calls are actually made. Idea came from a posting on this site & credited. See modMain.FauxDoEvents

I'm not 100% sure this is completely compatible with DBCS (Chinese/Japanese/Korean) systems since I don't have a good project to test against. If someone is willing to post a sample project, that would be great. Here is what I'd like to see in the sample project...
- project files in paths that include non-US characters
- file names that include non-US characters
- file extensions that include non-US characters (this could be a bit of a pain: save vb file (cls,frm,etc). Rename the file, then import into project)
- Variables that use non-US characters, especially the 1st and last characters of the variable
- Comments that use non-US characters
- Anything else that you think could possibly break a parser that expects ANSI text

----------


## Thierry69

Thanks Lavolpe, using your FauxDoEvents, 2 sec less on the big calculation in one of my app.

----------


## ChenLin

Hi LaVolpe,
The new version has solved the previous errors and can be scanned normally.

I upload a project, you see the screenshot, can you meet the test requirements you want?




ss.zip

----------


## dreammanor

Hi LaVolpe, now your new ProjectScanner can open VBP files containing Chinese chars normally. You are an enthusiastic and rigorous expert. Much appreciate.

There is a small problem, that is, my project is relatively large and Project Scanner scans them for a very long time. For example, it takes 425 seconds to scan one of my projects. If ProjectScanner could be optimized, that would be great.

----------


## LaVolpe

> There is a small problem, that is, my project is relatively large and Project Scanner scans them for a very long time. For example, it takes 425 seconds to scan one of my projects. If ProjectScanner could be optimized, that would be great.


I'll take a look. Honestly, didn't expect to find projects with nearly 1/2 million executable statements. There are 2 speed bumps with this project

1) Writing to the recordset. After about 50K - 100K entries, I have noticed a steady increase in write times. I'm sure that large project of yours has lots of recordset entries

2) Parsing the project, character by character to find statements, join multi-line statements, ignore comments, ignore VB Attribute statements, identify conditional compiler directives, and identify events. Parsing the declarations sections of project files shouldn't be too bad, but parsing the procedures this way can be re-worked to speed up initial parsing, skipping most of the statements within the procedures. Procedures are actually parsed twice: once to locate the start/end position of each procedure during initial project load and again during validation. That first time may not always need a full parsing of every character and optimization can be applied there.

Out of curiosity, the 425 seconds is just for the initial loading of the project? I'd imagine that doing a full validation will take even longer. P.S. Maybe a slightly better performance can be seen when compiled?

----------


## LaVolpe

> I upload a project, you see the screenshot, can you meet the test requirements you want?


Thank you, I will download it later today to see if I can find any logic flaws with my code that is related to your sample project.

----------


## Elroy

> Honestly, didn't expect to find projects with nearly 1/2 million executable statements.


Personally, I think those are precisely the projects where your scanner would be most useful.  However, so long as I know it's not hung, I'm fine with it taking a bit of time, as this analysis is sort of a one-time (or infrequently performed) thing.

Again, this is truly fantastic work.

Happy Holidays,
Elroy

----------


## dreammanor

> Out of curiosity, the 425 seconds is just for the initial loading of the project? I'd imagine that doing a full validation will take even longer. P.S. Maybe a slightly better performance can be seen when compiled?


Yes, the 425 seconds is just for the initial loading of the project, and I have not done anything else, such as a full validation.

----------


## LaVolpe

> Yes, the 425 seconds is just for the initial loading of the project, and I have not done anything else, such as a full validation.


Just a follow-up so you don't think I'm ignoring you  :Wink: 

After trying many different methods, it comes down to the recordset object when sorting a field containing strings. For example, a recordset with 500K records, following applies and on a relatively fast PC (different PCs will have different results):
Sorting on numeric field: 0.140625 seconds
Sorting on string field: 11.796875 seconds
That is a difference of 84 times slower
The project uses sorting often. So, I'll need to look at it again and find places where I can let the treeview do the sorting (will be displaying far less items than the recordset contains) and also where sorting strings in the recordset is used for grouping rather than sorting. In that case, I can use a CRC value of the string for grouping purposes.

The bottom line: I need to prevent the recordset from being sorted on string fields while still able to group/sort on those same fields in another way.

Just a side note: recordset.Find on a string in same recordset is up to 7x slower than finding a numeric value. If I'm using a numeric value for grouping strings, i.e., crc32, then I can use Find on the crc32 value vs the string value. The cost of creating a crc32 search value should be minimal.

----------


## wqweto

@LaVolpe: Does building in-memory index with rs.Fields(sSortField).Properties("Optimize").Value = True has any effect on rs.Sort = sSortField?

cheers,
</wqw>

----------


## LaVolpe

> @LaVolpe: Does building in-memory index with rs.Fields(sSortField).Properties("Optimize").Value = True has any effect on rs.Sort = sSortField?
> 
> cheers,
> </wqw>


It does, but just creating the index is a large speed hit on large recordsets. Additionally, if optimized, then the index seems to slow down new inserts; probably because the index needs to be modified/rebuilt. And here is the kicker. In a disconnected recordset, the optimized setting does not transfer to a clone, i.e., rs.Clone. 

Edited: FYI. Setting Optimize on my tests recordset (500K records). It took nearly 12 seconds. However, searching afterwards was significantly improved using Find/Filter methods.  I tried this method first, but was disappointed that the property didn't carry over to clones.

P.S. Don't want to set that before populating a large recordset
Without Optimize, inserting 500K records: 6 seconds
With Optimize set before inserting 500K records: 127 seconds
Update: And now I'll have to re-look at the Optimize property again. Creating a  clone after the rs was optimized did not carry over the property. But using the clone for searching/filtering must use the optimized indexes too since there was significant improvement there too -- that's good news. Unfortunately, the bad news is that ADO is kicking out "Unspecified Error" when optimizing multiple fields and/or mixing in clones. That error breaks navigation (EOF & BOF become True while RecordCount=500K). The size of the recordset might be an issue also? Too many unknowns to confidently use the Optimize property.

----------


## LaVolpe

@Elroy and Dreammanor
When you guys find the time, can you run this against your very large project(s) and tell me the speed difference?

Test Steps
1. Create new folder and backup your current scanner code into that folder
2. Then download this zip and overwrite the files in that new folder
3. Open both projects
4. In your original project, add these lines. The replacement files in zip below already have this code. But don't overwrite your original scanner project files.

in frmMain.pvLoadProject just before the line: If m_Project.ParseFile(sFileName, Me) = False Then


```
Dim t As Double: t = Timer
```

in the same procedure just after the "End If" block, add this 


```
Debug.Print "time: "; Timer - t; " seconds"
```

5. Now scan your large project using both versions. The immediate result will indicate the time taken

DO NOT run validations with the patched files below -- I need to make changes to them also. However, I want to know what kind of improvement we are looking at before I continue. In my tests, nearly 50% faster, but on much smaller projects. I don't own a 500+ code file project

Thanks to both of you in advance...

----------


## Elroy

Hi LaVolpe,

Okay, I just re-downloaded the ZIP from post #1 as my starting place.  And then, I followed all your directions in post #122.  Here are my results from the two runs:



```
time:  100.393343750002  seconds (old)  VBP

Time 81.6086250000008 seconds (new)  VBP
```


So, you've made some improvement, but I'm not sure it's enough to get terribly excited about.  Personally, either way, with the benefits your project provides, I don't see this as a huge hindrance.  But maybe it's become a challenge for you.   :Smilie: 

Also, this was run on my main VBP file (which has _many_ modules and is quite large).  I've also got a VBG file which loads all the ActiveX DLLs as well.  But I almost _never_ load the project that way.  But I suppose I could run your scanner against that VBG file if you'd like.  Just let me know and I'll do it.

Happy New Year,
Elroy

EDIT:  Hmmm, I'm not sure I can explain this.  I went ahead and ran both of your scanner versions on my VBG file.  This time, I ran the new version first (just because my Explorer was sitting in that folder).  Each time, I just opened your VBP and then ran the scan (from the IDE).  This time, here are the results:



```
Time 84.7072187500016 seconds (new)  VBG

time:  86.6345937500009  seconds (old)  VBG
```


I've got no idea why the  old way on VBG was faster than old way on VBP.  That makes no sense to me.  I copied the form from one of the scans just to show you that it actually loaded all the projects:



Also, the difference between the two scanner versions was much less this time.  I've got no explanation for that either.

----------


## LaVolpe

Elroy. Thank your for your time.

Question. Is your original scanner project the most recent one I updated in post #1 last week? Reason why I ask is that I had to make many modifications for DBCS and that version may be slower than the older version you have if you haven't donwloaded it? Your timings are curious if you are using the the recent version, I did expect a significant improvement between that version & the modified routines I gave you in my previous post.

----------


## Elroy

Hi LaVolpe,

I've got two versions of your scanner stored away (as ZIP files) in my library somewhere.  However, I didn't use either of those for my tests.

To start (at, let's say 9am Central time, a few minutes ago [actually, a couple of hours now]), I just re-downloaded the project in post #1.  And, from there, I performed the tests you outlined in post #122.

Would you like me to do something different?  You've given me _AMPLE_ help through the years (including this project), that I'd be delighted to do more tests for you.  Please just outline what you'd like.

Take Care,
Elroy

----------


## LaVolpe

Yes, can you use the most recent version as the base-line test and then use it again (a copy) with the replacement files posted above as the potential improvement test. Thank you.

----------


## Elroy

> most recent version as the base-line



From post #1:



> Last edited by LaVolpe; Dec 25th, 2018 at 11:55 AM.



I feel like that's what I did.  By "most recent version", you mean the version in post #1, correct?

I'll do it again though.  I'll make a subsequent post with the repeat results.  Sorry if I'm mis-understanding something.

----------


## Elroy

Ok, maybe all of the scans in post #123 were against the VBG file, as it does appear before the VBP file.  

However, this time, I was VERY careful.  (I thought I was before, but it is New Years Day.)

This time, ALL of my scans were on my primary VBP file, with the following showing after the scan:



Now, this time, here's the order I did things:

1) Open the scanner VBP file from post #1 (with timing code inserted).
2) Run, and then open/scan my primary VBP file, and let it report timings.
3) Immediately re-scan this file, again reporting timings.
4) Close down that scanner project.
5) Open the scanner VBP file from post #1 with the files from post #122 unzipped and used as replacements.
6) Run, and then open/scan my primary VBP file, and let it report timings.
7) Immediately re-scan this file, again reporting timings.

Here are all the timings with my annotations:



```
time:  38.7498124999984  seconds  (original, first scan)
time:  39.5207500000033  seconds  (original, re-scan)

Time 38.5684374999983 seconds  (modified, first scan)
Time 40.4113437499982 seconds  (modified, re-scan)
```


I'm also going to try it with your originally posted version of the scanner.  I'm hoping those timing lines will go in at the same spot.

----------


## Elroy

Ok, going back to the first version you posted (the one that shows the modules in the treeview as it's scanning), there's a definite improvement (and this time, I'm certain I scanned my VBP file).



```
time:  53.474374999998  seconds
```


And, even after it reported that, there was a good 30 second delay while it refreshed the treeview before it released the thread back to me (err, Windows).

If you want me to test the timing of some interim version (between your first version and the one found in post #1), you'd better attach it to a post, just so we know we're on the same page.

Take Care,
Elroy

----------


## LaVolpe

Hmm, confusing for me. You should have seen some improvement nonetheless. When I ran it on a relatively small group file (6 projects), it was nearly a 50% improvement. Simply removing the recordset sorting on string/text fields should be a no-brainer speed increase. Confused.

Also, you have 507 code files, dreammanor has 537 (I think) but about 3x the executable lines. Your project takes ~40 seconds, his takes 425 seconds. If we simply extrapolate 3x your 40 seconds, that's 120 for his project not 425. Even that cannot be a fair comparison, because extra procedure code doesn't necessarily slow down the process a lot in the pre-validation phase of the scan. Something else is in play here.

And yes, 40 seconds for 500+ files doesn't seem too unreasonable. Don't know how many total code files are in your vbg project, but having 7 projects, 80 seconds doesn't seem too bad either. Still looking at speeding up the validation process though.

Edited: think we were posting against each other. 53 seconds reduced to 39/40 seconds is a fair improvement. Thanks for the clarification. Regarding the 425 seconds for dreammanor's project, that still has me confused.

I do have another interim version, but it's significantly different code than any previous version. I'm still playing with it as a replacement that should really improve validation speed. That version wasn't in any of our conversation to this point

----------


## LaVolpe

> And, even after it reported that, there was a good 30 second delay while it refreshed the treeview before it released the thread back to me (err, Windows).


Still? I thought I resolved that. If you run it again, don't expand any nodes and hit the IDE pause button, can you report back the following?

In the immediate window, do this: ? frmMain.tvItems(0).Nodes.Count

I'm expecting that to be a small value of 14-ish. And if so, why would Windows add a 30-second delay for 14 nodes? Before, when the entire tree was populated, you'd have potentially 1000's of nodes and then I can understand it, but not with a dozen or so.

----------


## Elroy

> Still? I thought I resolved that. If you run it again, don't expand any nodes and hit the IDE pause button, can you report back the following?
> 
> In the immediate window, do this: ? frmMain.tvItems(0).Nodes.Count
> 
> I'm expecting that to be a small value of 14-ish. And if so, why would Windows add a 30-second delay for 14 nodes? Before, when the entire tree was populated, you'd have potentially 1000's of nodes and then I can understand it, but not with a dozen or so.


No no, in post #129, I reached back and grabbed the original-first-version of your project.  You have resolved that in subsequent versions.

But, to be clear, everything in post #128 was done with your latest.


Also, regarding differences between dreammanor and me, I'm running on a pretty good machine:

CPU: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz.
GPU (if it matters for this): NVIDIA GeForce GTX 1070.
C drive: HGST WD Travelstar 1TB, 7200rpm, 12ms seek.
RAM: 16GB

That might explain at least some of the difference.

EDIT1:  Also, I'll bundle up the main VBP and send it to you, if you like.  It'd take a bit to get it to where you could load and execute it, but I don't think you'd need that to run your scanner.  Just let me know.

----------


## Tech99

Certain Windows API's return short type (vb integer), like GetKeyState instead of int (vb long). 

I presume that project scanner currently has no actual checking against api declarations?

----------


## LaVolpe

> Certain Windows API's return short type (vb integer), like GetKeyState instead of int (vb long). 
> 
> I presume that project scanner currently has no actual checking against api declarations?


Correct and that False positive is explained in the description (help menu). It is more or less a warning/reminder to double check. There is no intent to identify all APIs, across all libraries, that legitimately use those in their signature. Problem with some code is that stuff that is copied from elsewhere is hardly ever checked by users for proper API parameter/return vartypes.  Again, meant just as a reminder.

----------


## dreammanor

> @Elroy and Dreammanor
> When you guys find the time, can you run this against your very large project(s) and tell me the speed difference?
> 
> Test Steps
> 1. Create new folder and backup your current scanner code into that folder
> 2. Then download this zip and overwrite the files in that new folder
> 3. Open both projects
> 4. In your original project, add these lines. The replacement files in zip below already have this code. But don't overwrite your original scanner project files.
> 
> ...


I'm back. Sorry for the late reply.

I added test time *t2*:


```
Private Sub pvLoadProject(Source As Variant)
    
    ' called from menu Load/Reload
    Dim sFileName As String
    
    If VarType(Source) = vbString Then      ' else existing database imported, but not yet displayed
        sFileName = Source
        If LenB(sFileName) = 0 Then
            Dim cBrowser As New CmnDialogEx
            With cBrowser
                .Filter = "Projects|*.vbp;*.vbg|VB Files|*.cls;*.ctl;*.dsr;*.bas;*.frm;*.pag;*.dob;*.vbp;*.vbg|" & _
                    "Classes|*.cls|Designers|*.dsr|Forms|*.frm|Modules|*.bas|Property Pages|*.pag|" & _
                    "User Controls|*.ctl|User Documents|*.dob|All Files|*.*"
                .FlagsDialog = DLG__BaseOpenDialogFlags
            End With
            If cBrowser.ShowOpen(Me.hWnd, , , m_OpenGUID) = False Then Exit Sub
            sFileName = cBrowser.FileName
            Set cBrowser = Nothing
            mnuFile(miRescan).Tag = sFileName
        End If
    End If
    
    tvItems(0).Nodes.Clear                  ' clear
    pvSetContextMenuItems False             ' disable some context menu items
    If mnuValidate(miToggleView).Checked = True Then _
        Call mnuValidate_Click(miToggleView) ' show primary treeview
    
    tvItems(0).Nodes.Clear: tvItems(0).Enabled = False ' make ready to populate
    tvItems(1).Nodes.Clear: tvItems(1).Enabled = False
    m_State = 0                             ' reset
    
    pvUpdateStatus chrsLoad, "Scanning Project"
    If LenB(sFileName) <> 0 Then            ' else recordset to be processed
        m_State = 2
        Set m_Project = New clsPrjFile
        m_Project.ValidateEvents = mnuOpt(0).Checked
        
Dim t As Double
t = Timer
        If m_Project.ParseFile(sFileName, Me) = False Then
            Set m_Project = Nothing         ' prevent options like saving/exporting
            If sFileName = mnuFile(miRescan).Tag Then mnuFile(miRescan).Tag = vbNullString
            pvUpdateStatus vbNullString, vbNullString
            If (m_State And 4) = 0 Then
                m_State = 0
            Else
                m_State = 0
                Unload Me
            End If
            Exit Sub
        End If
Debug.Print "Time " & Timer - t; " seconds"
        m_State = m_State Xor 2
    End If
    tvItems(0).Enabled = True
    tvItems(1).Enabled = True
    
Dim t2 As Double
t2 = Timer

    Set m_Records = m_Project.Records
    If Not m_Data Is Nothing Then m_Data.Close
    Set m_Data = m_Records.Data.Clone
    pvUpdateStatus "Project scanned...", "Preparing tree view"
    pvItemsAdd_RootNodes (LenB(sFileName) = 0)
    If tvItems(0).Nodes.Count <> 0 Then
        Set tvItems(0).SelectedItem = tvItems(0).Nodes(1)
        tvItems(0).SelectedItem.EnsureVisible
    End If
    pvUpdateStatus vbNullString, vbNullString
    
    With m_Project.Records.Data
        .Filter = SetQuery(constFldType, qryIs, ptSourceFile)
        sFileName = UCase$(Right$(.Fields(constFldAttr2).Value, 3))
        If sFileName = "VBG" Then
            mnuCtx(miVbg).Visible = True
            mnuCtx(miVbp).Caption = "Show VBP File"
        Else
            mnuCtx(miVbg).Visible = False
            mnuCtx(miVbp).Caption = "Show " & sFileName & " File"
        End If
        mnuValidate(miVbgUnhide).Visible = mnuCtx(miVbg).Visible
        .Filter = adFilterNone
    End With
    
    Debug.Print "Time2 " & Timer - t2; " seconds"
    
End Sub
```

The test results are as follows:

*Old PrjScan*
(1) Scan Krool's ComCtlsDemo.vbp(2018-09-07): Time1: 22.95 seconds;   Time2(DataClone): 1.62 seconds
(2) Scan DreamManor's Large.vbp: Time1: 429.19 seconds;   Time2(DataClone): 5.67 seconds

*New PrjScan*
(1) Scan Krool's ComCtlsDemo.vbp: Time1: 21.20 seconds;   Time2(DataClone): 0.19 seconds
(2) Scan DreamManor's Large.vbp: Time1: 422.05 seconds;   Time2(DataClone): 0.61 seconds

----------


## dreammanor

*Edit:*

My VBP is stored in the U-Disk (USB flash disk). I copied the VBP from the U-Disk to the hard disk and re-tested it. The test results did not change much:

*Old PrjScan*: 
Time1: 423.61 seconds;   Time2(DataClone): 5.98 seconds   (frmMain.tvItems(0).Nodes.Count = 16)

*New PrjScan*: 
Time1: 415.86 seconds;   Time2(DataClone): 0.59 seconds      (frmMain.tvItems(0).Nodes.Count = 14)

In addition, there are 507 files in Elroy's project and 567 in my project. IMO, the time of scanning depends not only on the speed of the computer and the number of project files, but also on the number of lines of source code. Krool's ComCtlsDemo.vbp has 107 files，which is one-fifth of my project, but it takes only one-twentieth of the time of my project.

----------


## Eduardo-

> ...was significantly improved using Find/Filter methods.


Hi LaVolpe, use Seek method instead, it is much faster (100 times faster or more).
If it is still not fast enough, then consider to switch to DAO.

----------


## LaVolpe

> Hi LaVolpe, use Seek method instead, it is much faster (100 times faster or more).
> If it is still not fast enough, then consider to switch to DAO.


Thanx for the thought. But if MSDN documentation is correct, this will not be an option as it requires server-side cursor where a disconnected recordset (no DB) is used in this project. A client-side cursor is required  for disconnected recordsets.




> Seek: This method is supported only with server-side cursors. Seek is not supported when the Recordset object's CursorLocation property value is adUseClient.

----------


## Eduardo-

> Thanx for the thought. But if MSDN documentation is correct, this will not be an option as it requires server-side cursor where a disconnected recordset (no DB) is used in this project. A client-side cursor is required  for disconnected recordsets.


Ah. Then I would consider creating a temporary database file. It is not a lie that Seek is extremely faster.

----------


## LaVolpe

> Ah. Then I would consider creating a temporary database file. It is not a lie that Seek is extremely faster.


Already tried that. The overhead of inserting records into the DB was significantly slower than using the disconnected RS. A sample project took over 2 seconds vs just 1/2 second for a RS. And yes, I used SQL Insert to add records, not the recordset.AddNew method.

P.S. I did get a PM from dreamManor. The main speed issue is parsing the procedures in his case. When he skipped that part of the process, the time needed was about 1/2. However, that was just to find where the main speed hit was occurring. I do have a faster routine to get through the procedures for the initial scan, but haven't offered that up yet. I will shortly and ask dreamManor to be kind enough to run that one for me. It's not ready yet though.

----------


## Eduardo-

My experience is that adding an index does not cause much overhead, and if the index is the primary key then the index is already there. 
But I use DAO, I don't know with ADO.

----------


## LaVolpe

DreamManor, I will continue to finish my latest version of this project.

But I don't think I'll be able to significantly improve the speed for you. In one of your recent posts, you mentioned that running Krool's 107 file ComCtlsDemo project, it took *21.20* seconds on your system. On my system, which is 10 years old, it took *8.2* seconds. I don't know how to resolve that large difference and do not think I can do it with code.

The improvements I made to prevent filtering on string/text recordset fields appears to work really well. In that same post, you mentioned that, with the previous version, it took 1.62 seconds after the project was scanned to the point of displaying the nodes. After you used the updated files, that time dropped to 0.19 seconds (8.5x faster). That part of the code is where most of the filtering is occurring.

----------


## Elroy

For grins, I took Krool's latest (here) and scanned them with the lastest scanner (in OP #1 of this thread, and with the timer lines from post #122 above), and got these results:



```
time:  6.03321875000256  seconds
```

 
EDIT:  I re-scanned a few times and got the following:



```
time:  5.46303125000122  seconds
time:  5.60793749999721  seconds
time:  5.42271875000006  seconds
time:  5.50193749999744  seconds
```

----------


## LaVolpe

Elroy, obviously your PC is much faster than mine. Regarding faster times when re-running that parsing... Think that has to do with disc read/write smart caching. I see that all the time too.

I have identified the bottleneck in the code. Time needed is directly related to how many "event"-related items and procedures exist in the project. In Krool's project, there are 950 items tested for events: 730 controls, 98 classes, 90 Implementations, and 32 variables declared WithEvents. Among those 950 items there were 1,435 total events found.

This means 950 queries were executed using a "Like [itemName]_%" clause to find those 1,435 events, i.e., Form1_Load. Designing a different/faster way of looking for events, reducing number of queries executed, would significantly increase performance. Have to think about this because there is no direct/easy way to look at a procedure name and say "Hey, that's an event for some object/item", other than positively excluding a procedure, as an event, if it doesn't have an underscore in its name. The remaining procedures, those with an underscore, still need to be verified as an event to prevent stuff like Sub MyCustomProc_VersionA and Sub MyCustomProc_VersionZ being interpreted as events.

Edited: Of those 950 items, there are only 85 unique "items". For example, there could be 25 different textbox items in the project, but each are of the same class: VB.TextBox. Looking at designing a faster method that requires 85 queries vs 950 queries. Doable, but gonna need to track some additional stuff to get there and compare overall speed when the new overhead is introduced.

----------


## dreammanor

> DreamManor, I will continue to finish my latest version of this project.
> 
> But I don't think I'll be able to significantly improve the speed for you. In one of your recent posts, you mentioned that running Krool's 107 file ComCtlsDemo project, it took *21.20* seconds on your system. On my system, which is 10 years old, it took *8.2* seconds. I don't know how to resolve that large difference and do not think I can do it with code.
> 
> The improvements I made to prevent filtering on string/text recordset fields appears to work really well. In that same post, you mentioned that, with the previous version, it took 1.62 seconds after the project was scanned to the point of displaying the nodes. After you used the updated files, that time dropped to 0.19 seconds (8.5x faster). That part of the code is where most of the filtering is occurring.



LaVolpe, You have done a lot for us, much appreciate. 

Your project has been great, and for the vast majority of people, its performance is good enough.

I got a lot from this forum and the only thing I can do for you is to do some testing for you. I'm very happy that my big projects can be good test samples. I like to do some extreme tests, for example: load a file containing 1 million lines of code into my golang code editor, although this won't happen in reality.

I always have a lot of work, I hope that one day I can make my own contribution to this forum just like you.

In addition, we have an internal development rule that the performance of the computer used by the developer must be lower than that of the users, so that the software we develop can run smoothly on the user's computer. This is why prjScan scans for a long time on the computer I used for development.

On my test computer (win10), prjScan scans Krool's latest ComCtlsDemo.vbp very fast: (re-scanned a few times)



```
Time 1.53790624999965 seconds
Time 1.34859375000087 seconds
Time 1.42934375000186 seconds
Time 1.40837499999907 seconds
Time 1.37512499999866 seconds
```


*Edit*: 
jpbro's VB6SourceProcessor scans very fast on the computer I used for development, but I haven't had time to carefully research and compare the source code of VB6SourceProcessor and prjScan.

----------


## dreammanor

Deleted duplicate post

----------


## dreammanor

Re-tested on my test computer (win10):

Krool's ComCtlsDemo.vbp(2018-09-07)


```
Time 1.12300000000105 seconds
Time2(Data Clone) .024000000001049 seconds

Time .936000000001922 seconds
Time2(Data Clone) 2.40000000000009E-02 seconds

Time .942000000000348 seconds
Time2(Data Clone) 2.50000000010466E-02 seconds

Time .941000000000464 seconds
Time2(Data Clone) 2.49999999977284E-02 seconds

Time .956999999997439 seconds
Time2(Data Clone) 2.49999999987196E-02 seconds
```

Krool's ComCtlsDemo.vbp(2019-09-04)


```
Time 1.03199999999715 seconds
Time2(Data Clone) 3.09999999974977E-02 seconds
Time .969000000000058 seconds
Time2(Data Clone) 2.79999999988938E-02 seconds
Time .965999999997088 seconds
Time2(Data Clone) 2.80000000001763E-02 seconds
Time .971999999999184 seconds
Time2(Data Clone) 2.80000000010467E-02 seconds
Time .957999999998663 seconds
Time2(Data Clone) 2.80000000033773E-02 seconds
```

----------


## Elroy

Hmmm, I just scanned Krool's latest ComCtlsDemo.vbp yet again, with the latest LaVolpe scanned (but before the replacement files in post #122), and got the following:



Everything looks fine on my end.  It was my understanding that, when the replacement files were inserted, this was just to be used for a speed-test.

----------


## LaVolpe

@dreamManor. If you still have that one line commented out to prevent processing procedures (previous PM), that would explain your speed increase from 24 seconds to 1 second. Commenting that line was only to help locate speed bumps in the code; not processing procedures is not really an option.

Regarding no statistics, there is a small bug which would prevent them from being added to the tree in some cases. I found that bug last week. However, in your case, I think the code version you are running has been modified (due to testing) too much. Not only do you not have statistics shown, you don't have the base path shown, nor any imported DLLs shown.




> jpbro's VB6SourceProcessor scans very fast on the computer I used for development, but I haven't had time to carefully research and compare the source code of VB6SourceProcessor and prjScan.


Can't really compare jpbro's project with this one. I haven't tried his project because it uses references I don't want to install and requires backing up files to prevent corruption. However, from reading his description of the project, it basically "parses" lines of code. That is about the only thing in common  our two projects have together.  Again, it is the process of identifying events that is the bottleneck. If we don't try to identify events, we would have amazing parsing times. But events are handled differently from other procedures during validation and _not_ identifying them is not an option. Without identifying them, you would get so many false positive validation warnings, especially during zombie checks, but with other checks also. To see what I mean, jump to the bottom of clsPrjFile.pvProcessProject and comment out the 4 lines of code that call the 4 pvValidateEvents_[xxx] routines.

@All. I'm still going to post one newer version to this and pretty much call it done unless bugs are found down the road. I probably could get better speed for very large projects, but would likely require starting over on much of the core logic -- don't want to invest that much time. Same reason I don't think I'll move this into a VB add-in. If an add-in, I would be able to use VB IDE to locate information for me instead of parsing it out manually.

Do note that all this talk about speed issues are particular to very large projects, those with hundreds of source code files. For your average project, say less than 30 code files, I feel this project's speed is sufficient.

----------


## dreammanor

Yes, the discussion about performance should stop, the performance of prjScan is good enough now. 

If I have time in the future, I'll try to improve the scanning speed of prjScan for large projects. I'll optimize it from two aspects:
(1) Try to use Sqlite-MemDB
(2) Use a faster array or collection instead of VB.Collection

Looking forward to the new version of prjScan, thank you LaVolpe.

----------


## LaVolpe

> If I have time in the future, I'll try to improve the scanning speed of prjScan for large projects. I'll optimize it from two aspects:
> (1) Try to use Sqlite-MemDB
> (2) Use a faster array or collection instead of VB.Collection
> 
> Looking forward to the new version of prjScan, thank you LaVolpe.


I'll post some test code files in a day or two. Already fixed the bottleneck with identifying control events. I can now scan Krool's project in less than 4 seconds; that's 1/2 the time it took me before.  Will get that a bit faster now that I know the new logic works well by applying that logic to the other 3 types of events: WithEvents, Implementation Events, and Class Events

P.S. VB.Collection isn't used often in that project. My updated pvValidateEvents_[xxxx] routines no longer use them. And elsewhere in the project where they are used, they are not that prominent, do not have many entries. I use them more for convenience for little stuff. When possible I prefer a sorted array for binary searching.

----------


## wqweto

@LaVolpe: Seeking in client-side recordsets w/ Find is dog slow compared to searching by key in a collection + setting recordset bookmark.

Here is a couple of (simplified) helper methods I'm using to speed up recordset seek w/ poor-man's "indexing collections" to explain the idea


thinBasic Code:
Option Explicit Public Function InitIndexCollection( _            rs As Recordset, _            sFld As String, _            Optional Fld2 As String, _            Optional RetVal As Collection) As Collection    Dim oFld            As ADODB.Field    Dim oFld2           As ADODB.Field    Dim vBmk            As Variant    Dim pCol            As Collection        On Error GoTo QH    Set RetVal = New Collection    With rs        vBmk = .Bookmark        If Not .EOF Or Not .BOF Then            .MoveFirst        End If        If LenB(sFld) <> 0 Then            Set oFld = .Fields(sFld)        End If        If LenB(Fld2) <> 0 Then            Set oFld2 = .Fields(Fld2)        End If        Set pCol = RetVal        If oFld Is Nothing Then            Do                pCol.Add .Bookmark, .Bookmark & vbNullString                .MoveNext            Loop While Not .EOF        ElseIf oFld2 Is Nothing Then            Do                pCol.Add .Bookmark, oFld.Value & vbNullString                .MoveNext            Loop While Not .EOF        Else            Do                pCol.Add .Bookmark, oFld.Value & "#" & oFld2.Value                .MoveNext            Loop While Not .EOF        End If        .Bookmark = vBmk    End With    Set InitIndexCollection = RetVal    Exit FunctionQH:    Resume NextEnd Function Public Function SetBookmark( _            rs As Recordset, _            vBmk As Variant, _            Optional Key As Variant) As Boolean    Dim cIndex          As Collection    Dim vTemp           As Variant        On Error GoTo QH    If IsObject(vBmk) Then        Set cIndex = vBmk        vTemp = cIndex.Item(Key)        If Not IsEmpty(vTemp) Then            rs.Bookmark = vTemp            rs.Bookmark = vTemp            SetBookmark = (rs.Bookmark = vTemp)        End If    ElseIf Not IsEmpty(vBmk) Then        rs.Bookmark = vBmk        rs.Bookmark = vBmk        SetBookmark = (rs.Bookmark = vBmk)    End If    Exit FunctionQH:    Resume NextEnd Function Private Sub Form_Load()    Dim rs          As Recordset    Dim cIndexID    As Collection    Dim cIndexCU    As Collection        Set rs = ReadFromExcel("d:\temp\aaa.csv", CsvHeader:=True)    Set cIndexID = InitIndexCollection(rs, "ID")    Set cIndexCU = InitIndexCollection(rs, "CU")    Debug.Print cIndexID.Count, cIndexCU.Count    If SetBookmark(rs, cIndexID, "'{8C0B9B7D-67B4-45F6-B840-E7993D614148}'") Then        Debug.Print rs!ID    End If    If SetBookmark(rs, cIndexCU, "'Dreem'") Then        Debug.Print rs!CU    End IfEnd Sub
This supports single-column and "composite" indexes btw -- just use "#" to concat values to form seeking key.

cheers,
</wqw>

----------


## Thierry69

NB LaVolpe, have you recieved my MP?

----------


## ahenry

Thanks for putting this out LaVolpe; it caught a bunch of Variant declarations that I thought I had fixed years ago!

I'm trying to think of suggestions for further validations to do, but most things just fall under "style guide" type items.  I suppose there are some internationalization / localization checks that might be possible (checking for string date literals), but none of the automated checks I can come up with would be particularly useful.  Can you think of a way to scan for missing On Error handling in event handler routines?

Edit: It would be nice to have some total statistics for a project group.

----------


## ahenry

Another thing; I don't remember this getting reported before:

I validated a project group with the "Variant vs. String Functions" check and without the Duplicate String Literals check.  The Variant vs String results kept getting copied from one project to the next; the last project in the group showed the Variant vs String results of all of the previous projects.  This didn't happen with any other validations.

----------


## dreammanor

> Thanks for putting this out LaVolpe; it caught a bunch of Variant declarations that I thought I had fixed years ago!
> 
> I'm trying to think of suggestions for further validations to do, but most things just fall under "style guide" type items.  I suppose there are some internationalization / localization checks that might be possible (checking for string date literals), but none of the automated checks I can come up with would be particularly useful.  Can you think of a way to scan for missing On Error handling in event handler routines?
> 
> Edit: It would be nice to have some total statistics for a project group.


jpbro's *VB6SourceProcessor* should be able to fully meet your requirements.
VB6 Automated Source Code Processing Helper

----------


## LaVolpe

@aHenry. I'll take a look at the Variant vs String oddity you mentioned. See if I can find out why you experienced that. Each project, in a group, creates its own temporary recordset for those types of discrepancies. So, I think it must be a filter I'm using to display the discrepancies and maybe I forgot to ensure the filter included the specific project. That could explain it. Instead of showing them by project, it showed all of them one project to the next.  Funny, but not really.

I did consider "On Error" handling reporting; however, I believe that's more of a personal style. Obviously not all procedures need error handling and I think such a check is truly only valuable to those that are in the habit of adding error handling to all routines, something like what MZTools did. The idea of group project statistics is a good idea -- this way we don't have to do mental math  :Wink: .  

@All. Think I got that speed bump resolved. This is the issue we've been discussing for past several days. Scanning Krool's project (104 files now vs. 107), before I made changes, took 8.2 seconds on my PC. Now it takes 2.82 seconds, nearly 3x faster. On much smaller projects that took 1-2 seconds are done in under 1 second -- much improved with a different way of identifying event procedures. I'll post the new version before end of the month. Still gotta revamp some of the validation portion now that I've gone and rebuilt my core statement & word parser.

@dreamManor and Elroy. Tomorrow, I'll post another zip of replacement files for you guys to play with. When you find the time, please report back on your impression of the speed gain or no gain? Thank you in advance.

----------


## jpbro

> Can't really compare jpbro's project with this one. I haven't tried his project because it uses references I don't want to install and requires backing up files to prevent corruption.


FYI - I know you're firmly in the anti-closed source third party library camp, so I don't expect you to register anything, but I just do want to clarify the "requires backing up files to prevent corruption" comment. As my project currently stands, backups are *not* a requirement. The project doesn't do any writing back to disk. It does however allow users to modify their source code and subsequently write it back to disk, which is why I provided the warning as a precaution/reminder that you _can_ muck things up if you aren't careful. In it's current form though, there is as close to 0 risk as possible with anything that touches your source files at all. 

That said, I always recommend backing up your source files regularly, but especially before touching them with third party software.




> However, from reading his description of the project, it basically "parses" lines of code. That is about the only thing in common  our two projects have together.


That's correct - right now my project simply splits code files into single physical lines (although logical line support is 90% complete and coming soon). You can then perform regex (or regular InStr) matches against lines of source and make substitutions on those matches as required. The main goal is to create a "Source Object Model" for VB6 code I guess. I personally use it for doing things similar to your project (finding stylistic issues, gotchas, common mistakes, etc...) but the difference is with my project is that devs have to write their own rules/detection routines, and they can automate changes and write them back to disk.




> Do note that all this talk about speed issues are particular to very large projects, those with hundreds of source code files. For your average project, say less than 30 code files, I feel this project's speed is sufficient.


I'd like to add that I don't think raw speed is really all that important with projects like this - IMO the idea is that you run them before doing final builds before public distribution to catch potential problems and fix them. Even if it takes 30 minutes, that's not really a big deal as detected problems will save you a tonne of time down the road.

----------


## LaVolpe

@jpbro. Regarding logical lines, if you want any "lessons learned" from me, I can certainly offer a few. Looking over your source code I did see some logic that could fail with legitimate logical lines; although it would take someone with a pretty unusual coding style to break a better-than-basic parser. 

Logical lines is the reason why I chose not to use InStr, RegEx and others forms of parsing. Not that those can't be used, but in some cases would require going back over the code time and again to catch all the legitimate variations that a logical line can written in, i.e., continuations, etc. I wanted a one-pass solution which is reason this project is parsed the way it is.

----------


## dreammanor

> @dreamManor and Elroy. Tomorrow, I'll post another zip of replacement files for you guys to play with. When you find the time, please report back on your impression of the speed gain or no gain? Thank you in advance.


Ok, look forward to your new files. Thank you very much, LaVolpe.




> That's correct - right now my project simply splits code files into single physical lines (although logical line support is 90% complete and coming soon). You can then perform *regex (or regular InStr)* matches against lines of source and make substitutions on those matches as required. The main goal is to create a "Source Object Model" for VB6 code I guess. I personally use it for doing things similar to your project (finding stylistic issues, gotchas, common mistakes, etc...) but the difference is with my project is that devs have to write their own rules/detection routines, and they can automate changes and write them back to disk.


Hi jpbro, for some time to come, I need to do some parsing of text files (such as HTML and JavaScript). I'm thinking about a question: Is the efficiency of *regex* higher than VB string functions (Instr, Replace, etc.)?

----------


## LaVolpe

> Hi jpbro, for some time to come, I need to do some parsing of text files (such as HTML and JavaScript). I'm thinking about a question: Is the efficiency of regular expressions higher than VB string functions (Instr, Replace, etc.)?


No offense meant, but could you PM him instead of using this thread for off-topic questions? Especially when the answer is likely not a simple yes or no. Thank you

----------


## dreammanor

> No offense meant, but could you PM him instead of using this thread for off-topic questions? Especially when the answer is likely not a simple yes or no. Thank you


Sorry for hijacking the thread. However, I think the performance of *RegEx* is also related to text parsing (project parsing).

----------


## jpbro

> @jpbro. Regarding logical lines, if you want any "lessons learned" from me, I can certainly offer a few. Looking over your source code I did see some logic that could fail with legitimate logical lines; although it would take someone with a pretty unusual coding style to break a better-than-basic parser.


That's very kind of you, and of course I always welcome input from a master! That said, let me flesh out my logical line code a bit and publish it before you waste any time on my code. If you're in the mood to post some general notes/"gotchas" that would be appreciated, but there's definitely no obligation.




> Logical lines is the reason why I chose not to use InStr, RegEx and others forms of parsing.


I agree, for my logical line parsing I'm doing char by char tests with flags to track things like comments, quoted strings, etc... The regex features are there for devs to us *after* lines have been parsed. There's no doubt I've made mistakes though, because I've really only run it against my own coding style, and I know there's tonnes of variation out there.




> Not that those can't be used, but in some cases would require going back over the code time and again to catch all the legitimate variations that a logical line can written in, i.e., continuations, etc. I wanted a one-pass solution which is reason this project is parsed the way it is.


I also agree with the one-pass solution. My goal is that my project does 1 pass, then the dev can write more complex tests that use regexes/instr/whatever, and can jump back and forth with their matching logic to do some fairly complex stuff. I see that you are a car enthusiast, so my best analogy is that I'm looking for a more manual vs. automatic approach. I'll supply the engine, wheels, and steering - you can be the driver  :Wink:

----------


## jpbro

> No offense meant, but could you PM him instead of using this thread for off-topic questions? Especially when the answer is likely not a simple yes or no. Thank you


I agree, and I didn't mean to hop on this thread until I saw a bit of commentary on mine. @dreammanor, ideally you can ask questions in my thread or start a new one though - i prefer either over PMs because then everyone gets to learn from the conversation.

----------


## dreammanor

> I agree, and I didn't mean to hop on this thread until I saw a bit of commentary on mine. @dreammanor, ideally you can ask questions in my thread or start a new one though - i prefer either over PMs because then everyone gets to learn from the conversation.


Yes, accept your and LaVolpe's advice, thank you.

Edit: It's weird. In the private message of vbForums, once I send a pm, I can't see it again unless the other party quotes my pm in the reply.

----------


## LaVolpe

> I agree, for my logical line parsing I'm doing char by char tests with flags to track things like comments, quoted strings, etc...


Interesting... my more recent parser version does something very similar - flags indicating whether next "word" is an operator, number, continuation, whether "word" ended on space, punctuation, and more. This does help when re-combining split lines and for my project, helping identify Obj.Method format of items inside and outside of With statements.

I'll check back every now and again on your project and wait til you post your latest update. Then I can offer some advice if you haven't addressed some of the stuff I saw when I skimmed your recent version. But here's the biggie and you probably addressed that already: Procedures do not need to start a physical line, nor does the "End Sub/Function/Property" need to start a physical line. And if I recall, you are not testing for procedures/variables that begin with "Static", but not 100% sure on that.

----------


## jpbro

> Interesting... my more recent parser version does something very similar - flags indicating whether next "word" is an operator, number, continuation, whether "word" ended on space, punctuation, and more. This does help when re-combining split lines and for my project, helping identify Obj.Method format of items inside and outside of With statements.


I didn't even think about "With" blocks yet, and you can see from my "comprehensive" list of logical line types, I've also missed events...so maybe my 90% was a bit optimistic  :Smilie: 

My logical line enum type list so far:



```
Public Enum e_LogicalLineType 
  logicallinetype_Empty = &H1& ' Blank line (empty or whitespace only)
   logicallinetype_Header = &H2& ' Stuff in the header section of an FRM file, or "Attributes"
   logicallinetype_Attribute = &H4& ' Hidden VB attribute (e.g. ProcedureID)
   logicallinetype_Option = &H8&  ' e.g. Option Explicit
   logicallinetype_ConditionalCompilation = &H10& ' e.g. #IF #ELSE #END IF
   logicallinetype_Comment = &H20&  ' Comment line
   logicallinetype_ApiDeclaration = &H40&  ' e.g. Declare Sleep lib "user32.dll"
   
   logicallinetype_EnumStart = &H80&  ' Start of an Enum e.g. Public Enum MyEnum
   logicallinetype_EnumMember = &H100&  ' elements/members of an Enum. E.g. enum_MyEnum1, enum_MyEnum2
   logicallinetype_EnumEnd = &H200&  ' End of en enum, e.g. "End Enum"
   
   logicallinetype_TypeStart = &H400&  ' Start of a UDT e.g. Public Type MyType
   logicallinetype_TypeMember = &H800&  ' Members of a UDT
   logicallinetype_TypeEnd = &H1000&  ' End of a UDT, e.g "End Type"
   
   logicallinetype_ConstantDeclaration = &H2000&   ' e.g. Private Const mc_MyConst As Long = 1
   logicallinetype_VariableDeclaration = &H4000&   ' e.g. Dim X As Long, Private Y As Integer
   logicallinetype_MethodStart = &H8000& ' Sub/Function/Property start line
   logicallinetype_MethodEnd = &H10000&  ' Sub/Function/Property end line
   logicallinetype_Label = &H20000&   ' Line label
   logicallinetype_Statement = &H40000&    ' Regular code line/statement
End Enum
```




> I'll check back every now and again on your project and wait til you post your latest update. Then I can offer some advice if you haven't addressed some of the stuff I saw when I skimmed your recent version.


It's an honour for you to have a look, and if there's anything in my project that can help with yours then feel free to take it. When you published your project it inspired me to polish up and release mine, so my hope is that both of our projects can help the remaining VB6 community.




> But here's the biggie and you probably addressed that already: Procedures do not need to start a physical line, nor does the "End Sub/Function/Property" need to start a physical line.


That's definitely an issue with my example processor that works on a physical line level, and one of the main reasons I hope to introduce up logical line processing. One "mental" problem I have with the line processing right now is how to handle something like this:

1) You detect a logical line of "Open "Something" For Binary Access Write As #1" and that happens to match the physical line. So you decide to replace it with "Dim ff As Integer" & vbnewline & "ff = FreeFile" & vbnewline & "Open "Something" For Binary Access Write As #ff". Well now you have 3 physical lines replace 1 physical line. I guess I just reparse the file in that case?

2) You detect a logical line of "Open "Something" For Binary Access Write As #1" that appears as physical lines of:



```
Open "Something" _
For Binary _ 
Access Write _
As #1
```

And then replace it with a single physical line of "Open "Something" For Binary Access Write As #1".  4 physical lines is now 1 physical line. I guess I will just have to reparse after each substitute, but I'm still trying to wrap my head around what to do every possible case.




> And if I recall, you are not testing for procedures/variables that begin with "Static", but not 100% sure on that.


You are correct - I didn't even know you could have "Static" procedures!

----------


## Thierry69

I did it on one of my small projects, (add-in for Outlook), and worked perfectly, finding 5 unused array/variable I left vonluntary.

I tried it on one of my biggest project, it took 2,5h to read the project.
I tried to validate it, then canceled, and lost the loading of the projecT.
Now, did it again, then I 'll validate it again, but it is quite long.

----------


## LaVolpe

> I tried to validate it, then canceled, and lost the loading of the projecT.
> Now, did it again, then I 'll validate it again, but it is quite long.


Don't  use the current project in post #1 for large projects. Later this afternoon, I'm posting some replacement files for the project. Then I'm interested on speed gains for large projects when those replacements are used. However, with the replacement files, validation will be turned off -- the validation portion of the project is no longer compatible with the newer parsing logic and when I fix that I'll re-post the entire project to post #1.

----------


## Thierry69

In fact, I wasn't reading it again, but saving to XML, and I stopped it after 2h

I can test again tomorrow on the big project once you post the new version.

----------


## dreammanor

> I tried it on one of my biggest project, it took 2,5h to read the project.


It seems that your project is 20 times larger than my project. I have not scanned/tested my biggest project, it's 2-3 times larger than my current project.

----------


## Thierry69

One of the project has 480k lines of code
244 forms
62 modules
27 controls

For info, my VBIDEUtils is taking 8 minutes to analyse the whole code and detect dead variables/procedures (not perfect, but 90% of dead variables)

----------


## LaVolpe

> In fact, I wasn't reading it again, but saving to XML, and I stopped it after 2h


Unless there is a reason to save to XML, I wouldn't do it. Export the recordset instead. You can reload that recordset. The XML option was originally added so one could use it later as a reference to address any validation discrepancies noted. With this version, you can actually keep track of what you've "fixed" by hiding the items you've fixed. When you re-export the scan recordset, the state of those hidden items is kept for the next time you reload the scan.

----------


## LaVolpe

For any/all that want to test latest changes against very large projects, here is the zip of those replacement files.

Unzip the files into the same folder you downloaded this project to. If you need the original project from post #1, just go and get it at any time. It will remain there for another week at least.

While playing with this "version" of the files, the validation option is turned off. The replacement files aren't ready for production, but are usable as is. I'm hoping that improvement in overall scanning speed has increased for you.

----------


## dreammanor

*New PrjScan*
(1) Scan Krool's ComCtlsDemo.vbp: Time: 7.421875 seconds
(2) Scan DreamManor's Large.vbp: Time: 164.437999999995 seconds

Very nice, scan time less than 180 seconds is acceptable.


In addition, there is a small bug:  (when sName = "X", the software error)


```
Private Function pvIsEvent(sName As String) As Boolean

    Dim iPos As Long:       Dim iLen As Long
    
    iLen = Len(sName)
    If iLen > 1 Then            '--- DreamManor Added 2019-01-09 ---
        iPos = InStrRev(sName, "_", Len(sName) - 1)
    End If
    
    Do While iPos > 1
        m_EventLUT.Find SetQuery(constGrpIdxID, qryIs, CRCItem(LCase$(Left$(sName, iPos - 1)), True)), , , 1&
        If m_EventLUT.EOF = False Then
            pvIsEvent = True: Exit Do
        End If
        iPos = InStrRev(sName, "_", iPos - 1)
    Loop
    
End Function
```

----------


## LaVolpe

> *New PrjScan*
> (1) Scan Krool's ComCtlsDemo.vbp: Time: 7.421875 seconds
> (2) Scan DreamManor's Large.vbp: Time: 164.437999999995 seconds
> 
> Very nice, scan time less than 180 seconds is acceptable.


That large project of yours is the one that took 425 seconds before?  If so, I'm satisfied with the improvement too.




> In addition, there is a small bug:  (when sName = "X", the software error)


Good catch, I didn't even consider 1 character procedure names

Thanx for giving these changes a test. Now I can focus on fixing conflicts in parsing with the new routines... for example, items declared as arrays don't parse correctly now -- but an easy fix

----------


## Elroy

Hey LaVolpe,

You're probably not interested, but I did come up with a few additional ideas that your scanner might examine:

constant type not specified in declarationintellisense fix not performed for enumerationa Function, Sub, or Property declared with no Private, Public, or Friend specification---implicit typecasting in variable-from-variable assignmentimplicit typecasting in variable-from-literal assignmentimplicit typecasting in variable-from-expression assignmentimplicit typecasting in constant-from-literal assignmentimplicit typecasting in constant-from-expression assignmentimplicit typecasting in constant-from-constant assignmentimplicit typecasting in arguments of a call (sub, function, etc)
Some of this is quite nit-picky, but it is stuff that I try and clean-up when I'm mucking around in various VB6 source code areas.

In any fairly large project, I doubt we'd be able to clean up every speck of that, but I do try to clean up as much of it as I can.

And again, a FANTASTIC project.  VERY impressive.

Best Regards,
Elroy

EDIT2:  Added a couple more to the list.

----------


## LaVolpe

Not interested? Hardly. Not persuaded? Possibly  :Wink: 




> *constant type not specified in declaration
> *intellisense fix not performed for enumeration
> *a Function, Sub, or Property declared with no Private, Public, or Friend specification


Though above may be a preference for some, they are not what I would consider something worth pointing out during validations. Just not persuaded. I think this is more for apps like MZTools and their ilk vs. what I was attempting with this project; i.e., potential for oops/efficiency vs. coding style

- Constants don't need type-declarations. Per Microsoft, they get their type from their values. For example, Const Author = "LaVolpe" is same as Const Author As String = "LaVolpe". This is why the project doesn't report constants that aren't specifically var-typed as a validation discrepancy.

- Intellisense "fixes" are a personal preference. Note: though typically used for enums, this fix can apply to parameters/variables/procedures too. How many times have you pasted an API with a hWnd parameter spelled "hwnd" and see all your hWnd variable/property names change to lower case?

- Declaring procedure scope is not required and "Public" is default if not explicitly declared. I think many know that and simply don't type the extra 6 characters: Public. And I think this is another case of preference. Though this one suggestion I am considering as it can apply not only to procedures, but for Constants, APIs and UDTs also where the Public/Private declaration is optional in bas-modules for the latter two, but required in other code files. If I do include it, I'll just initialize it as an unselected option.

- Regarding the variant type-casting items, if I understand your intent, is not really doable. Why? Well, I may track items to see _if_ they are used (zombie checks), but I make no attempt to track parsed items and _how_ they are used nor _what_ they are passed to/from. Although I agree with your suggestion, I definitely won't be going there.

Regarding Enums, here's maybe not a completely known fact by many VBers... 
Question: What's wrong with this?


```
Private Enum NotWhatYouExpect
   nwyeSng = 1.789          ' Single
   nwyeInt = 123%           ' Integer, note VB will remove the %, but technically Integer value
   nwyeDate = #1/1/2001#    ' Date
   nwyeStr = "123E+5"       ' String
End Enum
```

Answer: syntactically nothing. However, the real value of the enum members are longs, always longs. VB can accept any enum member value as long as it can be converted to Long. The Long value will be used in code.

I think one can argue that pointing out not explicitly type declaring variables and leaving VB to default to Variant might be a personal preference, along with not using Option Explicit. In this case, we know there are pitfalls with variants that can be avoided. So, I feel that those scenarios should be pointed out. As for unexpected results, here's an example:


```
Dim v1, v2
v1 = "6789": v2 = 6789
MsgBox v1 > v2
MsgBox v1 = v2
' now try again after changing declares to: Dim v1 As String, v2 As Long
```

----------


## dreammanor

> That large project of yours is the one that took 425 seconds before?


Yes.

----------


## Thierry69

I tested the new version on my large project.

It seemed a little bit faster.

I found 
- a few variables unused 
- a few functions returning variant.
- a few functions that should be declared as sub

And also A LOT of optional parameters unused. it could be a parameter to not check this

A great enhancement could be to jump directly into  VB6 at the right place where the code is.
For the moment, manual research, so takes time
Maybe add in the menu over a procedure/function etc...  Copy the name to the clipboard, so can be paste in the Debug area to go to the definition

A variable was declared like : 
Dim sFile
And used as iterator for a collection, and throw it as an error.
I added "As Variant", but maybe, should not be thrown as a warning

Otherwise, it helped me to optimize/clean a bit.

----------


## LaVolpe

> And also A LOT of optional parameters unused. it could be a parameter to not check this


Don't know. If you have parameters but  not using them, then why include them in the function/sub? One of the things I wanted to do with this project was to try to point out things where code can be made more efficient. If you can remove those parameters because they are not used, then VB doesn't have do extra work passing and/or returning them.




> A great enhancement could be to jump directly into  VB6 at the right place where the code is. For the moment, manual research, so takes time Maybe add in the menu over a procedure/function etc...  Copy the name to the clipboard, so can be paste in the Debug area to go to the definition


The treeview can be clicked on (like Explorer) to send it into "edit" mode. You can copy whatever you need from there. Don't worry about accidentally changing the treeview node by mistake. The project won't allow you to do that.




> A variable was declared like : 
> Dim sFile
> And used as iterator for a collection, and throw it as an error.
> I added "As Variant", but maybe, should not be thrown as a warning


I believe it should throw the warning. As mentioned in the "help", this warning is for you to look at your declaration and double check to make sure you intended it to be Variant. If you intended it to be, but didn't include the "As Variant" in the declaration, then the warning is considered a false positive. However, I have done it and others do also where you simply forget the "As ... whatever" by mistake. That warning is to help identify those.




> Otherwise, it helped me to optimize/clean a bit.


I'm glad. That is the intent

----------


## Thierry69

> Don't know. If you have parameters but  not using them, then why include them in the function/sub? One of the things I wanted to do with this project was to try to point out things where code can be made more efficient. If you can remove those parameters because they are not used, then VB doesn't have do extra work passing and/or returning them.


I know, but I use sometimes optional parameters (maybe not used), but to have same kind of definitions for procedures




> The treeview can be clicked on (like Explorer) to send it into "edit" mode. You can copy whatever you need from there. Don't worry about accidentally changing the treeview node by mistake. The project won't allow you to do that.


Maybe, I had VB6 openened and directly modifying it it, then after a few modification, compilation to be sure, all is ok





> I believe it should throw the warning. As mentioned in the "help", this warning is for you to look at your declaration and double check to make sure you intended it to be Variant. If you intended it to be, but didn't include the "As Variant" in the declaration, then the warning is considered a false positive. However, I have done it and others do also where you simply forget the "As ... whatever" by mistake. That warning is to help identify those.


This how I took it, and also fixed the 2 variables that were in the case




> I'm glad. That is the intent


Yep.

now, running a new turn with the 2 options unchecked by default
Hopefully, i have several computers  :Smilie:

----------


## jpbro

> a Function, Sub, or Property declared with no Private, Public, or Friend specification


I see LaVolpe wasn't too keen on this one, but there is a situation where it would be nice to have. When creating Multiuse /Global-Multiuse classes in ActiveX DLL projects, it's possible to accidentally create a Public procedure by omitting an explicit "Friend/Private" declaration. You can then get stuck with it for binary compatibility purposes if you happen to compile the project before noticing the omission. It might be nice to have this detection as an optional feature for this purpose (or maybe just active in public classes of ActiveX DLL/EXE projects?)

----------


## LaVolpe

> I see LaVolpe wasn't too keen on this one... It might be nice to have this detection as an optional feature for this purpose (or maybe just active in public classes of ActiveX DLL/EXE projects?)


Ok, now looking seriously at it. When I was looking at it from the context of "personal style", I wasn't persuaded. But when looking at it from the context of a potential problem with binary compatibility, am now being persuaded.

----------


## dreammanor

> I see LaVolpe wasn't too keen on this one, but there is a situation where it would be nice to have. When creating Multiuse /Global-Multiuse classes in ActiveX DLL projects, it's possible to accidentally create a Public procedure by omitting an explicit "Friend/Private" declaration. You can then get stuck with it for binary compatibility purposes if you happen to compile the project before noticing the omission. It might be nice to have this detection as an optional feature for this purpose (or maybe just active in public classes of ActiveX DLL/EXE projects?)


Great idea.

----------


## jpbro

> Ok, now looking seriously at it. When I was looking at it from the context of "personal style", I wasn't persuaded. But when looking at it from the context of a potential problem with binary compatibility, am now being persuaded.


Not that _I've_ ever screwed up like that before....<ahem>...Nope not me.  :Wink:

----------


## Eduardo-

> Not that _I've_ ever screwed up like that before....<ahem>...Nope not me.


I my case I always delare the procedures explicitely as Public, Private or Friend, but I had that problem in the past with some functions that I copy/pasted from other authors.

----------


## jpbro

I always try to explicitly declare Public/Private/Friend too, but I have made mistakes on occasion. Same as forgetting to declare the return type of a function, which can also have annoying binary compatibility problems if you later realize you're meant to return something other than Variant. 

Running LaVolpe's scanner before compiling would help prevent this of course, which would be great.

----------


## LaVolpe

Yep, I'll add that check but probably initialize it as unselected. Since I'm doing that, I'll probably add a sub-option to it which will report any constants, types, enums, and APIs that are not explicitly scoped -- also initially unselected. Not sure that would have any compatibility problems, but it could if Enums were public by mistake and changes were needed to them.

I also saw something in Krool's sample project that I didn't expect and will likely include a check for that in the zombie checks. He declared a variable WithEvents but did not have any events coded. Not sure if VB will create an event handler for that variable in that case, when compiled. But no harm in identifying those too.

----------


## Eduardo-

Hello LaVolpe,

I tried to use the scanner to detect some unused procedures but I found that it didn't detect them. They are Public properties in Class modules, but the classes instancing are Private so they are not exposed to the outside of the project.

Since the project is an ActiveX, I tried changing it to standard exe and run the scanner again, but I got the same result.

----------


## LaVolpe

Eduardo...

All public properties (actually all public methods), not in a bas-module, are excluded from zombie checks. I know this may sound like a cop-out, but I justified that decision on: public properties were defined by the coder because they may be called from inside/outside the project, whether they are actually called or not ... Think of someone planning for future enhancements or sharing a class from some other project where the property is called.

Even if I tried to include them, I couldn't catch many of them. The project does not track how a variable is used in code. Looking at the following statements, the scanner has no knowledge that oObject is a class reference.


```
 Dim oObject As Object
...
   Set oObject = myClass
   oObject.Property = someValue
```

Now it could be possible to track something like the following via cross-referencing...


```
Dim oObject As myClass  ' or Dim oObject As New MyClass
...
    oObject.Property = someValue
```

When assignment happens at the declaration statement, I think that might be something I can incorporate. In that first example, just can't do it without getting creative. In simple cases like above where keyword "Set" is used and variable is followed with equal sign and immediately following that is the class name with/without "New" keyword, then that could be handled without much difficulty.

I have no intention to try to follow code to determine how variables are used. Here's an example of what I mean...


```
 Dim oObject As Object
...
   Set oObject = someOtherClass.GetItem()  ' which returns a class the scanner has no knowledge of
   oObject.Property = someValue

' ... or something like this
    Set oObject = myCollection.Item(x) ' no way of knowing what this is

' ... or something like this
   SomeSub oObject, X, Y  ' where oObject is returned as some instantiated class
```

Public properties defined in bas modules are doable, and am doing that now, because we cannot assign a module to a variable.

----------


## Eduardo-

Yes, I understand LaVolpe.
You would have to follow the object's variables through the code. And let's also consider that the objects can have default members.

Thank you.

----------


## LaVolpe

> Yes, I understand LaVolpe.
> You would have to follow the object's variables through the code. And let's also consider that the objects can have default members.
> 
> Thank you.


I won't be following code -- not interested in building a mini-compiler  :Wink: 
But ugh -- default members; that'll mean parsing Attribute statements to determine which is a default, if any

----------


## wqweto

> I won't be following code -- not interested in building a mini-compiler


Nooo. . . I had secret hopes here :-))

Was about to suggest to migrate parser to PEG grammar as to be able to generate the parser with VbPeg.

Edit: ziglang recently switched to PEG grammar to document their syntax. Here is their parser.peg grammar in VbPeg test-runner that passes compilation and tests successfully.

cheers,
</wqw>

----------


## LaVolpe

> Here is their parser.peg grammar in VbPeg test-runner that passes compilation and tests successfully.</wqw>


Interesting. 

My scanner is 99.9% complete now. I completely rewrote the logic over the past couple weeks. The logic does require the project to have correct syntax in order to return valid results. I really don't want to have it validate syntax -- should be no reason. VB does that with a simple Ctrl+F5. The logic I used really speeds up the validations because when files are parsed to find whole statements, it also tracks "words" within the statement and keeps a small list of where each word exists in the statement & its length, along with some attributes for the word, i.e., operator, punctuation, parenthesis parameters, etc. When statement is passed to various validation routines, statement doesn't need to be re-parsed, it just jumps right to the statement words & uses the attributes to determine what to do with it relative to the specific validation routine. This extra tracking stuff is not persisted, JIT and dumped.

The only thing I have left to do is come up with a better way of determining when to join words beginning with a dot when the dot is separated by a line break/continuation. VB allows breaks just about everywhere in a statement and there are so many variations, but haven't found a hard & fast ruleset to help me out. For example, an extreme case


```
    With myUDT
        ListView1 _
            . _
            ListItems( _       << belongs to ListView
            . _
            Index Xor _        << belongs to myUDT
            . _
            Offset) _          << belongs to myUDT
            . _   
            Text _               << belongs to ListItems
            = myCollection _
            . _
            Item( _             << belongs to myCollection
            . _
            Key)                 << belongs to myUDT
    End With

  ' and consistency isn't always there inside of VB, for example
   With Me
        Interaction.AppActivate .Tag    << no issues with VB
        Interaction.AppActivate . _
                                Tag        << no issues with VB
        Interaction.AppActivate _
                                .Tag       << ERROR won't compile (Ctrl+F5)
        Interaction.AppActivate _
                                . _
                                Tag        << ERROR won't compile (Ctrl+F5)
   End With
```

My logic requires objects to be "joined", whether a control, class, UDT, etc. So the above statement objects would be parsed as: myUDT.Index, myUDT.Offset, myUDT.Key, myCollection.Item, & ListView1.ListItems.Text.

Based on those compile errors in above example, it's fairly easy to guess how VB joins objects. Overly simplified... Because VB requires a space before the underscore continuation character, it needs to determine whether to preserve that space during compilation. So, in these cases where a dot follows the continuation, VB assumes it belongs to the previous word (if not an operator, number, literal, a reserved keyword or other special cases). Well, Interaction.AppActivate is a sub, not a class (or method that returns a class) and therefore an error.

In my head, this logic works, and I'll see if it pans out. Of course, those lines in sample code above that cause compile errors will fail to parse correctly using this logic. They would look like: Interaction.AppActivate.Tag. But I have stressed that code should be syntax error-free before scanning.


```
when not following continuation
Dot Scenario                Dot Belongs To:
1st word of statement       active "With" statement
previous char is ) ]        word before opening ( [
all other scenarios         active "With" statement

when following a continuation; previous word is the one before the continuation
the space-underscore of the continuation line are never looked at in these scenarios
Dot Scenario                Dot Belongs To:
1st word of statement       active "With" statement
    can only occur if previous word is open parenthesis
previous char is ) ]        word before opening ( [
previous word is:           active "With" statement
    comma, semicolon, number,
    Date , literal, keyword, operator, unary
all other scenarios         previous word

Note: the term "word" is flexible. Typically a word is surrounded by white space
or terminates on a VB-word break: comma, closed parenthesis, colon, etc
So words can include multiple words (Debug.Print) or single characters (+ , -).
Parentheses & square brackets are treated as words because they have special
meaning in my parser. Open parenthesis begins recursion, treating contained code
as a new "statement" up until its closing parenthesis.
```

Anyway, I thought that was the last part to rewrite until Eduardo brought up public class methods. Thanx Eduardo  :Wink: 

Edited: I'll probably post the updated version without Eduardo's enhancement idea and work on that for the next version

----------


## Elroy

LaVolpe,

I'm not sure I understand.  I've got my own project scanners (not ready for prime-time nor publishing), but one of the first things I do when processing a module is to combine all continued lines (" _") and split all mult-statement lines (": ").  In addition to being syntactically correct, I assume it was saved by the VB6 IDE.  That gives me additional assurance that I know certain things (like no space after the colon on a line label).

It would seem that, if you did that, the problem of what the preceding dots go with is solved.

Just my two-cents.

Best Regards,
Elroy

EDIT:  I also tend to strip out all the comments too, before trying to process it.

----------


## LaVolpe

Elroy, you can't just combine lines willy nilly. Sometimes the space before the continuation character needs to be preserved, sometimes it must not be in order to maintain syntax.

For example, look at some of these. If you were to simply "join" all the dots to the previous characters, it would be wrong in some cases.


```
  sText = myClass. _    << space not maintained
    Caption

  Debug.Print ListView1 _   << space not maintained
    .ListItems _     << space not maintained
    (1) _     << space not maintained
    .Text & _   << space maintained
    " blah"

  With myClass    
    Debug.Print 123 Xor _  << space maintained
       .Value
    CallMySub .Value, _     << space maintained
        .Index
  End With
```

And I can list a bunch of other examples such as the space between an API Alias and parameter's open parenthesis is maintained, but the space between a class Public Event statement's name and its parameter's open parenthesis is not maintained -- though not dot-related

Edited: Now it truly isn't necessary to get a lot of this stuff perfect, because VB can still remove/add spaces you may have mistakenly failed to do while manually joining lines. But the dots following continuations are an exception -- gotta be right otherwise you are joining properties/method to stuff that doesn't apply. And P.S. I don't actually join anything, except "objects" or if something needs to be displayed. Taking the time to join in other scenarios is wasted CPU cycles and time for my scanner -- that was 1st version and a mistake. My scanner does not update/edit VB files.

Tip: Trying to split text on ": " will fail in cases like the following


```
Private Enum CrazyEnum
   [__/ : \__]
   [>>[ : ]<<]
End Enum
 X = 1:: Y = 2
' and of course in literals 
s = "List follows: 1, 2, 3, 4"
```

I assume you are joining on underscore carriage return combinations, but these are valid statements:


```
 X = _
 _
 _
 1

X = 1 _
```

----------


## Elroy

Interesting ... I definitely see your point.  I guess I'm just not in the habit of using continuations in that way.  Truth be told, I'd never dream of using line continuations in many of the ways you did in your above example.  However, I "get" that it's valid syntax, and that you're therefore trying to cover those cases.

Therefore, for a totally robust project scanner, scratch my post #197, or make your concatenation algorithm VERY smart.   :Smilie:

----------


## LaVolpe

> Interesting ... I definitely see your point.  I guess I'm just not in the habit of using continuations in that way.  Truth be told, I'd never dream of using line continuations in many of the ways you did in your above example.  However, I "get" that it's valid syntax, and that you're therefore trying to cover those cases.


Before I started this, I downloaded code from various sites just to get a feel for coding styles I've never really seen before. That helped. Then that evolved into questions like, "are we allowed to do [fill in the blank]?" which made me sit up and notice.

----------


## dreammanor

Hi LaVolpe,

The original version of Project Scanner cannot handle Chinese characters, and now the new version can. I'd like to know what steps you've taken to deal with Chinese characters? Thanks!

----------


## qvb6

> Hi LaVolpe,
> 
> The original version of Project Scanner cannot handle Chinese characters, and now the new version can. I'd like to know what steps you've taken to deal with Chinese characters? Thanks!


You can use WinMerge to see what changed between two VB projects.

----------


## dreammanor

> You can use WinMerge to see what changed between two VB projects.


Hi qvb6, the tool you recommend is extremely useful, thank you very much.

----------


## qvb6

> - Zombie declarations and procedures. Items that are created/declared but not referenced within your code
> - Strings that are duplicated within your code. These can be consolidated to constants


I haven't downloaded your Project Scanner yet, but from memory; I know that VB6 compiler omits procedures that are not called from anywhere(unless they are Public in a Class, I think). This is called function-level linking in the C world. Also, VB consolidates duplicate string literals into one, so it appears once in the EXE space, but in some cases using constants is better.

----------


## Eduardo-

Hello LaVolpe, I found a bug in the pvScanProcedures procedure of clsPrjFile. It raises an error in the line:



```
sToken = Mid$(sLine, iPos + 1, lFlags - iPos - 1)
```

because lFlags is 0.

I traced the error and it is misinterpreting a line that is:



```
Static sVariableName as Boolean
```

It is interpreting it as it was a Static procedure.

It is a bit strange because I have several other Static variables in the project but they didn't rise the error.

I momentarily fixed it by adding these lines:



```
    Do
        iPos = 0: sToken = modMain.GetToken(sLine, iPos, chrSpace)
        If lScope = 0 Then
            If sToken = ITEM_STATIC Then
                sToken = modMain.GetToken(sLine, iPos, chrSpace)
            End If
            Select Case sToken
            Case ITEM_PUBLIC, ITEM_PRIVATE, ITEM_FRIEND
                If sToken = ITEM_PRIVATE Then
```

Because even when a procedure is Static, it also must have Sub, Function, Etc.

----------


## Eduardo-

Update. I found the cause. I was experiencing other anomalies and the cause are some commented lines that end with _ (underscores).

It seems that the scanner intrerprets all lines finished with underscores as continuation to the next line. But that rule doesn't actually apply to comment lines, it is only for code lines.

Also another detail: The procedure named 'Main' in a bas module shouln't be listed as zombie because "it is not used".

----------


## LaVolpe

> Update. I found the cause. I was experiencing other anomalies and the cause are some commented lines that end with _ (underscores).
> 
> It seems that the scanner intrerprets all lines finished with underscores as continuation to the next line. But that rule doesn't actually apply to comment lines, it is only for code lines.
> 
> Also another detail: The procedure named 'Main' in a bas module shouln't be listed as zombie because "it is not used".


Good catches. I do want to return to this project and finish it up. I'll keep these in mind when I come back to this project. If not, I'll ensure that is addressed too.

Sub Main(): I'm not looking at my code, but I thought I only mark it zombie if it isn't the startup routine and no other code calls it.

Underscores on comments: Yep, didn't expect those. Will need to tweak the line parser for that scenario.

----------


## Eduardo-

> Sub Main(): I'm not looking at my code, but I thought I only mark it zombie if it isn't the startup routine and no other code calls it.


Yes... I had forgot to select the Sub Main in the project properties (it is a component).
Now that I fixed that, it disappeared from the zombie report.
The scanner is working fine in that regard, then.




> Underscores on comments: Yep, didn't expect those. Will need to tweak the line parser for that scenario.


Yes, it is then the only thing I'm reporting.

For my case, I already changed those comment lines to avoid underscores at the end.

Thank you.

----------


## wqweto

Line continuations (underscores) *do* work for comment lines like this. 


thinBasic Code:
Option Explicit
 Private Sub Form_Load()
    Rem First line _
        Second line
        
    '-- First line _
        Second line
End Sub
. . . both first and second lines are part of the comment or am I not understanding the problem?

cheers,
</wqw>

----------


## LaVolpe

@wqweto, that is how I  believe I coded the scanner now that I am thinking about it more. I know I've used comments like that in the past -- looks more pleasing to my eyes with just one tick/REM statement

@Eduardo, is this the scenario that was triggering false positives..


```
Static sVariableName as Boolean ' some comments _
```

If not, could you provide a simple example so I can look at the code in more detail down the road?

It may have interpreted it as  a method, but shouldn't have. I'm pretty sure the code requires Sub, Function, Property to flag methods. My guess is that the flags were simply wrong due to failed parsing and 'method' is just a byproduct of bitwise comparison of the faulty flag value.

----------


## Eduardo-

LaVolpe, in the case of the Static variable, the scan process was silently aborted because of a runtime error. The scanner didn't work at all until I traced the issue to that procedure and made that change.

But after that, I found others issues, this time as false positives like variables not used when they were in fact used. I had several cases like that.

About the issue of line continuations in comment lines, I see what wqweto points (thanks). Then, perhaps it is not that comments lines are handled differently by VB regarding to line continuations, but that line continuations *require one space* before the last underscore?

Here is a sample of what caused the problem:



```
' declarations...
' declarations...

'_SOME_COMMENT_
' declarations...
Private mVariable As Long ' mVariable Reported as not used
' declarations...
'_

' code...
' code...

'_SOME_COMMENT_
Private Sub NameOfSub()
    Static sVariable As Boolean ' Static variable that caused the run time error
    
    If Not sVariable Then
        sVariable = True
        ' code...

        ' code...
    End If
End Sub
'_

' code...
' code...

'_SOME_COMMENT_
Private Function F1() As Boolean
    Dim i As Long
 
    ' code...

    i = mVariable ' mVariable is used

    ' code...
End Function
'_
```

Or simpler, causing only the first problem:



```
' code...
' code...

'_SOME_COMMENT_
Private Sub NameOfSub()
    Static sVariable As Boolean ' Static variable that caused the run time error
    
    If Not sVariable Then
        sVariable = True
        ' code...

        ' code...
    End If
End Sub
'_
```

----------


## LaVolpe

The problem is likely the trailing underscore. I know I coded to ensure a 'space underscore' is a continuation, but may not have done that for comments. I doubt it is the leading underscore. In any case, I'll need to check the code in more detail.

Thanx Eduardo for the bug report

----------


## Eduardo-

> ```
> ' code...
> ' code...
> 
> '_SOME_COMMENT_
> Private Sub NameOfSub()
>     Static sVariable As Boolean ' Static variable that caused the run time error
>     
>     If Not sVariable Then
> ...


After I changed the comments to:



```
' code...
' code...

'_SOME_COMMENT_1
Private Sub NameOfSub()
    Static sVariable As Boolean ' Static variable that caused the run time error
    
    If Not sVariable Then
        sVariable = True
        ' code...

        ' code...
    End If
End Sub
'_2
```

all the problems went away.

----------


## wqweto

So the problem is that in comments trailing underscore has to be space prefixed to be treated as line continuation by the parser. 

Here is another sample where no comment/code continuation should happen


thinBasic Code:
Option Explicit
 Private Line_ As Long
 Private Sub Second(A As Long)
 End Sub
 Private Sub Form_Load()
    '-- First Line_
        Second Line_
End Sub

Not that final underscore can be part of variable name in normal code lines so space-underscore combo is actually required to be non-ambiguous.

cheers,
</wqw>

----------


## ahenry

I thought of another check that would be useful under the "zombie" category - labels without a corresponding Goto or On Error Goto. It would catch some non-functioning error handlers.

----------


## LaVolpe

Updated project posted.

----------


## LinkFX

Excellent project LaVolpe!

I made a couple very small modifications to this so it can accept a project file through command line arguments and open it directly. That way I can have the compiled .EXE in my MZ-Tools app shortcuts toolbar and launch it for the currently opened project with just one click. It's such a trivial modification it hardly seems worth it to post the code but there's an idea that you might wanna consider implementing yourself that probably takes no more than a few minutes!  :Smilie:

----------


## LaVolpe

> ...you might wanna consider implementing yourself that probably takes no more than a few minutes!


Not a bad idea. Might even want to add command line switches to run silent & auto-save validation reports? Food for thought if I update again. And I probably will update again since this was a complete rewrite, logic-wise, which means I might have a logic error or two not yet found

----------


## Mith

I tested your Project Scanner today and i don't understand why this Item is in zombie state:



```
Items in zombie state
   Method: txtCommentEdit_Validate
```

It's the Validate event of a text box control (VBCCR16's TextBoxW).
The CausesValidation property of the text box is set to True.

----------


## Eduardo-

I want to add that with Krool controls, GotFocus and LostFocus procedures are also reported as zombie.

----------


## LaVolpe

I'll look into it this weekend. I may have forgotten to ensure the extender events were included with usercontrols or a logic flaw exists. Once that is resolved, extender events won't show up as zombies

edited: yep, logic flaw. Testing for extender events was mistakenly not applied to uncompiled usercontrols - doh. I'll post an update this weekend after a bit more testing and when I have more time.

Done. Project updated and re-uploaded.

----------


## Eduardo-

Thanks LaVolpe. Now those zombies does not appear any more.

BTW, another issue that I noticed is that public variables of class modules (that are actually properties in practice) appear as zombies if they are not used in the class module.

----------


## LaVolpe

> BTW, another issue that I noticed is that public variables of class modules (that are actually properties in practice) appear as zombies if they are not used in the class module.


I'll take a look. It's something that I may have forgotten to address during the rewrite. I know I addressed it in the previous version. Bug reports always welcomed.

Edited: patched & updated

----------


## MountainMan

LaVolpe,

Is there any documentation for what things went into the list of things to be flagged and what didn't? Some are self-evident like zombies but some I am guessing at (such as the use of CreateObject or CreateProcess) and I would like to know if my ideas match yours. Thanks.

----------


## LaVolpe

@MountainMan. Yes, press F1. Then Ctrl+F and search for "Safety Warnings". Its near the bottom of the page

----------


## MountainMan

Thanks. I didn't know you had a Help system in the program.

----------


## LaVolpe

You're welcome. In the clsValidation, you see a method not-best-named: pvListMaliciousDLLs
That would be the complete list if there's any difference in the help page -- not 100% that page is 100% accurate any  longer with recent tweaking and updates.

That listing in code is just DLLs. The VB functions on the hit list are in the resource file. Open the string table and item #2 is what you are after. You can scroll in the res editor window, but easier to select all & copy/paste elsewhere. 

Edited: Don't mess with the string table, unless you keep the sorting correct. Throughout that project, I use binary searches on strings and CRC values. Very fast, but requires lists to be sorted. Having an unsorted list will break the binary search to the point is almost unusable. Elsewhere, if a list isn't coming from the string table, the sorting is done real-time, as needed.

----------


## MountainMan

You might want to modifyyour menu for dummies like me such that you have a Help menu item with the View Validation Descriptions as part of that. What you wrote is very helpful though.

One area I go tripped up on was the malicious code thing. I was thinking "hwy of course my ShellW module has a CreateProces call in it. I wrote it and I know it is okay and not malicious." I wasn't thinking that this would be a useful tool for folks who bring in code from elsewhere without going through it. I don't do that; anything I use I take it apart partly to make sure I understand what is going on and partly to avoid the potentially malicious things that might be in the code. I haven't seen any of that on this forum so I wasn't thinking of having to do that. But I can see where it might be useful to some. Thanks, you have widened my perspective today... :-)

----------


## LaVolpe

> One area I go tripped up on was the malicious code thing. I was thinking "hwy of course my ShellW module has a CreateProces call in it. I wrote it and I know it is okay and not malicious." I wasn't thinking that this would be a useful tool for folks who bring in code from elsewhere without going through it. I don't do that; anything I use I take it apart partly to make sure I understand what is going on and partly to avoid the potentially malicious things that might be in the code. I haven't seen any of that on this forum so I wasn't thinking of having to do that. But I can see where it might be useful to some. Thanks, you have widened my perspective today... :-)


That was the intent. I actually started that for me and expanded it over time. One of my pet peeves is forgetting to check for SaveSettings and DLLs that tend to write stuff to the registry. If I'm only helping debug it, I don't want registry stuff written. If I'm only downloading out of curiosity, I don't want registry stuff written. Guess you can tell, that get's under my skin  :EEK!:

----------


## LaVolpe

Minor modifications made.

When conditional compilation directives are used, i.e., #If #Else, it was possible that the scanner could include statements it should not. Fixed.

Added ability to drag/drop a file onto the compiled exe file. The scanner will open and immediately start processing the dropped vbp/vbg file.

----------


## Tech99

Excellent project.

One feature comes to mind, which could be handy. I constantly find it hard for programmers to avoid mishandling dynamically dimensioned arrays especially udt arrays. Typical error, is passing module level array element as an argument to a procedure or function.
https://docs.microsoft.com/en-us/off...ocked-error-10

----------


## LaVolpe

Well, I am not looking at trying to follow code execution. In your example, one would need to identify fixed arrays (easy enough), but then would need to see if it is passed to a method, then look into that method to see how it is used, i.e., using ReDim on it. That is outside the scope of the scanner. 

That is also a reason why the scanner does not attempt to validate that a Public method in a class, usercontrol, etc is in zombie state. It would require the scanner to track usage of objects/variants and more just to see if one of them were declared as the class, form, etc and determine what methods it may be calling. For example, we can assign a class instance to a variable declared as: Object, item in Object(), Variant, item in Variant(), or a Collection item. And from any of those, we can call any public method directly from the object. To make it even more daunting, is that is possible that the reference is passed yet to another routine where, within that other routine, the public method is eventually called from that item.

----------


## wqweto

> That is also a reason why the scanner does not attempt to validate that a Public method in a class, usercontrol, etc is in zombie state. It would require the scanner to track usage of objects/variants and more just to see if one of them were declared as the class, form, etc and determine what methods it may be calling. For example, we can assign a class instance to a variable declared as: Object, item in Object(), Variant, item in Variant(), or a Collection item. And from any of those, we can call any public method directly from the object. To make it even more daunting, is that is possible that the reference is passed yet to another routine where, within that other routine, the public method is eventually called from that item.


Yes, this is impossible to track. What *is* possible is to detect method names used late-bound in a global list. Then if a public method is not directly used early-bound (which is easier to detect) its *name* can be searched in the global late-bound calls list and if not found there too it's probably dead code (unless it's a public method of a public class of an ActiveX DLL/OCX and can be used by external projects).

cheers,
</wqw>

----------


## LaVolpe

> Yes, this is impossible to track. What *is* possible is to ...


This comment is really intended for others. If I cannot determine zombie state in all scenarios with good confidence, then one can argue it is pointless to try. The results could be 99% correct in 'easy' cases and be less than 50% correct in many other cases. The scanner already has the potential of creating zombie false-positives, but those should be few. I don't want to introduce a scenario where the false positives can be commonplace.

An example of a zombie false-positive. For sake of this argument, a method is not an event. If the scanner checks private methods and a private method is never called, then how can the zombie state be a false-positive?  Answer: thunks. Many thunks are created to call private methods of a class, form, etc. No other code in that code page will be calling the method. The thunk calls the method by its pointer, not its name and thunks are 'outside' code, not available until run-time. That private method will be flagged as a zombie even though it is not, but the scanner has no way of knowing that.

As to the larger question. Is it possible to create a 'call tree' for the entire scanned project? Yes. I'll leave that as an exercise for others when they get bored and want to spend a few months on a 'big' project.

----------


## xiaoyao

DO YOU HAVE CODE FOR SPLIT A function?
can split every part :Frown: public ,private),return type,optional default value


```
private function sum(byval a as long ,optional byval b as long=12 ) as long 'sum(3,4)=5
 sum=a+b
end function
```

----------


## LaVolpe

Splitting a function? In the scanner project, that is done because it needs to be done to find the properties of the function:
- start/end of method
- scope
- method type
- return vartype
- parameter count, parameters, parameter vartypes
- executable lines within the method

However, there is no single function in the project that does all of that. The scanner processes code files in two passes. Only if user  chooses to validate the project, then a second pass is done to parse out parameters and the executable lines within the method.

If you are asking me to write a routine for you, I will not. It is not that difficult to do, but you have other scenarios you may need to handle. For example, if a method is broken into 2 or more lines using continuation characters. And there are other things that can exist in a method: ParamArray for example, arrays in the parameters and return value. ByRef,ByVal and vartypes are optional also. And scope can be Public, Private, Friend (none are required), and can also include keyword: Static. 

If you have any further questions on how to parse something, post it in the general VB forums section. I won't be addressing "how-to" hints and workarounds on this thread regarding that topic. Happy parsing!

----------


## baka

I was looking for something like this, so I did a try, I think I found a bug,
I get the report:




> Results for config
> 	Items in zombie state
> 		Variable: fa in method login_setting_render
> 		Variable: kk in method login_setting_render
> 		Variable: ff in method login_setting_render




```
Sub login_setting_render()
    Dim ff&, kk&, fa&, ka&

    Mouse = 0
    engine.Render 7, 70, 188, 0, 0, 660, 185
    engine.Render 7, 70, 373, 0, 480, 660, 25
    engine.Render 7, 80, 211, 10, 1241, 640, 176
    WriteText 17, BS.MHelper(23), 380, 196
    renderOptions 232
End Sub
```

ka is missing.

and the same in another:
Variable: i in method BoxRender

but I have Dim i&, yy& and I cant find neither in the function.

and the same here:
		Variable: weak in method ShowMonster
		Variable: strong in method ShowMonster
		Variable: c in method ShowMonster

Dim a&, b&, c&, strong$, weak$, immune$

and immune should also be reported.
and a couple more places with similar results.

suggestions:
- check if a function is called from "module1" that is resident in "module2" and make sure its not "private"
(I know your program is not checking, since I can put a "Private" function on another module that is needed from another module, and it will not tell) that function need to be changed to Public to work.
sure if I try to compile it will give me an error, but its good to have. 
also, do you check like this:

module1: theres a call to "MyFUNC" that is in module1, but theres another MyFUNC in module2. so doublets. one is Private and one if Public.
from module3 I call MyFUNC believing it will take the "Private" one, but it will instead take the "Public" one. so good to report if theres such scenario.

----------


## LaVolpe

I'll have to double check and make sure when 2+ variables are declared on same line, the splitting routines are not off-by-one in some cases, ignoring the final variable.

For those routines where you see a 'bug', can you zip up the routines? Also include your module/class-level declarations. Don't worry about including other stuff called from those modules -- it does not need to be able to be compiled. I just want to dump those routines to a module or class in a test project and scan those.

I like your idea of looking for duplicate methods in bas-modules only, where one is public & the others are not. As for the other scenario. I thought I was checking for private methods in a bas-module that were not used -- maybe it's something new I was adding? Need to revisit that. However, the project won't check if a method should be public instead of private. One of the key 'rules' for using this tool is that the project must be able to be compiled, otherwise, false results can be reported.

I've been tweaking that project off & on over the past few months and might be time to post an update but I'd like to verify any changes work with what you are reporting.

----------


## LaVolpe

> Variable: i in method BoxRender
> 
> but I have Dim i&, yy& and I cant find neither in the function.


baka, the yy& may be a bug mentioned in previous reply where last variable in line might be ignored -- will fix that if that is the case.

But the i&, do you have i declared in the declarations section of that code page or publicly in a bas module? If so, then I have a bug where the i variable was found out of scope of the method & determined not a zombie when it should've been. This question is just to help me narrow down where to look.

----------


## Thierry69

Nb : La Volpe, have you seen my message?

----------


## baka

I managed to remove a lot and just show a bit and the functions that resulted in the missing variables.

yes its about the last variable.

----------


## LaVolpe

> I managed to remove a lot and just show a bit and the functions that resulted in the missing variables.


Thank you. I'll begin looking at it later this week, also to add in that other duplication check you suggested.

I found the bug where the last variable in a line with 2+ variables is being ignored. So that was an easy fix. If interested, you can fix that now... but the same fix needs to be applied in two different areas:

module: modMain, method: pvParseName_Variable
module: clsValidation, method: pvParseDeclares
change:


```
from...
        If n = p Then Exit Do
to...
        If n = p And lMode = 0 Then Exit Do
```

*edited*: the patch needed to be applied in 2 methods, not one. Above has been updated

----------


## fabel358

Really useful! many thanks LaVolpe!

----------


## AAraya

Useful tool - thanks for making this available!

I think I've found a bug... On the Standard Scan report window, the Save to File menu doesn't work.  It appends a portion of the text from some other line of the report to the end of the current line.  Strange behavior.

----------


## LaVolpe

> Useful tool - thanks for making this available!
> 
> I think I've found a bug...


Egads, that's embarrassing. You found some test code I forgot to remove.
Open the frmValidationHelp form and in the mnuMain_Click event, remove the 6 test-lines starting with "Dim s$"

----------


## AAraya

> Egads, that's embarrassing. You found some test code I forgot to remove.
> Open the frmValidationHelp form and in the mnuMain_Click event, remove the 6 test-lines starting with "Dim s$"


Happens to all of us - glad to help with the code clean-up!

----------


## LaVolpe

> Happens to all of us - glad to help with the code clean-up!


Hmmm, how to get the scanner to alert on test code  :Roll Eyes (Sarcastic):

----------


## georgekar

Attachment 178750

Checked. M2000 Environment (M2000 Language)..

----------


## fabel358

Sorry. Trying project scanner on a vb6 project of mine, just after loading the project the application stops with this error message. Why? How can i remedy?
Attachment 179969
Attachment 179968

----------


## xiaoyao

If you want to explain a huge BB 6 project works, you should use multi-threading, the speed can be increased by five to ten times.

----------


## Dragokas

Impressive work !
Thanks a much for this great tool !!!

----------

