rubberduck-vba / Rubberduck

Every programmer needs a rubberduck. COM add-in for the VBA & VB6 IDE (VBE).
https://rubberduckvba.com
GNU General Public License v3.0
1.91k stars 299 forks source link

VB6 IDE support #298

Closed wqweto closed 6 years ago

wqweto commented 9 years ago

Is this add-in supposed to work in VB6 IDE?

I think ANTLR parser has great potential for really useful features in the IDE. RegExp find&replace within routines with {Name} placeholder comes to mind.

rubberduck203 commented 9 years ago

Neither of us have an old copy of Visual Studio that supports VB6, so we've not tested it. The add-in is designed with Office VBA in mind. I know the unit testing framework isn't compatible though.

You might be able to get it to work if you know how to manually register the add-in, but I'm not sure it's compatible. Please feel free to test it and/or submit a pull request adding support.

retailcoder commented 9 years ago

@wqweto we have an actual Rename refactoring scheduled for version 1.3, much better than Find&Replace (even with regex) if you asked me ;)

As for VB6 support, that would be awesome...like @ckuhn203 said, unit testing wouldn't work, but I can see the improved navigation, code inspections and refactorings be useful in a VB6 environment.. only thing is, I don't have a VB6 IDE to play with - if you have one, and want to contribute, we'll be more than happy to accept a pull request that makes our ducky work in VB6!

wqweto commented 9 years ago

@retailcoder: I don't think current ANTLR parser is ready for rename a la VS.Net style. There is no symbol tables implementation and you can't move beyond parse trees (to AST) so you cannot tell if an identifier is a method or a local variable. Current parser cannot tell if A(i) is call method A with arg i or access array A with index i.

retailcoder commented 9 years ago

In computer science, a symbol table is a data structure used by a language translator such as a compiler or interpreter, where each identifier in a program's source code is associated with information relating to its declaration or appearance in the source, such as its type, scope level and sometimes its location. http://en.wikipedia.org/wiki/Symbol_table

That sounds an awful lot like what the IdentifierUsageInspector class is trying to achieve. [not-so-far-in-the-]future versions will see a better implementation, as it has already been noted that it's working harder than it needs to.

Thanks for putting us on the do-it-right track! Refactor/Rename will happen, I promise. :)

wqweto commented 9 years ago

I think it will be awful lot of work to do it with current (buggy) grammar. Besides, you need a two-pass parser as VBA allows usage without forward declaration (which is required in C/C++ outside classes).

For instance if you have a module with Public Test() As Long array and then in a form you have this:

Private Sub Form_Load()
    Dim result As Long

    result = Test(2)
End Sub

Private Function Test(ByVal Idx As Long) As Long
    Test = 42
End Function

Here Form_Load calls Test function and does not access global Test array.

So, unfortunately yes, you'll need a first pass to build symbol tables or you can never be sure about symbol reference/usage. (How to implement Refactor->Rename on the global Test array from sample?)

Besides, current grammar has no idea about preprocessor and if does by chance it needs inside #If ... Then/#Else/#End If to find valid statement which is not always the case, i.e.

#If True Then
    Select Case Idx
#Else
    Select Case Idx + 1
#End If
    Case 1
        Test = 123
    Case 2
        Test = 987
    End Select

is valid VBA code

rubberduck203 commented 9 years ago

@wqweto the grammar does understand pre-processor. https://github.com/retailcoder/Rubberduck/blob/GrammarIsFun/Rubberduck.Parsing/VBA.g4#L770-L773

I'm unsure if we've taken it into account in our current code base though.

wqweto commented 9 years ago

Btw, it's documented in the header that statements cannot be parsed if split by the preprocessor.

https://github.com/retailcoder/Rubberduck/blob/GrammarIsFun/Rubberduck.Parsing/VBA.g4#L39

retailcoder commented 9 years ago

Back to the supporting VB6 topic - I think all I'm missing is exactly where to register the add-in. For Office the key goes under HKCU/Software/Microsoft/VBA/VBE/6.0/Addins[64] - I believe if we simply verified if there's a key for the VB6 IDE we can register Rubberduck under it and it would just work.

