Jump to content
Sign in to follow this  
Memnarch

Possible error in generated Units in "Windows API from WinMD"

Recommended Posts

Hello,
I need a second opinion on this. Either I am drunk or there is a huge error in the generated Windows units one can install through GetIT from Emarcadero called "Windows API from WinMD". If the following is an error, I'll create a ticket. If not, someone needs to explain this to me.

 

I am trying to use IMMDeviceEnumerator from Windows.Media.Audio.pas. Calling GetDefaultAudioEndpoint, I was unable to get the defaultdevice. This is how the interface is declared:
 

  ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/mmdeviceapi/nn-mmdeviceapi-immdeviceenumerator</summary>
  ///<remarks>
  ///<para>Supported since: <i>windows6.0.6000</i></para>
  ///</remarks>
  IMMDeviceEnumerator = interface(IUnknown)
  ['{A95664D2-9614-4F35-A746-DE8DB63617E6}']
    ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdeviceenumerator-enumaudioendpoints</summary>
    function EnumAudioEndpoints(dataFlow: EDataFlow; dwStateMask: Cardinal; out ppDevices: IMMDeviceCollection): HRESULT; stdcall;
    ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdeviceenumerator-getdefaultaudioendpoint</summary>
    function GetDefaultAudioEndpoint(dataFlow: EDataFlow; role: ERole; out ppEndpoint: IMMDevice): HRESULT; stdcall;
    ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdeviceenumerator-getdevice</summary>
    function GetDevice(pwstrId: PWSTR; out ppDevice: IMMDevice): HRESULT; stdcall;
    ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdeviceenumerator-registerendpointnotificationcallback</summary>
    function RegisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
    ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/mmdeviceapi/nf-mmdeviceapi-immdeviceenumerator-unregisterendpointnotificationcallback</summary>
    function UnregisterEndpointNotificationCallback(pClient: IMMNotificationClient): HRESULT; stdcall;
  end;

Suspecting something in the method declaration (i.e. out) was wrong. I copied it over to my code...and it started to work? Like, I didn't change a thing or did I? So after being confused for a moment, I looked at the interafce it inherits from.
When the Interface is in my code, due to the units in my uses, it derives from System.IUnknown which is declared like this:
 

  IInterface = interface
    ['{00000000-0000-0000-C000-000000000046}']
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

  IUnknown = IInterface;

However, in the generated code, it derives from Windows.Foundation.IUnknown which is declared like this:
 

  IUnknown = interface
  ['{00000000-0000-0000-C000-000000000046}']
    ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(q)</summary>
    ///<remarks>
    ///<para>Can return errors as success</para>
    ///</remarks>
    function QueryInterface(riid: PGuid; out ppvObject: Pointer): HRESULT; stdcall;
    ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/unknwn/nf-unknwn-iunknown-addref</summary>
    function AddRef: Cardinal; stdcall;
    ///<summary>Documentation: https://docs.microsoft.com/windows/win32/api/unknwn/nf-unknwn-iunknown-release</summary>
    function Release: Cardinal; stdcall;
  end;

So unless I am missing something crucial, this IUnknown-Interface is adding the same 3 methods it already has thorugh the IInterface-Baseinterface and offsets the entire VMT by 3 entries across the board.
And by adding/removing Windows.Foundation from my uses I can make the copy of the interface work/break at will. So this seems like an oversight and that IUnknown should just inherit from IInterface or be an alias, or am I wrong?

Share this post


Link to post

Hi,

6 hours ago, Memnarch said:

So unless I am missing something crucial, this IUnknown-Interface is adding the same 3 methods it already has thorugh the IInterface-Baseinterface and offsets the entire VMT by 3 entries across the board.
And by adding/removing Windows.Foundation from my uses I can make the copy of the interface work/break at will. So this seems like an oversight and that IUnknown should just inherit from IInterface or be an alias, or am I wrong?

VMT is not a problem here, VMT for such interfaces are different beast from VMT for objects/classes, VMT tables with COM interfaces (interfaces with GUID which i like to call "named interfaces" or "IDed interfaces") are separated into their own tables identified by their GUID, even for one object/interface, so VMT will be alright no matter what inheritance is there, also VMT for each interface are agnostic for other GUID declaration.

 

As for the interfaces you listed IInterface and IUnknown, this might be a problem as they declared with the same GUID (TGUID) hence they will compete to replace one another, they are identical in structure but different in parameters (declaration), so they will work unless the compiler will complain about stuff like Integer vs Cardinal or TGUID vs PGUID..., the problem here is how compiler see them and when.

 

I might be wrong here, but the fix should be removing Windows.Foundation.IUnknown , in other words, the already known interfaces should not be redeclared/generated.

Share this post


Link to post

There were other discussion about WINMD, that seems to be buggy, look there: https://en.delphipraxis.net/topic/13720-bugs-on-winmd-who-can-clarify/?do=findComment&comment=105350

 

