0

I am using this Python implementation of RTD server.

I get an error:

pywintypes.com_error: (-2147221008, 'CoInitialize has not been called.', None, None)

The error occurs when the excel type-libe call InvokeTypes(10, LCID, 1, (24, 0), (),).

I gather form this question that every time a thread wants to use COM, I should call CoInitialize(). Now I don't know why it is not implemented in the code than. Anyway I tried to add this inside the Update function, which start a timer thread:

def Update(self):
    # Get our wake-up thread ready...
    pythoncom.CoUninitialize()
    pythoncom.CoInitialize()
    self.ticker = threading.Timer(self.INTERVAL, self.Update)
    try:
        # Check if any of our topics have new info to pass on
            for topic in self.topics.values():
                topic.Update(self)
                if topic.HasChanged():
                    refresh = True
        if len(self.topics):
            refresh = False
                topic.Reset()

            if refresh:
                self.SignalExcel()
    finally:
        self.ticker.start()  # Make sure we get to run again

The uninitialize is called based on the documentation saying import pythoncom alone makes the first initialization.

Perhaps I should do the initialization somewhere else in the code?

I realize the whole complexity of the problem is not explained in this post, so please ask questions.

The rules for which object an apartment lives in are slightly more complex. If the COM object in question is implemented in any way other than an InProc DLL (for example, a LocalServer or RemoteServer EXEbased object), the question becomes moot, as the object is running in a separate process, and therefore can not be in the same apartment. For DLL-implemented objects, the apartment is determined both by the apartment of the thread creating the object and the threading models actually supported by the object.

Borut Flis
  • 15,715
  • 30
  • 92
  • 119
  • Don't call CoUninitialize() there. IFF some other code called CoInitialize(), then they're responsible for calling the cleanup function. You're calling CoInitialize(), but it's not in the new thread. It's in the thread (main thread?) that is creating the new thread. But the call needs to be from a point of execution inside the new thread...as well as CoUninitialize() when you're done. – Joseph Willcoxson Apr 02 '21 at 13:54
  • self.ticker = threading.Timer(self.INTERVAL, self.Update) calls the function recursively after the interval so the call to Coinitialize is inside the thread. – Borut Flis Apr 03 '21 at 12:27
  • When reading the book Python Programming on win32 I found out that if the COM is not ran through InProc, that somehow changes the thread appratment. I added a quotation at the end. – Borut Flis Apr 03 '21 at 13:08
  • Not sure what “if the COM is not ran InProc... changes the thread apartment” could mean. Can you quote the exact sentence? I think you misunderstood what it said and when you paraphrased it became part-nonsense. – Euro Micelli Apr 04 '21 at 00:25
  • Hey, @EuroMicelli I added the quotation, I forgot to do it before. Yes my knowledge on this topic is very limited. I think this might be a useful hint because I had trouble running my COM object through InProc, so I am using LocalServer. Furthermore, the code I am using should work, so I am looking for something that might be wrong in the environment. – Borut Flis Apr 05 '21 at 07:43
  • 1
    Right, that makes more sense. But the paragraph you quote is essentially irrelevant to your question. – Euro Micelli Apr 05 '21 at 19:42
  • 1
    It is not quite right that “every time a thread wants to use COM it needs to call CoInitialize()”. Rather, every thread that needs to use COM must call CoInitialize() before using it, and call CoUninitialize() after done. Note the difference between “using COM (the infrastructure)” and “using *a* COM object”. Most commonly a thread using COM will make exactly one call to CoInitialize() early on, and exactly one call to CoUninitialize() shortly before terminating. – Euro Micelli Apr 05 '21 at 19:54
  • 1
    Unfortunately I don’t know enough about COM in Python to write a full answer; but I’ll give you some pointers. If you are getting that message then definitely CoInitialize() has not been called in the thread that issues the error. In manually created threads, that is up to your code to do. However in threads managed by some environment things get more complicated, because your code doesn’t really own the thread. For example, a thread from a thread pool is owned by the pool, not you. It’s very dangerous to call CoInitialize/CoUninitialize in such a thread without direct guidance from docs... – Euro Micelli Apr 05 '21 at 20:00
  • 1
    I don’t know whether the timer triggers in a separate thread but typically such things are done via messages to the same thread. I really don’t know about Python threading, please research that. My gut instinct is that the timer runs in the same thread, and that you should call CoInitialize near the *beginning* of your *program*, then call CoUninitialize shortly before shutting down the program. But that is not based on actual Python COM knowledge. Also, don’t be too afraid of “leaving COM ‘on’ for long periods”; it’s a library not a light bulb. – Euro Micelli Apr 05 '21 at 20:06
  • 1
    Also, whether the COM object is in-process or Out-of-Process is largely irrelevant to when you call CoInitialize/CoUninitialize. – Euro Micelli Apr 05 '21 at 20:11
  • @EuroMicelli Thank you for the ideas. Something I found interesting in your comment, when you said "every thread that needs to use COM must call CoInitialize()". To clarify, the server where the code is executed is called by excel, than the error occurs where the server calls excel back via COM. So I supose the thread needs to call coinitialize to call Excel. – Borut Flis Apr 08 '21 at 21:31
  • Oh so it’s a callback to another thread in your process. Yes, the new thread running the callback must call CoInitialize() (but really, use CoInitializeEx(NULL, COINIT_APARTMENTHREADED) ). But also, be aware you cannot pass any COM references to the new thread directly, including a reference to the Excel object itself. You must “marshal” the reference between threads. Marshaling is a long topic; start maybe with https://devblogs.microsoft.com/oldnewthing/20151020-00/?p=91321 and https://devblogs.microsoft.com/oldnewthing/20151021-00/?p=91311 – Euro Micelli Apr 08 '21 at 22:12
  • Thanks for the tip. I tried to add CoInitializeEx(NULL, COINIT_APARTMENTHREADED) ). I got an error: pywintypes.com_error: (-2147417842, 'The application called an interface that was marshalled for a different thread.', None, None) . I will look into the text you send about marshalling. – Borut Flis Apr 11 '21 at 18:12
  • I read that appartments can be either single or multi threaded. Is it possible that the appartment of my COM object is single-threaded and that I need reconfigure that? – Borut Flis Apr 12 '21 at 12:13

0 Answers0