-
-
Notifications
You must be signed in to change notification settings - Fork 30.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace ctypes.DllGetClassObject and remove DllCanUnloadNow #127369
Comments
I suspect these may be here because No doubt it's a useful and important hack at some point, but without a more concrete use case they're probably not so necessary. Changing default behaviour to not import If |
For those interested in this discussion, I would like to introduce some technical references related to A notable reference book on OLE and related COM topics is "Inside OLE, 2nd Edition, Kraig Brockschmidt, Microsoft Press, 1995, ISBN: 1-55615-843-2". Regarding
Regarding
|
Below is documentation for using ¹⁾ The |
This comment was marked as outdated.
This comment was marked as outdated.
Here are my current thoughts on this:
I am not against changing However, just as COM interfaces can be implemented by projects other than I think the hooks defined in Regarding hook handling, I propose creating a relationship similar to def DllCanUnloadNow():
return __dll_can_unload_now__()
def __dll_can_unload_now__():
...
def DllGetClassObject(rclsid, riid, ppv):
return __dll_get_class_object__(rclsid, riid, ppv)
def __dll_get_class_object__(rclsid, riid, ppv):
... The main challenge is that we have yet to find modern use cases for implementing COM servers using To make production changes, I believe comprehensive testing that covers actual use cases is essential. The If agreeable, I plan to reach out to them and invite them to participate in this discussion to provide their insights and cooperation. |
Agreed. There are no shortage of use cases for using COM servers, but very few that require implementing them (especially when they're then going to be activated through COM's global interfaces, as opposed to being directly passed in). I assume this only applies to in-process activation as well? If you are registering a COM server for out-of-proc activation then I'm pretty sure If anyone has any such cases for implementing/registering a COM server in Python, modern or legacy, it would be helpful to list them here. If we can't find sufficient modern cases to motivate this (and I emphasise modern here because we need to justify users who can update their CPython to a later version but somehow can't modify their own code or update other parts of their system - legacy cases where nobody has touched it in 20 years don't count, because you couldn't update CPython in that case), then I think deprecation and complete removal is on the table. |
But, it's not safe to unload
IMO, that would make sense if def __dll_get_class_object__(rclsid, riid, ppv):
return -2147221231 # CLASS_E_CLASSNOTAVAILABLE (There's a magic number because If we need something more complex than a replaceable hook, i was thinking about a list, named for example (We could instead make the list internal, and add a “register” function, but then we'd also need “unregister” and “clear” and so on -- a list sounds more convenient.) Unlike with |
I am one of the developers using comtypes to make servers rather than clients that @junkmd asked to weigh in on this discussion. I was going to say that I know absolutely nothing about this subject because I have only used local servers rather than inproc ones, but that felt a bit underwhelming, so I have done a bit of extra digging to try and understand at least a little bit that I might be able to contribute, apologies if none of this is new or interesting. Local servers made intuitive sense to me because registering them just sets the Python executable and a script to run when the server is created, but it didn't make sense to me how an inproc server could work - how would the interpreter get set up, etc? So I looked at the comtypes registration code for inproc servers here which requires a From somewhere in my distant memories that reminded me that comtypes had been associated with py2exe, so I checked that out and sure enough turned up this page on how to generate a COM DLL server. Some further digging through the code turned up the file I know that @theller was heavily involved in the early days of From my perspective, there doesn't seem to be any reason to keep these functions in I will confess that I personally have little appetite for taking on the responsibility of updating Py2exe to no longer depend on these functions from |
It seems that py2exe has special support for If it is, looks like it will call |
@encukou an inproc COM server is one which is contained in a DLL and so can be instantiated in the calling process, and COM objects so produced can be used directly via function pointers. In the more common case (at least in my experience) the COM server is a separate process and methods are called via RPC with Windows messages. There are different hooks for different purposes in the DLL. I think the most important point to emphasise is that the You can see here that the Py2exe DLL implementations of In fact, I think there is definitely a case to be made for moving all of |
Based on previous discussions, I think the ideal approach is to implement the hooks directly in freezing tools like Considering cases where implementing COM servers with Moreover, as seen in py2exe/py2exe#24 (and mhammond/pywin32#868), it seems that registering COM servers with It might also be worthwhile to invite the maintainers of |
I opened py2exe/py2exe#217. |
So, no one knows :) |
Thanks to @bennyrowland's insights and @forderud's improvements (enthought/comtypes#678), we were able to revive tests in I ran these revived tests using a Python build from #127766 branch. results of executing the unit tests
Notably, a |
@junkmd great job on getting those tests to run. I was very surprised to see the deprecation warning come up, so I have been digging through the code once again and it appears that I must have missed a possible way to register an InprocServer using When registering a Python COM object as Inproc Sorry for causing confusion with my previous claim that InprocServers would only work with Py2Exe. This unfortunately does reraise the question of what to do with these |
Thank you for your investigation.
Don't worry.
As you mentioned, including a DLL in the build of As an alternative, based on my earlier suggestion and encukou's advice, I currently think it would be better to implement a system where each project can optionally set hooks. if _os.name == "nt": # COM stuff
__dll_get_class_object_hooks__ = [] # or set()?
def DllGetClassObject(rclsid, riid, ppv):
if not __dll_get_class_object_hooks__:
return -2147221231 # CLASS_E_CLASSNOTAVAILABLE
results = {f(rclsid, riid, ppv) in f for __dll_get_class_object_hooks__}
if 0 in results:
return 0 # S_OK
if len(results) == 1:
return tuple(results)[0]
return -2147467259 # E_FAIL For example, a package like import ctypes
def dll_get_class_object_hook(rclsid: int, riid: int, ppv: int) -> int:
"""Returns S_OK, E_FAIL, or CLASS_E_CLASSNOTAVAILABLE.
See also https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-dllgetclassobject
"""
...
if sys.version_info >= (3, x):
ctypes.__dll_get_class_object_hooks__.append(dll_get_class_object_hook) Of course, this implementation is merely an example, and I have no objections if a better implementation or operation is found that removes unnecessary dependencies from the standard library and preserving the concepts of third-party projects. Also, I do NOT strongly desire to change the implementation of I appreciate everyone participating in this discussion. |
I'd be fine with
But what's not clear to me is if, with current If building a DLL is necessary, I think it's better to remove this functionality from If building a DLL is not necessary for the third-party library to provide a useful COM server, let's
|
If APIs are needed to implement, the COM specification has remained unchanged for decades since its inception, so once the I plan to spend my free time this weekend writing tests to implement, register, and unregister a COM server using |
@encukou The code in @junkmd the current I do not see how the hook mechanism can work though, because the current mechanism for creating an InprocServer looks like:
So there is no point at which @junkmd if you want to keep |
As I mentioned earlier, I attempted to write tests this weekend to implement, register, and unregister an in-process COM server using only First, I investigated the In my PoC repository, I defined a simple results of executing the unit tests
When Here, if I patch I believe it is necessary to perform actions similar to what If you have any ideas for an easier way to implement an in-process COM server, please feel free to let me know. |
Thank you for your detailed explanation. However, I want the functionality in That said, I also understand the challenges of continuing to maintain this functionality. |
As far as I can see, one way for CPython to avoid hardcoding However, that's a lot of new functionality. If we put that functionality in CPython, it would be hard to iterate on it -- the release schedule is slow, and if we find an issue, users wouldn't be able to easily update only the COM support part. |
Breaking the dependency on I have no objections to launching the Once It is worth noting that even if these hooks are removed from I also think that if users have the permissions to install Python, |
As far as I can tell, these functions are hooks: third-party code is meant to replace them.
Their implementation in
ctypes
(i.e. their default behaviour) is to import and call the same-named functions from a third-party library,comtypes.server.inprocserver
. This is not good.comtypes
should instead register their hook on import.Here's a possible plan to make the API boundary better without breaking users.
DllCanUnloadNow
While the Python interpreter is running, it is not safe to unload the shared library that contains
_ctypes
. Therefore:DllCanUnloadNow
exported from _ctypes should be changed to always returnS_FALSE
. We should change that now, without a deprecation period. (Note that thecomtypes
hook already does this.)comtypes.server.inprocserver
. I'm not sure about the necessary deprecation period, but I think that it should be a non-breaking change and can also be done immediately. Or is someone relying on it for side effects? O_oDllGetClassObject
This one, on the other hand, sounds like a useful hook. It also looks like an inprocess COM server need a special build so it's not useful to allow multiple hooks -- replacing a global one is enough. Is that so?
If yes:
ctypes.DllGetClassObject
(the default implementation) should raise aDeprecationWarning
. In about Python 3.18, it should be changed to do nothing, just, returnCLASS_E_CLASSNOTAVAILABLE
.comtypes
should be changed: on import, it should replacectypes.DllGetClassObject
with its own hook.This should ensure that old versions of
comtypes
still work as before (until after the deprecation period).Does that sound reasonable?
cc @junkmd
Linked PRs
The text was updated successfully, but these errors were encountered: