Skip to content

Commit 41e2eac

Browse files
authored
Inject sys.frozen and sys.frozendllhandle as constructor arguments instead of referencing them directly in RegistryEntries. (#736)
* Make calling `_get_serverdll` in `RegistryEntries.__init__`. * Make `serverdll` to optional kwargs of `RegistryEntries.__init__`. * Make calling `_get_serverdll` in `Registrar.__init__`. * Quit calling `_get_serverdll` in `RegistryEntries.__init__`. * Make calling `getattr(sys, "frozen", None)` in `RegistryEntries.__init__`. * Make calling `getattr(sys, "frozen", None)` in `Registrar.__init__` and make `frozen` to optional kwargs of `RegistryEntries.__init__`. * Make calling `getattr(sys, "frozendllhandle", None)` in `RegistryEntries.__init__`. * Make calling `getattr(sys, "frozendllhandle", None)` in `Registrar.__init__` and make `frozendllhandle` to optional kwargs of `RegistryEntries.__init__`. * Replace calling `hasattr`s with referencing attributes in `Registrar`. * Improve `_get_serverdll` and Remove assigning the `serverdll` instance variable. * Fix a type annotation.
1 parent bd40642 commit 41e2eac

File tree

2 files changed

+81
-91
lines changed

2 files changed

+81
-91
lines changed

comtypes/server/register.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ class Registrar(object):
9999
work.
100100
"""
101101

102+
_frozen: Optional[str]
103+
_frozendllhandle: Optional[int]
104+
105+
def __init__(self) -> None:
106+
self._frozen = getattr(sys, "frozen", None)
107+
self._frozendllhandle = getattr(sys, "frozendllhandle", None)
108+
102109
def nodebug(self, cls: Type) -> None:
103110
"""Delete logging entries from the registry."""
104111
clsid = cls._reg_clsid_
@@ -154,7 +161,13 @@ def register(self, cls: Type, executable: Optional[str] = None) -> None:
154161
self._register(cls, executable)
155162

156163
def _register(self, cls: Type, executable: Optional[str] = None) -> None:
157-
table = sorted(RegistryEntries(cls))
164+
table = sorted(
165+
RegistryEntries(
166+
cls,
167+
frozen=self._frozen,
168+
frozendllhandle=self._frozendllhandle,
169+
)
170+
)
158171
_debug("Registering %s", cls)
159172
for hkey, subkey, valuename, value in table:
160173
_debug("[%s\\%s]", _explain(hkey), subkey)
@@ -164,14 +177,14 @@ def _register(self, cls: Type, executable: Optional[str] = None) -> None:
164177

165178
tlib = getattr(cls, "_reg_typelib_", None)
166179
if tlib is not None:
167-
if hasattr(sys, "frozendllhandle"):
168-
dll = _get_serverdll()
169-
_debug("LoadTypeLibEx(%s, REGKIND_REGISTER)", dll)
170-
LoadTypeLibEx(dll, REGKIND_REGISTER)
180+
if self._frozendllhandle is not None:
181+
frozen_dll = _get_serverdll(self._frozendllhandle)
182+
_debug("LoadTypeLibEx(%s, REGKIND_REGISTER)", frozen_dll)
183+
LoadTypeLibEx(frozen_dll, REGKIND_REGISTER)
171184
else:
172185
if executable:
173186
path = executable
174-
elif hasattr(sys, "frozen"):
187+
elif self._frozen is not None:
175188
path = sys.executable
176189
else:
177190
path = cls._typelib_path_
@@ -190,7 +203,12 @@ def unregister(self, cls: Type, force: bool = False) -> None:
190203
def _unregister(self, cls: Type, force: bool = False) -> None:
191204
# If force==False, we only remove those entries that we
192205
# actually would have written. It seems ATL does the same.
193-
table = [t[:2] for t in RegistryEntries(cls)]
206+
table = [
207+
t[:2]
208+
for t in RegistryEntries(
209+
cls, frozen=self._frozen, frozendllhandle=self._frozendllhandle
210+
)
211+
]
194212
# only unique entries
195213
table = list(set(table))
196214
table.sort()
@@ -221,17 +239,24 @@ def _unregister(self, cls: Type, force: bool = False) -> None:
221239
_debug("Done")
222240

223241

224-
def _get_serverdll() -> str:
242+
def _get_serverdll(handle: Optional[int]) -> str:
225243
"""Return the pathname of the dll hosting the COM object."""
226-
handle = getattr(sys, "frozendllhandle", None)
227244
if handle is not None:
228245
return GetModuleFileName(handle, 260)
229246
return _ctypes.__file__
230247

231248

232249
class RegistryEntries(object):
233-
def __init__(self, cls: Type) -> None:
250+
def __init__(
251+
self,
252+
cls: Type,
253+
*,
254+
frozen: Optional[str] = None,
255+
frozendllhandle: Optional[int] = None,
256+
) -> None:
234257
self._cls = cls
258+
self._frozen = frozen
259+
self._frozendllhandle = frozendllhandle
235260

236261
def _get_full_classname(self, cls: Type) -> str:
237262
"""Return <modulename>.<classname> for 'cls'."""
@@ -312,11 +337,11 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]:
312337
localsvr_ctx = bool(clsctx & comtypes.CLSCTX_LOCAL_SERVER)
313338
inprocsvr_ctx = bool(clsctx & comtypes.CLSCTX_INPROC_SERVER)
314339

315-
if localsvr_ctx and not hasattr(sys, "frozendllhandle"):
340+
if localsvr_ctx and self._frozendllhandle is None:
316341
exe = sys.executable
317342
if " " in exe:
318343
exe = f'"{exe}"'
319-
if not hasattr(sys, "frozen"):
344+
if self._frozen is None:
320345
if not __debug__:
321346
exe = f"{exe} -O"
322347
script = os.path.abspath(sys.modules[cls.__module__].__file__) # type: ignore
@@ -328,11 +353,16 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]:
328353

329354
# Register InprocServer32 only when run from script or from
330355
# py2exe dll server, not from py2exe exe server.
331-
if inprocsvr_ctx and getattr(sys, "frozen", None) in (None, "dll"):
332-
yield (HKCR, rf"CLSID\{reg_clsid}\InprocServer32", "", _get_serverdll())
356+
if inprocsvr_ctx and self._frozen in (None, "dll"):
357+
yield (
358+
HKCR,
359+
rf"CLSID\{reg_clsid}\InprocServer32",
360+
"",
361+
_get_serverdll(self._frozendllhandle),
362+
)
333363
# only for non-frozen inproc servers the PythonPath/PythonClass is needed.
334364
if (
335-
not hasattr(sys, "frozendllhandle")
365+
self._frozendllhandle is None
336366
or not comtypes.server.inprocserver._clsid_to_class
337367
):
338368
yield (

comtypes/test/test_server_register.py

Lines changed: 36 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,13 @@ def test_calls_cls_unregister(self):
191191

192192
class Test_get_serverdll(ut.TestCase):
193193
def test_nonfrozen(self):
194-
self.assertEqual(_ctypes.__file__, _get_serverdll())
194+
self.assertEqual(_ctypes.__file__, _get_serverdll(None))
195195

196196
@mock.patch.object(register, "GetModuleFileName")
197-
@mock.patch.object(register, "sys")
198-
def test_frozen(self, _sys, GetModuleFileName):
199-
handle, dll_path = 1234, r"path\to\frozendll"
200-
_sys.frozendllhandle = handle
197+
def test_frozen(self, GetModuleFileName):
198+
handle, dll_path = 1234, r"path\to\frozen.dll"
201199
GetModuleFileName.return_value = dll_path
202-
self.assertEqual(r"path\to\frozendll", _get_serverdll())
200+
self.assertEqual(dll_path, _get_serverdll(handle))
203201
(((hmodule, maxsize), _),) = GetModuleFileName.call_args_list
204202
self.assertEqual(handle, hmodule)
205203
self.assertEqual(260, maxsize)
@@ -409,60 +407,42 @@ class Cls:
409407

410408

411409
class Test_Frozen_RegistryEntries(ut.TestCase):
412-
@mock.patch.object(register, "sys")
413-
def test_local_dll(self, _sys):
414-
_sys.mock_add_spec(["executable", "frozen"])
415-
_sys.executable = sys.executable
416-
_sys.frozen = "dll"
417-
reg_clsid = GUID.create_new()
418-
reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER
410+
SERVERDLL = r"my\target\server.dll"
419411

420-
class Cls:
421-
_reg_clsid_ = reg_clsid
422-
_reg_clsctx_ = reg_clsctx
412+
# We do not test the scenario where `frozen` is `'dll'` but
413+
# `frozendllhandle` is `None`, as it is not a situation
414+
# we anticipate.
423415

424-
clsid_sub = rf"CLSID\{reg_clsid}"
425-
expected = [
426-
(HKCR, clsid_sub, "", ""),
427-
(HKCR, rf"{clsid_sub}\LocalServer32", "", sys.executable),
428-
]
429-
self.assertEqual(expected, list(RegistryEntries(Cls)))
430-
431-
@mock.patch.object(register, "sys")
432-
def test_local_frozendllhandle(self, _sys):
433-
_sys.mock_add_spec(["frozen", "frozendllhandle"])
434-
_sys.frozen = "dll"
435-
_sys.frozendllhandle = 1234
416+
def test_local_dll(self):
436417
reg_clsid = GUID.create_new()
437418
reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER
438419

439420
class Cls:
440421
_reg_clsid_ = reg_clsid
441422
_reg_clsctx_ = reg_clsctx
442423

424+
# In such cases, the server does not start because the
425+
# InprocServer32/LocalServer32 keys are not registered.
443426
expected = [(HKCR, rf"CLSID\{reg_clsid}", "", "")]
444-
self.assertEqual(expected, list(RegistryEntries(Cls)))
427+
entries = RegistryEntries(Cls, frozen="dll", frozendllhandle=1234)
428+
self.assertEqual(expected, list(entries))
445429

446-
@mock.patch.object(register, "sys")
447-
def test_inproc_windows_exe(self, _sys):
448-
_sys.mock_add_spec(["frozen"])
449-
_sys.frozen = "windows_exe"
430+
def test_local_windows_exe(self):
450431
reg_clsid = GUID.create_new()
451-
reg_clsctx = comtypes.CLSCTX_INPROC_SERVER
432+
reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER
452433

453434
class Cls:
454435
_reg_clsid_ = reg_clsid
455436
_reg_clsctx_ = reg_clsctx
456437

457-
expected = [(HKCR, rf"CLSID\{reg_clsid}", "", "")]
458-
self.assertEqual(expected, list(RegistryEntries(Cls)))
438+
expected = [
439+
(HKCR, rf"CLSID\{reg_clsid}", "", ""),
440+
(HKCR, rf"CLSID\{reg_clsid}\LocalServer32", "", sys.executable),
441+
]
442+
self.assertEqual(expected, list(RegistryEntries(Cls, frozen="windows_exe")))
459443

460-
@mock.patch.object(register, "_get_serverdll", lambda: r"my\target\server.dll")
461-
@mock.patch.object(register, "sys")
462-
def test_inproc_dll_frozendllhandle_clsid_to_class(self, _sys):
463-
_sys.mock_add_spec(["frozen", "frozendllhandle"])
464-
_sys.frozen = "dll"
465-
_sys.frozendllhandle = 1234
444+
@mock.patch.object(register, "_get_serverdll", return_value=SERVERDLL)
445+
def test_inproc_dll_nonempty_clsid_to_class(self, get_serverdll):
466446
reg_clsid = GUID.create_new()
467447
reg_clsctx = comtypes.CLSCTX_INPROC_SERVER
468448

@@ -474,43 +454,17 @@ class Cls:
474454
inproc_srv_sub = rf"{clsid_sub}\InprocServer32"
475455
expected = [
476456
(HKCR, clsid_sub, "", ""),
477-
(HKCR, inproc_srv_sub, "", r"my\target\server.dll"),
457+
(HKCR, inproc_srv_sub, "", self.SERVERDLL),
478458
]
479459

480460
with mock.patch.dict(comtypes.server.inprocserver._clsid_to_class):
481461
comtypes.server.inprocserver._clsid_to_class.update({5678: Cls})
482-
self.assertEqual(expected, list(RegistryEntries(Cls)))
483-
484-
@mock.patch.object(register, "_get_serverdll", lambda: r"my\target\server.dll")
485-
@mock.patch.object(register, "sys")
486-
def test_inproc_dll(self, _sys):
487-
_sys.mock_add_spec(["frozen", "modules"])
488-
_sys.frozen = "dll"
489-
_sys.modules = sys.modules
490-
reg_clsid = GUID.create_new()
491-
reg_clsctx = comtypes.CLSCTX_INPROC_SERVER
492-
493-
class Cls:
494-
_reg_clsid_ = reg_clsid
495-
_reg_clsctx_ = reg_clsctx
462+
entries = RegistryEntries(Cls, frozen="dll", frozendllhandle=1234)
463+
self.assertEqual(expected, list(entries))
464+
get_serverdll.assert_called_once_with(1234)
496465

497-
clsid_sub = rf"CLSID\{reg_clsid}"
498-
inproc_srv_sub = rf"{clsid_sub}\InprocServer32"
499-
full_classname = f"{__name__}.Cls"
500-
expected = [
501-
(HKCR, clsid_sub, "", ""),
502-
(HKCR, inproc_srv_sub, "", r"my\target\server.dll"),
503-
(HKCR, inproc_srv_sub, "PythonClass", full_classname),
504-
(HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)),
505-
]
506-
self.assertEqual(expected, list(RegistryEntries(Cls)))
507-
508-
@mock.patch.object(register, "_get_serverdll", lambda: r"my\target\server.dll")
509-
@mock.patch.object(register, "sys")
510-
def test_inproc_dll_reg_threading(self, _sys):
511-
_sys.mock_add_spec(["frozen", "modules"])
512-
_sys.frozen = "dll"
513-
_sys.modules = sys.modules
466+
@mock.patch.object(register, "_get_serverdll", return_value=SERVERDLL)
467+
def test_inproc_reg_threading(self, get_serverdll):
514468
reg_clsid = GUID.create_new()
515469
reg_threading = "Both"
516470
reg_clsctx = comtypes.CLSCTX_INPROC_SERVER
@@ -525,9 +479,15 @@ class Cls:
525479
full_classname = f"{__name__}.Cls"
526480
expected = [
527481
(HKCR, clsid_sub, "", ""),
528-
(HKCR, inproc_srv_sub, "", r"my\target\server.dll"),
482+
(HKCR, inproc_srv_sub, "", self.SERVERDLL),
483+
# 'PythonClass' and 'PythonPath' are not required for
484+
# frozen inproc servers. This may be bugs but they do
485+
# not affect the server behavior.
529486
(HKCR, inproc_srv_sub, "PythonClass", full_classname),
530487
(HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)),
531488
(HKCR, inproc_srv_sub, "ThreadingModel", reg_threading),
532489
]
533-
self.assertEqual(expected, list(RegistryEntries(Cls)))
490+
self.assertEqual(
491+
expected, list(RegistryEntries(Cls, frozen="dll", frozendllhandle=1234))
492+
)
493+
get_serverdll.assert_called_once_with(1234)

0 commit comments

Comments
 (0)