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.