3

我有一个用 Delphi 7 编写的程序,它也是一个自动化服务器。

自动化服务器通过以下方式注册:

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

我有两个 COM 加载项,一个用于 Word,一个用于 Outlook。他们都使用自动化服务器从主程序中获取一些信息。从加载项调用以下代码,即:当用户单击加载项中的按钮时:

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

问题是: 大多数时候代码工作正常。如果主应用程序已经在运行,插件将连接到自动化服务器并执行它们的操作,如果它没有运行,那么插件将启动主应用程序。

但是由于一些未知的情况,尤其是使用 Outlook,有时会出现即使主程序正在运行,加载项也不会连接到它,而是会再次重新启动主应用程序并连接到自动化服务器这个新实例。灾难来了:因为我的应用程序不允许自己在两个实例中运行,所以第二个应用程序实例只会显示一条错误消息,而我的加载项将冻结整个 Outlook。

为什么会这样?为什么大多数时候 CreateOleObject 会像它应该的那样连接,并且不时地再次启动我的应用程序?

4

1 回答 1

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.

于 2014-05-15T10:52:50.927 回答