Showing posts with label Tips and Tricks. Show all posts
Showing posts with label Tips and Tricks. Show all posts

Dynamic Declarations and Value Assignments

Version: 2.00.00 - last update: Saturday, March 22, 2014, 12:30:00

Previous Entry (disabled ATM)Tips & Tricks HomeNext Entry (disabled ATM)


This is all about how to avoid VFP's Macro Substitutions and other SLOW VFP commands, and how to substitute them with FASTER alternatives using VFP9.0

Intro

In case you do not already have noticed it, let me tell you again: I dislike VFP's Macro Substitutions! At least when they are used unnecessarily, because IMHO, this is either evidence for the lack of VFP skills, or even worse, intellectual laziness! In most contexts macro substitutions are superfluous! I will give you examples how to code it the right way instead of lazily using macros. But here will be more…

What will be in here

Beneath VFP's Macro Substitution there are a lot of functions and commands that run slower than others which do the same job in VFP 9. I will use this post to collect my findings, either found by myself, or found in the internet (then I will link to the source of course!), to give you a good starting point and a profound platform for your own 'native' speed optimizations.

What will NOT be in here

This is not the place to discuss the basics of good programming. E.g. we will not talking about FOR…NEXT loop constructs and that it would be better to initialize variables outside the loop to speed things up. I'll leave this trivia to the plethora of Beginners Books out there in the wild. Go and get one of these if you're just starting your career as a programmer…
Keep in mind: This is a VFP 9.0 blog! Sure, some VFP 9.0 functionality I'm talking about now wasn't available in older versions. Maybe you still have to use VFP 8.0 (or even 7.0) - What a pity! I do believe that most of us are using VFP 9.0 today. Thus, there will be no discussions in this post how to solve a particular problem with one of the older versions of VFP!

Please let me know!

If you have one or more good examples for speeding up your applications by simply replacing a slow VFP command with another faster one, please let me know! Please add a comment at the end of this post. I will review and add it when I'm going to moderate incoming comments next time.


Content

At the moment this section still is pretty disordered – I'm just starting to collect the tips. I will try to create some kind of useful categorization later.


Manipulation of variables

Example: Create a private variable with a name stored in a class property

* Instead of doing it the uncool way:
Local lcName
lcName = This.cVarName
Private &lcName.
* Code it short & sweet like so:
Private (This.cVarName)

Example: Assign a value to the variable above

* Instead to prove your brain death:
Local lcName
lcName = This.cVarName
&lcName. = "123456"
* Code it smart like
this: Store "123456" TO (This.cVarName)

Note: Using parenthesis with VFP's STORE command tells FoxPro not to store the value to the memory the variable is pointing to but to the memory the content of the variable is pointing to. A regular variable is called a (named) pointer. What we get when bracketing a variable is a pointer to a pointer, which also is called a handle. You can assign a value to a handle using VFP's STRORE command only!


Restore Saved VFP Settings

Example: Saving Set("SAFETY")

Local lcSafety AS String
lcSafety = Set("Safety")
Set Safety Off
*\\ Do wild things now
*//    and when done...
* Instead of doing it the lazy way:
Set Safety &lcSafety.
* Code it running fast and keep it portable!
If m.lcSafety == "ON"
   Set Safety ON
EndIf

Note: You temporarily saved a VFP setting and changed it to the state you needed. If it is an ON/OFF setting you do NOT need a complete IF…THEN..ELSE construct during restore, but a simple IF…ENDIF only! What do you think runs faster in average? I mean there is a good 50/50 chance that you will not have to reset the setting at all because it was already set just the way you needed it!


Access Fields of an Aliased Table

Example: Read the value from a field named "PKey" of a table opened under an alias name stored in a property.

* Instead of taking the long way home:
Local lcAlias
lcAlias = This.cAlias
Return &lcAlias..Pkey
* Code it as a one-liner and win!
Return Evaluate(This.cAlias+".Pkey")

Evaluate() runs faster than a Macro Substitution! Thus, you should always prefer Evaluate() to Macro Substitutions when dealing with table.fieldnames access!


<to be continued…>


Previous Entry (disabled ATM)Tips & Tricks HomeNext Entry (disabled ATM)

Write Protection for Properties

Version: 2.00.00 - last update: Friday, March 28, 2014, 2:48:00

Previous Entry (disabled ATM)Tips & Tricks HomeNext Entry (disabled ATM)


This is about a useful, generic protection scheme you may want to add to your Base Classes.

Intro

Encapsulating data is one of the basics of OOP design. Protecting a property is called information hiding. Sometimes we do not want data to be totally hidden but accessible without loosing control over its integrity. That's the point when read-only properties come into play. There is no mystery about how to write protect a property in VFP. Just add an _Assign() method to it and you're almost done… unless you have to grant write access for an elected set of other objects, also called Friends.

Once upon a time…

… I started trying something like this:

Define Class MySpecialOne As Custom
   yResult = $0.0000
   Protected Procedure yResult_Assign
      Lparameters tyResult As Currency
      Error 1740, "yResult" && "name" is a read-only property
   Endproc
Enddefine

