1

I have an ActiveX (COM) DLL that makes windows system calls (such as ReadFile() and WriteFile()). My GUIs (in Python or C#) create an instance of the DLL in the main GUI thread. However, in order to make calls to it from threads, a new instance of the DLL must be created in each thread, regardless of using C# or Python. (As a side note, the original instance could be called from a thread in C#, but this blocks the main thread; doing this in Python crashes the GUI.) Is there any way to avoid creating a new instance of the DLL in threads?

The reason why using the original DLL instance is desired: The DLL allows connection to a HID microcontroller. The DLL provides an option to allow only one exclusive handle to the microcontroller. If the GUI designer chooses this option (which is necessary in certain situations), the second DLL instance would not work as desired, since only one instance of the DLL could make the connection.

tosa
  • 411
  • 1
  • 3
  • 23
  • What's your issue with creating a new instance in a thread? Just to avoid it? – Quintium Jun 07 '12 at 21:50
  • Good question; I updated the original question since it is an important detail. – tosa Jun 07 '12 at 23:44
  • ActiveX can be only called by a GUI thread, and one that also STA. [See this post](http://stackoverflow.com/questions/5497796/background-worker-issue.) You'll need to marshal your calls from background threads to the GUI thread, in order to use the ActiveX object. – Chris O Jun 12 '12 at 02:12
  • I believe that I can, and in fact, do, create the ActiveX object in threads other than the main thread: "However, in order to make calls to it from threads, a new instance of the DLL must be created in each thread, regardless of using C# or Python". However, my objective is to NOT do that. – tosa Jun 12 '12 at 04:44
  • Could you explain what you mean by "However, in order to make calls to it from threads, a new instance of the DLL must be created in each thread" in more details include why this is the case? – Danny Varod Jun 17 '12 at 12:52
  • @Danny Varod, please check the orig question. If I don't make the new DLL object (in other threads where I want to call the DLL), then C# blocks or Python crashes. – tosa Jun 18 '12 at 20:37
  • Have you tried dispatching to the main thread from the caller thread when you reach the ActiveX area? – Danny Varod Jun 18 '12 at 20:44
  • Not sure what you mean by dispatching to the main thread, but the reason for the thread is to not block the main thread, since there is a lengthy function in the DLL that is being called. – tosa Jun 18 '12 at 22:17

2 Answers2

1

I haven't worked with Phyton, but for C# I would suggest creating a helper class that contains the ActiveX as a static public property. Have the main thread create the ActiveX and then from there all threads access it as needed.

Quintium
  • 499
  • 5
  • 22
1

When you make an ActiveX/COM component you can specify threading model for your component, it could be e.g. "compartmentalized". Depending on which you choose ActiveX/COM takes care of serializing requests.

If you "open" and ActiveX/COM component multiple times (depending on threading model?) only one instance is actually created.

I'm assuming you use win32com.client.Dispatch(".") to "open" your ActiveX/COM component.

Also, don't forget pythoncom.CoInitialize() and CoUninitialize() pair of calls.

Google on what those actually do.

If you can't change given ActiveX/COM component and its threading model is unacceptable, you can wrap all "outbound" calls in one dedicated Python thread with monitor "interface."

Here's an outline for what code I wrote once faced with similar situation:

class Driver(threading.Thread):
    quit = False  # graceful exit
    con = None
    request = None
    response = None

    def __init__(self, **kw):
        super(Driver, self).__init__(**kw)
        self.setDaemon(True)  # optional, helps termination
        self.con = threading.Condition()
        self.request = None
        self.response = None

    def run(self):
        pythoncom.CoInitialize()
        handle = win32com.client.Dispatch("SomeActiveX.SomeInterface")
        try:
            with self.con:
                while not self.quit:
                    while not self.request: self.con.wait()                    # wait for work
                    method, args = self.request
                    try: self.response = getattr(handle, method)(*args), None  # buffer result
                    except Exception, e: self.response = None, e               # buffer exception
                    self.con.notifyAll()                                       # result ready
        finally:
            pythoncom.CoUninitialize()

    def call(method, *args):
        with self.con:
            while self.request: self.con.wait()       # driver is busy
            self.request = method, args
            self.con.notifyAll()                      # driver can start
            while not self.response: self.con.wait()  # wait for driver
            rv, ex = self.response
            self.request = self.response = None       # free driver
            self.con.notifyAll()                      # other clients can continue
            if ex: raise ex
            else: return rv
Dima Tisnek
  • 11,241
  • 4
  • 68
  • 120
  • This is a good idea and I am testing it out. However, there is a call to RegisterDeviceNotification() in one function in the DLL and it hangs my GUI when called. If called from the GUI thread, then it works fine. I am not sure why calling it from another thread should cause any issues since it is receiving the form's handle just as it would when called directly from the GUI thread.
    Also, I have access to modify the DLL. It's made STA; there's no option for MTA. What is your suggestion to make the DLL behave as desired?
    – tosa Jun 15 '12 at 23:17