Implemented DB driver call PrepareString; alarms table converted to new format
[public/netxms.git] / src / server / dbdrv / mssql / mssql.cpp
1 /*
2 ** Microsoft SQL Server Database Driver
3 ** Copyright (C) 2004-2009 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: mssql.cpp
20 **
21 **/
22
23 #include "mssqldrv.h"
24
25
26 //
27 // Constants
28 //
29
30 #define CONNECT_TIMEOUT 30
31
32
33 //
34 // DB library error handler
35 //
36
37 static int ErrorHandler(PDBPROCESS hProcess, int severity, int dberr,
38 int oserr, const char *dberrstr, const char *oserrstr)
39 {
40 MSDB_CONN *pConn;
41
42 if (hProcess != NULL)
43 {
44 pConn = (MSDB_CONN *)dbgetuserdata(hProcess);
45 if (pConn != NULL)
46 {
47 nx_strncpy(pConn->szErrorText, dberrstr, DBDRV_MAX_ERROR_TEXT);
48 RemoveTrailingCRLF(pConn->szErrorText);
49 if (dbdead(hProcess))
50 pConn->bProcessDead = TRUE;
51 }
52 }
53 return INT_CANCEL;
54 }
55
56
57 //
58 // Re-establish connection to server
59 //
60
61 static BOOL Reconnect(MSDB_CONN *pConn)
62 {
63 LOGINREC *loginrec;
64 PDBPROCESS hProcess;
65 BOOL bResult = FALSE;
66
67 loginrec = dblogin();
68 if (!strcmp(pConn->szLogin, "*"))
69 {
70 DBSETLSECURE(loginrec);
71 }
72 else
73 {
74 DBSETLUSER(loginrec, pConn->szLogin);
75 DBSETLPWD(loginrec, pConn->szPassword);
76 }
77 DBSETLAPP(loginrec, "NetXMS");
78 DBSETLTIME(loginrec, CONNECT_TIMEOUT);
79 hProcess = dbopen(loginrec, pConn->szHost);
80
81 if ((hProcess != NULL) && (pConn->szDatabase[0] != 0))
82 {
83 dbsetuserdata(hProcess, NULL);
84 if (dbuse(hProcess, pConn->szDatabase) != SUCCEED)
85 {
86 dbclose(hProcess);
87 hProcess = NULL;
88 }
89 }
90
91 if (hProcess != NULL)
92 {
93 dbclose(pConn->hProcess);
94 pConn->hProcess = hProcess;
95 pConn->bProcessDead = FALSE;
96 dbsetuserdata(hProcess, pConn);
97 bResult = TRUE;
98 }
99
100 return bResult;
101 }
102
103
104 //
105 // API version
106 //
107
108 extern "C" int EXPORT drvAPIVersion = DBDRV_API_VERSION;
109
110
111 //
112 // Prepare string for using in SQL query - enclose in quotes and escape as needed
113 //
114
115 extern "C" TCHAR EXPORT *DrvPrepareString(const TCHAR *str)
116 {
117 int len = (int)_tcslen(str) + 4; // + two quotes, N prefix, and \0 at the end
118 int bufferSize = len + 128;
119 TCHAR *out = (TCHAR *)malloc(bufferSize * sizeof(TCHAR));
120 _tcscpy(out, _T("N'"));
121
122 const TCHAR *src = str;
123 int outPos;
124 for(outPos = 2; *src != NULL; src++)
125 {
126 if (*src < 32)
127 {
128 TCHAR buffer[32];
129
130 _sntprintf(buffer, 32, _T("'+nchar(%d)+N'"), *src);
131 int l = (int)_tcslen(buffer);
132
133 len += l;
134 if (len >= bufferSize)
135 {
136 bufferSize += 128;
137 out = (TCHAR *)realloc(out, bufferSize * sizeof(TCHAR));
138 }
139 memcpy(&out[outPos], buffer, l * sizeof(TCHAR));
140 outPos += l;
141 }
142 else if (*src == _T('\''))
143 {
144 len++;
145 if (len >= bufferSize)
146 {
147 bufferSize += 128;
148 out = (TCHAR *)realloc(out, bufferSize * sizeof(TCHAR));
149 }
150 out[outPos++] = _T('\'');
151 out[outPos++] = _T('\'');
152 }
153 else
154 {
155 out[outPos++] = *src;
156 }
157 }
158 out[outPos++] = _T('\'');
159 out[outPos++] = 0;
160
161 return out;
162 }
163
164
165 //
166 // Initialize driver
167 //
168
169 extern "C" BOOL EXPORT DrvInit(const char *cmdLine)
170 {
171 BOOL bResult = FALSE;
172
173 if (dbinit() != NULL)
174 {
175 dberrhandle(ErrorHandler);
176 bResult = TRUE;
177 }
178 return bResult;
179 }
180
181
182 //
183 // Unload handler
184 //
185
186 extern "C" void EXPORT DrvUnload(void)
187 {
188 }
189
190
191 //
192 // Connect to database
193 //
194
195 extern "C" DB_CONNECTION EXPORT DrvConnect(const char *host, const char *login,
196 const char *password, const char *database)
197 {
198 LOGINREC *loginrec;
199 MSDB_CONN *pConn = NULL;
200 PDBPROCESS hProcess;
201
202 loginrec = dblogin();
203 if (!strcmp(login, "*"))
204 {
205 DBSETLSECURE(loginrec);
206 }
207 else
208 {
209 DBSETLUSER(loginrec, login);
210 DBSETLPWD(loginrec, password);
211 }
212 DBSETLAPP(loginrec, "NetXMS");
213 DBSETLTIME(loginrec, CONNECT_TIMEOUT);
214 hProcess = dbopen(loginrec, host);
215
216 if (hProcess != NULL)
217 {
218 dbsetuserdata(hProcess, NULL);
219
220 // Change to specified database
221 if (database != NULL)
222 {
223 if (dbuse(hProcess, database) != SUCCEED)
224 {
225 dbclose(hProcess);
226 hProcess = NULL;
227 }
228 }
229
230 if (hProcess != NULL)
231 {
232 pConn = (MSDB_CONN *)malloc(sizeof(MSDB_CONN));
233 pConn->hProcess = hProcess;
234 pConn->mutexQueryLock = MutexCreate();
235 pConn->bProcessDead = FALSE;
236 nx_strncpy(pConn->szHost, host, MAX_CONN_STRING);
237 nx_strncpy(pConn->szLogin, login, MAX_CONN_STRING);
238 nx_strncpy(pConn->szPassword, password, MAX_CONN_STRING);
239 nx_strncpy(pConn->szDatabase, CHECK_NULL_EX(database), MAX_CONN_STRING);
240 pConn->szErrorText[0] = 0;
241
242 dbsetuserdata(hProcess, pConn);
243 }
244 }
245
246 return (DB_CONNECTION)pConn;
247 }
248
249
250 //
251 // Disconnect from database
252 //
253
254 extern "C" void EXPORT DrvDisconnect(MSDB_CONN *pConn)
255 {
256 if (pConn != NULL)
257 {
258 dbclose(pConn->hProcess);
259 MutexDestroy(pConn->mutexQueryLock);
260 free(pConn);
261 }
262 }
263
264
265 //
266 // Execute query
267 //
268
269 static BOOL ExecuteQuery(MSDB_CONN *pConn, char *pszQuery, TCHAR *errorText)
270 {
271 BOOL bResult;
272
273 dbcmd(pConn->hProcess, pszQuery);
274 if (dbsqlexec(pConn->hProcess) == SUCCEED)
275 {
276 bResult = TRUE;
277 }
278 else
279 {
280 if (pConn->bProcessDead)
281 {
282 if (Reconnect(pConn))
283 {
284 bResult = (dbsqlexec(pConn->hProcess) == SUCCEED);
285 }
286 else
287 {
288 bResult = FALSE;
289 }
290 }
291 else
292 {
293 bResult = FALSE;
294 }
295 }
296
297 if (errorText != NULL)
298 {
299 if (bResult)
300 {
301 *errorText = 0;
302 }
303 else
304 {
305 nx_strncpy(errorText, pConn->szErrorText, DBDRV_MAX_ERROR_TEXT);
306 }
307 }
308
309 return bResult;
310 }
311
312
313 //
314 // Perform non-SELECT query
315 //
316
317 extern "C" DWORD EXPORT DrvQuery(MSDB_CONN *pConn, WCHAR *pwszQuery, TCHAR *errorText)
318 {
319 DWORD dwError;
320 char *pszQueryUTF8;
321
322 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
323 MutexLock(pConn->mutexQueryLock, INFINITE);
324
325 if (ExecuteQuery(pConn, pszQueryUTF8, errorText))
326 {
327 if (dbresults(pConn->hProcess) == SUCCEED)
328 while(dbnextrow(pConn->hProcess) != NO_MORE_ROWS);
329 dwError = DBERR_SUCCESS;
330 }
331 else
332 {
333 dwError = pConn->bProcessDead ? DBERR_CONNECTION_LOST : DBERR_OTHER_ERROR;
334 }
335 MutexUnlock(pConn->mutexQueryLock);
336 free(pszQueryUTF8);
337 return dwError;
338 }
339
340
341 //
342 // Perform SELECT query
343 //
344
345 extern "C" DB_RESULT EXPORT DrvSelect(MSDB_CONN *pConn, WCHAR *pwszQuery, DWORD *pdwError, TCHAR *errorText)
346 {
347 MSDB_QUERY_RESULT *pResult = NULL;
348 int i, iCurrPos, iLen, *piColTypes;
349 void *pData;
350 char *pszQueryUTF8;
351 LPCSTR name;
352
353 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
354 MutexLock(pConn->mutexQueryLock, INFINITE);
355
356 if (ExecuteQuery(pConn, pszQueryUTF8, errorText))
357 {
358 // Process query results
359 if (dbresults(pConn->hProcess) == SUCCEED)
360 {
361 pResult = (MSDB_QUERY_RESULT *)malloc(sizeof(MSDB_QUERY_RESULT));
362 pResult->iNumRows = 0;
363 pResult->iNumCols = dbnumcols(pConn->hProcess);
364 pResult->pValues = NULL;
365 pResult->columnNames = (char **)malloc(sizeof(char *) * pResult->iNumCols);
366
367 // Determine column names and types
368 piColTypes = (int *)malloc(pResult->iNumCols * sizeof(int));
369 for(i = 0; i < pResult->iNumCols; i++)
370 {
371 piColTypes[i] = dbcoltype(pConn->hProcess, i + 1);
372 name = dbcolname(pConn->hProcess, i + 1);
373 pResult->columnNames[i] = strdup(CHECK_NULL_A(name));
374 }
375
376 // Retrieve data
377 iCurrPos = 0;
378 while(dbnextrow(pConn->hProcess) != NO_MORE_ROWS)
379 {
380 pResult->iNumRows++;
381 pResult->pValues = (char **)realloc(pResult->pValues,
382 sizeof(char *) * pResult->iNumRows * pResult->iNumCols);
383 for(i = 1; i <= pResult->iNumCols; i++, iCurrPos++)
384 {
385 pData = (void *)dbdata(pConn->hProcess, i);
386 if (pData != NULL)
387 {
388 switch(piColTypes[i - 1])
389 {
390 case SQLCHAR:
391 case SQLTEXT:
392 case SQLBINARY:
393 iLen = dbdatlen(pConn->hProcess, i);
394 pResult->pValues[iCurrPos] = (char *)malloc(iLen + 1);
395 if (iLen > 0)
396 memcpy(pResult->pValues[iCurrPos], (char *)pData, iLen);
397 pResult->pValues[iCurrPos][iLen] = 0;
398 break;
399 case SQLINT1:
400 pResult->pValues[iCurrPos] = (char *)malloc(4);
401 if (pData)
402 _snprintf_s(pResult->pValues[iCurrPos], 4, _TRUNCATE, "%d", *((char *)pData));
403 break;
404 case SQLINT2:
405 pResult->pValues[iCurrPos] = (char *)malloc(8);
406 _snprintf_s(pResult->pValues[iCurrPos], 8, _TRUNCATE, "%d", *((short *)pData));
407 break;
408 case SQLINT4:
409 pResult->pValues[iCurrPos] = (char *)malloc(16);
410 _snprintf_s(pResult->pValues[iCurrPos], 16, _TRUNCATE, "%d", *((LONG *)pData));
411 break;
412 case SQLFLT4:
413 pResult->pValues[iCurrPos] = (char *)malloc(32);
414 _snprintf_s(pResult->pValues[iCurrPos], 32, _TRUNCATE, "%f", *((float *)pData));
415 break;
416 case SQLFLT8:
417 pResult->pValues[iCurrPos] = (char *)malloc(32);
418 _snprintf_s(pResult->pValues[iCurrPos], 32, _TRUNCATE, "%f", *((double *)pData));
419 break;
420 default: // Unknown data type
421 pResult->pValues[iCurrPos] = (char *)malloc(2);
422 pResult->pValues[iCurrPos][0] = 0;
423 break;
424 }
425 }
426 else
427 {
428 pResult->pValues[iCurrPos] = (char *)malloc(2);
429 pResult->pValues[iCurrPos][0] = 0;
430 }
431 }
432 }
433 }
434 }
435
436 if (pResult != NULL)
437 {
438 *pdwError = DBERR_SUCCESS;
439 }
440 else
441 {
442 *pdwError = pConn->bProcessDead ? DBERR_CONNECTION_LOST : DBERR_OTHER_ERROR;
443 }
444
445 MutexUnlock(pConn->mutexQueryLock);
446 free(pszQueryUTF8);
447 return (DB_RESULT)pResult;
448 }
449
450
451 //
452 // Get field length from result
453 //
454
455 extern "C" LONG EXPORT DrvGetFieldLength(MSDB_QUERY_RESULT *pResult, int iRow, int iColumn)
456 {
457 if ((iRow < 0) || (iRow >= pResult->iNumRows) ||
458 (iColumn < 0) || (iColumn >= pResult->iNumCols))
459 return -1;
460 return strlen(pResult->pValues[iRow * pResult->iNumCols + iColumn]);
461 }
462
463
464 //
465 // Get field value from result
466 //
467
468 extern "C" WCHAR EXPORT *DrvGetField(MSDB_QUERY_RESULT *pResult, int iRow, int iColumn,
469 WCHAR *pBuffer, int nBufSize)
470 {
471 if ((iRow < 0) || (iRow >= pResult->iNumRows) ||
472 (iColumn < 0) || (iColumn >= pResult->iNumCols))
473 return NULL;
474 MultiByteToWideChar(CP_ACP, 0, pResult->pValues[iRow * pResult->iNumCols + iColumn],
475 -1, pBuffer, nBufSize);
476 pBuffer[nBufSize - 1] = 0;
477 return pBuffer;
478 }
479
480
481 //
482 // Get number of rows in result
483 //
484
485 extern "C" int EXPORT DrvGetNumRows(MSDB_QUERY_RESULT *pResult)
486 {
487 return (pResult != NULL) ? pResult->iNumRows : 0;
488 }
489
490
491 //
492 // Get column count in query result
493 //
494
495 extern "C" int EXPORT DrvGetColumnCount(MSDB_QUERY_RESULT *pResult)
496 {
497 return (pResult != NULL) ? pResult->iNumCols : 0;
498 }
499
500
501 //
502 // Get column name in query result
503 //
504
505 extern "C" const char EXPORT *DrvGetColumnName(MSDB_QUERY_RESULT *pResult, int column)
506 {
507 return ((pResult != NULL) && (column >= 0) && (column < pResult->iNumCols)) ? pResult->columnNames[column] : NULL;
508 }
509
510
511 //
512 // Free SELECT results
513 //
514
515 extern "C" void EXPORT DrvFreeResult(MSDB_QUERY_RESULT *pResult)
516 {
517 if (pResult != NULL)
518 {
519 int i, iNumValues;
520
521 iNumValues = pResult->iNumRows * pResult->iNumCols;
522 for(i = 0; i < iNumValues; i++)
523 free(pResult->pValues[i]);
524 safe_free(pResult->pValues);
525 for(i = 0; i < pResult->iNumCols; i++)
526 safe_free(pResult->columnNames[i]);
527 safe_free(pResult->columnNames);
528 free(pResult);
529 }
530 }
531
532
533 //
534 // Perform asynchronous SELECT query
535 //
536
537 extern "C" DB_ASYNC_RESULT EXPORT DrvAsyncSelect(MSDB_CONN *pConn, WCHAR *pwszQuery,
538 DWORD *pdwError, TCHAR *errorText)
539 {
540 MSDB_ASYNC_QUERY_RESULT *pResult = NULL;
541 char *pszQueryUTF8;
542 LPCSTR name;
543 int i;
544
545 pszQueryUTF8 = UTF8StringFromWideString(pwszQuery);
546 MutexLock(pConn->mutexQueryLock, INFINITE);
547
548 if (ExecuteQuery(pConn, pszQueryUTF8, errorText))
549 {
550 // Prepare query results for processing
551 if (dbresults(pConn->hProcess) == SUCCEED)
552 {
553 // Fill in result information structure
554 pResult = (MSDB_ASYNC_QUERY_RESULT *)malloc(sizeof(MSDB_ASYNC_QUERY_RESULT));
555 pResult->pConnection = pConn;
556 pResult->bNoMoreRows = FALSE;
557 pResult->iNumCols = dbnumcols(pConn->hProcess);
558 pResult->piColTypes = (int *)malloc(sizeof(int) * pResult->iNumCols);
559 pResult->columnNames = (char **)malloc(sizeof(char *) * pResult->iNumCols);
560
561 // Determine column names and types
562 for(i = 0; i < pResult->iNumCols; i++)
563 {
564 pResult->piColTypes[i] = dbcoltype(pConn->hProcess, i + 1);
565 name = dbcolname(pConn->hProcess, i + 1);
566 pResult->columnNames[i] = strdup(CHECK_NULL_A(name));
567 }
568 }
569 }
570
571 if (pResult != NULL)
572 {
573 *pdwError = DBERR_SUCCESS;
574 }
575 else
576 {
577 *pdwError = pConn->bProcessDead ? DBERR_CONNECTION_LOST : DBERR_OTHER_ERROR;
578 MutexUnlock(pConn->mutexQueryLock);
579 }
580 free(pszQueryUTF8);
581 return pResult;
582 }
583
584
585 //
586 // Fetch next result line from asynchronous SELECT results
587 //
588
589 extern "C" BOOL EXPORT DrvFetch(MSDB_ASYNC_QUERY_RESULT *pResult)
590 {
591 BOOL success = TRUE;
592
593 if (pResult == NULL)
594 {
595 success = FALSE;
596 }
597 else
598 {
599 // Try to fetch next row from server
600 if (dbnextrow(pResult->pConnection->hProcess) == NO_MORE_ROWS)
601 {
602 pResult->bNoMoreRows = TRUE;
603 success = FALSE;
604 MutexUnlock(pResult->pConnection->mutexQueryLock);
605 }
606 }
607 return success;
608 }
609
610
611 //
612 // Get field length from async query result
613 //
614
615 extern "C" LONG EXPORT DrvGetFieldLengthAsync(MSDB_ASYNC_QUERY_RESULT *result, int column)
616 {
617 if ((result == NULL) || (column < 0) || (column >= result->iNumCols))
618 return -1;
619
620 switch(result->piColTypes[column])
621 {
622 case SQLCHAR:
623 case SQLTEXT:
624 case SQLBINARY:
625 return dbdatlen(result->pConnection->hProcess, column + 1);
626 case SQLINT1:
627 return 4;
628 case SQLINT2:
629 return 6;
630 case SQLINT4:
631 return 12;
632 case SQLFLT4:
633 return 30;
634 case SQLFLT8:
635 return 20;
636 }
637 return 0;
638 }
639
640
641 //
642 // Get field from current row in async query result
643 //
644
645 extern "C" WCHAR EXPORT *DrvGetFieldAsync(MSDB_ASYNC_QUERY_RESULT *pResult, int iColumn,
646 WCHAR *pBuffer, int iBufSize)
647 {
648 void *pData;
649 int nLen;
650
651 // Check if we have valid result handle
652 if (pResult == NULL)
653 return NULL;
654
655 // Check if there are valid fetched row
656 if (pResult->bNoMoreRows)
657 return NULL;
658
659 // Now get column data
660 pData = (void *)dbdata(pResult->pConnection->hProcess, iColumn + 1);
661 if (pData != NULL)
662 {
663 switch(pResult->piColTypes[iColumn])
664 {
665 case SQLCHAR:
666 case SQLTEXT:
667 case SQLBINARY:
668 nLen = MultiByteToWideChar(CP_ACP, 0, (char *)pData,
669 dbdatlen(pResult->pConnection->hProcess, iColumn + 1),
670 pBuffer, iBufSize);
671 pBuffer[nLen] = 0;
672 break;
673 case SQLINT1:
674 _snwprintf_s(pBuffer, iBufSize, _TRUNCATE, L"%d", *((char *)pData));
675 break;
676 case SQLINT2:
677 _snwprintf_s(pBuffer, iBufSize, _TRUNCATE, L"%d", *((short *)pData));
678 break;
679 case SQLINT4:
680 _snwprintf_s(pBuffer, iBufSize, _TRUNCATE, L"%d", *((LONG *)pData));
681 break;
682 case SQLFLT4:
683 _snwprintf_s(pBuffer, iBufSize, _TRUNCATE, L"%f", *((float *)pData));
684 break;
685 case SQLFLT8:
686 _snwprintf_s(pBuffer, iBufSize, _TRUNCATE, L"%f", *((double *)pData));
687 break;
688 default: // Unknown data type
689 pBuffer[0] = 0;
690 break;
691 }
692 }
693 else
694 {
695 pBuffer[0] = 0;
696 }
697
698 return pBuffer;
699 }
700
701
702 //
703 // Get column count in async query result
704 //
705
706 extern "C" int EXPORT DrvGetColumnCountAsync(MSDB_ASYNC_QUERY_RESULT *pResult)
707 {
708 return (pResult != NULL) ? pResult->iNumCols : 0;
709 }
710
711
712 //
713 // Get column name in async query result
714 //
715
716 extern "C" const char EXPORT *DrvGetColumnNameAsync(MSDB_ASYNC_QUERY_RESULT *pResult, int column)
717 {
718 return ((pResult != NULL) && (column >= 0) && (column < pResult->iNumCols)) ? pResult->columnNames[column] : NULL;
719 }
720
721
722 //
723 // Destroy result of async query
724 //
725
726 extern "C" void EXPORT DrvFreeAsyncResult(MSDB_ASYNC_QUERY_RESULT *pResult)
727 {
728 if (pResult != NULL)
729 {
730 // Check if all result rows fetchef
731 if (!pResult->bNoMoreRows)
732 {
733 // Fetch remaining rows
734 while(dbnextrow(pResult->pConnection->hProcess) != NO_MORE_ROWS);
735
736 // Now we are ready for next query, so unlock query mutex
737 MutexUnlock(pResult->pConnection->mutexQueryLock);
738 }
739
740 // Free allocated memory
741 if (pResult->piColTypes != NULL)
742 free(pResult->piColTypes);
743 for(int i = 0; i < pResult->iNumCols; i++)
744 safe_free(pResult->columnNames[i]);
745 safe_free(pResult->columnNames);
746 free(pResult);
747 }
748 }
749
750
751 //
752 // Begin transaction
753 //
754
755 extern "C" DWORD EXPORT DrvBegin(MSDB_CONN *pConn)
756 {
757 return DrvQuery(pConn, L"BEGIN TRANSACTION", NULL);
758 }
759
760
761 //
762 // Commit transaction
763 //
764
765 extern "C" DWORD EXPORT DrvCommit(MSDB_CONN *pConn)
766 {
767 return DrvQuery(pConn, L"COMMIT", NULL);
768 }
769
770
771 //
772 // Rollback transaction
773 //
774
775 extern "C" DWORD EXPORT DrvRollback(MSDB_CONN *pConn)
776 {
777 return DrvQuery(pConn, L"ROLLBACK", NULL);
778 }
779
780
781 //
782 // DLL Entry point
783 //
784
785 #ifdef _WIN32
786
787 BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
788 {
789 if (dwReason == DLL_PROCESS_ATTACH)
790 DisableThreadLibraryCalls(hInstance);
791 return TRUE;
792 }
793
794 #endif