Apparently too much of a good thing. Now, the property value couldn't be changed at all. After that first try I changed my class design to use a protected property. But then, read access was lost for all other external class instances. What a mess, I had to add Getter/Setter functions accordingly to solve that:

Define Class MySpecialThree As Custom
   Protected yResult
   yResult = $0.0000
   Protected Procedure Set_yResult
      Lparameters tyResult As Currency
      *\\ A typical private Setter
      This.yResult = m.tyResult
   Endproc
   Function Get_yResult() As Currency
      *\\ A typical public getter
      Return This.yResult
   Endproc
Enddefine

At that point, because VFP doesn't support the uniform access principle, my modification broke the inner workings of my application because I changed the interface of one class without reflecting that all over the place.

A Better Approach(?)

Finally, I returned to my unprotected property. This time, I created a protected flag variable in my class that had to be set before assigning a new value to my yResult property to signal write access granted:

Define Class MySpecialFour As Custom
   yResult = $0.0000
   Protected lWriteGranted
   lWriteGranted = .F.
   Protected Procedure yResult_Assign
      Lparameters tyResult As Currency
      If Not This.lWriteGranted
         Error 1740, "yResult" && "name" is a read-only property
      Else
         This.yResult = m.tyResult
      Endif
   Endproc
   Procedure CalculateWhatEver() As Void
      *\\ Do some wild things here...
      *\\ ... then store result
      This.lWriteGranted = .T.
      This.yResult = m.lyLocalValue
      *\\ Never forget to reset flag!
      This.lWriteGranted = .F.
   Endproc
Enddefine

This approach worked better, but there were three drawbacks: You had to write 2 additional lines of code (point one and two:-) and, if you forgot to reset the flag property at the end of your processing the whole write protection scheme rendered useless (point three). At least, the code had little impact on performance.

Refinement

After a while I came up with the following neat solution to solve all of the above problems.

Define Class MySpecialFive As Custom
   yResult = $0.0000
   Protected lWriteGranted
   lWriteGranted = .F.
   Protected Procedure yResult_Assign
      Lparameters tyResult As Currency;
         , tlInternal As Boolean
      If Not (This.lWriteGranted Or m.tlInternal)
         Error 1740, "yResult" && "name" is a read-only property
      Else
         This.lWriteGranted = m.tlInternal
         This.yResult = m.tyResult
      Endif
   Endproc
   Procedure CalculateWhatEver() As Void
      *\\ Do some wild things again...
      *\\ ... then store result
      This.yResult_Assign(m.lyLocalValue, .T.)
      *//
   Endproc
Enddefine

I must admit, I liked that solution, because it reduced the value assignment code down to one line again.

In fact, the code works well but takes twice as long to complete (no lunch for free)! Maybe you have to play with the class and watch the assignments in your debugger to understand what I mean. The slight performance degradation isn't noticeable on todays machines, all what remains is the clumpy assignment call. Anyway, write access can only be granted to methods of the class. All other external classes cannot write to the yResult property without additional arrangements are made for it.

Generic Getters/Setters

One way to jump over that hurdle is to create a pair of generic methods like shown below. We also have to create one additional security property in our classes to hold our 'grant access token'. In the class MyBaseCustom below it is called eSecToken and may hold any kind of value you might consider useful.

Define Class MyBaseCustom As Custom
   *\\ test properties
   Dimension aTest[1,2]
   cTest = "Hello World"
   *\\ class access security token
   Protected eSecToken
   eSecToken = "Secret"
   *//
   Procedure SetValue
      Lparameters tcName As String;
         , teValue As Variant@;
         , teSecToken As Variant
      If Not This.eSecToken == m.teSecToken
         *\\ This.eSecToken holds class-global security
         *\\		token granting write access
         Error "Security token wrong or missing"
         *//
      Else
         *\\ Write access granted
         tcName = Alltrim(Transform(m.tcName))
         If Not (Pemstatus(This, m.tcName, 5) And ;
               PemStatus(This, m.tcName,3) == "Property")
            Error "Property not found"
         Else
            If Type("This." + m.tcName, 1) == "A" And;
                  Type("m.teValue", 1) == "A"
               *\\ array -> array handling
               Dimension ("This."+m.tcName+"[1]")
               Local lcTmp As String
               lcTmp = "This."+m.tcName
               *\\ I do HATE macro substitution! But in
               *\\		this case we have to use it!
               Acopy(teValue, &lcTmp.)
            Else
               *\\ all other (scalar) teValue handling
               Store teValue To ("This."+m.tcName)
            Endif
         Endif
      Endif
   Endproc
   Function GetValue
      Lparameters tcName As String;
         , teSecToken As Variant;
         , tnResult As Integer@ && in/out
      tnResult = 0 && := scalar value will be returned
      Local leRetVal As Variant;	&& scalar return value
      , llSilent As Integer	&& no parameter errors
      leRetVal = .Null.
      llSilent = Pcount() > 2
      If Not This.eSecToken == m.teSecToken
         *\\ The .eSecToken property holds class-global
         *\\		security token granting write access
         tnResult = -1 && error
         If Not m.llSilent
            Error "Security token wrong or missing"
         Endif
         *//
      Else
         *\\ Read access granted
         tcName = Alltrim(Transform(m.tcName))
         If Type("This."+m.tcName)== "U"
            *\\ Use Type() testing because we can be asked
            *\\		to return a single array element, too!
            tnResult = -2 && := error
            If Not m.llSilent
               Error "Property not found"
            Endif
            *//
         Else
            *\\ Don't forget: VFP 9 is able to return arrays!
            If Pemstat(This, m.tcName, 5) And ;
                  Type("This." + m.tcName, 1) == "A"
               *\\ full array was requested!
               Local lcTmp As String
               lcTmp = "This." + m.tcName
               tnResult = 1 && := just an info - no error!
               *\\ I do HATE macro substitution! But in
               *\\		this case we have to use it!
               Return @&lcTmp. && >>>>>>>>>>>>>>>>>>>>>>>>>
            Else
               *\\ property or single array element request
               leRetVal = Evaluate("This."+m.tcName)
            Endif
         Endif
      Endif
      *\\ at this point we will always return a non-scalar value
      Return m.leRetVal
   Endfunc