In one post, @pcoder suggest the see others metadata provider (like this https://www.winsoft.sk/win32api.htm ).

If you look insede of those sources, you will look that more types were derivered form SYSTEM unit, like IUNKNOWN for example.

Try this instead WINMD.

 

There are in QP some open issues about WINMD.

 

 

Share this post


Link to post
15 hours ago, Kas Ob. said:

VMT is not a problem here, VMT for such interfaces are different beast from VMT for objects/classes, VMT tables with COM interfaces (interfaces with GUID which i like to call "named interfaces" or "IDed interfaces") are separated into their own tables identified by their GUID, even for one object/interface, so VMT will be alright no matter what inheritance is there, also VMT for each interface are agnostic for other GUID declaration.

That is not right. Interfaces inherit from each other, and while an class only ever implements explicitly used interfaces, if you inherit IBar from IFoo, IBar has all methods from IFoo. if I do this:

 

IFoo = interface
  procedure DoSomething;
end;

DoSomething is not the first but fourth method in the table as it derives from IInterface, which already has 3 methods.

If I do this:

IFoo = interface(IUnknown)
  procedure DoSomething;
end;

My method, when using the IUnknown from System.pas, is still the fourth method in the table. All those interfaces declared in the API deriving from IUnknown expect to have their first method, being the fourth in the table, too. And on the Windows implementing side, it is exactly this. They derive from IUnknown, which has 3 methods and does not derive from anything else. So the windows interfaces start with 4 as their first method slot, just like any interface you write in Delphi.

 

However, given that IUnknown was just copied from the metadata during generation, in Delphi it derives from IInterface, which is Delphis "IUnknown". That way, it introduces 3 more methods to the table, making DoSomething of IFoo the 7th method. Therefore on the delphi side if you call DoSomething on the interface coming from a windows api class, you're off by 3 and call something entirely else. 

 

15 hours ago, DelphiUdIT said:

In one post, @pcoder suggest the see others metadata provider (like this https://www.winsoft.sk/win32api.htm ).

If you look insede of those sources, you will look that more types were derivered form SYSTEM unit, like IUNKNOWN for example.

Try this instead WINMD. 

Looked into it. That one looks correct! Thanks for sharing this!

Edited by Memnarch

Share this post


Link to post
8 hours ago, Memnarch said:

That is not right.

It is right.

8 hours ago, Memnarch said:

My method, when using the IUnknown from System.pas, is still the fourth method in the table. All those interfaces declared in the API deriving from IUnknown expect to have their first method, being the fourth in the table, too. And on the Windows implementing side, it is exactly this. They derive from IUnknown, which has 3 methods and does not derive from anything else. So the windows interfaces start with 4 as their first method slot, just like any interface you write in Delphi.

 

However, given that IUnknown was just copied from the metadata during generation, in Delphi it derives from IInterface, which is Delphis "IUnknown". That way, it introduces 3 more methods to the table, making DoSomething of IFoo the 7th method. Therefore on the delphi side if you call DoSomething on the interface coming from a windows api class, you're off by 3 and call something entirely else. 

OK, hold your horses here for second, and i want you to check if you are checking the VMT for the interfaces or something else like TObject, also are they named (with TGUID attached) ?

 

I recommend that you go to this blog post and and read this very article and study the output carefully, try to get how interface inheritance and their VMT works.

 

Here a modified example from the blog 

program Project6;

{$APPTYPE CONSOLE}

uses
  Classes,
  SysUtils,
  TypInfo,
  ComObj;

type
  IFoo = interface
    ['{11111111-0000-0000-0000-000000000001}']
    procedure DoSomething;
  end;

  IBar = interface
    ['{22222222-0000-0000-0000-000000000002}']
    procedure DoSomething;
  end;

  TFooBar = class(TObject, IUnknown, IFoo, IBar)
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    procedure DoSomething;
  end;

  TBarFoo = class(TObject, IBar, IFoo, IUnknown)
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    procedure DoSomething;
  end;

  TFooBarOnly = class(TObject, IFoo, IBar)
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    procedure DoSomething;
  end;

  TBarFooOnly = class(TObject, IBar, IFoo)
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    procedure DoSomething;
  end;

procedure DumpInterfaces(AClass: TClass);
var
  i: integer;
  InterfaceTable: PInterfaceTable;
  InterfaceEntry: PInterfaceEntry;
begin
  while Assigned(AClass) do
  begin
    InterfaceTable := AClass.GetInterfaceTable;
    if Assigned(InterfaceTable) then
    begin
      writeln('Implemented interfaces in ', AClass.ClassName);
      for i := 0 to InterfaceTable.EntryCount - 1 do
      begin
        InterfaceEntry := @InterfaceTable.Entries[i];
        Write(Format('%d. GUID = %s', [i, GUIDToString(InterfaceEntry.IID)]));

        Writeln('  VMT addr: ', IntToHex(Cardinal(InterfaceEntry.VTable), SizeOf(Pointer) * 2), ' offest: ', InterfaceEntry.IOffset);
      end;
    end;
    AClass := AClass.ClassParent;
  end;
  writeln;
end;

{ TFooBar }

procedure TFooBar.DoSomething;
begin
end;
function TFooBar.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
end;
function TFooBar._AddRef: Integer;
begin
end;
function TFooBar._Release: Integer;
begin
end;

{ TBarFoo }

procedure TBarFoo.DoSomething;
begin
end;
function TBarFoo.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
end;
function TBarFoo._AddRef: Integer;
begin
end;
function TBarFoo._Release: Integer;
begin
end;

{ TBarFooOnly }

procedure TBarFooOnly.DoSomething;
begin
end;
function TBarFooOnly.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
end;
function TBarFooOnly._AddRef: Integer;
begin
end;
function TBarFooOnly._Release: Integer;
begin
end;

{ TFooBarOnly }

procedure TFooBarOnly.DoSomething;
begin
end;
function TFooBarOnly.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
end;
function TFooBarOnly._AddRef: Integer;
begin
end;

function TFooBarOnly._Release: Integer;
begin

end;

begin
  DumpInterfaces(TComponent);
  DumpInterfaces(TComObject);
  DumpInterfaces(TComObjectFactory);

  DumpInterfaces(TFooBar);
  DumpInterfaces(TBarFoo);
  DumpInterfaces(TFooBarOnly);
  DumpInterfaces(TBarFooOnly);

  readln;
end.

The output from my XE8 

Implemented interfaces in TComponent
0. GUID = {E28B1858-EC86-4559-8FCD-6B4F824151ED}  VMT addr: 0048C71C offest: 56
1. GUID = {00000000-0000-0000-C000-000000000046}  VMT addr: 0048C72C offest: 60

Implemented interfaces in TComObject
0. GUID = {DF0B3D60-548F-101B-8E65-08002B2BD119}  VMT addr: 004CF25C offest: 24
1. GUID = {00000000-0000-0000-C000-000000000046}  VMT addr: 004CF26C offest: 28

Implemented interfaces in TComObjectFactory
0. GUID = {B196B28F-BAB4-101A-B69C-00AA00341D07}  VMT addr: 004CF93C offest: 72
1. GUID = {00000001-0000-0000-C000-000000000046}  VMT addr: 004CF93C offest: 72
2. GUID = {00000000-0000-0000-C000-000000000046}  VMT addr: 004CF93C offest: 72

Implemented interfaces in TFooBar
0. GUID = {22222222-0000-0000-0000-000000000002}  VMT addr: 004D2880 offest: 4
1. GUID = {11111111-0000-0000-0000-000000000001}  VMT addr: 004D2890 offest: 8
2. GUID = {00000000-0000-0000-C000-000000000046}  VMT addr: 004D2880 offest: 4

Implemented interfaces in TBarFoo
0. GUID = {00000000-0000-0000-C000-000000000046}  VMT addr: 004D2AC4 offest: 4
1. GUID = {11111111-0000-0000-0000-000000000001}  VMT addr: 004D2AC4 offest: 4
2. GUID = {22222222-0000-0000-0000-000000000002}  VMT addr: 004D2AD4 offest: 8

Implemented interfaces in TFooBarOnly
0. GUID = {22222222-0000-0000-0000-000000000002}  VMT addr: 004D2D08 offest: 4
1. GUID = {11111111-0000-0000-0000-000000000001}  VMT addr: 004D2D18 offest: 8

Implemented interfaces in TBarFooOnly
0. GUID = {11111111-0000-0000-0000-000000000001}  VMT addr: 004D2F34 offest: 4
1. GUID = {22222222-0000-0000-0000-000000000002}  VMT addr: 004D2F44 offest: 8

 

Now to simplify your miss understanding, if what you said about off by 3 then the position of the interface (order) in the declaration will matter, and that can't be, right ?

Share this post


Link to post

Here another and simpler example, try this interface with the above function

  TInterfacedObjectFooBar = class(TInterfacedObject, IBar, IFoo)
    procedure DoSomething;
  end;

  TInterfacedObjectFooBarEx = class(TInterfacedObjectFooBar, IBar, IFoo)
    procedure DoSomething;
  end;

And its result 

Implemented interfaces in TInterfacedObjectFooBar
0. GUID = {11111111-0000-0000-0000-000000000001}  VMT addr: 004D3160 offest: 12
1. GUID = {22222222-0000-0000-0000-000000000002}  VMT addr: 004D3170 offest: 16
Implemented interfaces in TInterfacedObject
0. GUID = {00000000-0000-0000-C000-000000000046}  VMT addr: 00402358 offest: 8

Implemented interfaces in TInterfacedObjectFooBarEx
0. GUID = {11111111-0000-0000-0000-000000000001}  VMT addr: 004D32F8 offest: 20
1. GUID = {22222222-0000-0000-0000-000000000002}  VMT addr: 004D3308 offest: 24
Implemented interfaces in TInterfacedObjectFooBar
0. GUID = {11111111-0000-0000-0000-000000000001}  VMT addr: 004D3160 offest: 12
1. GUID = {22222222-0000-0000-0000-000000000002}  VMT addr: 004D3170 offest: 16
Implemented interfaces in TInterfacedObject
0. GUID = {00000000-0000-0000-C000-000000000046}  VMT addr: 00402358 offest: 8

So make sure you are looking at the right VMT, remember that TObject VMT order is irrelevant with its interfaces VMTs structure and order.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×