Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Add support for ProgID registration (3.0) #7573

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions Documentation/design-docs/COM-activation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
"<clsid>": {
"assembly": "<assembly_name>",
"type": "<type_name>"
"type": "<type_name>",
"progid": "<prog_id>"
}
}
```
Expand All @@ -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>")`][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. `<UseComHost>true</UseComHost>`
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. `<EnableComHosting>true</EnableComHosting>`
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.
Expand Down
10 changes: 9 additions & 1 deletion src/corehost/cli/comhost/clsidmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -210,7 +217,8 @@ clsid_map comhost::get_clsid_map()
// {
// "<clsid>": {
// "assembly": <assembly_name>,
// "type": <type_name>
// "type": <type_name>,
// "progid": <prog_id> [Optional]
// },
// ...
// }
Expand Down
170 changes: 153 additions & 17 deletions src/corehost/cli/comhost/comhost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::remove_pointer<LPOLESTR>::type, decltype(&::CoTaskMemFree)>
{
Expand All @@ -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;

Expand Down Expand Up @@ -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,
&regKeyRaw,
&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<const BYTE*>(entry.type.c_str()),
static_cast<DWORD>(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,
&regProgIdClsidRaw,
&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<const BYTE*>(clsidAsStr),
static_cast<DWORD>(::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;
Expand Down Expand Up @@ -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,
&regProgIdKeyRaw,
&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<const BYTE*>(entry.progid.c_str()),
static_cast<DWORD>(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;
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/corehost/cli/comhost/comhost.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<CLSID, clsid_map_entry>;
Expand Down