Enddefine

Introducing the new GetValue() / SetValue() method pair we now have a generic, secured way to grant or revoke read/write access to our properties based upon a security token. This might be a string, a number or even an object. Our properties can be either natively PROTECTED properties, or properties decorated with an _ASSIGN() method as discussed at the beginning.

Raising an Event

I would like to show you another possible implementation of read-only properties based on VFP's Event Binding. Because Event Binding is dynamically established at runtime it can be switched On and Off at will. Let's have a peek at the demo code:

Define Class EvtBindAccess As Custom
   *\\ Write Protected property
   cTest = "sensitive content"
   *\\ 'Grant Access' security token TypeOf(CHAR[20])
   Protected eSecTokens
   eSecTokens = "TeStaBChElLo123ItsME!"
   *//
   Protected Function Init()
      *\\ Enable 'Bypass Write Protection' by default
      This.ByPassSwitch("ON")
      *//
      Return .T.
   Endfunc
   Protected Procedure ByPassSwitch(tcOnOff As String) As Void
      If m.tcOnOff == "ON"
         = Bindevent(This, "cTest", This, "cTest_Enabler", 2)
      Else
         = Unbindevent(This, "cTest", This, "cTest_Enabler")
      Endif
   Endproc
   Protected Procedure cTest_Assign(tcTest As String) As Void
      *\\ wellknown write protection implementation
      If Vartype(m.VeryComplexAndSecretVariableName) == "U"
         Error 1740, "tcTest" && "name" is a read-only property
      Else
         This.cTest = m.tcTest
      Endif
   Endproc
   Protected Procedure cTest_Enabler(tcValue As String;
         , tcSecToken As Sting) As Void
      tcSecToken = Alltrim(Transform(m.tcSecToken))
      If Len(m.tcSecToken) > 4 And ;
            At(m.tcSecToken, This.eSecTokens) > 0
         Private VeryComplexAndSecretVariableName
         VeryComplexAndSecretVariableName = .T.
      Endif
      *\\ assig value
      This.cTest = m.tcValue
      *//
   Endproc
Enddefine

There is a property called eSecTokens which holds a string (used as an array of characters) that offers 15 different security tokens each of five characters (an arbitrary length in this example). The protected method BypassSwitch() is called from object's Init() to bind the cTest_Enabler() event handler to the cTest property. Therefor, the 'bypass write protection' feature is enabled by default. Have a closer look at the event handler's signature: the cTest_Enabler() method accepts TWO parameters although it is only bound to a scalar property! If you're not sure what I'm pointing you to, go and read this post first!

Raising an event on the cTest property will raise the bound cTest_Enabler() event handler if we call it this way: RaiseEvent(m.loEvtBindAccess,"cTest","New Precious Value","TeStaB")
We pass two parameters to our cTest_Enabler() event handler. The event handler, in turn, checks the minimum security token length, and then verifies the token. If both tests pass it creates a PRIVATE (secret) variable and finally does the assignment. If the tests do not pass, no private variable is created – that's the only difference. Assigning the new value to our property raises the cTest_Assign() method which, in turn, checks if the secret variable exists…

Things to mention

If you ever read VFP's BINDEVENT help topic from start to end reflecting on each single sentence, like I did, you might wonder why I'm trying to bind to a PROTECTED method in my EvtBindAccess Class. VFP's help states:

You can bind to any valid Visual FoxPro object event, property, or method, including the Access and Assign methods. However, the event and delegate methods must be public, not protected or hidden, members of the class.

VoilĂ , we've just encountered another ambiguity in VFP's help files! You do can bind to any property of your own class from within your own class. The help file statement "must be public, not protected or hidden…" applies to external objects at runtime only!

The cTest_Assign() method that implements the write protection now looks for a variable named VeryComplexAndSecretVariableName to be defined to grant write access. This differs from all previous examples and will also be used in our final implementation…

Friends