Of course there's a number of features that would require some more thought - namely unit testing and source control integration - although SC would actually be simpler to implement in VB6 than in VBA... but I have no idea how I'd approach unit testing without an Application.Run method in the host (this limitation is also why unit tests don't work in Outlook).

Bottom line, I don't think there's any major showstopper in supporting VB6 - and given how VB6 projects are certainly larger than most VBA projects, the code inspections, refactorings and navigation features would definitely be more than welcome in that environment as well.

rubberduck203 commented 9 years ago

Relevant. I'm not sure which version of VS supported VB6. http://www.mztools.com/articles/2011/MZ2011012.aspx

rubberduck203 commented 9 years ago

I found the key buried in this old MSDN article.

https://msdn.microsoft.com/en-us/library/aa239603%28v=vs.60%29.aspx

= new RegKey(RegKey.getRootKey(RegKey.USER_ROOT),
        "Software\\Microsoft\\VisualStudio\\6.0\\Addins\\ 
        MyAddin.Connect ", RegKey.KEYOPEN_CREATE);
     regKey.setValue("Description", "Addin description goes 
  here.");
wqweto commented 9 years ago

Here is a sample VB6 add-in registration

[HKEY_CURRENT_USER\Software\Microsoft\Visual Basic\6.0\Addins\vbAdvance.Connect]
"Description"="#101"
"FriendlyName"="#100"
"SatelliteDllName"="vbAdvance.dll"
"CommandLineSafe"=dword:00000001
"LoadBehavior"=dword:00000003

Description and FriendlyName are either resource ids in SatelliteDllName are just plain strings.

retailcoder commented 9 years ago

Looks like extending the VB6 IDE requires a different interface?

vb6-rd-exception

This message box is Rubberduck's OnConnection code running:

    public void OnConnection(object Application, ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
    {
        try
        {
            _app = new App((VBE)Application, (AddIn)AddInInst);
        }
        catch (Exception exception)
        {
            MessageBox.Show(exception.Message, "Rubberduck Add-In Could Not Be Loaded", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
retailcoder commented 9 years ago

Asked on SO: http://stackoverflow.com/q/29294260/1188513

retailcoder commented 9 years ago

I think I got a definitive answer from a trustworthy source:

This is an Office add-in, it can only run in-process inside an Office app like Excel or Word. Trying to use it from the VB6 IDE is not meaningful, it is not anything like an Office app. There's an ancient KB article still around that covers Office extension development in VB6. Your existing code will not be helpful with that. — Hans Passant 35 secs ago

VB6 isn't going to happen, at least not without some major parts getting rewritten.

retailcoder commented 8 years ago

Removing by-design and, heck, status-declined tags. If other add-ins can do it, we can too.

retailcoder commented 7 years ago

The merge of #2289 is now making this very possible.

I don't have a VB6 IDE handy ATM, but I believe if the registry VB6 keys are created, Rubberduck will now try to run with the VB6 wrappers.... which will throw a NotImplementedException before long, but hey, progress!

The wrapper types that need to be implemented are under Rubberduck.VBEditor.SafeComWrappers.VB6. Right now there seems to be a problem with the VB6 IDE extensibility library we're using, looks like a lot of important members are missing, which makes implementing these wrappers pretty hard.

retailcoder commented 7 years ago

Oh, and Hans didn't know what he was talking about.

mansellan commented 6 years ago

I have a VB6 license (it's part of Visual Studio 6.0). I could take a look, but this looks like it could get a bit hairy...

retailcoder commented 6 years ago

@mansellan pretty much everything is setup already - you basically "just" need to implement the SafeComWrapper<T> types' members under the VB6 wrappers namespace, mirrorring what we have in the VBA wrappers namespace.

mansellan commented 6 years ago

And some COM registration I'm guessing.

retailcoder commented 6 years ago

@mansellan already taken care of. AFAICT all we need is wrappers that don't throw new NotImplementedException();

mansellan commented 6 years ago

@retailcoder cool, I'll take a look.

mansellan commented 6 years ago

OK, so there's more to this than initially thought.

We have a reference to a VB6IDE interop assembly Microsoft.VB6.Interop.VBIDE in the Rubberduck.VBEditor assembly (from \libs\Microsoft.VB6.Interop.VBIDE.dll. This IA was created by tlbimport, with its dependant Office assembly imported from the Office 12 typelibs (see https://chat.stackexchange.com/transcript/message/39454960#39454960).

Unfortunately, the VB6 extensibility API provides objects which implement the Office 8 interfaces, which are incompatible and thus cause runtime COM exceptions. To resolve this, we need to recreate the VB6IDE IA using an Office 8 tlb as a reference. That's the easy bit.

Were we then to reference the resultant IA from the Rubberduck.VBEditor, it would cause widespread typename collisions with the already referenced Office 12 types, as they both import to a namespace of Micorsoft.Office.Core

There are several options available:

  1. Ensure that the Office namespaces are disambiguated. There is an option on tlbimp.exe to specify a target namespace. Unfortunately, this only seems to affect the root assembly, with no way to do likewise for referenced assemblies. To overcome this, I tried:
    • Creating the Office IA first, then referencing it when creating the VB6IDE IA. Tlbimp chokes when doing this, as hidden types required by VB6IDE are not imported into the Office IA.
    • Disassembling the created VB6IDE and Office IAs, and adjusting the namepsaces manually. This seems to be a non-starter, as the decompiled code from ILSpy does not compile and is not easily encouraged to do so.

Therefore, Option 1 appears to be a dead end.

  1. Import both Office assemblies into the Rubberduck.VBEditor project, and alias them both to disambiguate. This would work, but litter the code with alias noise.

  2. Move the SafeComWrappers for VBA and VB6 into separate projects.

mansellan commented 6 years ago

Note also, that once this is resolved there will be further work to do, as the Office 8 API is missing some members we use on the VBA side, and has a different system for events.

retailcoder commented 6 years ago

Let's have Rubberduck.VBEditor.VBA.dll and Rubberduck.VBEditor.VB6.dll assemblies.

ThunderFrame commented 6 years ago

IIRC that's more inline with how MZTools does it.

Hosch250 commented 6 years ago

3 is easily my first pick.

mansellan commented 6 years ago

I've completed my initial investigations into what's needed. I was wrong about the namespace clash - Office 8 imports to a namespace of 'Office', which separates it neatly from Office 12's 'Microsoft.Office.Core'. That helps us tremendously, and means that we don't need to (and shouldn't) create additional projects.

I would recommend:

  1. Wait for the CW port
  2. Open a branch in the central RD repo where the work can be isolated until ready
  3. Do some housekeeping on the ComWrapper namespaces and folder layouts to better organise between VBA\VB6 and Office8\Office12
  4. Implement the new ComWrappers for VB6 and Office8
mansellan commented 6 years ago

I've reviewed all the interop assemblies to note differences in any members we're currently abstracting:

Not in Office.v8:

ICommandBar.Id - currently only used in hash\equality, along with many other properties. Use Name instead. ICommandBarButton.Click() - Events exposed from VBE. Major hassle, but fixable. ICommandBarButton.Picture, Mask - Workaround available (PasteFace) ICommandBarControl.IsPriorityDropped - Unused, appears redundant. Remove. IVBComponents.AddMTDesigner - Unused and undocumented. Remove?

Not in VB6 Extensibility:

IVBProject.Application - Only contains a version property, unused. Reachable from many other places, remove. IVBProject.Mode - No equivalent. May need to hack from window caption. IVBProject.Protection - Not applicable. Return "Unprotected". IVBProject.Open(string) - Equivalent is AddFromFile(string)?

With respect to enums, there are some differences, but the values that overlap match numerically. Suggest we just comment where enum values are applicable to one target only.

wqweto commented 6 years ago

IVBProject.Mode looks very similar to Private Declare Function EbMode Lib "vba6" () As Long although the API returns global state for the IDE (not per project).

retailcoder commented 6 years ago

To be fair I don't understand what Mode (design, running, break/debug) has to do with a VBProject. It definitely belongs at the IDE level.

mansellan commented 6 years ago

Been away for a while, but back now and looking at this again.

mansellan commented 6 years ago

image Getting there...

Hosch250 commented 6 years ago

YEAH!

mansellan commented 6 years ago

Command bar events working:

image

Implementation of the VB6 wrappers may be suboptimal and needs to be checked for leaks. I haven't touched the VBA side (save for a few namespace edits). From a quick click-test, most functionality appears to be working.

Note however that we will need a grammer that's tweaked for VB6 - the VBA grammar chokes on VBForms (looks like an attributes issue from the call stack), and likely other places too.

Doing final diff and lint before PR.

rubberduck203 commented 6 years ago

So, I’m a little out of the loop as far as what we’ve done with the grammar, but VBA and VB6 are really the same language, are they not?

Unless we’ve put VBA specific things in the grammar, I would expect we could share a single grammar. Can you elaborate a bit?

Awesome work btw. I know there are some folks who’ve been wanting this for a long time.

mansellan commented 6 years ago

this is the exception I get if I try and parse a VBForm:

image

I have no idea how to read \ debug it!

retailcoder commented 6 years ago

@Mansellan likely a fairly simple assumption that the grammar is making. Paste an exported MSForm's attributes in a new issue if you're not comfortable making the grammar changes, we'll fix this. The format can't have changed so much as to require a totally new grammar. I don't think the TypeLib API will work with VB6, so we'll likely need to keep the attributes pass for VB6 hosts.