From aa09e694d001597604974a1465059ce796e5549e Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Mon, 5 Aug 2019 13:55:07 -0700 Subject: [PATCH] Add support for ProgID registration (dotnet/core-setup#7551) * Add support for ProgID registration * Update COM activation documentation Commit migrated from https://github.com/dotnet/core-setup/commit/490c40397e4645cf0c47e528466b6d4a301cc04c --- docs/installer/design-docs/COM-activation.md | 11 +- src/installer/corehost/cli/comhost/clsidmap.cpp | 10 +- src/installer/corehost/cli/comhost/comhost.cpp | 170 +++++++++++++++++++++--- src/installer/corehost/cli/comhost/comhost.h | 2 + 4 files changed, 170 insertions(+), 23 deletions(-) diff --git a/docs/installer/design-docs/COM-activation.md b/docs/installer/design-docs/COM-activation.md index a0c71f6..5ebd412 100644 --- a/docs/installer/design-docs/COM-activation.md +++ b/docs/installer/design-docs/COM-activation.md @@ -116,13 +116,14 @@ The `DllRegisterServer()` and `DllUnregisterServer()` functions adhere to the [C ##### CLSID map format -The `CLSID` mapping manifest is a JSON format (`.clsidmap` extension when on disk) that defines a mapping from `CLSID` to an assembly name and type name tuple. Each `CLSID` mapping is a key in the outer JSON object. +The `CLSID` mapping manifest is a JSON format (`.clsidmap` extension when on disk) that defines a mapping from `CLSID` to an assembly name and type name tuple as well as an optional [ProgID](https://docs.microsoft.com/windows/win32/com/-progid--key). Each `CLSID` mapping is a key in the outer JSON object. ``` json { "": { "assembly": "", - "type": "" + "type": "", + "progid": "" } } ``` @@ -132,9 +133,9 @@ The `CLSID` mapping manifest is a JSON format (`.clsidmap` extension when on dis 1) A new .NET Core class library project is created using [`dotnet.exe`][dotnet_link]. 1) A class is defined that has the [`GuidAttribute("")`][guid_link] and the [`ComVisibleAttribute(true)`](https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.comvisibleattribute). - In .NET Core, unlike .NET Framework, there is no generated class interface generation (i.e. `IClassX`). This means it is advantageous for users to have the class implement a marshalable interface. -1) The `UseComHost` property is added to the project file. - - i.e. `true` -1) During class project build, the following actions occur if the `UseComHost` property is `true`: +1) The `EnableComHosting` property is added to the project file. + - i.e. `true` +1) During class project build, the following actions occur if the `EnableComHosting` property is `true`: 1) A `.runtimeconfig.json` file is created for the assembly. 1) The resulting assembly is interrogated for classes with the attributes defined above and a `CLSID` map is created on disk (`.clsidmap`). 1) The target Framework's shim binary (i.e. `comhost.dll`) is copied to the local output directory. diff --git a/src/installer/corehost/cli/comhost/clsidmap.cpp b/src/installer/corehost/cli/comhost/clsidmap.cpp index d2d343f..c398806 100644 --- a/src/installer/corehost/cli/comhost/clsidmap.cpp +++ b/src/installer/corehost/cli/comhost/clsidmap.cpp @@ -77,10 +77,17 @@ namespace clsid_map_entry e{}; + e.clsid = clsidMaybe; + json::object &val = prop.second.as_object(); e.assembly = val.at(_X("assembly")).as_string(); e.type = val.at(_X("type")).as_string(); + // Check if a ProgID was defined. + auto prodIdMaybe = val.find(_X("progid")); + if (prodIdMaybe != val.cend()) + e.progid = prodIdMaybe->second.as_string(); + mapping[clsidMaybe] = std::move(e); } @@ -210,7 +217,8 @@ clsid_map comhost::get_clsid_map() // { // "": { // "assembly": , - // "type": + // "type": , + // "progid": [Optional] // }, // ... // } diff --git a/src/installer/corehost/cli/comhost/comhost.cpp b/src/installer/corehost/cli/comhost/comhost.cpp index c3cf23a..ab1f734 100644 --- a/src/installer/corehost/cli/comhost/comhost.cpp +++ b/src/installer/corehost/cli/comhost/comhost.cpp @@ -152,7 +152,8 @@ COM_API HRESULT STDMETHODCALLTYPE DllCanUnloadNow(void) namespace { - const WCHAR EntryKeyFmt[] = _X("SOFTWARE\\Classes\\CLSID\\%s"); + const WCHAR ClsidKeyFmt[] = _X("SOFTWARE\\Classes\\CLSID\\%s"); + const WCHAR ProgIDKeyFmt[] = _X("SOFTWARE\\Classes\\%s"); struct OleStr : public std::unique_ptr::type, decltype(&::CoTaskMemFree)> { @@ -168,17 +169,10 @@ namespace { } }; - HRESULT RemoveClsid(_In_ REFCLSID clsid) + // Removes the key and all sub-keys + HRESULT RemoveRegistryKey(_In_z_ LPCWSTR regKeyPath) { - HRESULT hr; - - LPOLESTR clsidAsStrRaw; - RETURN_IF_FAILED(::StringFromCLSID(clsid, &clsidAsStrRaw)); - - OleStr clsidAsStr{ clsidAsStrRaw }; - - WCHAR regKeyPath[1024]; - ::swprintf_s(regKeyPath, EntryKeyFmt, clsidAsStr.get()); + assert(regKeyPath != nullptr); LSTATUS res; @@ -208,20 +202,125 @@ namespace return S_OK; } - HRESULT RegisterClsid(_In_ REFCLSID clsid, _In_opt_z_ const WCHAR *threadingModel) + HRESULT RemoveProgId(_In_ const comhost::clsid_map_entry &entry) + { + if (entry.progid.empty()) + return S_OK; + + HRESULT hr; + + WCHAR regKeyPath[1024]; + ::swprintf_s(regKeyPath, ProgIDKeyFmt, entry.progid.c_str()); + + // Remove ProgID key + RETURN_IF_FAILED(RemoveRegistryKey(regKeyPath)); + + return S_OK; + } + + HRESULT RemoveClsid(_In_ const comhost::clsid_map_entry &entry) + { + HRESULT hr; + + LPOLESTR clsidAsStrRaw; + RETURN_IF_FAILED(::StringFromCLSID(entry.clsid, &clsidAsStrRaw)); + + OleStr clsidAsStr{ clsidAsStrRaw }; + + WCHAR regKeyPath[1024]; + ::swprintf_s(regKeyPath, ClsidKeyFmt, clsidAsStr.get()); + + // Remove CLSID key + RETURN_IF_FAILED(RemoveRegistryKey(regKeyPath)); + RETURN_IF_FAILED(RemoveProgId(entry)); + + return S_OK; + } + + HRESULT RegisterProgId(_In_ const comhost::clsid_map_entry &entry, _In_z_ LPOLESTR clsidAsStr) + { + assert(!entry.progid.empty() && clsidAsStr != nullptr); + + WCHAR regKeyProgIdPath[1024]; + ::swprintf_s(regKeyProgIdPath, ProgIDKeyFmt, entry.progid.c_str()); + + HKEY regKeyRaw; + DWORD disp; + LSTATUS res = ::RegCreateKeyExW( + HKEY_LOCAL_MACHINE, + regKeyProgIdPath, + 0, + REG_NONE, + REG_OPTION_NON_VOLATILE, + (KEY_READ | KEY_WRITE), + nullptr, + ®KeyRaw, + &disp); + if (res != ERROR_SUCCESS) + return __HRESULT_FROM_WIN32(res); + + RegKey regKey{ regKeyRaw }; + + // Set the default value for the ProgID to be the type name + // This value is only used for user consumption and has no + // functional impact. + res = ::RegSetValueExW( + regKey.get(), + nullptr, + 0, + REG_SZ, + reinterpret_cast(entry.type.c_str()), + static_cast(sizeof(entry.type.size() + 1) * sizeof(entry.type[0]))); + if (res != ERROR_SUCCESS) + return __HRESULT_FROM_WIN32(res); + + WCHAR regKeyProgIdClsidPath[ARRAYSIZE(regKeyProgIdPath) * 2]; + ::swprintf_s(regKeyProgIdClsidPath, L"%s\\CLSID", regKeyProgIdPath); + + HKEY regProgIdClsidRaw; + res = ::RegCreateKeyExW( + HKEY_LOCAL_MACHINE, + regKeyProgIdClsidPath, + 0, + REG_NONE, + REG_OPTION_NON_VOLATILE, + (KEY_READ | KEY_WRITE), + nullptr, + ®ProgIdClsidRaw, + &disp); + if (res != ERROR_SUCCESS) + return __HRESULT_FROM_WIN32(res); + + regKey.reset(regProgIdClsidRaw); + + // The value for the key is the CLSID + res = ::RegSetValueExW( + regKey.get(), + nullptr, + 0, + REG_SZ, + reinterpret_cast(clsidAsStr), + static_cast(::wcslen(clsidAsStr) + 1) * sizeof(clsidAsStr[0])); + if (res != ERROR_SUCCESS) + return __HRESULT_FROM_WIN32(res); + + return S_OK; + } + + HRESULT RegisterClsid(_In_ const comhost::clsid_map_entry &entry, _In_opt_z_ const WCHAR *threadingModel) { HRESULT hr; // Remove the CLSID in case it exists and has undesirable settings - RETURN_IF_FAILED(RemoveClsid(clsid)); + RETURN_IF_FAILED(RemoveClsid(entry)); LPOLESTR clsidAsStrRaw; - RETURN_IF_FAILED(::StringFromCLSID(clsid, &clsidAsStrRaw)); + RETURN_IF_FAILED(::StringFromCLSID(entry.clsid, &clsidAsStrRaw)); OleStr clsidAsStr{ clsidAsStrRaw }; WCHAR regKeyClsidPath[1024]; - ::swprintf_s(regKeyClsidPath, EntryKeyFmt, clsidAsStr.get()); + ::swprintf_s(regKeyClsidPath, ClsidKeyFmt, clsidAsStr.get()); HKEY regKeyRaw; DWORD disp; @@ -309,6 +408,43 @@ namespace return __HRESULT_FROM_WIN32(res); } + // Check if a Prog ID is defined + if (!entry.progid.empty()) + { + // Register the ProgID in the CLSID key + WCHAR regKeyProgIdPath[ARRAYSIZE(regKeyClsidPath) * 2]; + ::swprintf_s(regKeyProgIdPath, L"%s\\ProgID", regKeyClsidPath); + + HKEY regProgIdKeyRaw; + res = ::RegCreateKeyExW( + HKEY_LOCAL_MACHINE, + regKeyProgIdPath, + 0, + REG_NONE, + REG_OPTION_NON_VOLATILE, + (KEY_READ | KEY_WRITE), + nullptr, + ®ProgIdKeyRaw, + &disp); + if (res != ERROR_SUCCESS) + return __HRESULT_FROM_WIN32(res); + + regKey.reset(regProgIdKeyRaw); + + // The default value for the key is the ProgID + res = ::RegSetValueExW( + regKey.get(), + nullptr, + 0, + REG_SZ, + reinterpret_cast(entry.progid.c_str()), + static_cast(entry.progid.size() + 1) * sizeof(entry.progid[0])); + if (res != ERROR_SUCCESS) + return __HRESULT_FROM_WIN32(res); + + RETURN_IF_FAILED(RegisterProgId(entry, clsidAsStr.get())); + } + return S_OK; } } @@ -345,7 +481,7 @@ COM_API HRESULT STDMETHODCALLTYPE DllRegisterServer(void) for (clsid_map::const_reference p : map) { // Register the CLSID in registry - RETURN_IF_FAILED(RegisterClsid(p.first, _X("Both"))); + RETURN_IF_FAILED(RegisterClsid(p.second, _X("Both"))); // Call user-defined register function cxt.class_id = p.first; @@ -395,7 +531,7 @@ COM_API HRESULT STDMETHODCALLTYPE DllUnregisterServer(void) RETURN_IF_FAILED(unreg(&cxt)); // Unregister the CLSID from registry - RETURN_IF_FAILED(RemoveClsid(p.first)); + RETURN_IF_FAILED(RemoveClsid(p.second)); } return S_OK; diff --git a/src/installer/corehost/cli/comhost/comhost.h b/src/installer/corehost/cli/comhost/comhost.h index 0b194bb..7a89565 100644 --- a/src/installer/corehost/cli/comhost/comhost.h +++ b/src/installer/corehost/cli/comhost/comhost.h @@ -38,8 +38,10 @@ namespace comhost { struct clsid_map_entry { + CLSID clsid; pal::string_t assembly; pal::string_t type; + pal::string_t progid; }; using clsid_map = std::map; -- 2.7.4