I used the write protection scheme shown in the MySpecialFour class above a long time, until I encountered Unit-Testing. Suddenly, my herds of read-only (and protected) PEMs were no longer a proof of good OOP design only, but actively hindered my Unit-Test routines modifying object internals to successfully complete their tasks.
A Unit-Test instance is nothing else but another external object seen from the perspective of the class instance being tested. Thus, the Unit-Test instance cannot write to the tested classes' read-only properties as long as you use any of the approaches described above! The Unit-Test instance should be considered "a friend" of your class, that is, the Unit-Test object should be granted read/write access to ALL of your test classes' PEMs! And, the Unit-Test scenario is only a representative for many others sharing the same 'design problem'!

Friends Class Instances

A Friends Class Instance (let's name them Friends from now on) should be able to access otherwise locked PEMs of another Friend. The only way to get there is to use variables. Lets script a first draft:

Define Class MySpecialSix As Custom
   yResult = $0.0000
   Procedure yResult_Assign
      Lparameters tyResult As Currency
      If Type("SECRET_FLAG") == "U"
         Error 1740, "yResult" && "name" is a read-only property
      Else
         This.yResult = m.tyResult
      Endif
   Endproc
   Procedure CalculateWhatEver() As Void
      *\\ Do some wild things here...
      *\\ ... then store result
      Private SECRET_FLAG
      SECRET_FLAG = .T.
      This.yResult = -4654.1234
      *//
   Endproc
Enddefine

Sidekick: Have a look at the MySpecialFive class above: Type("SECRET_FLAG") == "U" isn't the fastest way to check for existence of a memory variable! Using Vartype(m.SECRET_FLAG) == "U" does the same job but executes faster!

Nice: Because the private variable goes out of scope when we returning from the method it was created in, we cannot forget to reset it.

Drawbacks: We have to assign a value to a private variable to really 'create' it. The instruction 'Private SECRET_FLAG' differs from 'Local SECRET_FLAG' in that the latter does create a (boolean) variable for us, the Private statement only declares that we are going to do so!

Extending the Scheme

Let's play on the fact that we have to store a value to our private variable to create it. Instead of just storing a .T. or .F. in it we could add more 'friendship' information there. But there is more we should consider! Think about a chain of subsequent calls to sub-routines. A private variable stays in scope form the point we created it along the following calls to sub-methods, even if some of the subsequent calls jump out of your object's boundaries.

Breaking Encapsulation with Intend

If you create a private variable and then call a method of another (external) object, or access one of its properties, your private variable still is visible there. That's why you should otherwise avoid using private variables in OOP, because that's how they break encapsulation!
In our case this is just what we want to achieve: Establishing a controlled way to bypass object encapsulation!

Shadowing

One 'feature' of private variables in VFP is called shadowing. If you are going to create a generic protection algorithm based on "carrying private variables around", this feature comes in handy! Think about the following scenario:
You have three different classes called Class_A, Class_B, and Class_C all stemming from the same superclass. Thus, they all implement the same protection algorithm (behavior). Depending on the roles they play at runtime Class_A should be a friend of Class_B and Class_B in turn should be a friend of Class_C. Class_A should be granted read/write access to one or more PEMs of Class_B, but not to PEMs of Class_C; Class_B should be granted read/write access to one or more PEMs of Class_C.

Now, if Class_A writes to a property of Class_B from within one of its methods like this…

Procedure CallFriend(tcMyFriendsName As String) As Void
   Local loMyFriend As Object
   loMyFriend = This.GetRefFromName(m.tcMyFriendsName)
   If Vartype(m.loMyFriend) == "O"
      *\\ enable bypassing write protection
      Private pcSECRET_FLAG
      pcSECRET_FLAG = This.Name
      *\\ write to the r/o property
      m.loMyFriend.cProtectedValue = This.cProtectedValue
   Endif
Endproc

… then the assignment could trigger a subsequent message from Class_B to Class_C like so:

Procedure cProtectedValue_Assign(tcNewVal As String) As Void
   If Vartype(m.pcSECRET_FLAG) == "C" And;
         Len(m.pcSECRET_FLAG) > 7 And ;
         At(m.pcSECRET_FLAG, This.cMyFriendsNames) > 0
      *\\ assign value
      This.cProtectedValue = m.tcNewVal
      *\\ cascade message to my friend
      This.CallFriend(This.cMyFriendsName)
   Else
      Error 1740, "cProtectedValue" && r/o property
   Endif
Endproc

The method CallFriend() of the Class_B instance that gets called from its own cProtectedValue_Assign() method will create a new private variable pcSECRET_FLAG, now holding the name of Class_B. This private variable, although it has the same name, does not simply overwrite the content of the first one created by the Class_A instance, but overlays it opaquely. In fact, at the moment when our Class_B instance assigns its value to the Class_C instance, two pcSECRET_FLAG variables exist, where the second one SHADOWS the first one created by the Class_A instance. When returning from the CallFriend() method of the Class_B instance the second pcSECRET_FLAG variable goes out of scope. Thus, the first is not longer shadowed, it becomes visible/accessible again.

Nice: VFP's shadowing feature of private variables frees us from taking care about name clashes!

Identifying Friends

Sometimes it is not enough to know that an object sending a message is the instance of a friends class. Maybe hundreds of instances of the same class exist at the same time and we only want to grant write access to few of them depending on the role they play. The question is, how can we identify a unique instance without having its object reference to compare to?

Object Identity

It is said that an object is described by three attributes: State, Behavior, and Identity. Let's focus on the aspect of object identity. VFP gives each object a unique identity. In fact, when we compare objects with each other using the equal operator (=) we are comparing their Identities! VFP has another CompObj() function that allows us to compare objects' State & Appearance instead.

Examples

Type in the following lines in your command window and watch the results echoed to your VFP's desktop:

goX = CreateObject("container")
goY = CreateObject("container")
? m.goX = m.goY && .F.
? m.goX = m.goX && .T.
? m.goY = m.goY && .T.
? Compobj(m.goX, m.goY) && .T. same state and appearance
*\\ changing the appearance
goY.Top = 10
? Compobj(m.goX, m.goY) && .F. appearance differs
? m.goY = m.goY && .T. always
*\\ reset appearance
goY.Top = 0
? Compobj(m.goX, m.goY) && .T. same appearance again
*\\ changing appearance 
Addpropery(m.goY, "Bottom", m.goY.Top+m.goY.Height)
? Compobj(m.goX, m.goY) && .F. appearance differs
*\\ assign object reference to another variable
m.goX = m.goY
? m.goX = m.goY && .T. both variables pointing to the same object
? Compobj(m.goX, m.goY) && .T. of course

Drawback: We must use object references to compare these special object attributes. Another important thing to keep in mind is that State & Appearance, Behavior, and Identity are runtime related attributes in this context. We are not talking about our classes, but about their instances!

Text-Based Object Identifiers

What we need is some simple representation of an Object's Identity. Surely, we could generate GUIDs and store them in each class instance we create. But that would be too much of a good thing because GUIDs are long and time-consuming to create. We do not need a worldwide unique identifier to tag our instances, but a tinier one, only scoped to the running VFP session where our objects live. The following class definition shows a short excerpt of my solution:

Define Class _CustomTest As Custom
   Protected cCls2015, cSys2015
   cCls2015 = Sys(2015)
   Function Init() As Boolean
      This.cSys2015 = Sys(2015)
      Return .T.
   Endfunc
   Function GetIdentity(tcCaller As String) As String
      *\\ protection scheme left out in this demo
      Return This.cCls2015+This.cSys2015
   Endfunc
Enddefine

The value assignment cCls2015 = Sys(2015) stores a unique procedure name (10 characters) to VFP's ClassTemplate Object. Thus, ALL instances of that class will have the same ID that was generated when the first object of that class was instantiated in the current VFP process. The (second) value assignment cSys2015 = Sys(2015) stores a unique procedure name (10 characters) to each Individual Instance. Thus, we will end up returning a 20 character string from the GetIdentity() method which is the Identity (string) we need. We now can use LEFT(m.cID, 10) to query the unique runtime ID of the class template. Using RIGHT(m.ID, 10) will return the unique runtime ID of the class instance instead.

BTW: In our case it is absolutely sufficient to use SYS(2015) generated values because we only need unique IDs generated within the same VFP process. And that is exactly what SYS(2015) does!

Generic Implementation

There is nothing bad about using private variables within our OOP environment in a controlled manner. It is a design decision we make to simplify some generic implementation issues. Let's gather our requirements encountered so far.

  • First, we need an Access Protection Scheme (read-write, read-only, write-only, internal-only) that incorporates our Friends Access Pattern.
  • In addition to that it would be nice to be able to Signal a given State while running along an execution path without the need to pass the state information through additional parameters from method to method.
  • Finally, these requirements should work seamlessly with our concept of Text-based Object Identifiers.

Text-Based Object Identifiers are build upon VFP's Sys(2015) function and are always stored in protected properties of our classes. Once the classes' ID keys are created they must not change - under no circumstances! We will implement a Secured Getter to enable Friends Classes to read each other's Object Identifier values. In addition to that we will implement Secured Setters to enable classes to establish a Friends Link among each others at runtime.
Therefor, we have to define a Common Security ID. This Global Security Token should be stored in a global include file which will be included in all classes that are using our Friends Classes Scheme. Using a well-designed hierarchy of include files can help us achieving our goals without much work.

The following function shows how the Common Security ID is passed in and then gets compared against the object's own copy of it:

Function AGetFriends(tcAuthorityID As String) As Array
   tcAuthorityID = Transform(m.tcAuthorityID)
   If m.tcAuthorityID == This.cAutId
      Return @This.aFriends
   Endif
Endfunc

Two Different Kinds of Private Variables

We will implement two different ways of wrapping our sensitive information in private variables. The first will use static variable names to benefit from VFP's shadowing feature, the other will use secret variable names based on the Object Identifiers Naming Scheme. The first will be used to pass the Secret Object Identifiers around, the second will be used to pass around arbitrary data of any type. The _Custom class below is a 'ready to use' SuperClass with behavior that can be transferred to other VFP base class types, too.

Define Class _Custom As Custom
   *\\ Public interface
   eWhatever = .Null.
   oParent = .Null.
   *\\ Protected (internal only)
   Protected Array aFriends[1]
   Dimension aFriends[1]
   Protected cCls2015, cSys2015, cAutId, cAutCnt
   cCls2015 = Sys(2015)
   cSys2015 = ""
   cAutId = "{B02103F0-1AA8-4cda-9CCA-DEB455BB9574}"
   cAutCnt = "pcAuthorityContainer"
   *\\ Hidden (this classLevel only)
   *\\			--- none ---
   *//______________________________________________________
   Protected Function Init(teWhatEver As Variant) As Boolean
      Private pcWorkInProgress
      Store "INIT" To m.pcWorkInProgress
      Local llOkay As Boolean
      llOkay = .T.
      With This
         .aFriends[1] = ""
         .eWhatever = m.teWhatEver
         .cSys2015 = Sys(2015)
      Endwith
      If This.InitBefore(@llOkay)
         llOkay = This.InitDo(m.llOkay)
      Endif
      This.InitAfter(@llOkay)
      This.eWhatever = .Null.
      Return m.llOkay
   Endfunc
   Protected Function InitBefore(tlOkay As Boolean@) As Boolean
   Endfunc
   Protected Function InitDo(tlOkay As Boolean) As Boolean
      Return m.tlOkay
   Endfunc
   Protected Procedure InitAfter(tlOkay As Boolean) As Void
   Endproc
   *\\______________________________________________________
   Protected Function Destroy() As Void
      Private pcWorkInProgress
      Store "DESTROY" To m.pcWorkInProgress
      If This.DestroyBefore()
         This.DestroyDo()
      Endif
      This.DestroyAfter()
   Endfunc
   Protected Function DestroyBefore() As Boolean
   Endfunc
   Protected Procedure DestroyDo() As Void
   Endfunc
   Protected Procedure DestroyAfter() As Void
   Endproc
   *\\______________________________________________________
   Function GetIdentity(tcAuthorityID As String) As String
      Return Iif(Trans(m.tcAuthorityID) == This.cAutId,;
         This.cCls2015+This.cSys2015, "")
   Endfunc
   *\\______________________________________________________
   Protected Function IsFriend(tcIdentity As String) As Boolean
      Return Ascan(This.aFriends, m.tcIdentity,1,-1,1,2+4) > 0
   Endfunc
   *\\______________________________________________________
   Procedure AddFriend(tcAuthorityID As String ;
         , tcIdentity As String) As Void
      tcAuthorityID = Transform(m.tcAuthorityID)
      tcIdentity = Transform(m.tcIdentity)
      If m.tcAuthorityID == This.cAutId Or ;
            m.tcAuthorityID == This.cCls2015+This.cSys2015
         If Empty(This.aFriends[1])
            This.aFriends[1] = m.tcIdentity
         Else
            If Not This.IsFriend(m.tcIdentity)
               Local lnNewFriendsCount As Integer
               lnNewFriendsCount = Alen(This.aFriends) + 1
               Dimension This.aFriends[m.lnNewFriendsCount]
               This.aFriends[m.lnNewFriendsCount] = m.tcIdentity
            Endif
         Endif
      Endif
   Endproc
   *\\______________________________________________________
   Function GetFriendByIndex(tcAuthorityID As String ;
         ,tnIndex As Integer) As String
      tcAuthorityID = Transform(m.tcAuthorityID)
Local lcIdentity As String
lcIdentity = ""
If m.tcAuthorityID == This.cAutId Or ; m.tcAuthorityID == This.cCls2015+This.cSys2015 Do Case Case Empty(This.aFriends[1]) Case Not Vartype(m.tnIndex) == "N" Case Not Between(m.tnIndex, 1, Alen(This.aFriends)) Otherwise lcIdentity = This.aFriends[m.tnIndex] Endcase Endif Return m.lcIdentity Endfunc *\\______________________________________________________ Function AGetFriends(tcAuthorityID As String) As Array tcAuthorityID = Transform(m.tcAuthorityID) If m.tcAuthorityID == This.cAutId Return @This.aFriends Endif Endfunc *\\______________________________________________________ Procedure ResetObject(m.tcIdentity As String) As Void Local lcMode As String *\\ get mode; flag unauthorized calls lcMode = Iif(Not (m.tcIdentity == This.cAutId Or; m.tcIdentity == This.cCls2015+This.cSys2015); , "REJECT" ; , Iif(Vartype(m.pcWorkInProgress) == "U" ; , "FULL", m.pcWorkInProgress)) If m.lcMode == "REJECT" Return && >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Endif With This *\\ nullify properties that may hold external references Store .Null. To .oParent Endwith If m.lcMode == "DESTROY" *\\ we're done Return && >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Endif If Not m.lcMode == "INIT" *\\ do NOT redimension array properies when called *\\ from INIT() because they are preset in class Dimension This.aFriends[1] Endif *\\ run (the rest of) full object reset from here on With This Store "" To .aFriends Store .Null. To .eWhatever Endwith *// Endproc Enddefine

Actually, the ResetObject() method does not belong to the core of our Friends Classes concept, but shows how a private variable with a fixed name (m.pcWorkInProgress) can be utilized to pass around additional process attributes without using a property one could forget to reset at the end of the process.

That's all for now

Hope I could help to enlighten you a little bit :-) To be honest, there is nothing wrong with any of the approaches described above! As long as you select one and stay with it to be consistent across your projects, I'm fine with it :-)

