1

I have this function:

var
  _WordApplicationExistsCache: Integer = -1; // Cache result

function WordApplicationExists: Boolean;
var
  WordObj: OleVariant;
begin
  if (_WordApplicationExistsCache = -1) then
  begin
    Result := False;
    try
      try
        WordObj := CreateOleObject('Word.Application');
        WordObj.Visible := False;
        WordObj.Quit;
        WordObj := Unassigned;
        Result := True;
      except
        // error
      end;
    finally
      _WordApplicationExistsCache := Ord(Result); // 0;1
    end;
  end
  else
  begin
    Result := Boolean(_WordApplicationExistsCache);
  end;
end;

I'm trying to call this function only once in the Application lifetime. I might not call this function at all.

Is this the correct pattern? Can this be done better?

EDIT: Another way I can think of, in this case is to use 2 variables:

var
  _WordApplicationExistsInitialized: Boolean = False; // Cache result
  _WordApplicationExistsCacheResult: Boolean; // Undefined ?

function WordApplicationExists: Boolean;
var
  WordObj: OleVariant;
begin
  if not _WordApplicationExistsInitialized then
  begin
    _WordApplicationExistsInitialized := True;
    Result := False;
    try
      try
        WordObj := CreateOleObject('Word.Application');
        WordObj.Visible := False;
        WordObj.Quit;
        WordObj := Unassigned;
        Result := True;
      except
        // error
      end;
    finally
      _WordApplicationExistsCacheResult := Result;
    end;
  end
  else
  begin
    Result := _WordApplicationExistsCacheResult;
  end;
end;

What bugs me a bit about the first version is the type casting Boolean<->Integer. If Boolean could be initialized to nil it would have been perfect (I think).

zig
  • 4,524
  • 1
  • 24
  • 68
  • 2
    Use a TriState `TriState = (tsUnknown, tsFalse, tsTrue);` or a `Nullable` (see [Spring4D](https://bitbucket.org/sglienke/spring4d)) – Sir Rufo Dec 06 '15 at 10:37
  • Just a word of warning about caching any kind of result like this (not just Boolean): E.g. if whatever error condition that prevented creating the object is resolved, you would be need to restart your application so that it can reset its cache. I would advise thinking quite a bit more than just twice before embarking on this path. – Disillusioned Dec 08 '15 at 09:20

3 Answers3

6

Use a TriState type for the cached result.

type
  TTriState = ( tsUnknown, tsFalse, tsTrue );

var
  _WordApplicationExists : TTriState = tsUnknown;

function WordApplicationExists : Boolean;
var
  WordObj: OleVariant;
begin
  if _WordApplicationExists = tsUnknown 
  then
    try
      WordObj := CreateOleObject('Word.Application');
      WordObj.Visible := False;
      WordObj.Quit;
      WordObj := Unassigned;
      _WordApplicationExists := tsTrue;
    except
      _WordApplicationExists := tsFalse;
    end;

  Result := _WordApplicationExists = tsTrue;
end;
Sir Rufo
  • 18,395
  • 2
  • 39
  • 73
4

This code will work fine, and is correctly implemented. A nullable boolean or a tristate enum will read better, but fundamentally the logic would be the same.

It's heavy handed and clunky approach though, invoking an instance of Word that is then thrown away. Personally I would read the registry to check whether or not the COM object is registered. I would not attempt to anticipate the case where the object is registered but cannot be created. In my view that is an exceptional case that should be handled when it occurs, but not before.

Another way to go is simply not to attempt to check ahead of time for the Word COM object being available. Just go ahead and attempt to create the object when you need to use it. If this fails, deal with that. If you wish to remember that it failed, do so. But you really should avoid creating the object twice when once will suffice.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 2
    The result should be renamed to `WordApplicationExistsAndCanBeCreated`because that is, what the value represents. If the result should only store `WordApplicationRegistered` then it is sufficient to check the registry. You have to take care what you check and what conclusion you can get from that check. – Sir Rufo Dec 06 '15 at 09:25
  • @SirRufo, You have a point. I might change it to `WordApplicationCanCreate` or `WordApplicationReady`. – zig Dec 06 '15 at 10:36
  • @David, In my special case, I use a 3rd party component that (tries to) creates the COM object, but **handles** the exception in a way that I don't want (ShowMessage with error details). So, I need to handle this Exception myself, before the component does. But what really interest me is the Boolean value being cast from/to Integer. – zig Dec 06 '15 at 10:37
  • I'd resolve that by fixing the third party code, or replacing it. That approach is simply untenable. – David Heffernan Dec 06 '15 at 11:45
  • I'm sorry I even mentioned the COM in my question. never the less your answer is valuable. And your insights about the COM issue are wise and confirm what I already knew. Caching the results of this function will cause word COM to be instantiated only once in my application before calling the actual COM work (and yes, throw it away). I can live with that. +1 And thank you. – zig Dec 06 '15 at 14:34
0

This could be done also with a Variant type. Variants are set to Unassigned. (reference)

var
  _WordApplicationCanCreate: Variant; // Unassigned (VType = varEmpty)

function WordApplicationCanCreate: Boolean;
var
  WordObj: OleVariant;
begin
  if VarIsEmpty(_WordApplicationCanCreate) then
  try
    WordObj := CreateOleObject('Word.Application');
    WordObj.Visible := False;
    WordObj.Quit;
    WordObj := Unassigned;
    _WordApplicationCanCreate := True;
  except
    _WordApplicationCanCreate := False;
  end;

  Result := _WordApplicationCanCreate = True;
end;
Community
  • 1
  • 1
zig
  • 4,524
  • 1
  • 24
  • 68
  • You could use a floating point value too, or a string, or indeed any type that can hold more than three distinct values. But why would you. A variant is a poor idea. – David Heffernan Dec 06 '15 at 15:04
  • @DavidHeffernan, Why is it a poor idea in my case? it's simple enough, where is the catch? – zig Dec 06 '15 at 15:35
  • Would you be fine to use a string? No, I didn't think so. A variant is for something that can vary type. Here you want a nullable boolean, or a tri state enum or two booleans. U – David Heffernan Dec 06 '15 at 15:59