From 0b86b3f00843c5ff0e21835c151320857be5687f Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:31 +0900 Subject: [PATCH 01/11] Make calling `_get_serverdll` in `RegistryEntries.__init__`. --- comtypes/server/register.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index 9ae2b508..0f31afc3 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -232,6 +232,7 @@ def _get_serverdll() -> str: class RegistryEntries(object): def __init__(self, cls: Type) -> None: self._cls = cls + self._serverdll = _get_serverdll() def _get_full_classname(self, cls: Type) -> str: """Return . for 'cls'.""" @@ -329,7 +330,7 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]: # Register InprocServer32 only when run from script or from # py2exe dll server, not from py2exe exe server. if inprocsvr_ctx and getattr(sys, "frozen", None) in (None, "dll"): - yield (HKCR, rf"CLSID\{reg_clsid}\InprocServer32", "", _get_serverdll()) + yield (HKCR, rf"CLSID\{reg_clsid}\InprocServer32", "", self._serverdll) # only for non-frozen inproc servers the PythonPath/PythonClass is needed. if ( not hasattr(sys, "frozendllhandle") From 9a7edbdd51433d0a29c4390150734f2716cd87a2 Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:31 +0900 Subject: [PATCH 02/11] Make `serverdll` to optional kwargs of `RegistryEntries.__init__`. --- comtypes/server/register.py | 4 +-- comtypes/test/test_server_register.py | 44 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index 0f31afc3..6e8234a7 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -230,9 +230,9 @@ def _get_serverdll() -> str: class RegistryEntries(object): - def __init__(self, cls: Type) -> None: + def __init__(self, cls: Type, *, serverdll: Optional[str] = None) -> None: self._cls = cls - self._serverdll = _get_serverdll() + self._serverdll = serverdll if serverdll else _get_serverdll() def _get_full_classname(self, cls: Type) -> str: """Return . for 'cls'.""" diff --git a/comtypes/test/test_server_register.py b/comtypes/test/test_server_register.py index e252dfae..011ec3e8 100644 --- a/comtypes/test/test_server_register.py +++ b/comtypes/test/test_server_register.py @@ -329,6 +329,26 @@ class Cls: ] self.assertEqual(expected, list(RegistryEntries(Cls))) + def test_dll_specified_inproc_server(self): + reg_clsid = GUID.create_new() + reg_clsctx = comtypes.CLSCTX_INPROC_SERVER + + class Cls: + _reg_clsid_ = reg_clsid + _reg_clsctx_ = reg_clsctx + + serverdll = r"\path\to\my\injected.dll" + clsid_sub = rf"CLSID\{reg_clsid}" + inproc_srv_sub = rf"{clsid_sub}\InprocServer32" + full_classname = f"{__name__}.Cls" + expected = [ + (HKCR, clsid_sub, "", ""), + (HKCR, inproc_srv_sub, "", serverdll), + (HKCR, inproc_srv_sub, "PythonClass", full_classname), + (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), + ] + self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) + def test_inproc_server_reg_threading(self): reg_clsid = GUID.create_new() reg_threading = "Both" @@ -505,6 +525,30 @@ class Cls: ] self.assertEqual(expected, list(RegistryEntries(Cls))) + @mock.patch.object(register, "sys") + def test_dll_specified_inproc_server(self, _sys): + _sys.mock_add_spec(["frozen", "modules"]) + _sys.frozen = "dll" + _sys.modules = sys.modules + reg_clsid = GUID.create_new() + reg_clsctx = comtypes.CLSCTX_INPROC_SERVER + + class Cls: + _reg_clsid_ = reg_clsid + _reg_clsctx_ = reg_clsctx + + serverdll = r"\path\to\my\injected.dll" + clsid_sub = rf"CLSID\{reg_clsid}" + inproc_srv_sub = rf"{clsid_sub}\InprocServer32" + full_classname = f"{__name__}.Cls" + expected = [ + (HKCR, clsid_sub, "", ""), + (HKCR, inproc_srv_sub, "", serverdll), + (HKCR, inproc_srv_sub, "PythonClass", full_classname), + (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), + ] + self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) + @mock.patch.object(register, "_get_serverdll", lambda: r"my\target\server.dll") @mock.patch.object(register, "sys") def test_inproc_dll_reg_threading(self, _sys): From d686fd84f1f98ae971c0ae176d191dedb2b36a75 Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:31 +0900 Subject: [PATCH 03/11] Make calling `_get_serverdll` in `Registrar.__init__`. --- comtypes/server/register.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index 6e8234a7..e7d9c460 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -99,6 +99,9 @@ class Registrar(object): work. """ + def __init__(self) -> None: + self._serverdll = _get_serverdll() + def nodebug(self, cls: Type) -> None: """Delete logging entries from the registry.""" clsid = cls._reg_clsid_ @@ -165,9 +168,8 @@ def _register(self, cls: Type, executable: Optional[str] = None) -> None: tlib = getattr(cls, "_reg_typelib_", None) if tlib is not None: if hasattr(sys, "frozendllhandle"): - dll = _get_serverdll() - _debug("LoadTypeLibEx(%s, REGKIND_REGISTER)", dll) - LoadTypeLibEx(dll, REGKIND_REGISTER) + _debug("LoadTypeLibEx(%s, REGKIND_REGISTER)", self._serverdll) + LoadTypeLibEx(self._serverdll, REGKIND_REGISTER) else: if executable: path = executable From b9b8422234ed3ad35850caf3af0af8aef974c74e Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:31 +0900 Subject: [PATCH 04/11] Quit calling `_get_serverdll` in `RegistryEntries.__init__`. --- comtypes/server/register.py | 8 ++-- comtypes/test/test_server_register.py | 69 ++++++++------------------- 2 files changed, 25 insertions(+), 52 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index e7d9c460..d6dc4641 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -157,7 +157,7 @@ def register(self, cls: Type, executable: Optional[str] = None) -> None: self._register(cls, executable) def _register(self, cls: Type, executable: Optional[str] = None) -> None: - table = sorted(RegistryEntries(cls)) + table = sorted(RegistryEntries(cls, serverdll=self._serverdll)) _debug("Registering %s", cls) for hkey, subkey, valuename, value in table: _debug("[%s\\%s]", _explain(hkey), subkey) @@ -192,7 +192,7 @@ def unregister(self, cls: Type, force: bool = False) -> None: def _unregister(self, cls: Type, force: bool = False) -> None: # If force==False, we only remove those entries that we # actually would have written. It seems ATL does the same. - table = [t[:2] for t in RegistryEntries(cls)] + table = [t[:2] for t in RegistryEntries(cls, serverdll=self._serverdll)] # only unique entries table = list(set(table)) table.sort() @@ -234,7 +234,7 @@ def _get_serverdll() -> str: class RegistryEntries(object): def __init__(self, cls: Type, *, serverdll: Optional[str] = None) -> None: self._cls = cls - self._serverdll = serverdll if serverdll else _get_serverdll() + self._serverdll = serverdll def _get_full_classname(self, cls: Type) -> str: """Return . for 'cls'.""" @@ -332,6 +332,8 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]: # Register InprocServer32 only when run from script or from # py2exe dll server, not from py2exe exe server. if inprocsvr_ctx and getattr(sys, "frozen", None) in (None, "dll"): + if self._serverdll is None: + raise TypeError("'serverdll' is not specified.") yield (HKCR, rf"CLSID\{reg_clsid}\InprocServer32", "", self._serverdll) # only for non-frozen inproc servers the PythonPath/PythonClass is needed. if ( diff --git a/comtypes/test/test_server_register.py b/comtypes/test/test_server_register.py index 011ec3e8..f17bba35 100644 --- a/comtypes/test/test_server_register.py +++ b/comtypes/test/test_server_register.py @@ -318,18 +318,19 @@ class Cls: _reg_clsid_ = reg_clsid _reg_clsctx_ = reg_clsctx + serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" full_classname = f"{__name__}.Cls" expected = [ (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", _ctypes.__file__), + (HKCR, inproc_srv_sub, "", serverdll), (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), ] - self.assertEqual(expected, list(RegistryEntries(Cls))) + self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) - def test_dll_specified_inproc_server(self): + def test_fails_inproc_server(self): reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_INPROC_SERVER @@ -337,17 +338,9 @@ class Cls: _reg_clsid_ = reg_clsid _reg_clsctx_ = reg_clsctx - serverdll = r"\path\to\my\injected.dll" - clsid_sub = rf"CLSID\{reg_clsid}" - inproc_srv_sub = rf"{clsid_sub}\InprocServer32" - full_classname = f"{__name__}.Cls" - expected = [ - (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", serverdll), - (HKCR, inproc_srv_sub, "PythonClass", full_classname), - (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), - ] - self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) + with self.assertRaises(TypeError) as e: + list(RegistryEntries(Cls)) + self.assertEqual(str(e.exception), "'serverdll' is not specified.") def test_inproc_server_reg_threading(self): reg_clsid = GUID.create_new() @@ -359,17 +352,18 @@ class Cls: _reg_threading_ = reg_threading _reg_clsctx_ = reg_clsctx + serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" full_classname = f"{__name__}.Cls" expected = [ (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", _ctypes.__file__), + (HKCR, inproc_srv_sub, "", serverdll), (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), (HKCR, inproc_srv_sub, "ThreadingModel", reg_threading), ] - self.assertEqual(expected, list(RegistryEntries(Cls))) + self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) def test_reg_typelib(self): reg_clsid = GUID.create_new() @@ -405,6 +399,7 @@ class Cls: _reg_desc_ = reg_desc _reg_clsctx_ = reg_clsctx + serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" local_srv_sub = rf"{clsid_sub}\LocalServer32" @@ -419,13 +414,13 @@ class Cls: (HKCR, rf"{reg_novers_progid}\CurVer", "", reg_progid), (HKCR, rf"{reg_novers_progid}\CLSID", "", str(reg_clsid)), (HKCR, local_srv_sub, "", f"{sys.executable} {__file__}"), - (HKCR, inproc_srv_sub, "", _ctypes.__file__), + (HKCR, inproc_srv_sub, "", serverdll), (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), (HKCR, inproc_srv_sub, "ThreadingModel", reg_threading), (HKCR, rf"{clsid_sub}\Typelib", "", libid), ] - self.assertEqual(expected, list(RegistryEntries(Cls))) + self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) class Test_Frozen_RegistryEntries(ut.TestCase): @@ -477,7 +472,6 @@ class Cls: expected = [(HKCR, rf"CLSID\{reg_clsid}", "", "")] self.assertEqual(expected, list(RegistryEntries(Cls))) - @mock.patch.object(register, "_get_serverdll", lambda: r"my\target\server.dll") @mock.patch.object(register, "sys") def test_inproc_dll_frozendllhandle_clsid_to_class(self, _sys): _sys.mock_add_spec(["frozen", "frozendllhandle"]) @@ -490,18 +484,18 @@ class Cls: _reg_clsid_ = reg_clsid _reg_clsctx_ = reg_clsctx + serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" expected = [ (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", r"my\target\server.dll"), + (HKCR, inproc_srv_sub, "", serverdll), ] with mock.patch.dict(comtypes.server.inprocserver._clsid_to_class): comtypes.server.inprocserver._clsid_to_class.update({5678: Cls}) - self.assertEqual(expected, list(RegistryEntries(Cls))) + self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) - @mock.patch.object(register, "_get_serverdll", lambda: r"my\target\server.dll") @mock.patch.object(register, "sys") def test_inproc_dll(self, _sys): _sys.mock_add_spec(["frozen", "modules"]) @@ -514,30 +508,7 @@ class Cls: _reg_clsid_ = reg_clsid _reg_clsctx_ = reg_clsctx - clsid_sub = rf"CLSID\{reg_clsid}" - inproc_srv_sub = rf"{clsid_sub}\InprocServer32" - full_classname = f"{__name__}.Cls" - expected = [ - (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", r"my\target\server.dll"), - (HKCR, inproc_srv_sub, "PythonClass", full_classname), - (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), - ] - self.assertEqual(expected, list(RegistryEntries(Cls))) - - @mock.patch.object(register, "sys") - def test_dll_specified_inproc_server(self, _sys): - _sys.mock_add_spec(["frozen", "modules"]) - _sys.frozen = "dll" - _sys.modules = sys.modules - reg_clsid = GUID.create_new() - reg_clsctx = comtypes.CLSCTX_INPROC_SERVER - - class Cls: - _reg_clsid_ = reg_clsid - _reg_clsctx_ = reg_clsctx - - serverdll = r"\path\to\my\injected.dll" + serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" full_classname = f"{__name__}.Cls" @@ -549,7 +520,6 @@ class Cls: ] self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) - @mock.patch.object(register, "_get_serverdll", lambda: r"my\target\server.dll") @mock.patch.object(register, "sys") def test_inproc_dll_reg_threading(self, _sys): _sys.mock_add_spec(["frozen", "modules"]) @@ -564,14 +534,15 @@ class Cls: _reg_threading_ = reg_threading _reg_clsctx_ = reg_clsctx + serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" full_classname = f"{__name__}.Cls" expected = [ (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", r"my\target\server.dll"), + (HKCR, inproc_srv_sub, "", serverdll), (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), (HKCR, inproc_srv_sub, "ThreadingModel", reg_threading), ] - self.assertEqual(expected, list(RegistryEntries(Cls))) + self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) From 14ddaa53b908b5f921373e29d4364136e3d11ac5 Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:31 +0900 Subject: [PATCH 05/11] Make calling `getattr(sys, "frozen", None)` in `RegistryEntries.__init__`. --- comtypes/server/register.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index d6dc4641..eef0d1c2 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -235,6 +235,7 @@ class RegistryEntries(object): def __init__(self, cls: Type, *, serverdll: Optional[str] = None) -> None: self._cls = cls self._serverdll = serverdll + self._frozen = getattr(sys, "frozen", None) def _get_full_classname(self, cls: Type) -> str: """Return . for 'cls'.""" @@ -319,7 +320,7 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]: exe = sys.executable if " " in exe: exe = f'"{exe}"' - if not hasattr(sys, "frozen"): + if self._frozen is None: if not __debug__: exe = f"{exe} -O" script = os.path.abspath(sys.modules[cls.__module__].__file__) # type: ignore @@ -331,7 +332,7 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]: # Register InprocServer32 only when run from script or from # py2exe dll server, not from py2exe exe server. - if inprocsvr_ctx and getattr(sys, "frozen", None) in (None, "dll"): + if inprocsvr_ctx and self._frozen in (None, "dll"): if self._serverdll is None: raise TypeError("'serverdll' is not specified.") yield (HKCR, rf"CLSID\{reg_clsid}\InprocServer32", "", self._serverdll) From 7f7a9b681f02e3fcf10785bcc4bf6c28be311cc7 Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:31 +0900 Subject: [PATCH 06/11] Make calling `getattr(sys, "frozen", None)` in `Registrar.__init__` and make `frozen` to optional kwargs of `RegistryEntries.__init__`. --- comtypes/server/register.py | 18 ++++++++++--- comtypes/test/test_server_register.py | 37 ++++++++++++++------------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index eef0d1c2..9b50f4dc 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -101,6 +101,7 @@ class Registrar(object): def __init__(self) -> None: self._serverdll = _get_serverdll() + self._frozen = getattr(sys, "frozen", None) def nodebug(self, cls: Type) -> None: """Delete logging entries from the registry.""" @@ -157,7 +158,9 @@ def register(self, cls: Type, executable: Optional[str] = None) -> None: self._register(cls, executable) def _register(self, cls: Type, executable: Optional[str] = None) -> None: - table = sorted(RegistryEntries(cls, serverdll=self._serverdll)) + table = sorted( + RegistryEntries(cls, serverdll=self._serverdll, frozen=self._frozen) + ) _debug("Registering %s", cls) for hkey, subkey, valuename, value in table: _debug("[%s\\%s]", _explain(hkey), subkey) @@ -192,7 +195,12 @@ def unregister(self, cls: Type, force: bool = False) -> None: def _unregister(self, cls: Type, force: bool = False) -> None: # If force==False, we only remove those entries that we # actually would have written. It seems ATL does the same. - table = [t[:2] for t in RegistryEntries(cls, serverdll=self._serverdll)] + table = [ + t[:2] + for t in RegistryEntries( + cls, serverdll=self._serverdll, frozen=self._frozen + ) + ] # only unique entries table = list(set(table)) table.sort() @@ -232,10 +240,12 @@ def _get_serverdll() -> str: class RegistryEntries(object): - def __init__(self, cls: Type, *, serverdll: Optional[str] = None) -> None: + def __init__( + self, cls, *, serverdll: Optional[str] = None, frozen: Optional[str] = None + ) -> None: self._cls = cls self._serverdll = serverdll - self._frozen = getattr(sys, "frozen", None) + self._frozen = frozen def _get_full_classname(self, cls: Type) -> str: """Return . for 'cls'.""" diff --git a/comtypes/test/test_server_register.py b/comtypes/test/test_server_register.py index f17bba35..a754a5de 100644 --- a/comtypes/test/test_server_register.py +++ b/comtypes/test/test_server_register.py @@ -426,9 +426,8 @@ class Cls: class Test_Frozen_RegistryEntries(ut.TestCase): @mock.patch.object(register, "sys") def test_local_dll(self, _sys): - _sys.mock_add_spec(["executable", "frozen"]) + _sys.mock_add_spec(["executable"]) _sys.executable = sys.executable - _sys.frozen = "dll" reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER @@ -441,12 +440,11 @@ class Cls: (HKCR, clsid_sub, "", ""), (HKCR, rf"{clsid_sub}\LocalServer32", "", sys.executable), ] - self.assertEqual(expected, list(RegistryEntries(Cls))) + self.assertEqual(expected, list(RegistryEntries(Cls, frozen="dll"))) @mock.patch.object(register, "sys") def test_local_frozendllhandle(self, _sys): - _sys.mock_add_spec(["frozen", "frozendllhandle"]) - _sys.frozen = "dll" + _sys.mock_add_spec(["frozendllhandle"]) _sys.frozendllhandle = 1234 reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER @@ -456,12 +454,11 @@ class Cls: _reg_clsctx_ = reg_clsctx expected = [(HKCR, rf"CLSID\{reg_clsid}", "", "")] - self.assertEqual(expected, list(RegistryEntries(Cls))) + self.assertEqual(expected, list(RegistryEntries(Cls, frozen="dll"))) @mock.patch.object(register, "sys") def test_inproc_windows_exe(self, _sys): - _sys.mock_add_spec(["frozen"]) - _sys.frozen = "windows_exe" + _sys.mock_add_spec([]) reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_INPROC_SERVER @@ -470,12 +467,11 @@ class Cls: _reg_clsctx_ = reg_clsctx expected = [(HKCR, rf"CLSID\{reg_clsid}", "", "")] - self.assertEqual(expected, list(RegistryEntries(Cls))) + self.assertEqual(expected, list(RegistryEntries(Cls, frozen="windows_exe"))) @mock.patch.object(register, "sys") def test_inproc_dll_frozendllhandle_clsid_to_class(self, _sys): - _sys.mock_add_spec(["frozen", "frozendllhandle"]) - _sys.frozen = "dll" + _sys.mock_add_spec(["frozendllhandle"]) _sys.frozendllhandle = 1234 reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_INPROC_SERVER @@ -494,12 +490,14 @@ class Cls: with mock.patch.dict(comtypes.server.inprocserver._clsid_to_class): comtypes.server.inprocserver._clsid_to_class.update({5678: Cls}) - self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) + self.assertEqual( + expected, + list(RegistryEntries(Cls, serverdll=serverdll, frozen="dll")), + ) @mock.patch.object(register, "sys") def test_inproc_dll(self, _sys): - _sys.mock_add_spec(["frozen", "modules"]) - _sys.frozen = "dll" + _sys.mock_add_spec(["modules"]) _sys.modules = sys.modules reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_INPROC_SERVER @@ -518,12 +516,13 @@ class Cls: (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), ] - self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) + self.assertEqual( + expected, list(RegistryEntries(Cls, serverdll=serverdll, frozen="dll")) + ) @mock.patch.object(register, "sys") def test_inproc_dll_reg_threading(self, _sys): - _sys.mock_add_spec(["frozen", "modules"]) - _sys.frozen = "dll" + _sys.mock_add_spec(["modules"]) _sys.modules = sys.modules reg_clsid = GUID.create_new() reg_threading = "Both" @@ -545,4 +544,6 @@ class Cls: (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), (HKCR, inproc_srv_sub, "ThreadingModel", reg_threading), ] - self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) + self.assertEqual( + expected, list(RegistryEntries(Cls, serverdll=serverdll, frozen="dll")) + ) From 9d3817d5a4b513bdebd2851b1a48bf70763f3422 Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:31 +0900 Subject: [PATCH 07/11] Make calling `getattr(sys, "frozendllhandle", None)` in `RegistryEntries.__init__`. --- comtypes/server/register.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index 9b50f4dc..6724e264 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -246,6 +246,7 @@ def __init__( self._cls = cls self._serverdll = serverdll self._frozen = frozen + self._frozendllhandle = getattr(sys, "frozendllhandle", None) def _get_full_classname(self, cls: Type) -> str: """Return . for 'cls'.""" @@ -326,7 +327,7 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]: localsvr_ctx = bool(clsctx & comtypes.CLSCTX_LOCAL_SERVER) inprocsvr_ctx = bool(clsctx & comtypes.CLSCTX_INPROC_SERVER) - if localsvr_ctx and not hasattr(sys, "frozendllhandle"): + if localsvr_ctx and self._frozendllhandle is None: exe = sys.executable if " " in exe: exe = f'"{exe}"' @@ -348,7 +349,7 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]: yield (HKCR, rf"CLSID\{reg_clsid}\InprocServer32", "", self._serverdll) # only for non-frozen inproc servers the PythonPath/PythonClass is needed. if ( - not hasattr(sys, "frozendllhandle") + self._frozendllhandle is None or not comtypes.server.inprocserver._clsid_to_class ): yield ( From ca1bc43a28c2888c80bbe1cbbeac33d46b48c093 Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:31 +0900 Subject: [PATCH 08/11] Make calling `getattr(sys, "frozendllhandle", None)` in `Registrar.__init__` and make `frozendllhandle` to optional kwargs of `RegistryEntries.__init__`. --- comtypes/server/register.py | 22 ++++++++++++++++++---- comtypes/test/test_server_register.py | 15 +++++++-------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index 6724e264..f1b02c12 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -102,6 +102,7 @@ class Registrar(object): def __init__(self) -> None: self._serverdll = _get_serverdll() self._frozen = getattr(sys, "frozen", None) + self._frozendllhandle = getattr(sys, "frozendllhandle", None) def nodebug(self, cls: Type) -> None: """Delete logging entries from the registry.""" @@ -159,7 +160,12 @@ def register(self, cls: Type, executable: Optional[str] = None) -> None: def _register(self, cls: Type, executable: Optional[str] = None) -> None: table = sorted( - RegistryEntries(cls, serverdll=self._serverdll, frozen=self._frozen) + RegistryEntries( + cls, + serverdll=self._serverdll, + frozen=self._frozen, + frozendllhandle=self._frozendllhandle, + ) ) _debug("Registering %s", cls) for hkey, subkey, valuename, value in table: @@ -198,7 +204,10 @@ def _unregister(self, cls: Type, force: bool = False) -> None: table = [ t[:2] for t in RegistryEntries( - cls, serverdll=self._serverdll, frozen=self._frozen + cls, + serverdll=self._serverdll, + frozen=self._frozen, + frozendllhandle=self._frozendllhandle, ) ] # only unique entries @@ -241,12 +250,17 @@ def _get_serverdll() -> str: class RegistryEntries(object): def __init__( - self, cls, *, serverdll: Optional[str] = None, frozen: Optional[str] = None + self, + cls, + *, + serverdll: Optional[str] = None, + frozen: Optional[str] = None, + frozendllhandle: Optional[int] = None, ) -> None: self._cls = cls self._serverdll = serverdll self._frozen = frozen - self._frozendllhandle = getattr(sys, "frozendllhandle", None) + self._frozendllhandle = frozendllhandle def _get_full_classname(self, cls: Type) -> str: """Return . for 'cls'.""" diff --git a/comtypes/test/test_server_register.py b/comtypes/test/test_server_register.py index a754a5de..5640ba47 100644 --- a/comtypes/test/test_server_register.py +++ b/comtypes/test/test_server_register.py @@ -444,8 +444,7 @@ class Cls: @mock.patch.object(register, "sys") def test_local_frozendllhandle(self, _sys): - _sys.mock_add_spec(["frozendllhandle"]) - _sys.frozendllhandle = 1234 + _sys.mock_add_spec([]) reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER @@ -454,7 +453,8 @@ class Cls: _reg_clsctx_ = reg_clsctx expected = [(HKCR, rf"CLSID\{reg_clsid}", "", "")] - self.assertEqual(expected, list(RegistryEntries(Cls, frozen="dll"))) + entries = RegistryEntries(Cls, frozen="dll", frozendllhandle=1234) + self.assertEqual(expected, list(entries)) @mock.patch.object(register, "sys") def test_inproc_windows_exe(self, _sys): @@ -471,8 +471,7 @@ class Cls: @mock.patch.object(register, "sys") def test_inproc_dll_frozendllhandle_clsid_to_class(self, _sys): - _sys.mock_add_spec(["frozendllhandle"]) - _sys.frozendllhandle = 1234 + _sys.mock_add_spec([]) reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_INPROC_SERVER @@ -490,10 +489,10 @@ class Cls: with mock.patch.dict(comtypes.server.inprocserver._clsid_to_class): comtypes.server.inprocserver._clsid_to_class.update({5678: Cls}) - self.assertEqual( - expected, - list(RegistryEntries(Cls, serverdll=serverdll, frozen="dll")), + entries = RegistryEntries( + Cls, serverdll=serverdll, frozen="dll", frozendllhandle=1234 ) + self.assertEqual(expected, list(entries)) @mock.patch.object(register, "sys") def test_inproc_dll(self, _sys): From 18961305e190163e418a4958b1831e3903b4e61c Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:32 +0900 Subject: [PATCH 09/11] Replace calling `hasattr`s with referencing attributes in `Registrar`. --- comtypes/server/register.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index f1b02c12..03406dad 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -176,13 +176,13 @@ def _register(self, cls: Type, executable: Optional[str] = None) -> None: tlib = getattr(cls, "_reg_typelib_", None) if tlib is not None: - if hasattr(sys, "frozendllhandle"): + if self._frozendllhandle is not None: _debug("LoadTypeLibEx(%s, REGKIND_REGISTER)", self._serverdll) LoadTypeLibEx(self._serverdll, REGKIND_REGISTER) else: if executable: path = executable - elif hasattr(sys, "frozen"): + elif self._frozen is not None: path = sys.executable else: path = cls._typelib_path_ From 79d3a9c20917a5c9a2a41e34218568b3cc57cff1 Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:14:32 +0900 Subject: [PATCH 10/11] Improve `_get_serverdll` and Remove assigning the `serverdll` instance variable. --- comtypes/server/register.py | 29 +++--- comtypes/test/test_server_register.py | 127 ++++++++------------------ 2 files changed, 50 insertions(+), 106 deletions(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index 03406dad..b4f68ac0 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -99,8 +99,10 @@ class Registrar(object): work. """ + _frozen: Optional[str] + _frozendllhandle: Optional[int] + def __init__(self) -> None: - self._serverdll = _get_serverdll() self._frozen = getattr(sys, "frozen", None) self._frozendllhandle = getattr(sys, "frozendllhandle", None) @@ -162,7 +164,6 @@ def _register(self, cls: Type, executable: Optional[str] = None) -> None: table = sorted( RegistryEntries( cls, - serverdll=self._serverdll, frozen=self._frozen, frozendllhandle=self._frozendllhandle, ) @@ -177,8 +178,9 @@ def _register(self, cls: Type, executable: Optional[str] = None) -> None: tlib = getattr(cls, "_reg_typelib_", None) if tlib is not None: if self._frozendllhandle is not None: - _debug("LoadTypeLibEx(%s, REGKIND_REGISTER)", self._serverdll) - LoadTypeLibEx(self._serverdll, REGKIND_REGISTER) + frozen_dll = _get_serverdll(self._frozendllhandle) + _debug("LoadTypeLibEx(%s, REGKIND_REGISTER)", frozen_dll) + LoadTypeLibEx(frozen_dll, REGKIND_REGISTER) else: if executable: path = executable @@ -204,10 +206,7 @@ def _unregister(self, cls: Type, force: bool = False) -> None: table = [ t[:2] for t in RegistryEntries( - cls, - serverdll=self._serverdll, - frozen=self._frozen, - frozendllhandle=self._frozendllhandle, + cls, frozen=self._frozen, frozendllhandle=self._frozendllhandle ) ] # only unique entries @@ -240,9 +239,8 @@ def _unregister(self, cls: Type, force: bool = False) -> None: _debug("Done") -def _get_serverdll() -> str: +def _get_serverdll(handle: Optional[int]) -> str: """Return the pathname of the dll hosting the COM object.""" - handle = getattr(sys, "frozendllhandle", None) if handle is not None: return GetModuleFileName(handle, 260) return _ctypes.__file__ @@ -253,12 +251,10 @@ def __init__( self, cls, *, - serverdll: Optional[str] = None, frozen: Optional[str] = None, frozendllhandle: Optional[int] = None, ) -> None: self._cls = cls - self._serverdll = serverdll self._frozen = frozen self._frozendllhandle = frozendllhandle @@ -358,9 +354,12 @@ def __iter__(self) -> Iterator[Tuple[int, str, str, str]]: # Register InprocServer32 only when run from script or from # py2exe dll server, not from py2exe exe server. if inprocsvr_ctx and self._frozen in (None, "dll"): - if self._serverdll is None: - raise TypeError("'serverdll' is not specified.") - yield (HKCR, rf"CLSID\{reg_clsid}\InprocServer32", "", self._serverdll) + yield ( + HKCR, + rf"CLSID\{reg_clsid}\InprocServer32", + "", + _get_serverdll(self._frozendllhandle), + ) # only for non-frozen inproc servers the PythonPath/PythonClass is needed. if ( self._frozendllhandle is None diff --git a/comtypes/test/test_server_register.py b/comtypes/test/test_server_register.py index 5640ba47..b14c371d 100644 --- a/comtypes/test/test_server_register.py +++ b/comtypes/test/test_server_register.py @@ -191,15 +191,13 @@ def test_calls_cls_unregister(self): class Test_get_serverdll(ut.TestCase): def test_nonfrozen(self): - self.assertEqual(_ctypes.__file__, _get_serverdll()) + self.assertEqual(_ctypes.__file__, _get_serverdll(None)) @mock.patch.object(register, "GetModuleFileName") - @mock.patch.object(register, "sys") - def test_frozen(self, _sys, GetModuleFileName): - handle, dll_path = 1234, r"path\to\frozendll" - _sys.frozendllhandle = handle + def test_frozen(self, GetModuleFileName): + handle, dll_path = 1234, r"path\to\frozen.dll" GetModuleFileName.return_value = dll_path - self.assertEqual(r"path\to\frozendll", _get_serverdll()) + self.assertEqual(dll_path, _get_serverdll(handle)) (((hmodule, maxsize), _),) = GetModuleFileName.call_args_list self.assertEqual(handle, hmodule) self.assertEqual(260, maxsize) @@ -318,29 +316,16 @@ class Cls: _reg_clsid_ = reg_clsid _reg_clsctx_ = reg_clsctx - serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" full_classname = f"{__name__}.Cls" expected = [ (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", serverdll), + (HKCR, inproc_srv_sub, "", _ctypes.__file__), (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), ] - self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) - - def test_fails_inproc_server(self): - reg_clsid = GUID.create_new() - reg_clsctx = comtypes.CLSCTX_INPROC_SERVER - - class Cls: - _reg_clsid_ = reg_clsid - _reg_clsctx_ = reg_clsctx - - with self.assertRaises(TypeError) as e: - list(RegistryEntries(Cls)) - self.assertEqual(str(e.exception), "'serverdll' is not specified.") + self.assertEqual(expected, list(RegistryEntries(Cls))) def test_inproc_server_reg_threading(self): reg_clsid = GUID.create_new() @@ -352,18 +337,17 @@ class Cls: _reg_threading_ = reg_threading _reg_clsctx_ = reg_clsctx - serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" full_classname = f"{__name__}.Cls" expected = [ (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", serverdll), + (HKCR, inproc_srv_sub, "", _ctypes.__file__), (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), (HKCR, inproc_srv_sub, "ThreadingModel", reg_threading), ] - self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) + self.assertEqual(expected, list(RegistryEntries(Cls))) def test_reg_typelib(self): reg_clsid = GUID.create_new() @@ -399,7 +383,6 @@ class Cls: _reg_desc_ = reg_desc _reg_clsctx_ = reg_clsctx - serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" local_srv_sub = rf"{clsid_sub}\LocalServer32" @@ -414,37 +397,23 @@ class Cls: (HKCR, rf"{reg_novers_progid}\CurVer", "", reg_progid), (HKCR, rf"{reg_novers_progid}\CLSID", "", str(reg_clsid)), (HKCR, local_srv_sub, "", f"{sys.executable} {__file__}"), - (HKCR, inproc_srv_sub, "", serverdll), + (HKCR, inproc_srv_sub, "", _ctypes.__file__), (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), (HKCR, inproc_srv_sub, "ThreadingModel", reg_threading), (HKCR, rf"{clsid_sub}\Typelib", "", libid), ] - self.assertEqual(expected, list(RegistryEntries(Cls, serverdll=serverdll))) + self.assertEqual(expected, list(RegistryEntries(Cls))) class Test_Frozen_RegistryEntries(ut.TestCase): - @mock.patch.object(register, "sys") - def test_local_dll(self, _sys): - _sys.mock_add_spec(["executable"]) - _sys.executable = sys.executable - reg_clsid = GUID.create_new() - reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER + SERVERDLL = r"my\target\server.dll" - class Cls: - _reg_clsid_ = reg_clsid - _reg_clsctx_ = reg_clsctx + # We do not test the scenario where `frozen` is `'dll'` but + # `frozendllhandle` is `None`, as it is not a situation + # we anticipate. - clsid_sub = rf"CLSID\{reg_clsid}" - expected = [ - (HKCR, clsid_sub, "", ""), - (HKCR, rf"{clsid_sub}\LocalServer32", "", sys.executable), - ] - self.assertEqual(expected, list(RegistryEntries(Cls, frozen="dll"))) - - @mock.patch.object(register, "sys") - def test_local_frozendllhandle(self, _sys): - _sys.mock_add_spec([]) + def test_local_dll(self): reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER @@ -452,26 +421,28 @@ class Cls: _reg_clsid_ = reg_clsid _reg_clsctx_ = reg_clsctx + # In such cases, the server does not start because the + # InprocServer32/LocalServer32 keys are not registered. expected = [(HKCR, rf"CLSID\{reg_clsid}", "", "")] entries = RegistryEntries(Cls, frozen="dll", frozendllhandle=1234) self.assertEqual(expected, list(entries)) - @mock.patch.object(register, "sys") - def test_inproc_windows_exe(self, _sys): - _sys.mock_add_spec([]) + def test_local_windows_exe(self): reg_clsid = GUID.create_new() - reg_clsctx = comtypes.CLSCTX_INPROC_SERVER + reg_clsctx = comtypes.CLSCTX_LOCAL_SERVER class Cls: _reg_clsid_ = reg_clsid _reg_clsctx_ = reg_clsctx - expected = [(HKCR, rf"CLSID\{reg_clsid}", "", "")] + expected = [ + (HKCR, rf"CLSID\{reg_clsid}", "", ""), + (HKCR, rf"CLSID\{reg_clsid}\LocalServer32", "", sys.executable), + ] self.assertEqual(expected, list(RegistryEntries(Cls, frozen="windows_exe"))) - @mock.patch.object(register, "sys") - def test_inproc_dll_frozendllhandle_clsid_to_class(self, _sys): - _sys.mock_add_spec([]) + @mock.patch.object(register, "_get_serverdll", return_value=SERVERDLL) + def test_inproc_dll_nonempty_clsid_to_class(self, get_serverdll): reg_clsid = GUID.create_new() reg_clsctx = comtypes.CLSCTX_INPROC_SERVER @@ -479,50 +450,21 @@ class Cls: _reg_clsid_ = reg_clsid _reg_clsctx_ = reg_clsctx - serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" expected = [ (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", serverdll), + (HKCR, inproc_srv_sub, "", self.SERVERDLL), ] with mock.patch.dict(comtypes.server.inprocserver._clsid_to_class): comtypes.server.inprocserver._clsid_to_class.update({5678: Cls}) - entries = RegistryEntries( - Cls, serverdll=serverdll, frozen="dll", frozendllhandle=1234 - ) + entries = RegistryEntries(Cls, frozen="dll", frozendllhandle=1234) self.assertEqual(expected, list(entries)) + get_serverdll.assert_called_once_with(1234) - @mock.patch.object(register, "sys") - def test_inproc_dll(self, _sys): - _sys.mock_add_spec(["modules"]) - _sys.modules = sys.modules - reg_clsid = GUID.create_new() - reg_clsctx = comtypes.CLSCTX_INPROC_SERVER - - class Cls: - _reg_clsid_ = reg_clsid - _reg_clsctx_ = reg_clsctx - - serverdll = r"my\target\server.dll" - clsid_sub = rf"CLSID\{reg_clsid}" - inproc_srv_sub = rf"{clsid_sub}\InprocServer32" - full_classname = f"{__name__}.Cls" - expected = [ - (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", serverdll), - (HKCR, inproc_srv_sub, "PythonClass", full_classname), - (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), - ] - self.assertEqual( - expected, list(RegistryEntries(Cls, serverdll=serverdll, frozen="dll")) - ) - - @mock.patch.object(register, "sys") - def test_inproc_dll_reg_threading(self, _sys): - _sys.mock_add_spec(["modules"]) - _sys.modules = sys.modules + @mock.patch.object(register, "_get_serverdll", return_value=SERVERDLL) + def test_inproc_reg_threading(self, get_serverdll): reg_clsid = GUID.create_new() reg_threading = "Both" reg_clsctx = comtypes.CLSCTX_INPROC_SERVER @@ -532,17 +474,20 @@ class Cls: _reg_threading_ = reg_threading _reg_clsctx_ = reg_clsctx - serverdll = r"my\target\server.dll" clsid_sub = rf"CLSID\{reg_clsid}" inproc_srv_sub = rf"{clsid_sub}\InprocServer32" full_classname = f"{__name__}.Cls" expected = [ (HKCR, clsid_sub, "", ""), - (HKCR, inproc_srv_sub, "", serverdll), + (HKCR, inproc_srv_sub, "", self.SERVERDLL), + # 'PythonClass' and 'PythonPath' are not required for + # frozen inproc servers. This may be bugs but they do + # not affect the server behavior. (HKCR, inproc_srv_sub, "PythonClass", full_classname), (HKCR, inproc_srv_sub, "PythonPath", os.path.dirname(__file__)), (HKCR, inproc_srv_sub, "ThreadingModel", reg_threading), ] self.assertEqual( - expected, list(RegistryEntries(Cls, serverdll=serverdll, frozen="dll")) + expected, list(RegistryEntries(Cls, frozen="dll", frozendllhandle=1234)) ) + get_serverdll.assert_called_once_with(1234) From db816ef81fb81345329b8eafb9619637c60bbb7c Mon Sep 17 00:00:00 2001 From: junkmd Date: Wed, 8 Jan 2025 08:46:09 +0900 Subject: [PATCH 11/11] Fix a type annotation. --- comtypes/server/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comtypes/server/register.py b/comtypes/server/register.py index b4f68ac0..c8a4e827 100644 --- a/comtypes/server/register.py +++ b/comtypes/server/register.py @@ -249,7 +249,7 @@ def _get_serverdll(handle: Optional[int]) -> str: class RegistryEntries(object): def __init__( self, - cls, + cls: Type, *, frozen: Optional[str] = None, frozendllhandle: Optional[int] = None,