Keep things rolling…


Previous Entry (disabled ATM)Tips & Tricks HomeNext Entry (disabled ATM)

VFP's Preprocessor's Constant Definition Handling

Version: 1.00.01 - last update: Tuesday, July 31, 2014, 12:35:00 [fixed some minor typos and reformatted font size]

Previous Entry (ATM disabled!)Tips & Tricks HomeNext Entry (ATM disabled!)


Things you should really know about VFP's compile-time constants

Intro

There are some new things I've learned about Include Files and their content lately! Here comes what could be said is common knowledge amongst VFP developers:

  1. It is always good to let all your classes include one .H-file!
  2. It is even better if these "low-level" includes are populated sparsely, including one (or more) "top-level" include files in turn.
  3. Defined constants cannot be redefined, unless they are undefined first! Even worse, VFP will complain about redefining constants! (Well this is only half the truth as I will show you!)
  4. The last point above implies that you have to maintain a well-ordered hierarchy of including include files!



A Well-Organized Hierarchy

Maintaining a well-organized hierarchy of include files means that we have to be aware of which .H-files include which others and HOW they include them! 
If you ask yourself why I'm stressing the word HOW, let me tell you, there is a good reason to do so! I will show you in a minute why…

My Favorite Approach

    My favorite approach is to give each of my include file levels a name.

  1. The top level is the ROOT Level, which is FoxPro itself. There is the FoxPro.H file that we all know well, that should be made our top-level include.

  2. The second level is the Framework Level – in my case this is the FoxQuill Framework. Thus, there is a the FoxQuill.H file, which in turn includes some further Framework Includes.

  3. The third level is the Project Level – this is where you should put your project related constants.

  4. Finally, the forth level is the ClassLibrary Level – each class in a class library should at least share one common include file, or include its own.

