Skip to content

Commit 87fbdfd

Browse files
committed
Bug 1674904: Part 2 - New dll registration code in mscom/oop/module; r=Jamie
We add new DLL registration code. This is a rather generic function that permits the following: * Registering multiple `CLSID`s for the same DLL; * Registering an optional `AppID`. Registering an `AppID` allows us to use a `DllSurrogate` to host the DLL out-of-process using Windows' built-in `dllhost.exe`. I'll be using this feature in a future bug. * Supporting all available threading modelsl; * Capable of registering either inproc servers or inproc handlers; * Using the transaction-based registry API so that we can cleanly rollback during registration if any part(s) of it fail. Differential Revision: https://phabricator.services.mozilla.com/D95606
1 parent 99bc464 commit 87fbdfd

File tree

5 files changed

+383
-0
lines changed

5 files changed

+383
-0
lines changed

ipc/mscom/Utils.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
# endif
1717
#endif
1818

19+
#include "mozilla/ArrayUtils.h"
20+
#include "mozilla/DebugOnly.h"
1921
#include "mozilla/mscom/Objref.h"
2022
#include "mozilla/mscom/Utils.h"
2123
#include "mozilla/RefPtr.h"
@@ -139,6 +141,50 @@ uintptr_t GetContainingModuleHandle() {
139141
return reinterpret_cast<uintptr_t>(thisModule);
140142
}
141143

144+
namespace detail {
145+
146+
long BuildRegGuidPath(REFGUID aGuid, const GuidType aGuidType, wchar_t* aBuf,
147+
const size_t aBufLen) {
148+
constexpr wchar_t kClsid[] = L"CLSID\\";
149+
constexpr wchar_t kAppid[] = L"AppID\\";
150+
constexpr wchar_t kSubkeyBase[] = L"SOFTWARE\\Classes\\";
151+
152+
// We exclude null terminators in these length calculations because we include
153+
// the stringified GUID's null terminator at the end. Since kClsid and kAppid
154+
// have identical lengths, we just choose one to compute this length.
155+
constexpr size_t kSubkeyBaseLen = mozilla::ArrayLength(kSubkeyBase) - 1;
156+
constexpr size_t kSubkeyLen =
157+
kSubkeyBaseLen + mozilla::ArrayLength(kClsid) - 1;
158+
// Guid length as formatted for the registry (including curlies and dashes),
159+
// but excluding null terminator.
160+
constexpr size_t kGuidLen = kGuidRegFormatCharLenInclNul - 1;
161+
constexpr size_t kExpectedPathLenInclNul = kSubkeyLen + kGuidLen + 1;
162+
163+
if (aBufLen < kExpectedPathLenInclNul) {
164+
// Buffer is too short
165+
return E_INVALIDARG;
166+
}
167+
168+
if (wcscpy_s(aBuf, aBufLen, kSubkeyBase)) {
169+
return E_INVALIDARG;
170+
}
171+
172+
const wchar_t* strGuidType = aGuidType == GuidType::CLSID ? kClsid : kAppid;
173+
if (wcscat_s(aBuf, aBufLen, strGuidType)) {
174+
return E_INVALIDARG;
175+
}
176+
177+
int guidConversionResult =
178+
::StringFromGUID2(aGuid, &aBuf[kSubkeyLen], aBufLen - kSubkeyLen);
179+
if (!guidConversionResult) {
180+
return E_INVALIDARG;
181+
}
182+
183+
return S_OK;
184+
}
185+
186+
} // namespace detail
187+
142188
long CreateStream(const uint8_t* aInitBuf, const uint32_t aInitBufSize,
143189
IStream** aOutStream) {
144190
if (!aInitBufSize || !aOutStream) {
@@ -271,6 +317,15 @@ void GUIDToString(REFGUID aGuid, nsAString& aOutString) {
271317
}
272318
}
273319

320+
#else
321+
322+
void GUIDToString(REFGUID aGuid,
323+
wchar_t (&aOutBuf)[kGuidRegFormatCharLenInclNul]) {
324+
DebugOnly<int> result =
325+
::StringFromGUID2(aGuid, aOutBuf, ArrayLength(aOutBuf));
326+
MOZ_ASSERT(result);
327+
}
328+
274329
#endif // defined(MOZILLA_INTERNAL_API)
275330

276331
#if defined(ACCESSIBILITY)

ipc/mscom/Utils.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ struct IUnknown;
1919

2020
namespace mozilla {
2121
namespace mscom {
22+
namespace detail {
23+
24+
enum class GuidType {
25+
CLSID,
26+
AppID,
27+
};
28+
29+
long BuildRegGuidPath(REFGUID aGuid, const GuidType aGuidType, wchar_t* aBuf,
30+
const size_t aBufLen);
31+
32+
} // namespace detail
2233

2334
bool IsCOMInitializedOnCurrentThread();
2435
bool IsCurrentThreadMTA();
@@ -31,6 +42,16 @@ bool IsProxy(IUnknown* aUnknown);
3142
bool IsValidGUID(REFGUID aCheckGuid);
3243
uintptr_t GetContainingModuleHandle();
3344

45+
template <size_t N>
46+
inline long BuildAppidPath(REFGUID aAppId, wchar_t (&aPath)[N]) {
47+
return detail::BuildRegGuidPath(aAppId, detail::GuidType::AppID, aPath, N);
48+
}
49+
50+
template <size_t N>
51+
inline long BuildClsidPath(REFCLSID aClsid, wchar_t (&aPath)[N]) {
52+
return detail::BuildRegGuidPath(aClsid, detail::GuidType::CLSID, aPath, N);
53+
}
54+
3455
/**
3556
* Given a buffer, create a new IStream object.
3657
* @param aBuf Buffer containing data to initialize the stream. This parameter
@@ -53,6 +74,12 @@ long CreateStream(const uint8_t* aBuf, const uint32_t aBufLen,
5374
*/
5475
long CopySerializedProxy(IStream* aInStream, IStream** aOutStream);
5576

77+
/**
78+
* Length of a stringified GUID as formatted for the registry, i.e. including
79+
* curly-braces and dashes.
80+
*/
81+
constexpr size_t kGuidRegFormatCharLenInclNul = 39;
82+
5683
#if defined(MOZILLA_INTERNAL_API)
5784
/**
5885
* Checks the registry to see if |aClsid| is a thread-aware in-process server.
@@ -78,6 +105,9 @@ long CopySerializedProxy(IStream* aInStream, IStream** aOutStream);
78105
bool IsClassThreadAwareInprocServer(REFCLSID aClsid);
79106

80107
void GUIDToString(REFGUID aGuid, nsAString& aOutString);
108+
#else
109+
void GUIDToString(REFGUID aGuid,
110+
wchar_t (&aOutBuf)[kGuidRegFormatCharLenInclNul]);
81111
#endif // defined(MOZILLA_INTERNAL_API)
82112

83113
#if defined(ACCESSIBILITY)

ipc/mscom/oop/Module.cpp

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,263 @@
66

77
#include "Module.h"
88

9+
#include <stdlib.h>
10+
11+
#include <ktmw32.h>
912
#include <memory.h>
1013
#include <rpc.h>
1114

15+
#include "mozilla/ArrayUtils.h"
16+
#include "mozilla/Assertions.h"
17+
#include "mozilla/mscom/Utils.h"
18+
#include "mozilla/Range.h"
19+
#include "nsWindowsHelpers.h"
20+
21+
template <size_t N>
22+
static const mozilla::Range<const wchar_t> LiteralToRange(
23+
const wchar_t (&aArg)[N]) {
24+
return mozilla::Range(aArg, N);
25+
}
26+
1227
namespace mozilla {
1328
namespace mscom {
1429

1530
ULONG Module::sRefCount = 0;
1631

32+
static const wchar_t* SubkeyNameFromClassType(
33+
const Module::ClassType aClassType) {
34+
switch (aClassType) {
35+
case Module::ClassType::InprocServer:
36+
return L"InprocServer32";
37+
case Module::ClassType::InprocHandler:
38+
return L"InprocHandler32";
39+
default:
40+
MOZ_CRASH("Unknown ClassType");
41+
return nullptr;
42+
}
43+
}
44+
45+
static const Range<const wchar_t> ThreadingModelAsString(
46+
const Module::ThreadingModel aThreadingModel) {
47+
switch (aThreadingModel) {
48+
case Module::ThreadingModel::DedicatedUiThreadOnly:
49+
return LiteralToRange(L"Apartment");
50+
case Module::ThreadingModel::MultiThreadedApartmentOnly:
51+
return LiteralToRange(L"Free");
52+
case Module::ThreadingModel::DedicatedUiThreadXorMultiThreadedApartment:
53+
return LiteralToRange(L"Both");
54+
case Module::ThreadingModel::AllThreadsAllApartments:
55+
return LiteralToRange(L"Neutral");
56+
default:
57+
MOZ_CRASH("Unknown ThreadingModel");
58+
return Range<const wchar_t>();
59+
}
60+
}
61+
62+
/* static */
63+
HRESULT Module::Register(const CLSID* const* aClsids, const size_t aNumClsids,
64+
const ThreadingModel aThreadingModel,
65+
const ClassType aClassType, const GUID* const aAppId) {
66+
MOZ_ASSERT(aClsids && aNumClsids);
67+
if (!aClsids || !aNumClsids) {
68+
return E_INVALIDARG;
69+
}
70+
71+
const wchar_t* inprocName = SubkeyNameFromClassType(aClassType);
72+
73+
const Range<const wchar_t> threadingModelStr =
74+
ThreadingModelAsString(aThreadingModel);
75+
const DWORD threadingModelStrLenBytesInclNul =
76+
threadingModelStr.length() * sizeof(wchar_t);
77+
78+
wchar_t strAppId[kGuidRegFormatCharLenInclNul] = {};
79+
if (aAppId) {
80+
GUIDToString(*aAppId, strAppId);
81+
}
82+
83+
// Obtain the full path to this DLL
84+
HMODULE thisModule;
85+
if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
86+
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
87+
reinterpret_cast<LPCWSTR>(&Module::CanUnload),
88+
&thisModule)) {
89+
return HRESULT_FROM_WIN32(::GetLastError());
90+
}
91+
92+
wchar_t absThisModulePath[MAX_PATH + 1] = {};
93+
DWORD actualPathLenCharsExclNul = ::GetModuleFileNameW(
94+
thisModule, absThisModulePath, ArrayLength(absThisModulePath));
95+
if (!actualPathLenCharsExclNul ||
96+
actualPathLenCharsExclNul == ArrayLength(absThisModulePath)) {
97+
return HRESULT_FROM_WIN32(::GetLastError());
98+
}
99+
const DWORD actualPathLenBytesInclNul =
100+
(actualPathLenCharsExclNul + 1) * sizeof(wchar_t);
101+
102+
// Use the name of this DLL as the name of the transaction
103+
wchar_t txnName[_MAX_FNAME] = {};
104+
if (_wsplitpath_s(absThisModulePath, nullptr, 0, nullptr, 0, txnName,
105+
ArrayLength(txnName), nullptr, 0)) {
106+
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
107+
}
108+
109+
// Manipulate the registry using a transaction so that any failures are
110+
// rolled back.
111+
nsAutoHandle txn(::CreateTransaction(
112+
nullptr, nullptr, TRANSACTION_DO_NOT_PROMOTE, 0, 0, 0, txnName));
113+
if (txn.get() == INVALID_HANDLE_VALUE) {
114+
return HRESULT_FROM_WIN32(::GetLastError());
115+
}
116+
117+
HRESULT hr;
118+
LSTATUS status;
119+
120+
// A single DLL may serve multiple components. For each CLSID, we register
121+
// this DLL as its server and, when an AppId is specified, set up a reference
122+
// from the CLSID to the specified AppId.
123+
for (size_t idx = 0; idx < aNumClsids; ++idx) {
124+
if (!aClsids[idx]) {
125+
return E_INVALIDARG;
126+
}
127+
128+
wchar_t clsidKeyPath[256];
129+
hr = BuildClsidPath(*aClsids[idx], clsidKeyPath);
130+
if (FAILED(hr)) {
131+
return hr;
132+
}
133+
134+
// Create the CLSID key
135+
HKEY rawClsidKey;
136+
status = ::RegCreateKeyTransactedW(
137+
HKEY_LOCAL_MACHINE, clsidKeyPath, 0, nullptr, REG_OPTION_NON_VOLATILE,
138+
KEY_ALL_ACCESS, nullptr, &rawClsidKey, nullptr, txn, nullptr);
139+
if (status != ERROR_SUCCESS) {
140+
return HRESULT_FROM_WIN32(status);
141+
}
142+
nsAutoRegKey clsidKey(rawClsidKey);
143+
144+
if (aAppId) {
145+
// This value associates the registered CLSID with the specified AppID
146+
status = ::RegSetValueExW(clsidKey, L"AppID", 0, REG_SZ,
147+
reinterpret_cast<const BYTE*>(strAppId),
148+
ArrayLength(strAppId) * sizeof(wchar_t));
149+
if (status != ERROR_SUCCESS) {
150+
return HRESULT_FROM_WIN32(status);
151+
}
152+
}
153+
154+
HKEY rawInprocKey;
155+
status = ::RegCreateKeyTransactedW(
156+
clsidKey, inprocName, 0, nullptr, REG_OPTION_NON_VOLATILE,
157+
KEY_ALL_ACCESS, nullptr, &rawInprocKey, nullptr, txn, nullptr);
158+
if (status != ERROR_SUCCESS) {
159+
return HRESULT_FROM_WIN32(status);
160+
}
161+
nsAutoRegKey inprocKey(rawInprocKey);
162+
163+
// Set the component's path to this DLL
164+
status = ::RegSetValueExW(inprocKey, nullptr, 0, REG_EXPAND_SZ,
165+
reinterpret_cast<const BYTE*>(absThisModulePath),
166+
actualPathLenBytesInclNul);
167+
if (status != ERROR_SUCCESS) {
168+
return HRESULT_FROM_WIN32(status);
169+
}
170+
171+
status = ::RegSetValueExW(
172+
inprocKey, L"ThreadingModel", 0, REG_SZ,
173+
reinterpret_cast<const BYTE*>(threadingModelStr.begin().get()),
174+
threadingModelStrLenBytesInclNul);
175+
if (status != ERROR_SUCCESS) {
176+
return HRESULT_FROM_WIN32(status);
177+
}
178+
}
179+
180+
if (aAppId) {
181+
// When specified, we must also create a key for the AppID.
182+
wchar_t appidKeyPath[256];
183+
hr = BuildAppidPath(*aAppId, appidKeyPath);
184+
if (FAILED(hr)) {
185+
return hr;
186+
}
187+
188+
HKEY rawAppidKey;
189+
status = ::RegCreateKeyTransactedW(
190+
HKEY_LOCAL_MACHINE, appidKeyPath, 0, nullptr, REG_OPTION_NON_VOLATILE,
191+
KEY_ALL_ACCESS, nullptr, &rawAppidKey, nullptr, txn, nullptr);
192+
if (status != ERROR_SUCCESS) {
193+
return HRESULT_FROM_WIN32(status);
194+
}
195+
nsAutoRegKey appidKey(rawAppidKey);
196+
197+
// Setting DllSurrogate to a null or empty string indicates to Windows that
198+
// we want to use the default surrogate (i.e. dllhost.exe) to load our DLL.
199+
status =
200+
::RegSetValueExW(appidKey, L"DllSurrogate", 0, REG_SZ,
201+
reinterpret_cast<const BYTE*>(L""), sizeof(wchar_t));
202+
if (status != ERROR_SUCCESS) {
203+
return HRESULT_FROM_WIN32(status);
204+
}
205+
}
206+
207+
if (!::CommitTransaction(txn)) {
208+
return HRESULT_FROM_WIN32(::GetLastError());
209+
}
210+
211+
return S_OK;
212+
}
213+
214+
/**
215+
* Unfortunately the registry transaction APIs are not as well-developed for
216+
* deleting things as they are for creating them. We just use RegDeleteTree
217+
* for the implementation of this method.
218+
*/
219+
HRESULT Module::Deregister(const CLSID* const* aClsids, const size_t aNumClsids,
220+
const GUID* const aAppId) {
221+
MOZ_ASSERT(aClsids && aNumClsids);
222+
if (!aClsids || !aNumClsids) {
223+
return E_INVALIDARG;
224+
}
225+
226+
HRESULT hr;
227+
LSTATUS status;
228+
229+
// Delete the key for each CLSID. This will also delete any references to
230+
// the AppId.
231+
for (size_t idx = 0; idx < aNumClsids; ++idx) {
232+
if (!aClsids[idx]) {
233+
return E_INVALIDARG;
234+
}
235+
236+
wchar_t clsidKeyPath[256];
237+
hr = BuildClsidPath(*aClsids[idx], clsidKeyPath);
238+
if (FAILED(hr)) {
239+
return hr;
240+
}
241+
242+
status = ::RegDeleteTreeW(HKEY_LOCAL_MACHINE, clsidKeyPath);
243+
// We allow the deletion to succeed if the key was already gone
244+
if (status != ERROR_SUCCESS && status != ERROR_FILE_NOT_FOUND) {
245+
return HRESULT_FROM_WIN32(status);
246+
}
247+
}
248+
249+
// Now delete the AppID key, if desired.
250+
if (aAppId) {
251+
wchar_t appidKeyPath[256];
252+
hr = BuildAppidPath(*aAppId, appidKeyPath);
253+
if (FAILED(hr)) {
254+
return hr;
255+
}
256+
257+
status = ::RegDeleteTreeW(HKEY_LOCAL_MACHINE, appidKeyPath);
258+
// We allow the deletion to succeed if the key was already gone
259+
if (status != ERROR_SUCCESS && status != ERROR_FILE_NOT_FOUND) {
260+
return HRESULT_FROM_WIN32(status);
261+
}
262+
}
263+
264+
return S_OK;
265+
}
266+
17267
} // namespace mscom
18268
} // namespace mozilla

0 commit comments

Comments
 (0)