1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 // The purpose of the class in this file is to try to aid in detecting
34 // when multiple Coin DLLs are loaded into the same run-time process
35 // image under Windows.
36 //
37 // This is useful because linking with multiple instances of Coin, for
38 // instance both a release build (e.g. indirectly through SoWin or
39 // SoQt) and a debug build (e.g. from the application executable
40 // linking) at the same time, will cause hard to find errors.
41 //
42 // A typical symptom of this problem would be an assert-error from the
43 // constructor of an SoBase-derived class because of missing library
44 // initialization for one of the Coin-libraries in memory.
45 
46 // *************************************************************************
47 
48 #include "CoinStaticObjectInDLL.h"
49 
50 #include <cstdio>
51 #include <cstdlib>
52 
53 #ifdef HAVE_CONFIG_H
54 #include <config.h>
55 #endif // HAVE_CONFIG_H
56 
57 #ifdef HAVE_WINDOWS_H
58 #include <windows.h>
59 #endif // HAVE_WINDOWS_H
60 
61 #include <Inventor/SbTime.h>
62 
63 // *************************************************************************
64 
65 void * CoinStaticObjectInDLL::mutexhandle = NULL;
66 CoinStaticObjectInDLL * CoinStaticObjectInDLL::singleton = NULL;
67 
68 // *************************************************************************
69 
70 #ifndef HAVE_WIN32_API
71 
CoinStaticObjectInDLL(void)72 CoinStaticObjectInDLL::CoinStaticObjectInDLL(void) { assert(FALSE); }
~CoinStaticObjectInDLL()73 CoinStaticObjectInDLL::~CoinStaticObjectInDLL() { assert(FALSE); }
init(void)74 void CoinStaticObjectInDLL::init(void) { /* will be called, so don't assert */ }
75 // dummy, avoid linker errors
mutexName(void)76 SbString CoinStaticObjectInDLL::mutexName(void) { return SbString(""); }
activateMutex(void)77 SbBool CoinStaticObjectInDLL::activateMutex(void) {  assert(FALSE); return TRUE; }
deactivateMutex(void)78 void CoinStaticObjectInDLL::deactivateMutex(void) { assert(FALSE); }
79 
80 #else // HAVE_WIN32_API
81 
82 // FIXME: this check should be possible to turn off with an envvar, as
83 // it is sometimes not an indication of a problem to have multiple
84 // Coin instances in the same process -- for instance if Coin is part
85 // of a browser plug-in (or several plug-ins).  20041021 mortene.
86 //
87 // UPDATE 20041108 mortene: an idea for improvement, from kyrah: if
88 // each built Coin library had a unique key / ID, we could incorporate
89 // that into the check. That would be an improvement, as most errors
90 // of "multiple-instance-inclusion" is very likely that a debug build
91 // and a release build of Coin is loaded at the same time, typically
92 // because e.g. SoWin or SoQt was linked against coin2d.dll, while the
93 // app links against coin2.dll.
94 
95 // *************************************************************************
96 
97 // Only one of these objects should be allocated for the process. If
98 // two or more of them are it means that multiple instances of the
99 // Coin library is loaded for the same process image -- and we'll
100 // throw up the error message box.
101 
102 #if 1 // enable / disable the check -- provides a quick workaround in
103       // case someone runs into trouble with it.
104 static CoinStaticObjectInDLL dllobject;
105 #endif // enable / disable
106 
107 // *************************************************************************
108 
109 // Must use the C library getenv() calls below, not Coin's
110 // coin_getenv(), since this code is executed before SoDB::init().
111 
112 static SbBool
debug(void)113 debug(void)
114 {
115   static int dbg = -1;
116   if (dbg == -1) {
117     const char * env = getenv("COIN_DEBUG_STATICOBJECT");
118     dbg = (env && (atoi(env) > 0)) ? 1 : 0;
119   }
120   return dbg;
121 }
122 
123 static SbBool
runtime_disabled(void)124 runtime_disabled(void)
125 {
126   static int val = -1;
127   if (val == -1) {
128     const char * env = getenv("COIN_INSTANCEMUTEX_DISABLE");
129     val = (env && (atoi(env) > 0)) ? 1 : 0;
130   }
131   return val;
132 }
133 
134 // *************************************************************************
135 
CoinStaticObjectInDLL(void)136 CoinStaticObjectInDLL::CoinStaticObjectInDLL(void)
137 {
138   if (runtime_disabled()) { return; }
139 
140   // Can't use SoDebugError, as nothing has been initialized yet.
141   if (debug()) {
142     printf("%p %f CoinStaticObjectInDLL constructor\n",
143            this, SbTime::getTimeOfDay().getValue());
144   }
145 
146   assert(CoinStaticObjectInDLL::singleton == NULL);
147   CoinStaticObjectInDLL::singleton = this;
148 
149   if (!CoinStaticObjectInDLL::activateMutex()) {
150     MessageBox(NULL,
151                "Detected two instances of the Coin library in the same\n"
152                "process image!!\n\n"
153 
154                "Application can not continue without errors, and\n"
155                "will exit when you quit this dialog box.\n\n"
156 
157                "This is an indication of a serious problem with the\n"
158                "settings in your project configuration.\n\n"
159 
160                "One likely cause of this error is that a different\n"
161                "configuration of the Coin library was linked with\n"
162                "the GUI-binding library (e.g. SoWin or SoQt) than\n"
163                "for the application's linker settings. Try for instance\n"
164                "to check if there is a mismatch between debug and release\n"
165                "libraries.\n\n"
166 
167                "The depends.exe program that comes with VisualC++ is a\n"
168                "good tool for tracking things like this down. Make sure\n"
169                "you inspect the complete path of each loaded DLL.\n\n"
170 
171                "If you are completely lost as how to find and fix\n"
172                "this problem on your own, try the\n"
173                "<https://groups.google.com/forum/#!forum/coin3d-discuss>\n"
174                "mailing list (or the support address <coin-support@coin3d.org>).\n",
175 
176 
177                "Fatal error!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
178     exit(1);
179   }
180 }
181 
~CoinStaticObjectInDLL()182 CoinStaticObjectInDLL::~CoinStaticObjectInDLL()
183 {
184   if (runtime_disabled()) { return; }
185 
186   if (debug()) {
187     printf("%p %f CoinStaticObjectInDLL destructor\n",
188            this, SbTime::getTimeOfDay().getValue());
189   }
190 
191   // Wrap in if-check, since the mutex would usually be deallocated
192   // from CoinStaticObjectInDLL::init(). This doesn't always happen,
193   // though, as a Coin DLL can be loaded and then unloaded without
194   // SoDB::init() being called (as from
195   // src/glue/dl.c:cc_dl_coin_handle()).
196 
197   if (CoinStaticObjectInDLL::mutexhandle) {
198     CoinStaticObjectInDLL::deactivateMutex();
199   }
200 
201   assert(CoinStaticObjectInDLL::singleton);
202   CoinStaticObjectInDLL::singleton = NULL;
203 }
204 
205 // Called from SoDB::init().
206 void
init(void)207 CoinStaticObjectInDLL::init(void)
208 {
209   if (runtime_disabled()) { return; }
210 
211   if (debug()) {
212     printf("%p %f CoinStaticObjectInDLL::init()\n",
213            CoinStaticObjectInDLL::singleton,
214            SbTime::getTimeOfDay().getValue());
215   }
216 
217   // Program control has been handed over to actual program code at
218   // this point, so we take away the mutex again -- the check should
219   // have hit by now if two Coin DLLs were loaded.
220 
221   // (Doing this removes the potential for a rather obscure problem
222   // we've seen: we sometimes "manually" load a Coin DLL as an
223   // indirect means to get at the values of OpenGL extension function
224   // symbols (in src/glue/dl.c:cc_dl_coin_handle()). That would
225   // sometimes trigger the instantiation of two CoinStaticObjectInDLL
226   // objects, which made the above check hit.)
227 
228   if (CoinStaticObjectInDLL::singleton) { // in case the static object
229                                           // has been disabled in the
230                                           // code
231     CoinStaticObjectInDLL::deactivateMutex();
232   }
233 }
234 
235 // Returns TRUE if mutex was created ok, or FALSE if the mutex was
236 // already created.
237 SbBool
activateMutex(void)238 CoinStaticObjectInDLL::activateMutex(void)
239 {
240   if (debug()) {
241     printf("%p %f CoinStaticObjectInDLL::activateMutex(), mutexname=='%s'\n",
242            CoinStaticObjectInDLL::singleton,
243            SbTime::getTimeOfDay().getValue(),
244            CoinStaticObjectInDLL::mutexName().getString());
245   }
246 
247   assert(CoinStaticObjectInDLL::mutexhandle == NULL);
248 
249   SetLastError(0); // so we don't react to an old error for the check below
250 
251   CoinStaticObjectInDLL::mutexhandle = (HANDLE)
252     CreateMutex(NULL, TRUE, CoinStaticObjectInDLL::mutexName().getString());
253   // (The mutex is automatically destructed by the operating system
254   // when the process exits.)
255 
256   return (GetLastError() == ERROR_ALREADY_EXISTS) ? FALSE : TRUE;
257 }
258 
259 void
deactivateMutex(void)260 CoinStaticObjectInDLL::deactivateMutex(void)
261 {
262   if (debug()) {
263     printf("%p %f CoinStaticObjectInDLL::deactivateMutex(), handle==%p\n",
264            CoinStaticObjectInDLL::singleton,
265            SbTime::getTimeOfDay().getValue(),
266            CoinStaticObjectInDLL::mutexhandle);
267   }
268 
269   // it's only necessary to close the mutex handle the first time
270   // CoinStaticObjectInDLL::init() is called. In subsequent calls the handle
271   // will be NULL.
272   if (CoinStaticObjectInDLL::mutexhandle != NULL) {
273     const BOOL ok = CloseHandle((HANDLE)CoinStaticObjectInDLL::mutexhandle);
274     if (!ok) { // just in case
275       if (debug()) {
276         printf("%p %f CoinStaticObjectInDLL::deactivateMutex(), "
277                "ERROR: CloseHandle(%p) failed!\n",
278                CoinStaticObjectInDLL::singleton,
279                SbTime::getTimeOfDay().getValue(),
280                CoinStaticObjectInDLL::mutexhandle);
281       }
282 
283       MessageBox(NULL,
284                  "CloseHandle() in CoinStaticObjectInDLL::deactivateMutex()\n"
285                  "failed! Please report to <coin-support@coin3d.org>.\n",
286                  "Warning!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
287     }
288     CoinStaticObjectInDLL::mutexhandle = (HANDLE)NULL;
289   }
290 }
291 
292 SbString
mutexName(void)293 CoinStaticObjectInDLL::mutexName(void)
294 {
295   SbString s;
296   s.sprintf("COIN_LIBRARY_PROCESS_%d", GetCurrentProcessId());
297   return s;
298 }
299 
300 #endif // HAVE_WIN32_API
301 
302 // *************************************************************************
303