VBA – Early Binding and Late Binding – Part 2

In my first post on the subject, VBA – Early Binding and Late Binding, I went over the basics, but I thought I’d push forward a little more to help demonstrate how to implement “The Best of Both World”.

As I explained, in my first article, the best of both worlds consists of developing using early binding, but deploying using late binding. The issue becomes needing to change the procedure declarations between each technique. Thankfully, Conditional Compilation Directives (CCDs) can solve this dilemma very easily.
 

What are Conditional Compilation Directives?

Conditional Compilation Directives, in this instance, is an approach in which we can get our VBA program to compile one sequence, or another, depending on a condition of our choosing.

In our case, this directly equate to compiling one set of declarations when we want Early Binding, and compiling another set of declarations when we want Late Binding.
 

How is This Done?

Actually, using Conditional Compilation Directives is surprisingly easy, once you are aware of it and it can be very useful far beyond the Early/Late Binding issue.

The Basic Concept of Conditional Compilation is:

    ' #Const EarlyBind = True 'Use Early Binding, Req. Reference Library
    #Const EarlyBind = False    'Use Late Binding
    #If EarlyBind = True Then
        'Early Binding Declarations
        Dim oOutlook          As Outlook.Application
        Dim oOutlookMsg       As Outlook.MailItem
    #Else
        'Late Binding Declaration/Constants
        Dim oOutlook          As Object
        Dim oOutlookMsg       As Object
        Const olMailItem = 0
    #End If

In the above we declare a Constant EarlyBind and depending on its value our program will choose to use one set of declaration or another. So when we’d be developing our program, we set our constant to

#Const EarlyBind = True

and include the necessary Reference Library, in the example above Microsoft Outlook XX.X Object Library to benefit from Intellisence during development and access to help, object browser, … .

On the other hand, when deploying our program to the client/end-users, we would remove the reference library and switch the constant to

#Const EarlyBind = False

to eliminate any versioning issues.

The Best of Both Worlds, you see!
 

Be Aware

Use of the Hashtag

Notice that in the example provide above both the IF and the Const are preceeded by a #. This is critical! This is what changes regular code into compiler directives and without it your code will not work as intended.

Declaration of the Compiler Constant

You may be tempted to simply include the #Const … directly in your procedure, but as a general rule you’d be much better served to declare it at a module level so it applies throughout your entire module rather than each procedure being independent from one another. This will make development easier as you will have but one value to alter instead of needing to do so for every procedure.

If you do go the route of declaring #Const in each procedure in the same module, you will need to ensure they all have unique names or else you will get compilation errors:

Compile error:
Duplicate definition

 

What Else Can Conditional Compilation Do For Me?

Beyond the aspect of Early and Late Binding for which Conditional Compilation Directives (CCD) can be very useful, CCDs are also extremely useful now that we are facing different bitnesses of our Office installations requiring different API declarations. As such, instead of trying to juggle different sets of APIs declarations, it can simply be easier to use CCDs to build a single declaration which will work in all scenarios, something like

    #If VBA7 And Win64 Then
        'x64 Declarations
    #Else
        'x32 Declarations
    #End If

A Concrete Example

An extremely common API is the apiGetUserName which we often use in the function fOSUserName to get the current user’s OS Username. The above function works great in x32 version of Office, but crashes in x64. So let’s put to use our new knowledge of CCDs to build a universal API call that will work in both scenarios.

#If VBA7 And Win64 Then
    'x64 Declarations
    Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long
#Else
    'x32 Declaration
    Declare PtrSafe Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long
#End If

Function fOSUserName() As String
    ' Returns the network login name
    Dim lngLen As Long, lngX  As Long
    Dim strUserName           As String
    strUserName = String$(254, 0)
    lngLen = 255
    lngX = GetUserName(strUserName, lngLen)
    If (lngX > 0) Then
        fOSUserName = Left$(strUserName, lngLen - 1)
    Else
        fOSUserName = vbNullString
    End If
End Function

You’ll also notice in the above example, we are not defining a #Const. VBA7, Win64 and others are defined by the system at runtime and this is what permits us to build such CCDs to make our code adaptive.

You can find a full list of all the available Compiler constants at:

 

A Global Compiler Constant

Sadly, we cannot use keywords like Public, Private, … with such constants. When we declare a constant it has a module level scope (even when created in a procedure).

#Const statement in code          Private to the module in which they are declaredMicrosoft

Thus, you must re-declare it in every module in which you need to use it. This is why I will try hard to group all my WMI procedures in one module, my WIA in another, … so I can have such a constant at the top that I switch and all my procedures switch.

