Office converter DLLs and thread safety
-
Hi all, I've been experimenting with the converter DLLs of Microsoft Office (under
HKLM\Software\Microsoft\Shared Tools\Text Converters\Import
). In a single-threaded environment, everything runs ok. But I'm running into difficulties when calling the exported conversion functions likeForeignToRtf32
in the multi-threaded version of my program, even if the libraries are loaded within the context of the calling thread. Does anyone know about the thread safety of these converters? -
Hi all, I've been experimenting with the converter DLLs of Microsoft Office (under
HKLM\Software\Microsoft\Shared Tools\Text Converters\Import
). In a single-threaded environment, everything runs ok. But I'm running into difficulties when calling the exported conversion functions likeForeignToRtf32
in the multi-threaded version of my program, even if the libraries are loaded within the context of the calling thread. Does anyone know about the thread safety of these converters?Mattias G wrote:
I'm running into difficulties when calling the exported conversion functions like
ForeignToRtf32
in the multi-threaded version of my program, even if the libraries are loaded within the context of the calling thread. Does anyone know about the thread safety of these converters?If I understand your question/problem correctly, this has nothing to do with the converter DLLs. What do you mean by "loaded within the context of the calling thread"? I suspect you mean that you call
::LoadLibrary()
for each thread that uses the library and you assume that it somehow will make calls to the DLL thread safe. Calling::LoadLibrary()
will load the desired DLL and map into the address space of the process the first time it is called. Subsequent calls to::LoadLibrary()
will only increment the reference count for the DLL in the process, which means that the code and variables will be shared among the calling threads. Calling::FreeLibrary()
will decrement the reference count. Read more here[^]."It's supposed to be hard, otherwise anybody could do it!" - selfquote
"High speed never compensates for wrong direction!" - unknown -
Mattias G wrote:
I'm running into difficulties when calling the exported conversion functions like
ForeignToRtf32
in the multi-threaded version of my program, even if the libraries are loaded within the context of the calling thread. Does anyone know about the thread safety of these converters?If I understand your question/problem correctly, this has nothing to do with the converter DLLs. What do you mean by "loaded within the context of the calling thread"? I suspect you mean that you call
::LoadLibrary()
for each thread that uses the library and you assume that it somehow will make calls to the DLL thread safe. Calling::LoadLibrary()
will load the desired DLL and map into the address space of the process the first time it is called. Subsequent calls to::LoadLibrary()
will only increment the reference count for the DLL in the process, which means that the code and variables will be shared among the calling threads. Calling::FreeLibrary()
will decrement the reference count. Read more here[^]."It's supposed to be hard, otherwise anybody could do it!" - selfquote
"High speed never compensates for wrong direction!" - unknownNo, I wasn't hoping that calling
LoadLibrary
from within the thread (that later will callForeignToRtf32
) would in some magical way make the DLLs thread safe. Did put some more effort into this, but couldn't find any real pattern. Did get rid of one exception type by balancing the calls toOleInitialize
andOleUninitialize
more carefully for each thread. Still getting a lot of exceptions of types0x800401FD
CO_E_OBJNOTCONNECTED
and0x8001010E
RPC_E_WRONG_THREAD
(though I'm not using any COM interfaces explicitly in my application). So back to my original question: Does anyone know anything about the thread safety of these libraries? Thanks -
No, I wasn't hoping that calling
LoadLibrary
from within the thread (that later will callForeignToRtf32
) would in some magical way make the DLLs thread safe. Did put some more effort into this, but couldn't find any real pattern. Did get rid of one exception type by balancing the calls toOleInitialize
andOleUninitialize
more carefully for each thread. Still getting a lot of exceptions of types0x800401FD
CO_E_OBJNOTCONNECTED
and0x8001010E
RPC_E_WRONG_THREAD
(though I'm not using any COM interfaces explicitly in my application). So back to my original question: Does anyone know anything about the thread safety of these libraries? ThanksMattias G wrote:
No, I wasn't hoping that calling
LoadLibrary
from within the thread (that later will callForeignToRtf32
) would in some magical way make the DLLs thread safe.Ok, I just couldn't find any other explanation to what you meant by "loading a DLL within the context of a thread". I interpreted your text as, whatever you meant by it, you thought it would prevent the problems you got when trying to use multiple threads when accessing the converter DLLs. But never mind. The only thing important is that you only need to call
::LoadLibrary()
once for any DLL, subsequent calls will do nothing in practice. I have never used the DLLs you are trying to use, nor have I found anything useful trying to google it. So I have no idea what kind of functionality the DLLs expose, how to use them or what their function prototypes may look like. But that may not be necessary....Mattias G wrote:
Still getting a lot of exceptions of types
0x800401FD
CO_E_OBJNOTCONNECTED
and0x8001010E
RPC_E_WRONG_THREAD
(though I'm not using any COM interfaces explicitly in my application).Ok, so that means the DLLs uses COM in order to perform its
ForeignToRtf32()
. When you use COM from different threads you have to initialize COM for every thread that makes use of the COM library by calling e.g.::CoInitialize()
or one of its equivalents. Failure to do so may seem to work, but will cause unpredictable errors being very hard to realize that they originate from not initializing COM. In addition you have to have a message pump for every thread that creates a COM server. Initializing COM for a thread is called setting up an "apartment" and COM interfaces are not allowed to cross apartment boundaries without being properly marshalled by using e.g.::CoMarshalInterThreadInterfaceInStream()
or the GlobalInterfaceTable(GIT). Using a COM interface from an apartment without proper marshalling will result in the errorRPC_E_WRONG_THREAD
. Since you do not use any COM interfaces in your application I assume that the converter DLLs create the necessary COM servers "under the hood". This probably means that you cannot possibly marshal any COM interface to any other thread than the one you have used to do whatever initialization needed for the converter DLLs, which should be ex -
Mattias G wrote:
No, I wasn't hoping that calling
LoadLibrary
from within the thread (that later will callForeignToRtf32
) would in some magical way make the DLLs thread safe.Ok, I just couldn't find any other explanation to what you meant by "loading a DLL within the context of a thread". I interpreted your text as, whatever you meant by it, you thought it would prevent the problems you got when trying to use multiple threads when accessing the converter DLLs. But never mind. The only thing important is that you only need to call
::LoadLibrary()
once for any DLL, subsequent calls will do nothing in practice. I have never used the DLLs you are trying to use, nor have I found anything useful trying to google it. So I have no idea what kind of functionality the DLLs expose, how to use them or what their function prototypes may look like. But that may not be necessary....Mattias G wrote:
Still getting a lot of exceptions of types
0x800401FD
CO_E_OBJNOTCONNECTED
and0x8001010E
RPC_E_WRONG_THREAD
(though I'm not using any COM interfaces explicitly in my application).Ok, so that means the DLLs uses COM in order to perform its
ForeignToRtf32()
. When you use COM from different threads you have to initialize COM for every thread that makes use of the COM library by calling e.g.::CoInitialize()
or one of its equivalents. Failure to do so may seem to work, but will cause unpredictable errors being very hard to realize that they originate from not initializing COM. In addition you have to have a message pump for every thread that creates a COM server. Initializing COM for a thread is called setting up an "apartment" and COM interfaces are not allowed to cross apartment boundaries without being properly marshalled by using e.g.::CoMarshalInterThreadInterfaceInStream()
or the GlobalInterfaceTable(GIT). Using a COM interface from an apartment without proper marshalling will result in the errorRPC_E_WRONG_THREAD
. Since you do not use any COM interfaces in your application I assume that the converter DLLs create the necessary COM servers "under the hood". This probably means that you cannot possibly marshal any COM interface to any other thread than the one you have used to do whatever initialization needed for the converter DLLs, which should be exA great step forward was to call
CoUnitialize
for each thread. Tried both with and without a message pump (withMsgWaitForMultipleObjects
), the message pump version produced less exceptions, but the actual result of the conversion is the same. I'm really close to giving up on this, the combination of a under-documented API and multi-threading is a bit much :-) Currently, I have a pool of 4 threads for different converters (not only the Office ones, but for HTML and various other formats who are unproblematic). In case of the Office DLL converters, each thread in the pool kicks off another separate thread doing the actual conversion (the only way I could figure out to put a message pump there, and this is how it's done in the MFC/ole/Wordpad sample of MSVC). Everything works fine (with no exceptions at all and correct conversion results) if I block reentry at the call toForeignToRtf32
with aCRITICAL_SECTION
mutex. So if the application should do something with let's say *.docx files only, the performance boost from multi-threading is ... exactly zero (!) Functional, but what a waste of effort... Thanks anyhow for the interesting reading of marshalling!