Base Include File Schema

The above leads us to the following Base Include File Schema:

FoxPro.h –> is included in –> FoxQuill.h –> is included in –> ProjectName.h –> is included in –> ClassName.h

You may do the same thing with your compile-time localization include files. E.g. like this:

General.En.h –> is included in –> Framework.En.h –> is included in –> ProjectName.En.h –> is included in –> ClassName.En.h

How VFP Resolves Constant Definitions

Let's have a look on how VFP resolves compile-time constants defined in your include files, especially, how VFP deals with duplicate declarations!

There is one error, I bet, you have already seen more than once:

VFP Compile-Time Error 'Constant is already created with #DEFINE'

This error (#1725) simply tells us that we've just tried to redefine an existing constant definition. Sometimes it's no fun to find the place, where the first definition takes place, especially when working with someone else's legacy code that uses a lot of nested include files! Sometimes, the best we could do is to add a quick #UNDEF statement before we #DEFINE our own constant. But this may break some legacy code relying on the old value! These kind of weird situations are all stemming from some simple mistakes made by the guys that wrote the legacy code a long time ago, because they were unaware of how to nest include files the right way!

The Base Include File Schema introduced above is a hierarchical one. That means, compared with a class inheritance tree, that higher level include files contain more common/global definitions. The deeper we dive into our include file hierarchy, the more specific our defines should become.

As Short Quiz

Let us approach the essence of this writing playing a little pop quiz. Try answering the following questions without consulting VFP's documentation or trying them out in VFP's command window!


Question#1: What code line below caused the Error Message Box above to pop up with error#1725 ?

#DEFINE FRAMEWORK_NAME	"FoxQuill  "
#DEFINE FRAMEWORK_NAME	"FoxQuill "
#DEFINE FRAMEWORK_NAME	"foxquill"

VFP's help topic #DEFINE ... #UNDEF Preprocessor Directive states it: "You can redefine a #DEFINE only if you do not change the value. If you change the #DEFINE to a different value, Visual FoxPro generates an error." That's why the right answer is: "The second line!". If you have answered in addition to that: "The 3rd line will cause the same error, too!", you will get an additional bonus point :-)


