简体   繁体   中英

Automation server is launched twice with CreateOleObject but not all the time

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?

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, eg:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM