3

I have a program written in Delphi 7 that is also an automation server.

The automation server is registered the following way:

  TAutoObjectFactory.Create(ComServer, TMyServer, Class_App,
    ciMultiInstance, tmSingle);

I have two COM add-ins, one for Word and one for Outlook. Both of them are using the automation server to get some info from the main program. The following code is called from the add-ins, ie.: when the user clicks on a button in the add-in:

 MyServerApp: Variant;
begin
 MyServerApp := CreateOleObject('MyServer.App');
 try
   MyServerApp.DoSomething;
 finally
   MyServerApp := UnAssigned;
 end–

Here is the problem: Most of the time the code works fine. If the main application is already running the add-ins will connect to the automation server and do their thing, if it is not running then the add-ins will launch the main application.

But due to some unknown circumstances, especially with Outlook, it can sometimes happen that even though the main program is running, the add-in will not connect to it, but will instead relaunch the main application a second time and connect to the automation server of this new instance. The disaster comes here: since my app doesn't allow itself to be run in two instances, the second application instance will just display an error message and my add-in will freeze whole Outlook.

Why does this happen? Why will CreateOleObject connect like it should most of the time, and launch my application again from time to time?

Steve
  • 2,510
  • 4
  • 34
  • 53
  • What version of Windows? I saw issues like this in XP but never in Windows 7 or Vista or Win8. – Warren P May 13 '14 at 17:17
  • It's Windows 7 with Outlook2010, but I remember seeing the error with different Win and Outlook versions too. – Steve May 13 '14 at 21:43

1 Answers1

1

You really should not ask multiple questions in one post.

Question 1

It happened to me a lot. The problem is that Office generate two calls for every event that triggers code in the Add-in. The solution I found was only respond to the first call.

I used Add-In Express for the COM addin, which gave me some events I could link into.
I'm not sure if you're using this but here's the code I used:

interface

....

var
  MyApp: TAddInModule = nil;

implementation

procedure TAddInModule.adxCOMAddInModuleAddInFinalize(Sender: TObject);
begin
  MyApp:= nil;
end;

procedure TAddInModule.adxCOMAddInModuleAddInInitialize(Sender: TObject);
begin
  if not(Assigned(MyApp)) then try
    MyApp:= Self;
  except
    {ignore}
  end; {if try}
end;

In event handler you'll have to test to see if the first instance is referenced, or the ghost instance. (Both get called at times).

procedure TAddInModule.adxCommandBar1Controls3Click(Sender: TObject);
begin
  if (Self <> MyApp) then exit;
  //ToggleDisplay
  if not(ExcelBezig(xbQuestion)) then try
    ToggleDisplay;
  except {ignore}
  end;
end;

This is a kludge (I admit), but it solved the problem once and for all and the add-in is rock stable ever since.

Don't recreate your link over and over
You should not be using CreateOleObject('MyServer.App'); every time you need to query the application. You call CreateOleObject once when the addin is activated, store that instance and then reuse the link. Something like:

procedure TAddInModule.adxCOMAddInModuleAddInInitialize(Sender: TObject);
begin
  if not(Assigned(MyApp)) then try
    MyApp:= Self;
    MyServerApp:= CreateOleObject('MyServer.App');
  except
    {ignore}
  end; {if try}
end;


procedure TAddInModule.adxCommandBar1Controls3Click(Sender: TObject);
begin
  if (Self <> MyApp) then exit;
  try
    MyServerApp.DoSomething;
  except
    {ignore}
  end;
end;

Using a variant to access an automation server is slow!
Because you're using a variant to store the reference to your automation service Delphi cannot resolve your call at compile time.
It also cannot help you to avoid typos and other errors.
Any call to a server accessed via a variant is valid.

So

 MyServer.StupidTyyyypo('hallo').doesnotexist('should be integer');

Will compile without error.
If you import the type lib and make your access variable a specific type, e.g.:

type
  TMyServer = IMyServer;

You get the IMyServer by importing the type library from your Delphi automation server, see: http://www.blong.com/Articles/Automation%20In%20Delphi/Automation.htm
Section: Controlling Automation Servers Using Interfaces and below.

Question 2

Why does CreateOleObject connect to the running application instance and not create a separate instance all the time?

See the official documentation: http://docwiki.embarcadero.com/Libraries/XE2/en/System.Win.ComObj.CreateOleObject

It states:

CreateOleObject creates a single uninitialized object of the class specified by the ClassName parameter. ClassName specifies the string representation of the Class ID (CLSID). CreateOleObject is used to create an object of a specified type when the CLSID is known, and when the object is on a local or in-proc server. Only objects that are not part of an aggregate are created using CreateOleObject.

Note: In Delphi code, CreateOleObject is called once to create each new single instance of a class. To create multiple instance of the same class, using a class factory is recommended.

Question 3

does the tmSingle threading model mean that all calls to the automation server are executed in the application's main thread?

You should ask that in a separate question.

Community
  • 1
  • 1
Johan
  • 74,508
  • 24
  • 191
  • 319
  • I don't understand *Answer 1*. I'm do using add-in express, but all my calls to the automation server of my main application happen inside user induced events - ie. in your example the adxCommandBar1Controls3Click event. I know that AddInFinalize or AddInInitialize may be called multiple times, but user induced events are called only once in my experience. *Answer 2:* I don't want to create multiple instances of the same class, I want to create multiple single instances. And this is what is happening most of the time, new instances are created within the main app but why not always? – Steve May 15 '14 at 12:54
  • I clarified the original question, please read again - maybe it makes more sense now. Thanks! – Steve May 15 '14 at 13:06
  • I think that creating the automation server once and reusing the variable is not a good practice, esp. in an add-in. A word add-in for example will have multiple instaces, as Word can be launched in multiple copies. These instances depending on how add-in express does things may share the same address space, meaning the use of a global variable could lead to utter confusion... Not even to talk about the Ole interface releasing the variable as it goes out of scope! Regarding CreateOleObject please check Answer 2 in my previous comment, the help files can be understood in different ways... – Steve May 15 '14 at 14:25