I'd like an IUnknown, I know you have many, I'll take any of them | The Old New...
source link: https://devblogs.microsoft.com/oldnewthing/20210101-00/?p=104639
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
I’d like an IUnknown, I know you have many, I’ll take any of them
Raymond
January 1st, 2021
A concrete implementation of a COM object may implement multiple interfaces. If you have a pointer to the concrete implementation, and you pass to a function that expects an IUnknown
, you will probably get an error complaining that IUnknown
is an ambiguous base, or that there is an ambiguous conversion to IUnknown
.
void DoSomething(IUnknown* unk); class MyClass : public IFred, public IBarney { ... void SomeMethod() { DoSomething(this); // fails to compile } }; using namespace Microsoft::WRL; class MyWrlClass : RuntimeClass<RuntimeClassFlags<ClassicCom>, IFred, IBarney> { ... void SomeMethod() { DoSomething(this); // fails to compile } };
The problem is that when you call DoSomething(this)
, the compiler doesn’t know whether you want to pass the IUnknown
that is a base class of IFred
, or the IUnknown
that is a base class of IBarney
.
What we know and the compiler doesn’t know is that it doesn’t matter which one you pass. They are functionally equivalent. (The only time you are particular about which one you get is if you are trying to get the canonical unknown, but that is typically only something needed by the QueryInterface
method.)
The usual solution is to cast the implementation pointer to one of the interfaces that it unambiguously implements, and then let the compiler convert that interface pointer to IUnknown
. This does require you to know which interfaces the object implements, which could be a source of fragility if the object gains or loses interfaces over time.
From a code size point of view, you want to choose the interface that is the first base class, assuming that the first base class is an interface. That way, the conversion is a nop.
DoSomething(static_cast<IFred>(this));
If you use the WRL template library to create your COM objects, then there’s a handy helper function: CastToUnknown
. This takes the implementation pointer and casts it to IUnknown
, saving you the trouble of having to decide which of the many possible paths to IUnknown
to use. This is a protected method, so you can use it from within the class, but not from the outside.
class MyWrlClass : RuntimeClass<RuntimeClassFlags<ClassicCom>, IFred, IBarney> { ... void SomeMethod() { DoSomething(this->CastToUnknown()); } }; void SomeFunction(MyWrlClass* p) { // this doesn't compile DoSomething(p->CastToUnknown()); }
The call to CastToUnknown
from the SomeFunction
is disallowed because the CastToUnknown
method is protected.
But you could choose to unprotected it.
class MyWrlClass : RuntimeClass<RuntimeClassFlags<ClassicCom>, IFred, IBarney> { public: using RuntimeClass::CastToUnknown; ... };
The using
statement imports the base class’s CastToUnknown
method, and since the using
statement is in the public
section, the imported function is now public
.
But really, the point of this article is to call out the existence of the CastToUnknown
method. It’s really handy when you need it, such as when you want to extend your object’s lifetime:
Callback<ISomething>( [lifetime = ComPtr<IUnknown>(this->CastToUnknown())]() { ... });
Unfortunately, it’s still quite a bit of a mouthful. You can factor it out to avoid having to type the whole thing out all the time.
template<typename T> ComPtr<T> AsComPtr(T* p) { return p; } Callback<ISomething>( [lifetime = AsComPtr(this)]() { ... });
Note that this isn’t quite the same as the previous version because the resulting ComPtr
is a ComPtr<MyWrlClass>
rather than a ComPtr<IUnknown>
, but that works just as well for the purpose of extending the object’s lifetime.
WRL was written when the latest version of the C++ language was C++11, so it doesn’t doesn’t have access to CTAD. If CTAD were around, it could have had a deduction guide:
template<typename T> ComPtr(T*) -> ComPtr<T>;
That would avoid the need for the AsComPtr
helper function.
Callback<ISomething>( [lifetime = ComPtr(this)]() { ... });
Recommend
-
1
How many engineers does it take to make subscripting work? 03 Mar 2021 Are you tired of this syntax in PostgreSQL? SELECT jsonb_column->'key' FROM table; UPDATE table SET jsonb_column = jsonb_s...
-
5
Business Transformation9 Reasons Why Many New Freelancers Fail (and how to avoid them)Freelancing doesn’t suffer fools. ...
-
5
I Don’t Know How Many Kids I HavePhoto by
-
4
‘Burn Their Homes’: Israeli WhatsApp Groups Are Organizing Attacks on ArabsOne WhatsApp group where Israelis have been inciting violence has been open for a year.April 5, 2022, 1:00pm
-
15
Too Many Streaming Services? The Good, the Bad, and How to Take Control By Gabriela Vatu Published 1 day ago Streaming...
-
1
What you need to knowWe asked out readers if they were keen on the idea of Elon Musk owning Twitter.Of the votes, 60% said that Elon Musk should be able to purchase Twitter.Plenty of users agree that Twitter ne...
-
4
In mid-May of 2022, the Ann Arbor office said so long to one of our interns, Erin Murphy. In reflecting on her time with us, a particularly memorable encounte...
-
10
Get WIRED for just $29.99 $10. Subscribe Now...
-
3
Creating a versatile vaccine to take on Covid-19 in its many guises Aided by machine...
-
2
Why do so many people buy MacBooks, only to run them in clamshell mode all of the time?...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK