// The following must all be LOW numbers to avoid any realistic chance of them matching the address of
// any function (namely a BIV_* function).
VAR_ALIAS // VAR_ALIAS must always have a non-NULL mAliasFor. In other ways it's the same as VAR_NORMAL. VAR_ALIAS is never seen because external users call Var::Type(), which automatically resolves ALIAS to some other type.
, VAR_NORMAL // Most variables, such as those created by the user, are this type.
, VAR_CLIPBOARD
, VAR_LAST_WRITABLE = VAR_CLIPBOARD // Keep this in sync with any changes to the set of writable variables.
, VAR_CLIPBOARDALL // Must be read-only because it's not designed to be writable.
, VAR_BUILTIN
, VAR_LAST_TYPE = VAR_BUILTIN
};
typedef UCHAR VarTypeType; // UCHAR vs. VarTypes to save memory.
typedef UCHAR AllocMethodType; // UCHAR vs. AllocMethod to save memory.
typedef UCHAR VarAttribType; // Same.
typedef DWORD VarSizeType; // Up to 4 gig if sizeof(UINT) is 4. See next line.
#define VARSIZE_MAX MAXDWORD
#define VARSIZE_ERROR VARSIZE_MAX
#define MAX_FORMATTED_NUMBER_LENGTH 255 // Large enough to allow custom zero or space-padding via %10.2f, etc. But not too large because some things might rely on this being fairly small.
class Var; // Forward declaration.
struct VarBkp // This should be kept in sync with any changes to the Var class. See Var for comments.
{
Var *mVar; // Used to save the target var to which these backed up contents will later be restored.
? var.mLength : strlen(var.Contents()); // Use Contents() vs. mContents to support VAR_CLIPBOARD.
}
char *Contents() // __forceinline() on Capacity, Length, and/or Contents bloats the code and reduces performance.
{
// Relies on the fact that aliases can't point to other aliases (enforced by UpdateAlias()).
Var &var = *(mType == VAR_ALIAS ? mAliasFor : this);
if (var.mType == VAR_NORMAL)
return var.mContents;
if (var.mType == VAR_CLIPBOARD)
// The returned value will be a writable mem area if clipboard is open for write.
// Otherwise, the clipboard will be opened physically, if it isn't already, and
// a pointer to its contents returned to the caller:
return g_clip.Contents();
return sEmptyString; // For reserved vars (but this method should probably never be called for them).
}
__forceinline Var *ResolveAlias()
{
// Relies on the fact that aliases can't point to other aliases (enforced by UpdateAlias()).
return (mType == VAR_ALIAS) ? mAliasFor : this; // Return target if it's an alias, or itself if not.
}
__forceinline void ConvertToNonAliasIfNecessary() // __forceinline because it's currently only called from one place.
// When this function actually converts an alias into a normal variable, the variable's old
// attributes (especially mContents and mCapacity) become dominant again. This prevents a memory
// leak in a case where a UDF is defined to provide a default value for a ByRef parameter, and is
// called both with and without that parameter.
{
mAliasFor = NULL; // This also sets its counterpart in the union (mLength) to zero, which is appropriate because mContents should have been set to blank by a previous call to Free().
mType = VAR_NORMAL; // It might already be this type, so this is just in case it's VAR_ALIAS.
}
__forceinline void UpdateAlias(Var *aTargetVar) // __forceinline because it's currently only called from one place.
// Caller must ensure that aTargetVar isn't NULL.
// When this function actually converts a normal variable into an alias , the variable's old
// attributes (especially mContents and mCapacity) are hidden/suppressed by virtue of all Var:: methods
// obeying VAR_ALIAS and resolving it to be the target variable. This prevents a memory
// leak in a case where a UDF is defined to provide a default value for a ByRef parameter, and is
// called both with and without that parameter.
{
// BELOW IS THE MEANS BY WHICH ALIASES AREN'T ALLOWED TO POINT TO OTHER ALIASES, ONLY DIRECTLY TO
// THE TARGET VAR.
// Resolve aliases-to-aliases for performance and to increase the expectation of
// reliability since a chain of aliases-to-aliases might break if an alias in
// the middle is ever allowed to revert to a non-alias (or gets deleted).
// A caller may ask to create an alias to an alias when a function calls another
// function and passes to it one of its own byref-params.
while (aTargetVar->mType == VAR_ALIAS)
aTargetVar = aTargetVar->mAliasFor;
// The following is done only after the above in case there's ever a way for the above
// to circle back to become this variable.
// Prevent potential infinite loops in other methods by refusing to change an alias
// to point to itself.
if (aTargetVar == this)
return;
mAliasFor = aTargetVar; // Should always be non-NULL due to various checks elsewhere.
mType = VAR_ALIAS; // It might already be this type, so this is just in case it's VAR_NORMAL.
}
ResultType Close(bool aIsBinaryClip = false)
{
// Relies on the fact that aliases can't point to other aliases (enforced by UpdateAlias()).
Var &var = *(mType == VAR_ALIAS ? mAliasFor : this);
if (var.mType == VAR_CLIPBOARD && g_clip.IsReadyForWrite())
return g_clip.Commit(); // Writes the new clipboard contents to the clipboard and closes it.
// The binary-clip attribute is also reset here for cases where a caller uses a variable without
// having called Assign() to resize it first, which can happen if the variable's capacity is already
// sufficient to hold the desired contents.
if (aIsBinaryClip)
var.mAttrib |= VAR_ATTRIB_BINARY_CLIP;
else
var.mAttrib &= ~VAR_ATTRIB_BINARY_CLIP;
return OK; // In all other cases.
}
// Constructor:
Var(char *aVarName, void *aType, bool aIsLocal)
// The caller must ensure that aVarName is non-null.
: mContents(sEmptyString) // Invariant: Anyone setting mCapacity to 0 must also set mContents to the empty string.
, mLength(0) // This also initializes mAliasFor within the same union.
, mHowAllocated(ALLOC_NONE)
, mAttrib(0), mIsLocal(aIsLocal)
, mName(aVarName) // Caller gave us a pointer to dynamic memory for this (or static in the case of ResolveVarOfArg()).
{
if (aType > (void *)VAR_LAST_TYPE) // Relies on the fact that numbers less than VAR_LAST_TYPE can never realistically match the address of any function.
{
mType = VAR_BUILTIN;
mBIV = (BuiltInVarType)aType; // This also initializes mCapacity within the same union.
}
else
{
mType = (VarTypeType)aType;
mCapacity = 0; // This also initializes mBIV within the same union.