Question#2: Does the second line below generates an error?

#DEFINE FRAMEWORK_NAME	"FoxQuill"
#DEFINE FRAMEWORK_NAME	"foxquill"

The right answer is: "Yes!". "foxquill" == ""FoxQuill" is not true because it is a different value for VFP's preprocessor (that doesn't care about your SET EXACT setting). We cannot redefine a constant to a different value. Thus, FoxPro will complain about it!


Question#3: Assume, we have an include file named FoxQuill.h with the following one line content:
#Define FoxQuill_Version V9.0

Now, look at the code lines below (without cheating/testing it!). What do you think? Will there be an error, and if so, which line will cause it.

#Define FOXQUILL_VERSION "V9.0"
#INCLUDE foxquill.h && file is accessible (in our search path)
#Define FOXQUILL_VERSION "V9.0"

The right answer#3 is: "No!". This code fragment will compile without errors! If you guessed it wrong, don't panic, here's another chance to collect some more points ;-)


Question#4: Let's reuse the above"FoxQuill.h" include file. Look at the code lines below. What do you think? Will there now be an error, and if so, which line will cause it this time?

#INCLUDE foxquill.h && file is accessible in our search path
#Define FOXQUILL_VERSION "V9.0"
#Define FOXQUILL_VERSION V9.0

