How it works
Packaged COM entries are read from the manifest and stored in a new catalog that the UWP deployment system manages. This solves one of the main problems in COM in which any application or installer can write to the registry and corrupt the system, e.g. overwriting existing COM registrations or leaving behind registry entries upon uninstall.
At run-time when a COM call is made, i.e. calling CLSIDFromProgID() or CoCreateInstance(), the system first looks in the Packaged COM catalog and, if not found, falls back to the system registry. The COM server is then activated and runs OOP from the client application.
When to use Packaged COM
Packaged COM is very useful for apps that expose third-party extension points, but not all applications need it. If your application uses COM only for its own personal use, then you can rely on COM entries in the application’s private hive (Registry.dat) to support your app. All binaries in the same package have access to that registry, but any other apps on the system cannot see into your app’s private hive. Packaged COM allows you explicit control over which servers can be made public and used by third-parties.
Limitations
As the Packaged COM entries are stored in a separate catalog, applications that directly read the registry (e.g. calling RegOpenKeyEx() or RegEnumKeyEx()) will not see any entries and will fail. In these scenarios, applications providing extensions will need to work with their partners to go through COM API calls or provide another app-to-app communication mechanism.
Support is scoped to OOP servers, allowing two key requirements. First, OOP server supports means the Desktop Bridge can maintain its promise of serviceability. By running extensions OOP, the update manager can shut down the COM server and update all binaries because there are no dlls in use loaded by other processes. Second, OOP allows for a more robust extension mechanism. If an in-process COM server is hung, it will also hang the app; for OOP, the host app will still function and can decide how to handle the misbehaving OOP server.
We do not support every COM and OLE registration entry, for the full list of what we support please refer to the element hierarchy in the Windows 10 app package manifest on MSDN: https://docs.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/root-elements
Taking a closer look
The keys to enabling this functionality are the new manifest extension categories “windows.comServer” and “windows.comInterface.” The “windows.comServer” extension corresponds to the typical registration entries found under the CLSID (i.e. HKEY_CLASSES_ROOT\CLSID\{MyClsId}) for an application supporting executable servers and their COM classes (including their OLE registration entries), surrogate servers, ProgIDs and TreatAs classes. The “windows.comInterface” extension corresponds to the typical registration entries under both the HKCR \Interface\{MyInterfaceID} and HKCR\Typelib\{MyTypelibID}, and supports Interfaces, ProxyStubs and Typelibs.
If you have registered COM classes before, these elements will look very familiar and straightforward to map from the existing registry keys into manifest entries. Here are a few examples.
Example #1: Registering an .exe COM server
In this first example, we will package ACDual for the Desktop Bridge. ACDual is an MFC OLE sample that shipped in earlier versions of Visual Studio. This app is an .exe COM server, ACDual.exe, with a Document CoClass that implements the IDualAClick interface. A client can then consume it. Below is a picture of the ACDual server and a simple WinForms client app that is using it:
Fig. 1 Client WinForms app automating AutoClick COM server
Store link: https://www.microsoft.com/store/apps/9nm1gvnkhjnf
GitHub link: https://github.com/Microsoft/DesktopBridgeToUWP-Samples/tree/master/Samples/PackagedComServer
Registry versus AppxManifest.xml
To understand how Packaged COM works, it helps to compare the typical entries in the registry with the Packaged COM entries in the manifest. For a minimal COM server, you typically need a CLSID with the LocalServer32 key, and an Interface pointing to the ProxyStub to handle cross-process marshaling. ProgIDs and TypeLibs make it easier to read and program against. Let’s take a look at each section and compare what the system registry looks like in comparison to Packaged COM snippets. First, let’s look at the following ProgID and CLSID entry that registers a server in the system registry:
[code]
; ProgID registration
[HKEY_CLASSES_ROOT\ACDual.Document]
@="AClick Document"
[HKEY_CLASSES_ROOT\ACDual.Document\CLSID]
@="{4B115281-32F0-11CF-AC85-444553540000}"
[HKEY_CLASSES_ROOT\ACDual.Document\DefaultIcon]
@=”C:\\VCSamples\\MFC\\ole\\acdual\\Release\\ACDual.exe,1”
; CLSID registration
[HKEY_CLASSES_ROOT\CLSID\{4B115281-32F0-11CF-AC85-444553540000}]
@="AClick Document"
[HKEY_CLASSES_ROOT\CLSID\{4B115281-32F0-11CF-AC85-444553540000}\InprocHandler32]
@="ole32.dll"
[HKEY_CLASSES_ROOT\CLSID\{4B115281-32F0-11CF-AC85-444553540000}\LocalServer32]
@="\"C:\\VCSamples\\MFC\\ole\\acdual\\Release\\ACDual.exe\""
[HKEY_CLASSES_ROOT\CLSID\{4B115281-32F0-11CF-AC85-444553540000}\ProgID]
@="ACDual.Document"
[/code]
For comparison, the translation into the package manifest is straightforward. The ProgID and CLSID are supported through the windows.comServer extension, which must be under your app’s Application element along with all of your other extensions. Regarding ProgIDs, you can have multiple ProgID registrations for your server. Notice that there is no default value of the ProgID to provide a friendly name, as that information is stored with the CLSID registration and one of the goals of the manifest schema is to reduce duplication of information. The CLSID registration is enabled through the ExeServer element with an Executable attribute, which is a relative path to the .exe contained in the package. Package-relative paths solve one common problem with registering COM servers declaratively: in a .REG file, you don’t know where your executable is located. Often in a package, all the files are placed in the root of the package. The Class registration element is within the ExeServer element. You can specify one or more classes for an ExeServer.
[code lang=”xml”]
<Applications>
<Application Id="ACDual" Executable="ACDual.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="ACDual" …/>
<Extensions>
<com:Extension Category="windows.comServer">
<com:ComServer>
<!– CLSID –>
<com:ExeServer Executable="ACDual.exe" DisplayName="AutoClick">
<com:Class Id ="4B115281-32F0-11cf-AC85-444553540000" DisplayName="AClick Document" ProgId="AutoClick.Document.1" VersionIndependentProgId="AutoClick.Document">
</com:Class>
</com:ExeServer>
<!– ProgId –>
<com:ProgId Id="AutoClick.Document" CurrentVersion="AutoClick.Document.1" />
<com:ProgId Id="AutoClick.Document.1" Clsid="4B115281-32F0-11cf-AC85-444553540000" />
</com:ComServer>
</com:Extension>
[/code]
The next step is TypeLib and interface registration. In this example, the TypeLib is part of the main executable, and the interface uses the standard marshaler (oleaut32.dll) for its ProxyStub, so the registration is as follows:
[code]
[HKEY_CLASSES_ROOT\Interface\{0BDD0E81-0DD7-11CF-BBA8-444553540000}]
@="IDualAClick"
[HKEY_CLASSES_ROOT\Interface\{0BDD0E81-0DD7-11CF-BBA8-444553540000}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"
[HKEY_CLASSES_ROOT\Interface\{0BDD0E81-0DD7-11CF-BBA8-444553540000}\TypeLib]
@="{4B115284-32F0-11CF-AC85-444553540000}"
"Version"="1.0"
;TypeLib registration
[HKEY_CLASSES_ROOT\TypeLib\{4B115284-32F0-11CF-AC85-444553540000}]
[HKEY_CLASSES_ROOT\TypeLib\{4B115284-32F0-11CF-AC85-444553540000}\1.0]
@="ACDual"
[HKEY_CLASSES_ROOT\TypeLib\{4B115284-32F0-11CF-AC85-444553540000}\1.0\0]
[HKEY_CLASSES_ROOT\TypeLib\{4B115284-32F0-11CF-AC85-444553540000}\1.0\0\win32]
@=" C:\\VCSamples\\MFC\\ole\\acdual\\Release\\AutoClik.TLB"
[HKEY_CLASSES_ROOT\TypeLib\{4B115284-32F0-11CF-AC85-444553540000}\1.0\FLAGS]
@="0"
[HKEY_CLASSES_ROOT\TypeLib\{4B115284-32F0-11CF-AC85-444553540000}\1.0\HELPDIR]
@=""
[/code]
In translating this into the package manifest, the windows.comInterface extension supports one or more TypeLib, ProxyStub and interface registrations. Typically, it is placed under the Application element so it is easier to associate with the class registrations for readability, but it may also reside under the Package element. Also, note that we did not have to remember the CLSID of the universal marshaler (the key where ProxyStubClsid32 = {00020424-0000-0000-C000-000000000046}). This is simply a flag: UseUniversalMarshaler=”true”.
[code lang=”xml”]
<com:Extension Category="windows.comInterface">
<com:ComInterface>
<!– Interfaces –>
<!– IID_IDualAClick –>
<com:Interface Id="0BDD0E81-0DD7-11cf-BBA8-444553540000" UseUniversalMarshaler="true">
<com:TypeLib Id="4B115284-32F0-11cf-AC85-444553540000" VersionNumber="1.0" />
</com:Interface>
<!– TypeLib –>
<com:TypeLib Id="4B115284-32F0-11cf-AC85-444553540000">
<com:Version DisplayName = "ACDual" VersionNumber="1.0" LocaleId="0" LibraryFlag="0">
<com:Win32Path Path="AutoClik.tlb" />
</com:Version>
</com:TypeLib>
</com:ComInterface>
</com:Extension>
</Extensions>
</Application>
</Applications>
[/code]
Now you can initialize and use the server from any language that supports COM and dual interface OLE automation servers.
Example #2: OLE support
In this next example, we will package an existing OLE document server to demonstrate the capabilities of the Desktop Bridge and Packaged COM. The example we will use is the MFC Scribble sample app, which provides an insertable document type called Scribb Document. Scribble is a simple server that allows an OLE container, such as WordPad, to insert a Scribb Document.
Fig 2. WordPad hosting an embedded Scribb Document
Store Link: https://www.microsoft.com/store/apps/9n4xcm905zkj
GitHub Link: https://github.com/Microsoft/DesktopBridgeToUWP-Samples/tree/master/Samples/PackagedOleDocument
Registry versus AppxManifest.xml
There are many keys to specify various OLE attributes. Again, the magic here is that the platform has been updated to work with Packaged COM, and all you have to do is translate those keys into your manifest. In this example, the entries for Scribble include the ProgID, its file type associations and the CLSID with entries.
[code]
;SCRIBBLE.REG
;
;FileType Association using older DDEExec command to launch the app
[HKEY_CLASSES_ROOT\.SCB]
@=”Scribble.Document”
[HKEY_CLASSES_ROOT\Scribble.Document\shell\open\command]
@=”SCRIBBLE.EXE %1”
;ProgId
[HKEY_CLASSES_ROOT\Scribble.Document]
@= Scribb Document
[HKEY_CLASSES_ROOT\Scribble.Document\Insertable]
@=””
[HKEY_CLASSES_ROOT\Scribble.Document\CLSID]
@= “{7559FD90-9B93-11CE-B0F0-00AA006C28B3}}”
;ClsId with OLE entries
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}]
@="Scribb Document"
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\AuxUserType]
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\AuxUserType\2]
@="Scribb"
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\AuxUserType\3]
@="Scribble"
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\DefaultIcon]
@="\"C:\\VC2015Samples\\scribble\\Release\\Scribble.exe\",1"
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\InprocHandler32]
@="ole32.dll"
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\Insertable]
@=""
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\LocalServer32]
@="\"C:\\VC2015Samples\\scribble\\Release\\Scribble.exe\""
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\MiscStatus]
@="32"
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\ProgID]
@="Scribble.Document"
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\Verb]
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\Verb\0]
@="&Edit,0,2"
[HKEY_CLASSES_ROOT\CLSID\{7559FD90-9B93-11CE-B0F0-00AA006C28B3}\Verb\1]
@="&Open,0,2"
[/code]
First, let’s discuss the file type association. This is an extension that was supported in the first release of the Desktop Bridge extensions. Note that specifying the file type association here automatically adds support for the shell open command.
Next, let’s take a closer look at the ProgID and CLSID entries. In this case, the simple example only has a ProgID and no VersionIndependentProgID.
Most of the excitement in this example is underneath the CLSID where all the OLE keys live. The registry keys typically map to attributes of the class, such as:
- Insertable key under either the ProgID or CLSID, mapping to the InsertableObject=”true” attribute
- If the InprocHandler32 key is Ole32.dll, use the EnableOleDefaultHandler=”true” attribute
- AuxUserType\2, mapping to ShortDisplayName
- AuxUserType\3, mapping to the Application DisplayName
- In cases where there were multiple values in a key, such as the OLE verbs, we’ve split those out into separate attributes. Here’s what the full manifest looks like:
[code lang=”xml”]
<Applications>
<Application Id="Scribble" Executable="Scribble.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements DisplayName="Scribble App" …/>
<Extensions>
<uap:Extension Category="windows.fileTypeAssociation">
<uap3:FileTypeAssociation Name="scb" Parameters="%1">
<uap:SupportedFileTypes>
<uap:FileType>.scb</uap:FileType>
</uap:SupportedFileTypes>
</uap3:FileTypeAssociation>
</uap:Extension>
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="Scribble.exe" DisplayName="Scribble">
<!– ClsId Registration –>
<com:Class Id="7559FD90-9B93-11CE-B0F0-00AA006C28B3" DisplayName="Scribb Document" ShortDisplayName="Scribb" ProgId="Scribble.Document.1" VersionIndependentProgId ="Scribble.Document" EnableOleDefaultHandler="true" InsertableObject="true">
<com:DefaultIcon Path="Scribble.exe" ResourceIndex="1" />
<com:MiscStatus OleMiscFlag="32"/>
<com:Verbs>
<com:Verb Id="0" DisplayName="&Edit" AppendMenuFlag="0" OleVerbFlag="2" />
<com:Verb Id="1" DisplayName="&Open" AppendMenuFlag="0" OleVerbFlag="2" />
</com:Verbs>
</com:Class>
</com:ExeServer>
<!– ProgId Registration –>
<com:ProgId Id="Scribble.Document" CurrentVersion="Scribble.Document.1" />
<com:ProgId Id="Scribble.Document.1" Clsid="7559FD90-9B93-11CE-B0F0-00AA006C28B3" />
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
[/code]
Additional support
The two examples above covered the most common cases of a COM server and an OLE document support. Packaged COM also supports additional servers like Surrogates and TreatAs classes. For more information, please refer to the element hierarchy in the Windows 10 app package manifest on MSDN: https://docs.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/root-elements
Conclusion
With UWP and Windows 10, applications can take advantage of several exciting new features while leveraging existing code investments in areas such as COM. With the Desktop Bridge platform and tooling enhancements, existing PC software can now be part of the UWP ecosystem and take advantage of the same set of new platform features and operating system capabilities.
For more information on the Desktop Bridge, please visit the Windows Dev Center.
Ready to submit your app to the Windows Store? Let us know!
Update – the Desktop App Converter has been updated to support new extensions available in the Windows 10 Creators Update, including support for Packaged COM. Please get the latest converter at http://aka.ms/converter.