If you truly want a project level (global) constant, there is one way to do so and that it to define the constant in the Project Properties dialog Conditional Compilation Arguments text box.

In the VBA Editor (VBE):

  1. From the main toolbars, click on Tools
  2. Click on Database Properties …
  3. On the General tab, enter the constant and its value in the Conditional Compilation Arguments text box. If you want to input multiple values simply separate them by a colon (:).

So, say we wanted to take my ‘#Const EarlyBind = True’ example and make it apply project wide, we would simply enter:

EarlyBind = -1

and if we wanted to enter a couple constants, we would enter:

WMI_EarlyBind = 0 : WIA_EarlyBind = -1
Important Difference!
Unlike the Module level syntax, the Conditional Compilation Arguments text box does not appear to support values of True/False. Instead, you must enter the numeric equivalent (as was shown in the examples above).

True => -1
False => 0

The rest of the code, the way we check and use constants in your procedures … remains unchanged.

Should you ever wish to automate these values in some way, note that you can read the current value of the Conditional Compilation Arguments by doing:

Debug.Print Application.GetOption("Conditional Compilation Arguments")

and you can update the value by doing:

Call Application.SetOption("Conditional Compilation Arguments", "Your New Value ...")

Overriding Project Level Constants

If you declare a Project level constant and also declare it in a procedure, the procedure level declaration will reign supreme. This can be very useful during development/testing.

I’d like to thank both AHeyne and John (see the comments section below) as a recent discussion on the matter helped enlighten me so I could expand this article to include this latest section.
 

Useful References

8 responses on “VBA – Early Binding and Late Binding – Part 2

  1. AHeyne

    Hi Daniel, you state this:
    > The simple fact of the matter is VBA won’t allow you to have multiple #Const declarations for the same variable in different procedures. So if you declare #Const EarlyBind in multiple procedures you will receive an “Compile Error: Duplicate definition” error. As such, always declare the #Const as a public module level variable so it can apply throughout your project and is easily accessible to change.

    Unfortunately I can’t follow that.
    First point is that I of course can have multiple same named compiler constants in different class modules.
    As far as I know I also need to have them to provide this constant for each of these class modules.

    Second point, and this is by far more interesting to me, is:
    How can one declare a ‘public/global’ compiler constant in only one module so that other (class) modules can see/work with it?
    Did you ever tried to? ‘Public’ or ‘Global’ are not accepted in the declaration of the compiler constant.

    1. Daniel Pineault Post author

      You are entirely right. It was an error at the time of writing the article.

      No, sadly, you can’t create a global conditional compilation directive. So you have to do so in each module. I too really wish we had such an ability it would simplify coding/testing/…

  2. John

    Hi Daniel, it is actually possible to create project-wide (so accessible from every module) compiler constants … not in code, rather in the Conditional Compilation Arguments text box in the Project Properties dialog (see https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/project-properties-dialog-box).

    These compiler constants can then be accessed using #If and #ElseIf directives in any module. You can then switch between early and late binding across all modules in a project just by changing the value of one constant.

      1. AHeyne

        Uh, I expected you already know this, because it is documented in one of your mentioned references (MSDN – Using Conditional Compilation). 😉

        1. Daniel Pineault Post author

          I guess I’m flawed in that I don’t remember absolutely everything I may, or may not have read. 🙁

          The dialog may work, but once again it is an extra 4 clicks each time I want to change a value. For a new article I’ve been working on I would have been in and out of that dialog at least 100 times, I would have gone insane using the dialog. A global declaration would be so much more helpful! It’s is nice to know of, but still isn’t the solution I was hoping for.

          What’s nice here though is that we can use:

          Application.GetOption("Conditional Compilation Arguments")
          And
          Application.SetOption("Conditional Compilation Arguments")

          To read/write the values, but even that, I don’t think will simplify things any further than simply using the Properties dialog. Still good to be aware of though.

      2. John

        Pleasure, I learn plenty of new tricks from your blog posts so glad to pay back! To clarify, you are right (in your article) that compiler constants that are defined in the Conditional Compilation Arguments text box can only be given an integer value – and that 0 equates to False and any other value equates to True.

        As the value is an integer then, naturally, its value has to range from -32768 to 32767. A point to note, though, is that if you set a value of -10000 or less then (while the VBE honours the value), the number (not the name of the constant) will disappear from the Conditional Compilation Arguments text box the next time you open the Project Properties window.

        What … bugs in a Microsoft product … never!!!