1
  • let we have I1 interface and I2 inherited from I1 ( struct I2 : public I1 {..} in c++ notation)
  • let we have pointer to I2 interface: I2* p2;
  • we need get pointer to I1 interface.

question:

are

I1* p1 = p2;
// use p1

always valid by semantic without any assumption, implementations details etc. and we not need call QueryInterface here (ie p2->QueryInterface(IID_PPV_ARGS(&p1) )

(from c++ syntax we even not need use explicit cast in I1* p1 = p2; here, in c need cast I1* p1 = (I1*)p2; but this is not language layer question)

or even more concrete example. say some function require pointer to I1 interface ( void fn(I1*)), but we have pointer to I2* p2. can we call/pass fn(p2) as is ( from c++ formal rules this is correct - we can pass pointer to derived class in place base pointer)

my opinion that yes, and this is obvious, but because exist another opinion - I just wonder to hear the arguments

so why is this cast ok?

  1. we can call any method from I2 by using p2 pointer? yes
  2. any method of I1 was method of I2 too ? yes, because I2 inherit from I1
  3. from 1 and 2 we can call any method of I1 by using p2 pointer

and this is enough. here no any assumption at all. by using p2 pointer as is (without any binary modification) we can call any methods of I1 interface. as result this is valid pointer to I1 interface too

possible and say next

I1* p1 = p2; // p1 == p2 on binary level, because COM interface have single vtable pointer
p1->func(..);// let func is  some method of I1 interface
p2->func(..);// will be the exactly same binary code as p1->func(..)

so p1->func(..) correct when and only when, p2->func(..) is correct. but p2->func(..) is correct

can somebody say what's wrong here? are exist hidden assumption? are possible provide example when this is wrong?


some additional notes


note#1:

please not confuse caste I2 to I1 with cast I1 to I2. this cast is wrong and require call to QueryInterface

I1* p1;
I2* p2;
p2 = static_cast<I2*>(p1);            // wrong !!
p1->QueryInterface(IID_PPV_ARGS(&p2)) // ok

why? let exist interface I3 which is also inherit from I1, but I3 not inherit from I2 and I2 not inherit from I3 and some object implement both I2 and I3 (as result and I1) this mean that object containing how minimum 2 different vtable pointers - one to I2 and one to I3 when we query to I1 interface, QueryInterface can return

static_cast<I1*>(static_cast<I2*>(this)) // pointer to I1 via I2 vtable

but can and return

static_cast<I1*>(static_cast<I3*>(this)) // pointer to I1 via I3 vtable

if p1 point I2 vtable cast will be ok, but if p1 point to I3 vtable - will be wrong result - p1 not valid pointer to I2 in this case but again - question about another conversion. so all this unrelated to question.


note#2

but after p2->QueryInterface(IID_PPV_ARGS(&p1)) we can got another binary pointer p1 ( p1 != p2 on binary level) ?

yes, can! the same as in previous note - if object inherit I2 and I3 (both it inherit from I1 but not from each other) in result of p2->QueryInterface(IID_PPV_ARGS(&p1)) call can be returned or

static_cast<I1*>(static_cast<I2*>(this)) // in this case will be p2 == p1

or

static_cast<I1*>(static_cast<I3*>(this)) // in this case will be p2 != p1

so if QueryInterface can return different binary pointer to I1 (not equal to original p2) - which one is correct? this say about p1 = p2; cast wrong ? no. the both pointers is correct and can be used for call methods of I1 interface.

many may be say here - wait, but how this can be?

if we call p1->func(..) and p2->func(..) and p1 != p2 the func() will be called with different pointers, but only one can be correct?

error here - that we have 2 different func() - in case p1 != p2 the p1->func can be != p2->func func here not concrete function address. but this address taken from vtable to which point p1 or p2. because p1 != p2 - here different vtables will be used and different pointers in this vtables. how minimum in one of this vtables will be adjustor thunk (possible in both) which adjust incoming p1 (or p2) pointer and then jump to "real" func implementation

so again - both pointers (p1 = p2 and p2->QueryInterface(IID_PPV_ARGS(&p1)) ) is correct here and both can be used


note#3:

please not confuse COM Interface and object which implement one or more com interfaces. from Interface Pointers and Interfaces

An instance of an interface implementation is actually a pointer to an array of pointers to methods - that is, a function table that refers to an implementation of all of the methods specified in the interface.

also look What Is a COM Interface?

i not ask about convert pointer to object which implement (inherit) interface to pointer to this interface. i ask only about interface pointer conversion.

interface from c/c++ declaration - this is pointer size object - containing single pointer to array of pointers to methods

interface IXxx
{
    CONST_VTBL struct IXxxVtbl *lpVtbl;
};

for instance

class I3 : public I1, I2
{
    // new methods
};

here I3 not interface, but object which implement I1 and I2 interfaces.


note#4 "layout of interface is unknown" - this is not true. layout of interface is always known and explicit defined. layout of object, which implement interface is unknown. but this is different things - i not assume any layout of object. again why example with

class I3 : public I1, I2
{
    // new methods
};

is wrong.

here binary pointer to I3 not valid pointer to I2 (require adjustment) despite I3 inherit from I2. but com interface can not be such defined. i say next if com interface I2 inherit from I1 interface - pointer to I2 is always valid pointer to I1 too. this is from interface definition

can only again repeat example

I1* p1 = p2;
p1->func(..);
p2->func(..);

p1 will be the same binary value as p2 here - this is due specific interfaces definitions. as result p1->func(..); and p2->func(..); produce the same binary code. if p1->func(..); will be wrong - p2->func(..); will be wrong too. but p2->func(..); is correct by definiton

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • I think this comes down to more of a style question -- if you obtain a base class pointer using QI then it is reference counted and you can release the old one etc. , whereas if you obtain by implicit conversion then you've got to be careful to be done with it before releasing the derived pointer – M.M Jul 01 '21 at 23:16
  • @M.M - the reference counting - this is another question. i not touch it here and not view any problem with it. question - are after direct cast `p1 = p2` - are `p1` will be **always** valid pointer to `I1` interface ? simply look for https://stackoverflow.com/questions/68205123/get-parent-com-object-from-a-child-com-object question - i "ask" only because this and sure wrong answer here – RbMm Jul 01 '21 at 23:20
  • Isn't this question exactly the same as that question you linked? – M.M Jul 01 '21 at 23:30
  • 1
    Imagine this scenario, the object was created by a different language (maybe on a different machine) and uses a different base class layout than your C++ compiler. Then the implicit cast to base class wouldn't work. It seems to me it would only work if you are in a scenario where it is guaranteed your code is the only code implementing the object – M.M Jul 01 '21 at 23:38
  • @M.M - *com* is language independed. and based on binary abi. i not make any assumption about language, base class layout , etc. i write at begin self arguments - why this is correct. if you think this is wrong - can you provide example - when this cast lead to wrong result and/or explain where i wrong ? – RbMm Jul 01 '21 at 23:48
  • @M.M *Isn't this question exactly the same..* - by sense - yes. but i sure there wrong answer and i ask question in more abstract terms. try also explain here - why - in more details – RbMm Jul 01 '21 at 23:50
  • I guess the question is whether the binary ABI guarantees an exact vtable layout , and whether whatever C++ compiler you're using uses that same layout – M.M Jul 01 '21 at 23:52
  • @M.M - *COM* is special design for use from different languages. this not for *c/c++* only. and where you view any my assumption about object layout, language, compiler, etc ? i written - *so why is this cast ok?...from 1 and 2 we can call any method of I1 by using p2 pointer* - what is wrong here ? and can you build concrete example, where such cast is wrong ? – RbMm Jul 01 '21 at 23:56
  • do you understand that different C++ compilers might use different memory layout for a vtable? (especially in the case of multiple inheritance). Or not even use vtables at all – M.M Jul 01 '21 at 23:59
  • @M.M - yes, of course - but how i based on this and how this break my code ? provide concrete example please or point to error – RbMm Jul 02 '21 at 00:00
  • imagine compiler A uses vtable layout A, and compiler B uses vtable layout B . you make the object using compiler A, and consume it in compiler B. Compiler B performs implicit base cast and gets some nonsense result since the vtable was not laid out the same . – M.M Jul 02 '21 at 00:01
  • @M.M - sorry - but i not understand what you say. all this unrelated at all here. reread my arguments - in 1.2.3 - *we can call any method from I2 by using p2 pointer? yes..* - i not make any assumption of compiler, layout, etc. and can you post **concrete** code (implementation of I1/I2) which break my code ? – RbMm Jul 02 '21 at 00:03
  • @M.M - *vtable layout A* - the layout of functions in vtable - is *hard-coded* by interface definition (language independed) all must use the same layout (if you mean order of functions in vtable) or about which "layout" you say at all. i say that vtable for `I2` will be valid vtable and for `I1` and can be used as pointer to `I1` interface (but bot visa versa - how i say in note#1) – RbMm Jul 02 '21 at 00:07
  • I'm not going to post a compiler implementation, no. You only need to understand that different implementations exist. The IDL does not specify a vtable layout . – M.M Jul 02 '21 at 00:12
  • @M.M - i great understand all topic internal. i not understand - what you mean under "vtable layout" - what you mean under this and how this related to my logic ? **where** i make any assumption about "layout" ? where i make any assumption and based on "implementation" ?! – RbMm Jul 02 '21 at 00:14
  • @M.M - *we can call any method from `I2` by using `p2` pointer? yes. any method of `I1` was method of `I2` too ? yes, because `I2` inherit from `I1`. from this we can call any method of `I1` by using `p2` pointer and this is enough.* - here is complete my argumentation – RbMm Jul 02 '21 at 00:16
  • 1
    @M.M, It appears you have a confusion about the COM rules. COM ABI doesn’t mandate the use C++; it’s just convenient. It also doesn’t mandate a specific C++ toolset. But it does mandate very explicitly interfaces’ layout in memory. You are correct that a C++ compiler, while uncommon, is allowed to implement virtual classes and inheritance using something other than VTables. Such a compiler might be a legal C++ compiler, but it is also incompatible with COM - at least not without a lot of manual work to force the layout to match. That’s why every Windows compatible C++ toolset uses vtables. – Euro Micelli Jul 02 '21 at 06:14
  • I don't have the answer to that question but I have the feeling it is language and compiler-vendor dependent. The "COM is language independent because it's an universal binary contract" idea is not so true (I personally believed it was for years..), it does implicitly expect some behavior when building vtables. See this discussion here: https://stackoverflow.com/questions/61542063/inversion-of-generated-vtable-functions-order-for-functions-with-the-same-name – Simon Mourier Jul 02 '21 at 06:33
  • @SimonMourier - i bit update question, add else one note, based on existing answer. may be question even better ask as - *if com interface `I2` inherit from `I1` interface - pointer to `I2` is always valid pointer to `I1` too.* ? for generic *c++* objects, which can have multiple inheritance - this is not true. but for interfaces ? which can not have multiple base classes (object which implement interface can, but not interface by self) – RbMm Jul 02 '21 at 07:41
  • If you look at Microsoft's only "spec" about this: https://learn.microsoft.com/en-us/windows/win32/com/iunknown-and-interface-inheritance it clearly says *Because no implementations are associated with interfaces, interface inheritance does not mean code inheritance*. Although it may work all the time in practice with known compilers. – Simon Mourier Jul 02 '21 at 08:02
  • @SimonMourier i say very simple thing- that pointer to `I2` already valid pointer to `I1` too. if `I2` inherit from `I1` (i not say this in versa direction). and explain in many details - why this is true. and if it false - this already break use `I2` when we call any `I1` method via `I2` pointer. or can somebody post **concrete** example when this is not true ? in what problem? if it not true ? – RbMm Jul 02 '21 at 08:18
  • @EuroMicelli your comment is in agreement with what I was trying to say -- I probably expressed myself poorly – M.M Jul 02 '21 at 08:21
  • @M.M - i think all this unrelated to topic. i not assume any language or compiler. i based only on definition - [*An instance of an interface implementation is actually a pointer to an array of pointers to methods - that is, a function table that refers to an implementation of all of the methods specified in the interface*](https://learn.microsoft.com/en-us/windows/win32/com/interface-pointers-and-interfaces) and i write proof (in bold) only in very generic words. here no any implementation assumption. all another - simply my clarifications - because i not view understanding – RbMm Jul 02 '21 at 08:26
  • 1
    You need to cool down a bit, I mean no harm :-) I have no proof of anything, not saying you're wrong or right (I'm not sure what you're after exactly as your english is hard to follow for me), just quoting the COM doc: A pointer points to an interface implementation and interface inheritance doesn't mean implementation inheritance. – Simon Mourier Jul 02 '21 at 08:33
  • @SimonMourier - *interface inheritance doesn't mean implementation inheritance* and ? how this break my proof or contradict with it? really how ? please reread what is bold in 1,2,3. if we can call methods of `I2` via valid `p2` interface pointer - we already can call methods of `I1` too.. this is simply fact. – RbMm Jul 02 '21 at 08:41
  • @RbMm Can you please publish a Github repo where this works? – Chef Gladiator Jul 02 '21 at 08:59
  • @ChefGladiator - can you publish any example where this is wrong ? in what problem ? if i mistake not hard build demo where simply assign `I1* p1 = p2` and use `p1` lead to wrong result. – RbMm Jul 02 '21 at 09:08
  • 2
    @sim The VTable layout is a contractual part of the ABI. It is not vendor-specific. Any language implementation that wants to author or consume a COM interface must adhere to this contract. Microsoft's C++ compiler produces COM-compatible VTables by default. But there's nothing keeping you from implementing the same VTable in another language, like [Rust](https://github.com/microsoft/windows-rs/blob/922d73ad0ecd704303088fae1427f4316f6b478a/src/interfaces/unknown.rs#L10-L15). – IInspectable Jul 02 '21 at 10:07
  • @IInspectable - The COM "contract" isn't precise about what is a "COM compatible" VTable, it assumes it's like what MIDL/MSVC generates. Also, AFAIK, this same tooling assumes the methods are stdcall (some old/rare aren't like ITextHost). But VTable can vary per compiler in specific cases, see this https://stackoverflow.com/questions/61542063/inversion-of-generated-vtable-functions-order-for-functions-with-the-same-name and test the code with various compilers with https://godbolt.org and you will see the VTable method order can vary which cause (rare) weird COM bugs with GCC or .NET. – Simon Mourier Jul 02 '21 at 10:35
  • @SimonMourier - here i agree with IInspectable - *VTable layout is a contractual part of the ABI* and common for all. the ABI is defined at begin. any language and tool must conform this ABI. and i and based on this - interface this is **pointer to an array of pointers to methods** - this if by definition. `I2` inherit from `I1` also have strict sense - the array of `I2` pointers - containing array of all `I1` pointers - with the same order and strict at the begin. on this i and based - pointer to `I2` interface - **already** pointer to `I1` interface too. and from this and follow – RbMm Jul 02 '21 at 10:48
  • 1
    @sim You are conflating two concepts here: COM's ABI (which rigorously specifies VTable layout), and a language implementation. The latter is not required to follow the former, though MSVC does. The link you posted is meaningless in the context of COM: COM doesn't allow function overloads (at the ABI), so when you have function overloads in a C++ class, that class doesn't implement a valid ABI for a COM interface. A compiler is free to do whatever in that case, even one that otherwise produces COM-compatible VTables (like MSVC). – IInspectable Jul 02 '21 at 11:11
  • @IInspectable - I'm just a practical guy. You should explain how COM works to Microsoft: https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nn-dcomp-idcompositionvisual – Simon Mourier Jul 02 '21 at 11:14
  • @IInspectable - are you agree that if `I2` com interface *inherit* from `I1` (`I2` array of pointers containing all methods of `I1` in the same order and exactly at the front) - then `I2` *array of pointers to methods* will be **always** and valid `I1` array of pointers to methods ? – RbMm Jul 02 '21 at 11:27
  • Look, we've been through this before. I explained this, Simon explained it as well. It seems like you are unable to understand the consequences of this: *"interface inheritance does not mean code inheritance"*. While everything you say is correct, `I2`'s VTable starts out with `I1`'s VTable, it is simply not legal in COM to cast an `I2*` to an `I1*` and use that `I1*`. I have written [an answer](https://stackoverflow.com/a/68206498/1889329) that explains why. – IInspectable Jul 02 '21 at 11:39
  • @IInspectable - *"interface inheritance does not mean code inheritance"* - what is this mean and how this is related to my logic ? *it is simply not legal in COM to cast an `I2*` to an `I1*` and use that `I1*`* (i be added when `I2` inherit from `I1` - only in this case) - why is not legal when binary result always will be the same ? because pointer to `I2` interface already pointer to `I1` interface too. call will be use the same pointer. – RbMm Jul 02 '21 at 11:44
  • @IInspectable if based on your logic i can for example say - by using pointer to `I2` interface - we can call only methods direct declared in this interface, but not inherited methods. why ? because *interface inheritance does not mean code inheritance*. this is your level of proof. not proof of course and wrong, but some link to doc. – RbMm Jul 02 '21 at 11:47
  • @sim There's no doubt that many teams inside Microsoft don't understand COM. If you look in the *dcomp.h* file there's one thing that should strike you: It doesn't start out with MIDL's warning comment, noting that the file were generated. It looks much like it was authored by a human, introducing a COM contract violation by way of overloads (MIDL would have renamed the overloads into unique identifiers). Good luck accessing that from JavaScript. Or Rust (I haven't checked how that works out, or if at all). – IInspectable Jul 02 '21 at 11:49
  • @IInspectable this is of topic already. despite will be interesting for me listen some another expert in this, like raymond chen. are he also think that i wrong here. until i try read Anders answer, this take some time for me – RbMm Jul 02 '21 at 11:52
  • 1
    Now look, if you're **that** curious, read my answer. It explains why doing what you are proposing isn't guaranteed to work in context of COM. I went over two contract violations, alongside explanations and rationale. That should get you started. And while this has been going on for far too long, I'll leave it at that and conclude with a quote by the famous Raymond Chen: [You must first become the master of the rules before you can start breaking them.](https://devblogs.microsoft.com/oldnewthing/20130412-00/?p=4683). I see you are still busy with the *becoming the master* part. – IInspectable Jul 02 '21 at 11:56
  • @IInspectable - *owner/owned window relationship* this is very interesting probably, but not related to com interfaces – RbMm Jul 02 '21 at 12:02

2 Answers2

0

If you have multiple inheritance, which is quite common for COM objects, you can't just use simple reinterpret casting - it doesn't always work, even in just C++ terms.

Consider:

class I3 : public I1, I2
{
    // new methods
};

If you try to call I1 methods on an I3 pointer, it will probably work. But to call I2 methods the pointer has to be adjusted so it will have the same layout as an I2 pointer.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • sorry - but `I3` - not com **interface** (com interface can not inherit from `I1` and `I2` at once) `I3` this is **class** which implement `I1` and `I2` interface. so this not fit to question - convert one **interface** pointer to another. more even `QI` never return pointer to class object which implement interfaces, but pointer to some interface (despite frequently pointer to interface can be binary equal to *this* ). and i **not use** `reinterpret casting` !!! – RbMm Jul 02 '21 at 00:35
  • in your example in case `I2* p2 = p3` - will be `p2 != p3` on binary (will be `p2 == p3 + sizeof(void*)` - here of course compiler adjust pointer, because it view and know `I3` layout. if call `p3->fn2` (assume `fn2` method of `I2` - the same adjust will be implicit at the call time) but all this unrelated to my question. because `I3` not com interface – RbMm Jul 02 '21 at 00:38
  • com interface this is object of `sizeof(void*)` - which containing only **single** pointer to table of functions. the `I3` not fit this condition. - here how minimum 2 pointers to vtables - `I1` and `I2` – RbMm Jul 02 '21 at 00:43
  • @RbMm I know it's not as simple as my example shows, because every interface inherits from `IUnknown` so you get a diamond inheritance. And as I said, it's quite common for COM objects to have multiple interfaces, you can't assume I3 won't be a COM interface. – Mark Ransom Jul 02 '21 at 00:44
  • i ask not about conversion pointer from **class** object to **interface** pointer, which this class implement. i ask about conversion **interface** pointer to another **interface** pointer, from which this interface **inherit** – RbMm Jul 02 '21 at 00:45
  • @RbMm you're terminally confused. `QueryInterface` exists for a reason, use it and save yourself a lot of heartache. – Mark Ransom Jul 02 '21 at 00:47
  • *you can't assume I3 won't be a COM interface.* sorry, afraid you not understand my question. `I3` of course not `com` interface – RbMm Jul 02 '21 at 00:47
  • are you read my question ? what is wrong in `we can call any method from I2 by using p2 pointer? yes. any method of I1 was method of I2 too ? yes, because I2 inherit from I1. from this we can call any method of I1 by using p2 pointer` ? and i explain also what can be even if `QI` return another pointer. in any case i very strong surprised by your answer – RbMm Jul 02 '21 at 00:49
  • if some function `void fn(I1* p)` require `I1*` pointer as input - and we have `I2* p2` pointer. and **`I2 : I1`** . are wrong call `fn(p2)` ? – RbMm Jul 02 '21 at 00:52
  • and please note my note#1. i not say that we can cast any interface to any. that we can cast base (I1) pointer to derived (I2) - we can not. and i explain why. but derived we can cast to base. and i also explain why – RbMm Jul 02 '21 at 00:54
  • 2
    @RbMm you're assuming COM adheres to C++ inheritance rules, but because it's language independent that's absolutely not a requirement. – Mark Ransom Jul 02 '21 at 01:01
  • no, i not assume any language rules. yes, com is language independed. and i nothing assume. (except any com interface - containing single pointer to vtable and nothing more. this is by definition and because this your "example" is wrong - `I3` not com interface`. again - what wrong or assume in *we can call any method from I2 by using p2 pointer? yes. any method of I1 was method of I2 too ? yes, because I2 inherit from I1. from this we can call any method of I1 by using p2 pointer* ? – RbMm Jul 02 '21 at 01:04
  • my "question" is very narrow - are we can use/pass `I2*` (derived) **com interface** (in exactly sense) pointer as `I1*` interface pointer (base, `I2 : I1`). – RbMm Jul 02 '21 at 01:07
  • and i hope you know - in com - we never have pointers to **object** which implement interfaces. we work only with opaque pointers to **interfaces**. interface - this is pointer to table of function - not less or more. not object with complex layout. you confuse here casting pointer to **object** (which we usually not have at all and dont know which object and how implement interfaces) to manipulations with interface pointers – RbMm Jul 02 '21 at 01:13
  • [*An instance of an interface implementation is actually a pointer to an array of pointers to methods - that is, a function table that refers to an implementation of all of the methods specified in the interface.*](https://learn.microsoft.com/en-us/windows/win32/com/interface-pointers-and-interfaces) and i update question – RbMm Jul 02 '21 at 01:32
  • The question wasn't about reinterpret cast - it was about using implicit conversion to base class (equivalent to dynamic cast; not reinterpret) – M.M Jul 02 '21 at 01:52
  • where you view **reinterpret_cast** ?! no such thing in my question !!. even in your example - *If you try to call `I1` methods on an `I3` pointer, it will probably work. But to call `I2` methods the pointer has to be adjusted so it will have the same layout as an `I2` pointer.* this will be not "probably" but 100% work and not only for `I1` but for `I2` methods too ! because when we use `I2* p2 = p3` - here and will be `p3` adjusted. note - **not** `I2* p2 = reinterpret_cast(p3)` but `I2* p2 = p3` !! but this already not exactly com question. i ask not about everything but special – RbMm Jul 02 '21 at 02:00
  • you absolute mistake - nothing related to dynamic cast too ! i convert only **interface** which layout is **known** (single pointer) - again - are you undertand this - *we can call any method from I2 by using p2 pointer? yes. any method of I1 was method of I2 too ? yes, because I2 inherit from I1. from this we can call any method of I1 by using p2 pointer* – RbMm Jul 02 '21 at 02:02
  • 1
    @RbMm you assume to know everything you need to know about interfaces, so at this point I'm unsure why you asked the question in the first place. I'm not interested in discussing this any more. – Mark Ransom Jul 02 '21 at 02:04
  • exist very simply think - pointer to interface `I2:I1` is also valid pointer to interface `I1` - we can call any method of `I1` with pointer to `I2` a say only about this. not say about I1 to I2 conversion. not say about conversion object <-> interface, etc – RbMm Jul 02 '21 at 02:04
  • ok. i think you assume also that i mistake. but may be you mistake – RbMm Jul 02 '21 at 02:05
  • *"If you have multiple inheritance, which is quite common for COM objects"* - COM [doesn't even allow multiple inheritance](https://learn.microsoft.com/en-us/windows/win32/midl/coclass). You must be confusing a particular language's implementation with the ABI. COM supports [aggregation](https://learn.microsoft.com/en-us/windows/win32/com/aggregation) which can be modeled in C++ using multiple inheritance. – IInspectable Jul 02 '21 at 09:54
  • @MarkRansom: Rather say "It is **especially** true for `IUnknown`", which is used for identity comparison between objects. If two different interface pointers to the same object were implicitly converted to `IUnknown*` without the `QueryInterface` call, identify comparison would fail. – Ben Voigt Jul 02 '21 at 19:08
  • 1
    @IInspectable: COM doesn't allow any inheritance. A `coclass` can implement any number of interfaces, but this is interface re-use only, not behavior re-use. Behavior re-use is only possible through aggregation. – Ben Voigt Jul 02 '21 at 19:11
  • @BenVoigt I was using `IUnknown` as an example that is called out specifically by Microsoft. I know about the identity comparison problem, but that's irrelevant to the discussion. There's a rule, and it should be followed if you want to stay out of trouble. – Mark Ransom Jul 02 '21 at 19:15
-1

yes, always ok is assign (cast) derived com interface to interface from which it inherit. and this is not require any assumption about interface implementation details.

at first - what is com interface ?

a pointer to an array of pointers to methods

what is mean that I2 inherit from I1 ? i not found formal definition, but try do this by self: this mean that I2 array of pointers to methods (let call this vtable ) containing the I1 vtable at begin - so containing all I1 array of pointers, with the same order at begin from 0 index.

what mean assign I2 pointer to I1 - this is mean exactly binary copy of value. it only change declared type, but not value itself. how this look like on language layer - depend from concrete language. for instance on c++ this look like:

I2* p2;
I1* p1 = p2;

not require explicit cast and not change value ( (void*)p1 == (void*)p2 )

call any method on p1 and p2 will be have the same binary code and as result effect if p1 == p2 - this is follow from com ABI.

so call p1->func() equal to p2->func() (if p1 = p2)

as result call methods with I1* p1 equal to call same methods with I2* p2, which is of course ok.

so assign I2 pointer to I1 by fact mean use/call methods I1 with I2 interface pointer (but methods of I1 was methods of I2 too)

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Now we have linked to the same document :) But what do you make of this statement "Objects with multiple interfaces can provide pointers to more than one function table"? This is very clear for something that implements IAbc and IXyz but less clear for I1 and I2. – Anders Jul 02 '21 at 23:06
  • @Anders - i not understand your note. interface (unlike object which implement it) is always single pointer from binary view (to same array of function). all what i try say all time - that use cast/assign to base interface - simply equal to call methods of base interface via derived (which is legal of course) and this is independent from any implementation. even from your example. if we have `I2* p2` and we want call method `f1` of `I1` (`I2 : I1`) - we have 2 variants: we can direct call `p2->f1()` and can first get `I1* p1` pointer via `QI` ( `p2->QI(&p1)` ) and than `p1->f1()`. – RbMm Jul 02 '21 at 23:18
  • "Adding or removing methods of an interface or changing semantics creates a new interface, not a new version of an old interface." so I1:f1 and I2:f1 is not the same method on the same interface, it is two different methods both implementing the same contract. In any sane implementation you cannot tell the difference from the outside as a caller and 99% of the time it IS the same method. – Anders Jul 02 '21 at 23:22
  • @Anders - wait - are you agree that call `(p1=p2)->f1()` have the same effect as `p2->f1()` ? and that call `p2->f1()` is legal (we can call methods of base interface on derived !! ) – RbMm Jul 02 '21 at 23:25
  • `p2->f1()` has always been legal, I'm just claiming that `&p1->f1` can be != `&p2->f1` and if it is, you have to pass the correct this pointer that belongs to that specific vtable. – Anders Jul 02 '21 at 23:28
  • @Anders - your quote is unrelated here. `I2` vtable (really I2 containing pointer to this vtable but not vtable itself) is valid `I1` vtable too. are you try say that illegal (let even in 0.0001% case) call method of base interface from derived ? – RbMm Jul 02 '21 at 23:28
  • @Anders - `&p1->f1 can be != &p2->f1` - **no** - simply because **p1 == p2** on binary level. are you understand this ? – RbMm Jul 02 '21 at 23:29
  • Why do you keep saying binary level, do you mean address they point to? Look at the code I posted, it clearly has two different vtables and QI could hand out different pointers where p1 points to vt1 and p2 points to vt2. – Anders Jul 02 '21 at 23:32
  • @Anders - all sense of assign `p1 = p2` that despite this is different types (`I1*` and `I2*` ) always will be have the **same** binary value. as result can not be `&p1->f1 != &p2->f1` - always `&p1->f1 == &p2->f1`. i really can not understand why nobody (?) still not understand this trivial (from my point of view) fact – RbMm Jul 02 '21 at 23:33
  • @Anders - *do you mean address they point to* - yes. i mean that after assign `p1 = p2` value of `p1` will be the same as `p2` (here never be adjustment like in generic c++ case (ie `struct I2: I0, I1` because interface limitation) – RbMm Jul 02 '21 at 23:35
  • @Anders - not yet view your code after edit - let me some time take for look it – RbMm Jul 02 '21 at 23:37
  • Of course if you manually assign `p1 = p2` they will point to the same place but I'm claiming that you are not allowed to do that, QI on the object instance decides the values of p1 and p2 and just as important, to which vtables they point to. – Anders Jul 02 '21 at 23:38
  • @Anders - again - all this including your code is unrelated here - * I'm just claiming that &p1->f1 can be != &p2->f1 and if it is, you have to pass the correct this pointer that belongs to that specific vtable.* - here your mistake - the key point - `p1 == p2` and can not be `&p1->f1 != &p2->f1` so for **any** implementation of object - this will be ok – RbMm Jul 02 '21 at 23:39
  • @Anders *but I'm claiming that you are not allowed to do that, QI on the object instance decides the values of p1 and p2 and just as important* - but i allowed call methods of `I1` via `p2` pointer ? yes of course. can you say - what is different between `(p1 = p2)->func_1()` (you say not allowed p1 = p2 here) and `p2->func_1()` ? – RbMm Jul 02 '21 at 23:42
  • Who says p1 == p2? Given `CoCreateInstance(..., &p2); p2->QueryInterface(IID_I1, &p1);` then QI controls the value of p1, not you. – Anders Jul 02 '21 at 23:42
  • @Anders - *Who says p1 == p2* - `Given CoCreateInstance(..., &p2); ` and then `I1* p1 = p2` - direct assign ! – RbMm Jul 02 '21 at 23:43
  • And if you assign directly then when you call `(p1 = p2)->f1()` you might not dereference the same vtable pointer as if you did `p2->QueryInterface(IID_I1, &p1); p1->f1()`. – Anders Jul 02 '21 at 23:46
  • @Anders - may be for clarify - we create `I2* p2` via `CoCreateInstance(..., &p2);`. and then we need call some api `void func(I1* )` which take pointer to `I1*` - by signature ? how call `func` in this case - are we can direct do `func(p2)` or need `I1* p1; p2->QI(&p1); func(p1)` ? both way is possible. i claim that if `func` wait pointer to `I1` -always correct pass and pointer to derived `I2` here – RbMm Jul 02 '21 at 23:48
  • @Anders - the reference counting - this is separate question and i not touch it here. of course we can direct call `AddRef` if we save `p1` in object or pass it to asynchronous call/thread . or not need do this this - if we call synchronous api `func(p2)`.. simply reference counting - separate topic – RbMm Jul 02 '21 at 23:51
  • Calling `void func(I1* )` with a p2 is legal but func will then call p2's implementation of the I1 contract (and there is nothing wrong with that). I'm just saying that a cast and a QI is not the same thing. (I never brought up reference counting). I guess what I'm saying is, I can code a COM object that implements I1 and I2 and when you call f1 I can tell from inside f1 if you called me as `(p1 = p2)->f1()` or `p1->f1()` from a `p2->QI(,&p1)`. – Anders Jul 02 '21 at 23:52
  • @Anders - of course *cast and a QI is not the same thing.* - i absolute agree with this. i know from begin that `QI` can return different binary value for base interface(look my note#2). but i about *Calling `void func(I1* )` with a `p2` is legal* - really in this is all sense of my question - are exactly this is legal. probably i not good enough ask from begin. *but `func` will then call p2's implementation of the I1* - yes - but you agree that *nothing wrong with that* – RbMm Jul 02 '21 at 23:56
  • 1
    I deleted my answer because I don't feel like it we are talking about the same thing and we are just talking in circles at this point. func() calling `I2::f1` is fine and legal. If you want func to call `I1::f1` (if you somehow care about the difference) then you must QI, not cast... – Anders Jul 03 '21 at 00:19
  • @Anders - thank you for understanding! *func() calling I2::f1 is fine and legal.* i agree, but i think that even if ask here question in such form - most people not agree with this too. i by mistake not clarify usage/sense of question - really if we have `I2` pointer - what sense cast it to `I1`, when we can just use it as is - call all methods of `I2` including all `I1` ? sense - if we need pass pointer to external agent - (function or assign to some struct member `struct S { I1* p; }`) and it declared as `I1` pointer - not `I2` - are we can pass `p2` pointer as is or need use `Qi` first – RbMm Jul 03 '21 at 00:33
  • @Anders *If you want func to call I1::f1 (if you somehow care about the difference) then you must QI, not cast.* we usually have not (and must not have) knowledge - are object implement several semantically different vtable for `I1` interface. and this is extremely rare i think. even if object implement `I3:I1` and `I2:I1` and 2 different `I1` vtables here - one (or both) is only adjustor thunk - which adjust pointer to interface (cast it to object *this*) and jump to common implementation – RbMm Jul 03 '21 at 00:40
  • 1
    I does not HAVE to be an adjustor thunk, it is legal for I1::f1 and I2::f1 to be two completely different implementations. But the caller only cares that the f1 it calls fulfills the contract, not what the actual implementation is. And no amount of casting is going to get you from p2 to I1::f1. – Anders Jul 03 '21 at 00:46
  • @Anders - *I does not HAVE to be an adjustor thunk* of course. i forget word **usual** in comment. sorry. – RbMm Jul 03 '21 at 00:48