Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debug.Print in VBA

In VBA Debug.Print prints to the Immediate window.

I just found out that using a semicolon (;) makes it print at the position of the caret/text cursor which seems odd.

Debug.Print "a" & "b" & "c"
Debug.Print ; "a"; "b"; "c"
Debug.Print "a", "b", "c"

Prints the following.

abc
abc
a             b             c

That was my main question before I found it in the documentation and understood it a little more.

Use a semicolon (;) to position the insertion point immediately following the last character displayed.

My question now is if it is possible to use a named argument like this:

Debug.Print object:="..."

Intellisense usually helps finding the arguments' names but it doesn't list any.

I also tried object or outputlist like it shows in the docs but it throws an error.

Is Debug.Print different in that regard?

like image 991
user7393973 Avatar asked Jan 27 '23 01:01

user7393973


1 Answers

Debug statements are indeed different from everything else. If you look for a Debug module in the Object Browser, you won't find it, even with hidden classes and members shown.

Debug.Print and Debug.Assert statements are literally baked into the language [parser] itself - the comma here doesn't mean "argument separator", instead it's a special-form syntax that is [apparently] reserved for Print methods (note: VBA user code cannot use Print for a method name, it's reserved).

So Debug statements are basically special kinds of keywords. IntelliSense / parameter quick-info is shown for argument list syntax elements, but syntactically, the "arguments" of a Debug.Print are an output list, exactly like the Print statement does.

Note that the VBE automatically turns a ? token into a Print statement:

Debug.? --> Debug.Print

There's quite a bit of historical baggage with Print: the keyword/command (and its ? shorthand) was used in old BASIC dialects to output things to a screen... or an actual [dot matrix!] printer.

So the short answer is, Debug statements are made with keywords, not member calls - and that's why IntelliSense isn't of any use with them, since there aren't any arguments.

The Rubberduck project has a fun story with these statements... because it isn't really possible to parse a typical Debug.Print statement any differently than any other implicit callStmt (i.e. it looks and parses like any other procedure call), we had to give the statement its own dedicated parser rule, and "declare" a fake DebugClass module and make Print a "method" of that "class" in order to be able to track uses like we do with other early-bound identifier references:

VBE7.DLL;VBA.DebugClass.Print (procedure)

But there really isn't such a thing: statements with an output list are baked into the language at the parser & compiler level, whereas literally every other member call you ever made in VBA was a member of some module - hit F2 and browse the members of the VBA standard library: you'll find CLng type conversion, Now and DateAdd date/time functions, MsgBox, DoEvents, and so many others - all belong to some module. But Debug statements are closer to a Stop or Resume keyword, handled at a lower level.


Further proof that there's more than meets the eyes - other than the simple fact that default syntax highlighting in the VBE will highlight both Debug and Print in bright keyword-blue, if you compile a COM-visible class written in C#:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("6F25002E-2C9F-4D77-8CCB-A9C0E4FB2EF1")]
public interface ITestClass
{
    [DispId(1)]
    void Print(string value);
    [DispId(2)]
    void DoSomething();
}

[ComVisible(true)]
[ComDefaultInterface(typeof(ITestClass))]
[ClassInterface(ClassInterfaceType.None)]
[Guid("6F25002E-2C9F-4D77-9624-6CA79D4F088A")]
[ProgId("PrintTest.Class1")]
[EditorBrowsable(EditorBrowsableState.Always)]
public class Class1 : ITestClass
{
    public void Print(string value)
    {
        /* no-op */
    }

    public void DoSomething()
    {
        /* no-op */
    }
}

..and then invoke it from VBA code...

PrintMethodTest client code

The DoSomething method can be invoked, but the Print method will throw error 438 - just like it would if you tried to qualify it with anything other than Debug. So how come Print works in an Access report's code-behind then?

The interface isn't documented so this is pure speculation, but there's an IVBPrint interface that looks very much like something VBA would be looking for:

[
  odl,
  uuid(000204F0-0000-0000-C000-000000000046),
  nonextensible
]
interface IVBPrint : IUnknown {
    HRESULT _stdcall WriteText([in] BSTR strText);
    [propput]
    HRESULT _stdcall Column([in] long retVal);
    [propget]
    HRESULT _stdcall Column([out, retval] long* retVal);
};

If that's the case, then error 438 is just VBA's way to say "IVBPrint implementation not found"

like image 121
Mathieu Guindon Avatar answered Jan 31 '23 11:01

Mathieu Guindon