Third party code is modifying the FPU control word
The introduction - the long and boring part
(The question is at the end)
I am getting severe head aches over a third party COM component that keeps changing the FPU control word.
My development environment is Windows and Visual C++ 2008. The normal FPU control word specifies that no exceptions should be thrown during various conditions. I have verified this with both looking at the _CW_DEFAULT
macro found in float.h
, as well as looking at the control word in the debugger at startup.
Everytime I make a call into the COM object, the control word is modified upon return. This is easy to defend against. I simply reset the control word, and all is good. The problem is when the COM component starts calling my event sink. I can protect my code by reseting the control word as soon as I receive the event call, but I can't do anything as soon as I return from the event call.
I don't have the source for this COM component, but I am in contact with the author. The responses I have had from him has been "Huh?". I don't think he has the slightest clue what I'm talking about, so I fear I have to do something about this myself. I believe that his runtime (I think it's either Delphi or Borland C++, because the DLL is full of symbol names, all starting with capital T) , or some other third party code he's using, that's causing the problem. I don't think his code explicitly modifies the FPU control word.
So, what can I do? From a business point of view, it is imperative to use this third party component. From a technical point of view, I could ditch it, and implement the communication's protocol myself. However, that would be really expensive, as this protocol involves handling credit card transactions. We don't want to take on the liability.
I desperately need a hack-around, or some useful information about FPU settings in Borland products that I can pass along to the author of the component.
The questions
Is there anything I can do? I don't think the component author has what it takes to fix it (by judging from his rather clueless responses).
I have been toying with the idea of installing my own exception handler, in which I just reset the control word in the handler, and tell Windows to continue executing. I tried installing the handler with SetUnhandledExceptionFilter()
, but for some reason, the exceptions are not caught.
- Why aren't I catching the exceptions?
- If I succeed with catching FPU exceptions, resetting the FPU control word, and just let the execution continue as nothing has happened - are all bets off then?
Update
I would like to thank everyone for their suggestions. I have sent the author instructions on what he can do to make life easier for not just me, but many other clients of his code. I suggested to him that he should sample the FPU control word at DllMain(DLL_PROCESS_ATTACH)
, and save the control word for later, so that he can reset FPU CW before calling my event handlers, and returning from my calls.
For now, I have a hack-around if anyone is interested. The hack-around is potentially a bad one, because I don't know what it'll do to his code. I have received confirmation earlier that he does not use any floating point numbers in his code, so this should be safe, barring some third party code he uses, that relies on FPU exceptions.
The two modi开发者_C百科fications I have made to my app:
- Wrap my message pump
- Install a window hook (
WH_CALLWNDPROC
) to catch the corner cases where the message pump is bypassed
In both instances, I check if the FPU CW has changed. If it has, I reset it to _CW_DEFAULT
.
I think your diagnosis that the component is written in an Embarcadero product is very likely to be true. Delphi's runtime library does indeed enable floating point exceptions, same for C++ Builder.
One of the nice things about Embarcaderos tools is that floating point errors get converted into language exceptions which makes numerical coding a lot easier. That is going to be of little consolation to you!
This entire area is a colossal PITA. There are no rules whatsoever regarding the FP controls word. It's a total free-for-all.
I don't believe that catching unhandled exceptions isn't going to get the job done because the MS C++ runtime will presumably already be catching these exceptions, but I'm no expert in that area and I may be wrong.
I believe that your only realistic solution is to set the FPU to what you want it to be whenever execution arrives in your code, and restore it when execution leaves your code. I don't know enough about COM event sinks to understand why they present an obstacle to doing this.
My product includes a DLL implemented in Delphi and I suffer from the reverse problem. Mostly the clients that call in have an FPU control word that disables exceptions. The strategy we adopt is to remember the 8087CW on entry, set it to the standard Delphi CW before executing code, and then restore it at the exit point. We take care to deal with callbacks too by restoring the caller's 8087CW before making the callback. This is a plain DLL rather than a COM object so it's probably a bit simpler.
If you decide to attempt to get the COM supplier to modify their code then they need to call the Set8087CW()
function.
However, since there are no rules to the game, I believe that the COM object vendor would be justified in refusing to change their code and to put the onus back on you.
Sorry if this is not a 100% conclusive answer, but I couldn't get all these thoughts into a comment!
Although FP control word is per-thread, dllmain functions are called when new threads are created, i don't think you can avoid this short of going to a new process.
I suggest you spin-off a new process to run the COM and chat with the process with your favorite inter-process communication method (e.g. windows message, out-of-proc COM, named pipe, socket, etc). In this way the COM server is free to do all kinds of damage (including crashing itself) without bring your host process down.
Another idea is to write a DLL whose sole purpose is to reset the FPU in its DllMain and load it immediately after the offending DLL. Windows is probably using the loading order to call DllMain when new threads are created, including threads created by the COM server. Note this depends on Windows's internal behavior. Also the COM server may actually depends on fp exceptions since it enables them. Disabling FP exceptions may causes the COM server to behave unexpectedly.
精彩评论