I used to think that type safety in Delphi with regard to Interfaces is maintained by setting a unique (optional, but unique if filled in) GUID to it.
Then there came that question: Unspecified error when calling Word CentimetersToPoints via OLE
Little follow-up on it: http://pastebin.ca/2369858
And i started looking in stock Delphi TWordApplication
component (namely Word200.pas
unit). And there i see:
// *********************************************************************//
// Interface: _Application
// Flags: (4560) Hidden Dual NonExtensible OleAutomation Dispatchable
// GUID: {00020970-0000-0000-C000-000000000046}
// *********************************************************************//
_Application = interface(IDispatch)
['{00020970-0000-0000-C000-000000000046}']
...
function CentimetersToPoints(Centimeters: Single): Single; safecall;
// *********************************************************************//
// DispIntf: _ApplicationDisp
// Flags: (4560) Hidden Dual NonExtensible OleAutomation Dispatchable
// GUID: {00020970-0000-0000-C000-000000000046}
// *********************************************************************//
_ApplicationDisp = dispinterface
['{00020970-0000-0000-C000-000000000046}']
...
function CentimetersToPoints(Centimeters: Single): Single; dispid 371;
or similar:
// *********************************************************************//
// Interface: _Global
// Flags: (4560) Hidden Dual NonExtensible OleAutomation Dispatchable
// GUID: {000209B9-0000-0000-C000-000000000046}
// *********************************************************************//
_Global = interface(IDispatch)
['{000209B9-0000-0000-C000-000000000046}']
...
function CentimetersToPoints(Centimeters: Single): Single; safecall;
// *********************************************************************//
// DispIntf: _GlobalDisp
// Flags: (4560) Hidden Dual NonExtensible OleAutomation Dispatchable
// GUID: {000209B9-0000-0000-C000-000000000046}
// *********************************************************************//
_GlobalDisp = dispinterface
['{000209B9-0000-0000-C000-000000000046}']
...
function CentimetersToPoints(Centimeters: Single): Single; dispid 371;
And i feel totally lost here.
I used to think that dispinterface
is "subclass" of interface
like TPersistent
to TObject
is ? If yes, then how can be two interfaces with same GUID in same project ?
Or are they from different unrelated frameworks, like Delphi new class
types to inherited TurboPascal object
types ? Neither _GlobalDisp
nor _ApplicationDisp
seems to be used in Word200.pas
so are they just like appendix, auto-imported but never actually used ?
I made the project, using both _Application
and _ApplicationDisp
and it compiles. But then i only wonder how does Delphi typcast it, if they have the SAME GUID ?
procedure TForm4.Button1Click(Sender: TObject);
procedure show(const s: Single);
begin
ShowMessage(FloatToStr(s));
end;
begin
show( WordApplication1.CentimetersToPoints(1.0) );
show( WordApplication1.Application.CentimetersToPoints(2.0) );
show( WordApplication1.DefaultInterface.CentimetersToPoints(3.0) );
show( _ApplicationDisp(WordApplication1.Application).CentimetersToPoints(4.0) );
show( (WordApplication1.DefaultInterface as _ApplicationDisp).CentimetersToPoints(5.0) );
end;
The dispinterface
is really just a convenient way to use IDispatch
for an automation interface. That's why they have the same GUID – they are exactly the same thing behind the scenes.
When you use IDispatch
to invoke a method you typically have to call GetIdsOfNames
to obtain the dispatch ID for your method. But since these are static, you can save time by skipping that step, if you know the dispatch ID. And that's what a dispinterface
allows you to do.
When you call a method on a dispinterface
you still end up calling Invoke
on the IDispatch
, but you skip the call to GetIdsOfNames
.
When you use QueryInterface
with an interface, you'll get the IDispatch
. You can then cast it to its corresponding dispinterface
. It's still the same interface, but when you invoke methods on the dispinterface
you'll save that call to GetIdsOfNames
.
So, if you have an IDispatch
for the Word application object, say, you can write code like this:
var
WordApp: Variant;
WordDisp: _ApplicationDisp;
....
WordApp := CreateOleObject('Word.Application');
WordDisp := _ApplicationDisp(IDispatch(WordApp));
The _ApplicationDisp()
cast is nothing more than a call to IntfCopy
. Which in turn is nothing more than a call to _AddRef
. And you can then write:
Writeln(WordApp.ProductCode);
Writeln(WordDisp.ProductCode);
Both produce the same output. The former first calls GetIdsOfNames
before calling Invoke
. The latter goes straight to Invoke
.