CppCon 2018: Victor Ciura “These Aren't the COM Objects You're Looking For”

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Maybe I'm really late to the party, but I actually didn't know about C++/WinRT. I think the title is rather misleading, but it's such a great library/tool being finally able to write C++/C# without the ugly C++/Cli.

👍︎︎ 2 👤︎︎ u/konanTheBarbar 📅︎︎ Oct 11 2018 🗫︎ replies
Captions
- Good morning everyone, thank you for coming. Especially at this hour at the morning and given the topic at hand I am very impressed by the turnout. My name is Victor and we'll be spending an hour this morning talking about a very sexy subject like COM objects. And back in July, this happened, my friend Simon tweeted this and I immediately identified with the problem, but I said I'm gonna roll with it. And my daughter keeps telling me that I do look like Gru. And just in case you're disoriented, this is the roadmap for Victor Ciura for the week. You're in the third talk, hope you're in the right one and you won't regret it. And let's move on with it. This is part one of N for this topic that I'm trying to tackle. It's an ambitious endeavor, given the interest in the community around this topic. And I'm hoping to get a lot of feedback, especially at places like this with such a diverse pool of programmers with such a diverse background experience in actually using and deploying applications on top of COM components. And depending on how well it goes and the feedback I receive, I plan to refine and improve this and take it even further. So I do really need your help, and your feedback, and ideas, and let's see how it goes. We even have some chairs here left over from a different panel, so we can do an impromptu COM panel if you like. So audience participation is encouraged, and feel free to say whatever that's on your mind, hope it's COM related though. So why COM, why are we talking about this? Have we really exhausted all the cool template topics that we can cover at CPPCon? Well for me at least, COM has been an important part of my professional career, and this is mainly because of the constraints of the application I've been working on. I've been working on this application, Advanced Installer, for over 13 years now. And Advanced Installer, I just need to give you a little bit of a background so that you can better relate to my COM story. Advanced Installer is an IDE and a tool chain that helps software developers to package and deploy their applications on Windows. And at the same time it helps IT pros repackage and provision operating systems and deploy line-of-business apps in enterprises at scale. So as you might imagine, Advanced Installer is a very wide, it's an umbrella project, it's a very wide application with very different components underneath. And given the task at hand in provisioning the operating system and deploying various kinds of applications, you can imagine it has a very wide API surface area, and it actually interacts with many components from the Windows subsystem, whether older or newer features of the operating system. So basically what we're trying is to help clients migrate their Win32 applications to the modern application model like MSIX and maybe some of them will bring their applications in the Windows Store, or Windows Store for Business. So as you might imagine this is a big endeavor. We interact with so many components in the operating system and as we all know and love Windows, you know that's all built on COM components. And just before we get started, I suspect I know the answer, but I wanna get the feel for the room. So how many of you have been doing COM programming in the last 20 years? Okay, this is the right crowd. (lightly laughs) Again I'm guessing, but how many of you are still doing this? Since you're here I'm guessing it's relevant to you at least, if not anecdotical. So let's dive into it. Another important takeaway I'm trying to push with this talk, and I wanna encourage a discussion around this, is that even if COM is not something of interest to you right now, or maybe it was in the past, there are important lessons to be learned about good API design. And examining COM as such an impactful infrastructure in the operating system, and as a library that has been around for 25 years now, it's very telling to examine how well some of the design choices stood the test of time, and to really see the seams in the grand design of the framework and see which parts have played well, and have aged well, and which parts really gave us so much friction around these years, and what can we learn from it. What does the modern operating system has to offer in this regard, have we learned something over these years? Is it easier now to interact with the operating system through its COM components or not? Are we in the same mess that we've been years ago? So I'm hoping to discover this together with you. So like any good COM programmer we have to initialize. And if you think about CoInitializeEx, it basically initializes the COM library on the calling thread, very important detail nowadays. And it sets the thread concurrency model to the one requested, nowadays a multi-threaded model. It creates a new apartment for the thread if that's what you need. And you really need to initialize COM on each thread that it actually needs to invoke COM related function, otherwise it won't work. So this aspect is really important, so. And ideally you would want to initialize COM only once per thread, at least once I mean, I guess. So multiple calls to CoInitializeEx on the same thread are allowed, but subsequent calls issue an S_FALSE H result. And this is relevant because COM error handling is cumbersome, to say the least. And you need to be careful in how you manage expectations and check the success of the different API calls. And in the case of CoInitialize, you wanna be a good citizen and uninitialize the COM on the calling thread when you're done using COM functionality. And if you're not properly matching those up, and not doing proper error checking to see if you're the one that actually initialized the COM on that thread, you might end up uninitializing COM on the thread, and somebody else in some other part of the functionality really needs it. So you can really shoot yourself in the foot by trying to be a good citizen. So you need to be graceful in uninitializing COM, and do it exactly when it needs to be done. So do or not do, there is no try. By the way, any Trekkies in the room? Yeah, some of this material may be unpleasant to you. Okay, so how do we fix CoInitializeEx? Well RAII is something that we're accustomed to nowadays. So we can build a simple helper like this one, where we can make sure that we acquire the COM dependency and release that dependencies when we're done with it. And this is where the proper error checking comes into play. As I said earlier, we need to remember if we were the ones that actually were the first to initialize COM on the calling thread. So that's why we need to save that mClose Boolean state to make sure that we're the ones that need to CoUninitialize it on this thread. So if you're doing this you can use it locally. For example you have a function scope. You have some function requiring COM functionality and use some API, you can initialize it locally and when it's done with it, with the current function context, you're done, uninitialized, it's okay. Or you can use it in with a class scope. For example, if you have a more complex object that has several methods requiring COM access, you can hold on to that dependency as long as you're done and your object is basically tied, as a dependency, to the COM itself. So that's pretty straightforward, has worked out very well for us. We actually use it heavily, I couldn't imagine living without such a functionality. - I have a question. - Yeah, sure. - [Audience Member] You showed this error RAII plus? - I can go back. - [Audience Member] Don't you need to handle a model change error code? Because you can actually change-- - The threaded model? - Yes. - Yeah. We don't do it, we don't do it. Generally speaking it's not a good idea. Technically you're right, you can actually initialize COM with a different threading model. For example using multi-threaded, or switch to apartment threaded, or something like that. Technically you can do it, but it's never a good idea to mix those up. So we generally try to stay away from mixing these modes up. We've been burned before by accidental initialization with different threading models, and there were components that needed to talk with each other and there are very careful rules of isolation about COM object instances in memory with regards to the apartment model. So we've been burned with this before and we try to stay away of mixing those up. - [Audience Member] Yeah, I'm just wondering if you wanna add some kind of check for that error code, made by a server end load-- - Maybe we should, at least an assertion would be a good idea because we've been burned by this. And by the way this is classes a little simplified, it's not exactly, it has to fit on a slide. But you're very right, this is a concern. And we've actually encountered, I think last year we've had a problem with this. We had a component that was not under our control, and it actually did a different threading model. - I had another comment. - Yep, sure. - [Male Audience Member] I thought this was correct but I wanted to look it up before I said anything. Doth quoth the MSDN documentation, let me find it. To close the COM library gracefully out of thread, each successful call to CoInitialize or CoInitialize, including those that return S_FALSE, must be balanced by a corresponding call to CoUninitialize. Your check I think is. Your enclose checks your result out of the page, should also check S_FALSE because you're required to call CoUninitialize even if it returns S_FALSE. - [Audience Member] Yeah, you can use succeeded COM calls. - I'll have to look at that, okay. I'll get back to you on that, I'll have to look it up. - [Audience Member] That's very typical for COM. You need resume every function-- - Yeah, it's telling, it's telling. I think this is telling. But thank you for pointing out, I should really look into that. - [Male Audience Member] I thought I was correct, but I wanted to look it up before I actually said-- - Okay, okay, I have to look it up. Cannot confirm it, okay. So COM initializer and RAII. You don't care about the thread you're on as long as you encapsulate this dependency. You don't care if it's already initialized or not on this thread, and most important thing, you can nest these objects as needed. You don't care at which point in the call stack you're in. And you don't need to about closing and unloading COM when you're done, that's the whole purpose. So let's talk about strings now. And I had to pick, COM is such a large topic and it would be a mess to try to cover too many things. So one of the earliest feedback I got on this talk idea is that I need to pick something and focus on something COM related, and try to analyze it throughout. And no question, I had to pick strings. For us at least, this has been the most sensitive issue around COM and I'm trying, at least for this part of the talk, to focus on COM strings a little bit. So what's the COM string type? - [Male Audience Member] BSTR. - BSTR, yeah, our friend. So for those of us who don't remember this ingenious piece of design, the BSTR is a string with a buffer in memory. It has a length prefix and is null terminated. - [Male Audience Member] Not always. - Yeah, that's why we have the length. And the most interesting part is that BSTR of course in the type system is a pointer, and it actually points to the data buffer in memory, not a length prefix, so there's always a trick of offsetting this pointer. So if you think you can do a thing like this, which I would expect to work, you can't. This compiles, and links, and it's incorrect. And you would say that well, I would never do this. It looks stupid. Yeah, you can by accident pass a string literal to a COM API function that has a BSTR form parameter. So I'm honest enough to admit I've done that before. So you can be easily burned by this. So what you have to do is allocate a string for this BSTR, and you have to check the success state. It almost never fails, right? Why check error codes? And use the string, and then free the string. So lot of hand-waving around just initializing a string. And somebody has to save our skins here. RAII for the win. Let's see what the Visual C++ compiler has offered us to mitigate this issue. We have a few RAII COM support classes. I know everybody loves this one, _bstr_t. By the way, these underscore T types, really, really bad in terms of standard conformance. They should be reserved names. But we're gonna roll with it. So _bstr_t, we have similar helpers for variants, and for COM pointers, and of course for our HRESULT error handling friend. And because we're focusing on string, let's see what _bstr_t has to offer for us. It encapsulates nicely the BSTR data type, manages resource allocation through SysAlloc, SysFree, what we would expect from an RAII wrapper. It uses references counting to avoid excessive overhead, so this is actually clever and uses Copy on Write underneath, so you can freely copy these things as if they're value types. It provides various conversion constructors like you would expect to initialize this data type. So meets the expectation, it's intuitive, handy. Very handy operators for lexicographic comparisons and concatenation, and you would use it probably something like this. Pass it around to some COM API that requires a BSTR, has an explicit method for getting that pointer to the buffer, and you can convert it to a standard string because you do need to inter-operate between your COM-related classes and your platform-independent code at some point, you need to do a two-way handshake in martialing data up between your model and your COM layer. And of course you can leverage the lexicographic compare operator if you need something like this. You can construct a _bstr_t from a standard string by getting its Cstring non-automated array and you can compare it very easily. So very handy, nice, clean, no unexpected results here. If we examine the other helper related to this, a little more convoluted if we need to deal with pointers. We need to do some magic macro wizardry, and we can actually tag each interface of interest and construct an ac-hoc smart point of reference by using this fancy macro, and we get an instant type, like for example I have here ITaskbarList3PTR which is a smart pointer. And I can get an instance of this class and use it as if it's a regular pointer. So a little weird using the macros, but we're used to that. So next bit of history, who's been using ATL for the past years? Yeah, ATL fans in the house. So just a quick orientation or a history lesson, this would be a simple ATL structure. The core object in ATL is CComObject, which is a template class. And I'm not gonna cover aggregate objects because it's a much more complicated topic for this discussion. But if we're talking just about a simple COM object, a COM object inherits its template parameter, and this would be a very, very contrived and simple example of actually creating an instance of this circle object. And invoking just one method of interest on it, SetRadius in this case. And this is what we would have to do, and all of this is just boilerplate, I don't care about any of this code but I kinda have to write it. And this isn't even the whole class, I couldn't fit it on the slide. This would be the actual code I care about, in being my constructor and my methods of interest. So having to write all this boilerplate code really messes your attention, and it's sometimes error-prone and definitely gets in your way. By the way, do you recognize this pattern? It has a name. - Curiously recurring-- - Yeah, yeah. CRTP, curiously reoccurring template pattern. So basically it achieves a similar effect to the use of virtual functions, but without the cost of dynamic polymorphism, or a VTABLE. And it has a binding at compile time of course, we want this for our COM objects. We don't want virtual code for each COM method. And the pattern is used extensively in ATL and WTL libraries. We actually use a lot of ATL in our application, and our whole GUI is built on top of WTL, so I know pain when I see it. But it's a very, very powerful construct. Building up objects this way is flexible enough, very powerful, not very friendly for the beginner. So we do have parallels and helpers in ATL for the same constructs. And we have CComPtr and CComBSTR, and of course I'm gonna pick on CComBSTR because we're talking about strings. So this is the ATL wrapper for BSTR type. It manages allocation again the same way, because it's the same type underneath. But this one does not implement Copy on Write, so this object is different. And in the latest versions of ATL it actually has move semantics, just in case you didn't know. So it provides, again, various conversion constructors that you would expect to initialize it. Same convenience operators, usage looks similar with _bstr_t, difference being that CComBSTR actually has an operator to convert it to a BSTR automatically. This might be helpful, or might be surprising to you depending on if you wrote the code or not. You can initialize a standard string from CComBSTR. Of course if we're talking about, I did mention this earlier, if we're talking about COM strings we're always talking about UTF16, so this is the underlying assumption in how the operating system is built. So you're gonna see only STD wstring in the slides. So you can match your data up back and forth between your model or your application logic and the COM components. You can construct the other way around, you can construct CComBSTR from a standard string, you can compare them, similar functionality with _bstr_t. So at this point I would like to run an ad-hoc poll: how many _bstr_t fans versus CComBSTR fans? So _bstr_t, _bstr_t, oh, not a love, no love for _bstr_t. CComBSTR, ATL, ATL for the win. Okay, just a personal curiosity. - [Audience Member] There's actually, I think it will be easy for people. If there is a piece of cross-platform code, I understand it's Windows related but sometimes you're have to work cross-platform library, you will be using post string. I remember the problem is that wstring will be four bytes on non-Windows platforms, while it will be UTF16 in Windows. So basically if you do have to write with pattern code you also will have to do either UDEF base conversions from wstring from Windows to wstring on non-Windows. - I'm just gonna summarize what you just said to be on the recording and other people to follow. So a very good observation. If you're dealing with any cross-platform development and you need to isolate a platform abstraction layer that's COM dependent, and you do need to do this martialing between standard string and COM string, you do need to be careful about the string representations on other platforms. And it's indeed a very good observation, and I remember a talk a few years back, some people on the Office team did a presentation that I thought it was fantastic, you should look it up on YouTube or Channel 9 or something. Don't remember the title, but the whole talk was around their experience in actually porting Office to iOS, and the challenges they faced around data type lengths, and NDNS, and character lengths on different platforms. So that talk goes into many details around data representation in memory, and adapting, and constructing a proper platform abstraction layer, and it's actually an actual real case study from the Office team that did all this work, and it wasn't easy. (lightly laughs) You should definitely, if you're interested in stuff like that you should definitely look it up. I'm sorry I don't remember the title, but it was an excellent talk. - [Male Audience Member] Can I make just a quick comment? Maybe two comments, 'cause I kinda raised my hand for both BSTR and ComBSTR, I use both of them. - Myself included. - [Male Audience Member] One advantage that I've found with CComBSTR is it does overload the address of operators. So if you're passing it to accommodate the item that takes a ret val, you can use CComBSTR to capture the ret val where you can't do that cleanly with _BSTR construct. The other note that I would have is in your construct where you're constructing wstring by passing the direct reference to the BSTR, the third line, I would be very careful about doing that in production code. The reason being, some languages return non-null terminated BSTRs through COM interfaces, which is allowed. And the wrapper classes, while providing convenient overloads for the effective null terminated strings in C++, don't check that necessarily. And if you use that construct with BSTR that came out of VisualBasic, for example, it will likely crash. - Yeah, very good two observations, and I'm gonna repeat them to be on record. The first one is that with regards to the poll I made earlier, _bstr_t versus the ATL CComBSTR, we actually use both, like you said. Different components, different authors, what are you gonna do, that's life. And the preference here, and the observation was made that if you have an API that actually returns a string as an output parameter, the CComBSTR, having a get address of function can actually be used to capture an output parameter, did I convey that correctly? - Sure. - Okay. And it has a clear advantage here over _bstr_t. The other observation, which is more subtle, is regarding embedded nulls, or if the string isn't null terminated. COM of course has projections in different languages, and if you're talking about COM, the whole idea is about inter-op and being language agnostic around a common ADI. And if you happen to get a hold of a string reference that is not null terminated, actually constructing a standard string this way like line three here is potentially dangerous, so you better watch out for the string length because it's not null terminated, did I convey this correctly? - [Male Audience Member] I was just gonna add to that. There's a way to construct a stood strings, passing the pointer and the length, and you can actually macro out of that which is what we've done in our code to help conversion. - Ah, I would rather be explicit than put a macro there, but the observation is valid. - [Male Audience Member] Well ATL has conversation back rows like CW to CT, so you can have a CSTR to BSTR, BSTR to CSTR. - Yeah, I'm not a fan of those but we do have them in the code base, so, question here? - [Audience Member] Yeah, so with the BSTR 32-bit problem, there are types in standard for incar 16T, and so you can lock yourself in-- - Yeah, and the basic string class is templatized. So wstring is just a template instance for WCarT, so you can actually use the other chart types in the standard. Good observation there, good observation. So again, this is a contrived example and it actually assumes a null terminated string, so yeah. Very good observations. I'm definitely in the right room. No seriously, actual audience participation in this talk highly improves the content. So there's a bigger fish here, there's always a bigger fish here. And this would be ATL CString, one of my favorites. So again, this is a templatized class, has support for char, wchar_t and StringStraits. It manages allocation, deallocation of course, uses reference counting to avoid excessive copy overhead. Again, this is a Copy on Write implementation, very handy. I see so many people trying to avoid CString copies and passing references around, and I always find that a futile effort. And it actually has a string ref internal representation and it correctly checks that it actually needs to duplicate that string when modifying it. Has lot more methods and operations than _bstr_t or CComBSTR. Has all the conversion constructors you would expect, including VARIANT, which is nice. All the operators you would expect for convenience. Lots of algorithms and utilities, as opposed to the other ones that are bare wrappers around the data type, this one actually has string algorithms like Find, FindOf, formatting strings, changing the case, the upper case, prefix, suffix, substrings find, reverse find, tokenize, what have you, all the goodies you would expect from a string class. So definitely my favorite. In terms of simple usage, again, you can imagine calling some API that requires a BSTR and you have to be explicit about requesting an OLE BSTR from the SysString. And the other way around, if you're trying to construct, this is the most frequent offender in our code base. Actually data martialing buffers between standard string and CString, I think is the most frequent operation we do in our application because we have all the application logic and the model of the application built on top of standard string, and each time we need to interact with the GUI, the GUI again is built on top of ATL and WTL, so everywhere is CString and interacting with COM, and we copy things around, yeah? - [Male Audience Member] You know that leaks, right? - Sorry, the line two? - Yeah. - Line two yeah, yeah, no, I'm just trying to show the conversion, the conversion function. Actually I've never used the AllocSysString function from CString, I never needed to, so. But the third line is the thing that we have, I think that's the most, if I do a search, maybe CString GetString is the most frequently used function in our code base, so that's unfortunate. - [Audience Member] You haven't clarified what is the leak? - Yeah, the AllocSysString method allocates a copy, a BSTR copy of the underlying CString buffer. - [Male Audience Member] Well and specifically BSTRs are allocated in global memory in COM, there's a Comalic call and they have to be freed by CO3 I believe, and AllocSysString calls Comalic and returns the raw BSTR pointer, so don't do this. Write a macro like CSTR to BSTR that returns one of the COM object for _bstr_t, then you can call this directly just using your macro around the STR. - It's the same problem you have with SysAllocString and SysFreeString, you have to manage those manually. So I would never advise using AllocSysString. There are some convenience macros like he mentioned. And it's rare that you actually need to do this back and forth, but in case you do you need to be aware that you get new BSTR instances. That's why I put the allocates comment there. And what I'm trying to do in this slide is with the type system itself, not the memory management, so the conversions that happen. So again, and the other way around. Again, this is a very frequent operation in our code base. Converting from a standard string to a CString to give it some GUI function or working with some component there. So these are a pest for us and they always give me headaches and I'm trying to get rid of those, so. But what about this modern COM I keep hearing about? Surely things have improved in the last 25 years. Enter Windows Runtime. Starting with Windows 8 and Windows 10 we have a new kid on the block. This is a new day, a new beginning. So the Windows Runtimes, for the ones that are still suck on Windows 7, yeah, we still need to deploy applications there, right? Is a modern, class-based, object-oriented Windows API. It has reach metadata about the classes and of members, properties. Has language projections for natural and familiar use in your preferred language. Okay, not Ruby but it could be done. And how do I get access to the Windows Runtime from C++? Well let's start with the beginning. We have WRL, yeah, that's how we start it, right? Before there were any real projections we have the Windows Runtime C++ Template Library, that's a mouthful. It enables you to more easily implement and consume COM components. It's a very thin abstraction over the ABI. It's template-based of course, it gives the ability to control the underlying code, it's very low-level, so it's not always pretty. Error handling is still HRESULT-based because it doesn't abstract away anything on this front. But its design is heavily inspired by ATL, so one huge benefit of this approach is that you can actually mix in the old COM code with new COM code, and this is the main reason that the Windows Runtime effort was bootstrapped to this way. I always consider the WRL implementation as a transitional period so that you can bootstrap and start adopting new APIs in your existing, old application, and to have a seamless interaction there. And of course it was easier for them to implement this, until we got an actual language projection in the form of C++/CX later. So it actually uses standard C++, and it's template-based like I said. Uses smart pointers, RAII over the place, it actually helps with activation, so it was a clear improvement over the pure ATL-based approach, rather verbose though. And of course it allowed us to add support for UWP applications and leverage new APIs there. Immediately after that, I'm doing, I think you got the gist of it, I'm doing a history pass here. So quickly we got a language projection that was C++/CX. If anybody remembers that pain with the hat. It uses a nonstandard C++ language extension, the Visual C++ compiler was enhanced to support these language extensions. The syntax is terse, clean, much less boilerplate, all the things the compiler does, all the messy work behind the scenes, we just write simple code, but there's a learning curve. This being a nonstandard language extension you need to get accustomed to the hats and how it works, and know the reference counting that happens behind it and understand it a little bit, so there's a learning curve. Definitely high-level abstraction here. Something nice about it, I'm a bit fan of exceptions, I'll admit it, not the right conference to do it but I'll admit it. So it actually encapsulates HRESULTs as exceptions, automates various housekeeping tasks, so if you need optimization, but it's discontinued. So let's start to see how it would look like, and it would be some includes and namespaces, nothing special there. RAII for the win, again, this time this helper is actually provided for us, it's not something I implemented. So very similar to what we had earlier, RoInitialize helper. Simple usage would require to get an activation factory, to get an instance of full. For example IUriRuntimeClass interface, so looks like ATL, little bit different, still very verbose. Not my cup of tea. But what's the Windows Runtime string type? Again, I'm going back to this theme of strings. So Windows Runtime string type? Hint, it's not BSTR, sorry? - [Audience Member] HSTRING. - HSTRING, yeah, that's always a new an improved model. It represents an immutable string, careful here, an immutable string in the Windows Runtime, it's basically a handle. And in order to get a hold of these HSTRINGs you have to use functions like this and man, they're ugly. That's a very sad API surface. But that's what we have to deal with. And we have class representation, for example a very simple odd thing, you cannot initialize it by construction with a string literal, you have to actually code a set method, I thought that was sad. So a very simple example of initialization there. Yeah, platform string is the language projection for C++/CX. So if you were using C++/CX that would be the language projection for it. This time around with the C++/CX projection it looks nice, I can see a clean initialization as opposed to this garbage. Has the expected operators, checking for equality, concatenating stuff, looks good. But you cannot stop change, and people don't like nonstandard language extensions and I consider the evolution a step in the right direction here, and going back to standard C++ is what you want to strive for, and you don't have to rely on a specific compiler for some language extension. So C++/WinRT comes into play. It's an ISO standard C++17 language projection for the Windows Runtime, it's a header-only library, it has C++ class wrappers for the WinRT APIs. You can author and consume Windows Runtime APIs with it, and officially supersedes the WRL and C++/CX, so definitely this is the present. How many of you are using C++/WinRT? Well that's sad, I guess we haven't got rid of Windows 7 yet, that's clear. So this is an open source project, it was a long time on GitHub, it actually moved to the Windows SDK a few months back. And I think this is the best documentation to start with if you're trying to learn about it. And we have to thank that man for this, and let's give him a round of applause please. (light crowd applause) Kenny did a very big effort in actually striving for bringing uniformity, and using a standard C++, and surfacing all the regions of the APIs in C++/WinRT, and I'm so happy that it's finally part of the Windows SDK, and it actually feels like it's a first-class citizen, and using C++ to program with Windows Runtime is actually legit now. I feel welcomed in the platform finally. So I highly recommend that you see this talk if you haven't seen it already, from last year's CPPCon. It's a talk all about WinRT. And from this year, a more recent version of using C++/WinRT to author UWP applications from the Build conference in spring. So how do I get it, well fortunately it does come with Visual Studio 2017. You just have to select the C++ workload and you're up and running, it's as simple as that. I would also add, I would like to see, I understand the reason of rolling out extensions in Visual Studio, it actually gives you the flexibility of updating on a different cycle and pushing things a little off base or off band than the regular product of it, but I would like to see more support right in the books, and this extension helps you. I highly recommend that you check it out. It actually helps with debug visualization for C++/WinRT projects and provides you with project templates to get started with building C++/WinRT applications, and has support for MSBuild, and generating projection headers and components. So this is a separate extension you can install. So let's start with the Windows Runtime again. Much more simple this time around. We have to actually give the linker some instructions there. Include either WinRT base or foundation, which automatically includes base, depending on your need. And you can just basically start going. Notice there it does require C++17, so I'm there, how many of you are able to leverage C++17 in your work? I say 1/5 of you? So it actually performs better and produces smaller binaries than any other language projection. It outperforms handwritten code using the ABI interface directly, like WRL did. The underlying abstraction uses modern C++ idioms that the Visual C++ compiler is designed to optimize for, like magic statics, empty base classes, string length solutions and so forth. So leveraging nonstandard stuff actually helps, because the optimizer doesn't look for improving nonstandard extensions. What's the CPP WinRT string type? And I'm getting to my thing of strings again. So what's the string type, anyone? - [Audience Member] Still string? - No, but very close. It's winrt::hstring, yes you guessed it. So yeah, question? - [Man With Glasses] We use IDL's interface for visual language by their visual interfaces. Can we have access to going into the IDL? - Maybe Kenny can help there, I don't know. Regarding ADL, the question was if he uses ADL to define its interface-- - Not ADL, IDL. - IDL, IDL, sorry. - [Man With Glasses] On IDL for the visual language, and whenever there's a new publish on the string we've lose beats there. - [Kenny] Yeah, so if you're offering a Windows Runtime interface it has to use an HSTRING, not a BSTR, so it's a classic COM interface it can be a WRL string, it could be a BSTR, it could be whatever you like. In the Windows Runtime type system it has to be an HSTRING. So that's the only string type that is permitted by IDL, by the metadata that it uses and the way it's at. - [Man With Glasses] That mean the old IDL and new IDL can't work together, is that correct? - [Kenny] Well they can work together in the sense that you can have a COM object that implements both kinds of interfaces, the one RT interface and the COM interface, but the one RT type system only supports one string type, and IDL is just a way for you to describe the interface and get some code in the binary metadata format, and that format only supports HSTRINGs as the single string type. - If I can summarize that for the camera, so maybe I wasn't very clear but Windows Runtime is not built on top of BSTR. Like Kenny said it's built on top of HSTRING which is a different representation. So if you're defining a component, it has to be defined in terms of HSTRING type, not BSTR. And I would also add that if we're talking about Middle, yeah, it's a different Middle version for authoring Windows Runtime, that was Middle two and that was a very baroque and very verbose language of describing interfaces, and it was improved in Middle Three, is a much more lightweight and airy mode of describing type libraries. So I have actually seen a very nice interview with Larry Osterman on Channel 9 around this, and the changes that he needed to operate on actually describing the Windows Runtime. So definitely the Windows Runtime is focused around HSTRING, not BSTR. - [Man With Glasses] So would it be related to conditions of people who are using IDL right now, how we can workflow using the access of the WinRT? - [Kenny] Do you wanna take it offline? - Yeah, I think it's best 'cause I'm out of time and I still have a few slides, yeah. Okay, so winrt::hstring represents an immutable string consistent with the underlying HSTRING. You can construct it like you would expect, has various constructors for your convenience there. Again, also the version with the length, and the pointer has an explicit constructor with string views. A few examples, you can construct it in various ways: from a string view, from a pointer, from a pointer and length, all the usual suspects there, nothing unexpected. The operator that converts it to a string view is interesting, in my opinion, and I have a whole talk on string views that happened on Monday, but you can catch it on recording. The string view is actually meant to be a glue between these string types, and my hope is that string view will solve all our inter-op problems at API design level, and will help us bridge this gap between different string types in our type system when we're talking, for example, of standard string and WinRT HSTRING for example, or other string types for another. And you can see in this example I can initialize a standard string from the return result of this domain method that returns an HSTRING because of that explicit operator that converts it to a string view, and of course I can construct a standard string from the string view, so that's a very nice to compose things. So HSTRING is also range, HSTRING exposes almost the same interface as standard string, and I think you asked a little bit earlier, you suspected that the standard string type for the WinRT's standard string. It's not, but it's something that's almost like a standard string. So it mimics the same API, same interface, although the string is immutable, but it looks and feels familiar and it can inter-operate seamlessly, so it actually feels like you're using the standard string, you don't have to worry about it too much. And you can use range for, you can treat it like a range, just like with standard string. Although each string, again, is UTF16 underneath, it does play nice with if you do need to do some data martialing in terms of UTF-8 text, and it has convenience methods to transform it to a narrow format, for example, like UTF-8, if that's needed in your case. We don't have that problem but I guess there are scenarios for that, mostly in terms of if you're doing cross-platform stuff, so. So we end up with something like this in our applications, and I love this slide. (light laughter) So the highlighted stuff is basically just your COM pleasure. So yeah, we kinda have to deal with all of these, so it's tough, so you have to think about okay, I'm doing my business logic, my platform independence stuff in standard string and doing all my platform dependence stuff, my GUI, my COM objects, my platform abstraction layer, whatever, I'm doing that with let's say XString, whatever that is, COM whatever, and I have to do this data martialing and inter-op and tie those things together so they make sense. And that's all fine until your application grows and you start to bundle up utilities and algorithms around strings and then pain comes into play, because we have to decide do I implement all my string algorithms in standard string, do I do overload sets for my XString types, whatever that is? Do I fall back to a common denominator thing and make all my algorithms in terms of pointers to characters and lengths, that's always ugly. So this is where string_view hopefully is the answer. Like Billy said it's the duct tape of string types, I like that type. And it's still an ongoing journey, but we're trying to discover if string_view is really the answer to all our string problems, and that's why I have this whole quest of actually trying to figure out the good practices, and all the gotchas around using string_view. And of course we have the limitation of actually being able to use this if you're on C++17. There are similar classes out there for C++11, like UpsellString_view for example, but again, you might have limitations on what you can use in your code base. So definitely a lightweight string-like view, over under eight characters, it basically maps this concept of pointer and length. It was designed as glue code, and to reduce the overload sets for your classes, that's its main purpose, but it does not manage the lifetime of the object clearly, it's a projection over an immutable sequence, so it maps really well to WinRT HSTRING, which again represents an immutable object. The lifetime issue is a problem, that's the main concern. It has all the usual suspects in terms of constructors and conversion operators, not all of them explicit unfortunately. Or fortunately, depending on how you see it. And because of that it does come with plenty of gotchas. So my whole talk about hanging yourself with string_view is around those gotchas, and trying to determine best practices and properly designing APIs around string_view, and where is it safe or comprehensible to use it in formal parameters rather than store it as a data member or in containers. And we're still figuring this out, string_view is a strange type, it actually deceives you in that it's actually a value type, but it fails to actually confirm into being a proper, regular type because of its assignment operator and how its comparison function works. So it's difficult, it's deceiving. So we're still figuring it out, and it has real potential to really help us here, especially, my hopes are especially around COM and string_view, and I'm trying to figure out how I can make this work in my code base. And it's a learning process, but I'm optimistic. So Windows COM is 25 years old and this shows in many corners. Yes it's still relevant today more than ever because Microsoft has bet its entire WinRT API on it, so we're gonna live with it. And with the advent of C++17, using COM objects and the new WinRT APIs feels like a new experience, in my opinion. So definitely something to strive for. So the dark side of COM is the pathway to many abilities some consider to be unnatural, but I'm very optimistic and I still enjoy programming COM. So with that, thank you. (crowd applause)
Info
Channel: CppCon
Views: 4,450
Rating: 4.6734695 out of 5
Keywords: Victor Ciura, CppCon 2018, Computer Science (Field), + C (Programming Language), Bash Films, conference video recording services, conference recording services, nationwide conference recording services, conference videography services, conference video recording, conference filming services, conference services, conference recording, conference live streaming, event videographers, capture presentation slides, record presentation slides, event video recording
Id: T_1zutIBHs0
Channel Id: undefined
Length: 59min 36sec (3576 seconds)
Published: Wed Oct 10 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.