8

Variants are always fun, eh?

I am working on a legacy application that was last in D2007 to migrate it to Delphi XE.

Variants have changed quite a bit in the interim.

This line of code:

if (VarType(Value) = varString) and (Value = '') then 
  Exit;

returned True and exited in D2007, but doesn't in Delphi XE.

I have changed it to this:

if VarIsStr(Value) and (VarToStr(Value) = '') then
    Exit;

I'm not convinced this is the "best" way to go. The Variants unit doesn't have a specific call to do this, and I certainly recall this being an issue for folks in the past. However, a search revealed no library function or any other accepted way.

Is there a "correct" or better way?

Nick Hodges
  • 16,902
  • 11
  • 68
  • 130
  • Incidentally `v =''` is true, if I explicitly assign it to `v := '';` - My guess is that there is more than one variant string subtype, perhaps B_STR and something else, and so the element comparison fails, even though the content is the same. – Warren P Mar 12 '12 at 19:58
  • Wont `if VarToStr(Value) = ''` alone do the job? – kobik Mar 12 '12 at 20:25
  • 1
    @kobik This fails, for example, when `Value` equals `Null`. – David Heffernan Mar 12 '12 at 22:24
  • @DavidHeffernan, works as expected in D5/D7. e.g. `if VarToStr(Null) = '' then beep`. – kobik Mar 12 '12 at 22:30
  • @kobik Null is not an empty string by dint of not being a string. – David Heffernan Mar 12 '12 at 22:38
  • @DavidHeffernan, I know that... maybe I didn't understand the Q. the Q might just be: "How to tell if a variant is a string". – kobik Mar 12 '12 at 22:47
  • @Nick Hodges: A bit of topic but please can you consider this [post](http://stackoverflow.com/questions/9952353/delphi-how-to-pass-a-parameter-from-the-instantiator-to-a-constructor-in-the-s) related to [tag:spring4d] ? Thanks in advance. – menjaraz Apr 01 '12 at 06:33

5 Answers5

13

VarIsStr is a perfectly plausible way to do it. This is implemented as:

function VarTypeIsStr(const AVarType: TVarType): Boolean;
begin
  Result := (AVarType = varOleStr) or (AVarType = varString)
    or (AVarType = varUString);
end;

function VarIsStr(const V: Variant): Boolean;
begin
  Result := VarTypeIsStr(FindVarData(V)^.VType);
end;

The change you are seeing is, of course, really due to the Unicode changes in D2009 rather than changes to variants. Your string will be varUString, aka UnicodeString. Of course, VarIsStr also picks up AnsiString/varString and WideString/BSTR/varOleStr.

If you want a truly faithful conversion of your Delphi 2007 code then you would write:

if (VarType(Value) = varUString) and (Value = '') then 
  Exit;

Exactly what you need to do, only you can know, but the key thing is that you have to account for the newly arrived varUString.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
4

Updated: String-specific to avoid exceptions:

    if VarIsStr(Value) and (Length(VarToStr(v))=0) then ...

Update3: If you want better performance and less string heap memory waste try this. Imagine that the strings are 64K in length. The code above does a VarToStr and allocates perhaps 64K of UnicodeString heap space to hold the data, just so we can just look for the nul terminator at the end of the string for BSTR, and for nil-pointers for other types.

The code below is a slightly odd in that one does not commonly reach into the internal representation of variants, but David pointed out the bugs and I re-re-tested it and it seems to work, although no warranty is expressed or implied. A unit test for this puppy would be good. At some future date if Delphi RTL gods decided to rename the internal representation of the Variant structure fields, the code below would need to be changed.

function VarStrEmpty(v:Variant):Boolean;
var
  data:PVarData;
begin
    data := FindVarData(V);
  case data^.VType of
     varOleStr:
            result := (data^.VOleStr^=#0);
     varString:
            result := (data^.VString=nil);
     varUString:
            result := (data^.VUString=nil);
     else
      result := false;
  end;
end;
Warren P
  • 65,725
  • 40
  • 181
  • 316
  • 2
    Nick is trying to avoid the exceptions that arise when the variant cannot be coerced to a string. That's why he needs the first check and short circuit evaluation. – David Heffernan Mar 12 '12 at 19:55
  • 1
    I'm struggling to see what all the extra complexity brings here. It also will give an erroneous answer for an empty OleStr I think because they are represented as a single null wchar_t. Or did I get that wrong? What's so bad about comparing against ''? – David Heffernan Mar 12 '12 at 23:45
  • It doesn't always work unless you flatten it to a UnicodeString with VartoString.To make an analogy, it's like counting all your pennies one by one to be sure you have ANY. It's unecessary and wasteful. – Warren P Mar 13 '12 at 01:57
  • The so called optimization here looks distinctly premature and pointless to me. Writing code this way leads to bugs. You tacitly accept this by saying that you could be missing some corner cases. It makes no sense to me to write code so complex that you are not sure of its correctness. Especially when trivially and clearly correct alternatives exist. On the other hand, clearly @Nick prefers this way so I could have missed something. I'd appreciate an explanation. – David Heffernan Mar 13 '12 at 05:44
  • Are you concerned about converting AnsiString to UnicodeString before then comparing against ''? Is that what you are trying to avoid? – David Heffernan Mar 13 '12 at 07:19
  • Yes. Imagine that the strings are 64K in length. Why should we allocate UnicodeString heap for all these strings so we can then simply ascertain if they are empty? – Warren P Mar 13 '12 at 13:14
  • OK, but perhaps you could say that in the answer. It wasn't obvious to me at least, but perhaps I was having a slow day. In any case, in Nick's original version he was testing for varString. That translates to a test for `varUString` at which point there is no conversion for the comparison. – David Heffernan Mar 13 '12 at 13:16
  • I don't think you quite understand me. An empty `BSTR` is not `nil`. An empty `BSTR` is a pointer to a block of memory containing a zero wide char, i.e. the null-terminator. Thus your test above does not work for `WideString`. – David Heffernan Mar 13 '12 at 13:34
  • Okay I've tested all the cases for the case statement above, and the nil and non-nil case for BSTR. – Warren P Mar 13 '12 at 13:56
  • 1
    `strlen` is wasteful. No point walking the string. I've taken the liberty of editing in an efficient way. I still think it feels like prem. opt. but there you go! ;-) – David Heffernan Mar 13 '12 at 14:07
  • Good edit! Upboat for you. Are you sure it can never be nil though? – Warren P Mar 13 '12 at 14:09
  • That's part of the contract of a BSTR. All a BSTR is is a wide char C string allocated off the COM heap. – David Heffernan Mar 13 '12 at 14:15
0

Have You tried

if VarSameValue(Value, '') then 
  Exit;
migpeti
  • 11
  • 4
0

Variants can be a Number or a String.

There might be a problem when the Variant (Number) has a negative value (-15).

Also your line

(VarType(Value) = varString) and (Value = '')

I always have to put ( )

((VarType(Value) = varString) and (Value = ''))
remio
  • 1,242
  • 2
  • 15
  • 36
Troy
  • 1
-1
if VarToStrDef(value, '') = '' then

Does the trick for me.

Salman Zaidi
  • 9,342
  • 12
  • 44
  • 61
  • 1
    You will default all cases when `value` is not a string to true. That logic does not match the question. – LU RD Sep 11 '14 at 09:18