Category Archives: Code Garage

Comparing Variants

0
Filed under Code Garage, VB Feng Shui

Here’s another handy bit from my VB6 code shed out back.

If you’ve worked much with databases in VB6, you know that you almost always end up having to deal with database NULLs at some point.

And, in VB6, the only variable type that can actually deal with nulls is the Variant.

For the most part, my code avoids nulls by coercing them to the most appropriate “null value”, either a 0, or a 0 length string, etc, depending on the underlying data type.

However, there are instances where I’ve needed to compare two values directly from the database, and didn’t want to coerce them up front.

That’s where this handy routine comes in.

Public Function CompareVariant(Var1 As Variant, Var2 As Variant, Optional CompareMethod As VbCompareMethod = vbBinaryCompare) As Long
   '---- Compare 2 variants that might contain NULL or empty values

   Dim bVal1Null As Boolean
   Dim bVal2Null As Boolean
   Dim bVal1Empty As Boolean
   Dim bVal2Empty As Boolean
   Dim bSameValues As Boolean

   bVal1Null = IsNull(Var1)
   bVal1Empty = IsEmpty(Var1)
   bVal2Null = IsNull(Var2)
   bVal2Empty = IsEmpty(Var2)

   '---- variants are the same if
   '     1) both are null
   '     2) both are empty
   '     3) they are otherwise equal
   If (bVal1Null And bVal2Null) _
      Or (bVal1Empty And bVal2Empty) Then
      CompareVariant = 0
   Else
      '---- you can only check for equal values is if neither of the values is Null or Empty
      If Not (bVal1Null Or bVal1Empty Or bVal2Null Or bVal2Empty) Then
         If CompareMethod = vbTextCompare Then
            CompareVariant = StrComp(CStr(Var1), CStr(Var2), vbTextCompare)
         Else
            If Var1 > Var2 Then
               CompareVariant = 1
            Else
               CompareVariant = (Var1 < Var2)
            End If
         End If
      ElseIf bVal1Null Then
         '---- This is arbitrary, I'm determining that NULL is < empty
         '     this might not be universally appropriate, though
         CompareVariant = -1
      Else
         CompareVariant = 1
      End If
   End If
End Function

Basically, the idea is similar to the built-in VB function StrComp, but it intelligently deals with potentially NULL or empty variants as well.

Are there faster ways to do this? Probably. But I find I need the functionality so infrequently, it hasn’t been a priority to optimize.

Still, if you need it, coding this up each time would be a complete pain in the ass (and unfortunately, I’ve seen that tack taken more than a few times).

If anyone can improve on this, please let me know!

Module Properties?

0
Filed under Code Garage, VB Feng Shui

I’m thinking of starting an ongoing series of posts highlighting some of the more “interesting” things you can do with VB6 that are not, at least to my knowledge, particularly well known. I figure, VB6 has started on that long slow road into SNOBOL-dom, the land where old languages go, not necessarily to die, but to continue living, just with very few people paying any attention. We can only hope C and C++ will head there eventually.

Anyway, might as well collect a few of my favorite hacks, tweaks, and WTF?! bits before they all end up only in the depths of the WayBack machine

Here’s one that you don’t see often. If I remember correctly, there is a bit of a performance hit with it, but it’s pretty minor.

Did you know a VB module can actually contain properties?

Huh, you say. Interesting, but so what?

Well, since public elements of modules are public project-wide, you can create some pretty handy functions this way.

For instance, say you have a function to read an entire text file in at one shot, and one to write a whole text file. You could have two subs to do the dirty work. Or you could do something like this (put this code in a BAS module, not a class):

Public Property Let FileTextContent(ByVal FileName$, ByRef NewValue$)
   '---- Block write the entire file's content in one shot
   Dim fh
   
   On Error Resume Next
   fh = FreeFile
   Open FileName$ For Output As fh Len = 2048
   Print #fh, NewValue$;
   Close #fh
End Property


Public Property Get FileTextContent(ByVal FileName$) As String
   '---- Block read the entire file's content in one shot
   Dim buf$
   Dim fh
   
   On Error Resume Next
   fh = FreeFile
   Open FileName$ For Binary Access Read As fh Len = 2048
   buf$ = Space$(LOF(fh))
   Get #fh, , buf$
   Close #fh
   FileTextContent = buf$
End Property

With a property like this in place, you can then read a file’s content with Just:

x$ = FileTextContent(MyFile$)

Which looks exactly like a function call, granted. But you can write a file’s content with:

FileTextContent(MyFile$) = x$

It’s a minor syntactic difference from just using two subs, or maybe a sub and a function. But personally, I think it reads a little nicer.

Of course, you can also use this technique to provide for global scope properties, ie properties that don’t belong to any particular class. This is sometimes handy when you need to expose a value globally, nut you also want to perform some processing on it during an assignment or a retrieval.

You could define the global property via a GLOBAL variable definition, but that gives you no “hook” to do your processing on the value when code retrieves it or sets it. Instead, use something like this in a BAS module:

Private rMyProp as long
Public Property Let MyProp(Byval NewValue as long)
   '---- Block write the entire file's content in one shot
   
   'DO YOUR CODE HERE
   rMyProp = NewValue
End Property

Public Property Get MyProp() As Long
   
   'DO YOUR CODE HERE   
   MyProp = rMyProp
End Property

Now, you have a convenient way to get and set the value from everywhere in your project, AND you have point at which you can hook in to preprocess or post-process the value as necessary.