The right answer is: "Yes! Line#2 will cause an error". The first local define is a redefinition of the FoxQuill_Version constant defined in our foxquill.h file from V9.0 to "V9.0". BTW: If you comment that line out the code will compile without errors, you should know why by now.

Be honest, how many points did you gather without cheating? Let me tell you, before I examined VFP's preprocessor behavior thoroughly, I had no clue!

Nesting Include Files as Usual

I was used to nest my include files like this:

* Content of MasterInc.H 
#Include FoxPro.h 
#Define MyFrameVersion 1.0 
#Define MasterPassword "Rumpelstielschen" 
*… many other definitions to follow 
* EOF MasterInc.H

* Content of ProjectInc.H 
#Include MasterInc.H 
#Define MyAppName "AppSpy.Exe" 
#Define MyAppVersion 1.0 
#Define AppPassword "VerySecret" 
* … many other definitions to follow 
* EOF ProjectInc.H

If you place your nested #INCLUDEs at the top of your include files you loose the feature to overwrite any definition made in those 'higher level' include files. On the other hand, using the approach above, you can rely on VFP complaining about any redefinitions you may have introduced in you actual include file unintentionally.

Actually, the latter is a feature I do not miss very much, if at all!

A Better Way of Nesting Include Files

Let's reorganize the above include files like so:

* Content of MasterInc.H 
#Define MyFrameVersion 1.0 
#Define MasterPassword "Rumpelstielschen" 
* … many other definitions to follow 
#Include FoxPro.h 
* EOF MasterInc.H

* Content of ProjectInc.H
#Define MyAppName "AppSpy.Exe" #Define MyAppVersion 1.0 #Define AppPassword "VarySecret" * … many other definitions to follow #Include MasterInc.H * EOF ProjectInc.H

Now, if we overwrite, lets say, the framework version like so:

* Content of ProjectInc.H 
#Define MyFrameVersion 2.0 
#Define MyAppName "AppSpy.Exe" 
#Define MyAppVersion 1.1 
#Define AppPassword "VarieSikret"
* … many other definitions to follow 
#Include MasterInc.H 
* EOF ProjectInc.H

Foxpro will not complain about the redefinition!

even better:

VFP will disregard the assignment made in the higher level MasterInc.H file completely.

As a logical consequence, we don't have to use any #UNDEFINE statements at at deeper nesting level any longer. Look at the content of the ProjectInc.h file above: The #Define MyFrameVersion 2.0 definition is the first time definition, which we know cannot be redefined later without releasing it beforehand.

Bug or Feature?

I am not sure if this is a feature or a bug in VFP's preprocessor! But my findings have proven it:

You can include duplicate redefinitions from another include file AFTER you have defined your primary ones! But not the other way round!

Therefor, if you accustom yourself to including higher level include files always and only at the end of your lower level include files, you will gain some kind of OOP-ish overwrite in subclass feature, and in addition to that, you will spare coding time (no #UNDEFINEs any more)!


Another Cool Preprocessor Feature

Another feature of VFP's preprocessor is widely unknown/disregarded! Although VFP's help file tells us: "Compile time constants are not recognized when placed within quotation marks" (see help topic #DEFINE ... #UNDEF Preprocessor Directive), this is not the whole truth!

Everybody knows, VFP recognizes three kinds of string delimiters, but VFP's preprocessor does only recognize two of them! Strings that are delimited with square brackets like [V9.0] are processed by VFP's preprocessor, indeed!

Have a look at he following lines:

#DEFINE FOXQUILL FoxQuill2
?[Welcome to FOXQUILL]
?[Welcome to foxquill]
?[Welcome to FoxQuillFramework]
?[Welcome to !foxquill.Framework]
?[Welcome to *foxquill-Framework]

They produce the following output:

Output of preprocessed strings

We can observe two things: While looking for replacements VFP's preprocessor does a case-insensitive search (1), and always matches whole words(2)!

Do not Pollute Your Include Files

What has the above to do with polluting include files? Well, what are you normally doing if you want to use a string constant in your project files all over the place? Right, you will define it in one of your include files maybe like this: #Define FRAMEWORK_NAME_STRING "FoxQuill" just in case you will decide one fine day to give your work another name, which is a good thing to have in place! Now, in your code you may address your constant like so:

oForm.Caption = "Welcome to the new version of the " + ;
			   FRAMEWORK_NAME_STRING + " Framework!"

Wow, this is a lot of code to type in! Now have a look at the non-polluting solution. First, drop the include file definition completely, then code:

oForm.Caption = [Welcome to the new version of the Foxquill Framework!]

That's all it takes! You are still as flexible as when using the first approach! If you ever will decide to change your framework name to something else it will be sufficiently early to add some #Define FOXQUILL FoxQuill.Net to your include file that day in the future!

Let's end today like Bob Ross always does:
"Happy painting coding!" ;-)

Previous Entry (ATM disabled!)Tips & Tricks HomeNext Entry (ATM disabled!)