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

Commit

Permalink
Add support for ProgID registration (3.0) (#7573)
Browse files Browse the repository at this point in the history
* Add support for ProgID registration

* Update COM activation documentation
  • Loading branch information
AaronRobinsonMSFT authored and vatsan-madhavan committed Aug 9, 2019
1 parent 1c6acff commit 8ebed65
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 23 deletions.
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

0 comments on commit 8ebed65

Please sign in to comment.