And, if you name the module something short and sweet, say, like UTILITY, then you’ll get handy intellisense as well (just type “UTILITY.” to get the props and functions available on it).

Native or Psuedo-Code VB6?

2
Filed under Code Garage, VB Feng Shui

VB6 has the interesting capacity to compile to either native code or pseudo-code.

Native code is actual Intel machine code. Pseudo-Code is much like the .NET “Intermediate Language” or IL, although, I suspect, a little less refined<G>

Most VB6 programmers I’ve known tend to compile to Native code although pseudo-code can be quite handy in some circumstances. First, it compiles much faster. Second, the generated EXE’s or DLL’s are typically much smaller than Native code EXE’s or DLL’s.

But what if you want, or need, to tell the difference at run time?

You could write up a conditionally compiled constant, but then you have to remember to switch the condition when appropriate. Still, that’s probably the ideal way to go.

But, that’s not really  a runtime solution, now, is it.

I happened upon the solution ages ago when I was researching an article I wrote for VBPJ about implementing TRY CATCH exceptions in VB6.

Essentially, you grab the first byte pointed to by a function pointer and check it. If it’s an &HA1, you’re in the IDE running pseudo-code.

If it’s &HBA, you’re running as compiled pseudo-code.

Anything else means you’re running compiled native code.

As far as I can tell, this technique works regardless of optimization settings, etc.

Yeah, it’s probably a little overkill, but that’s what hacking is all about. Plus, knowing whether you’re in the IDE without scrambling the state of the ERR variable is quite handy in some circumstances. I created a separate function for that purpose.

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)

Private Function pGetAddr(addr As Long) As Long
   '---- utility function for IsNative and IsIDE

   pGetAddr = addr
End Function


Public Function IsNative() As Boolean
   '---- determine whether we're running
   '     a Native code EXE, or a PsuedoCode exe
   '     Also can determine whether we're in
   '     IDE or not
   '
   '     Now this is HardCode!
   '
   Static Inited As Boolean
   Static bIsNative As Boolean
   Dim b As Byte

   If Not Inited Then
      CopyMemory b, ByVal pGetAddr(AddressOf pGetAddr), 1
      Select Case b
         Case &HA1
            '---- In IDE
            bIsNative = False
         Case &HBA
            '---- Psuedocode compiled
            bIsNative = False
         Case Else
            '---- native code Compiled
            '     the actual byte might be any number of things
            '     because of optimizations in use
            '     but will generally be either &H55, &HC7 or &H83
            bIsNative = True
      End Select
   End If
   IsNative = bIsNative
End Function


Public Function IsIDE() As Boolean
   '---- determine if we're running in environment
   Static Inited As Boolean
   Static bIsIDE As Boolean
   Dim b As Byte

   If Not Inited Then
      CopyMemory b, ByVal pGetAddr(AddressOf pGetAddr), 1
      '---- &HA1 means we're in the IDE, else we're not
      bIsIDE = (b = &HA1)
   End If
   IsIDE = bIsIDE
End Function

The VB6 CreateObject Function

8
Filed under Code Garage

This is one of those often used functions in VB6 that could use a little improvement. The main problem with CreateObject is when it throws an error, it doesn’t report what object it was attempting to create at the time, which would be quite a handy thing to know.

Fortunately, CreateObject is one of small number of intrinsic functions in VB that can be overridden quite easily.

In other words, you can define your own CreateObject function, and VB will call your version instead of it’s built in version anywhere that you use it. This is quite handy because it means you can use the enhanced version without changing any calling code.

For instance, here’s a CreateObject function I put together that expands on the default error message string if it encounters a problem creating the object asked for. It simply adds in the PROGID of the object you’re trying to create.

Public Function CreateObject(ByVal Class$, Optional ByVal ServerName$) As Object
   '---- override the CreateObject
   '     function in order to register what
   '     object is being created in any error message
   '     that's generated
   Dim Source$, Descr$, ErrNum

   On Error Resume Next
   If Len(ServerName$) Then
      Set CreateObject = VBA.CreateObject(Class$, ServerName$)
   Else
      Set CreateObject = VBA.CreateObject(Class$)
   End If

   If VBA.Err Then
      Source$ = VBA.Err.Source
      Descr$ = VBA.Err.Description
      ErrNum = VBA.Err
      Descr$ = Descr$ & " (ProgID: " & Class$
      If Len(ServerName$) Then
         Descr$ = Descr$ & ". Instantiated on Server '" & ServerName$ & "'"
      End If
      Descr$ = Descr$ & ")"
      On Error GoTo 0
      VBA.Err.Raise ErrNum, Source$, Descr$
   End If
   On Error GoTo 0
End Function

Put this function in a normal BAS module, and anywhere that you use CreateObject already, you’ll now be calling this function.

And to answer a few questions:

Q: Why do you use the VBA prefix for CreateObject and the ERR object in this piece of code?

A: In the case of CreateObject, you have to prefix it or this function would end up calling itself instead of the real VB CreateObject function.

In the case of the ERR object, see below.

Q: What other functions can be overridden?

I haven’t done an exhaustive study of it, but I can say that a particularly handy object to override is the built in VB ERR object defined in the VBA library.

Overriding that object allows you to extend error handling functionality in all sorts of ways.

For instance, you could add a LOG method to the ERR class, or a MSGBOX method that automatically formats an error for display in a message box.

But that’s a topic for another post. 

The Code Garage

0
Filed under Code Garage

I’m planning on making some select code snippets and other bits and pieces available and this is where they’ll end up.
For now, it’s basically just a placeholder page.