Fix freeing lists on process detach
[reactos.git] / reactos / dll / win32 / opengl32 / opengl32.c
1 /* $Id$
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: lib/opengl32/opengl32.c
6 * PURPOSE: OpenGL32 lib
7 * PROGRAMMER: Anich Gregor (blight), Royce Mitchell III
8 * UPDATE HISTORY:
9 * Feb 1, 2004: Created
10 */
11
12 #define WIN32_LEAN_AND_MEAN
13 #define WIN32_NO_STATUS
14 #include <windows.h>
15 #include <winreg.h>
16 #include "teb.h"
17
18 #include <string.h>
19 #include "opengl32.h"
20
21
22 /* function prototypes */
23 static void OPENGL32_AppendICD( GLDRIVERDATA *icd );
24 static void OPENGL32_RemoveICD( GLDRIVERDATA *icd );
25 static GLDRIVERDATA *OPENGL32_LoadDriver( LPCWSTR regKey );
26 static DWORD OPENGL32_InitializeDriver( GLDRIVERDATA *icd );
27 static BOOL OPENGL32_UnloadDriver( GLDRIVERDATA *icd );
28 static DWORD OPENGL32_RegGetDriverInfo( LPCWSTR driver, GLDRIVERDATA *icd );
29
30
31 /* global vars */
32 /* Do not assume it have the free value -1 set, any value can be in here */
33 DWORD OPENGL32_tls = -1;
34 GLPROCESSDATA OPENGL32_processdata;
35
36
37 static BOOL
38 OPENGL32_ThreadAttach()
39 {
40 GLTHREADDATA* lpData = NULL;
41 PROC *dispatchTable = NULL;
42 TEB *teb = NULL;
43
44 dispatchTable = (PROC*)HeapAlloc( GetProcessHeap(),
45 HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY,
46 sizeof (((ICDTable *)(0))->dispatch_table) );
47 if (dispatchTable == NULL)
48 {
49 DBGPRINT( "Error: Couldn't allocate GL dispatch table" );
50 return FALSE;
51 }
52
53 lpData = (GLTHREADDATA*)HeapAlloc( GetProcessHeap(),
54 HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY,
55 sizeof (GLTHREADDATA) );
56 if (lpData == NULL)
57 {
58 DBGPRINT( "Error: Couldn't allocate GLTHREADDATA" );
59 HeapFree( GetProcessHeap(), 0, dispatchTable );
60 return FALSE;
61 }
62
63 teb = NtCurrentTeb();
64
65 /* initialize dispatch table with empty functions */
66 #define X(func, ret, typeargs, args, icdidx, tebidx, stack) \
67 dispatchTable[icdidx] = (PROC)glEmptyFunc##stack; \
68 if (tebidx >= 0) \
69 teb->glDispatchTable[tebidx] = (PVOID)glEmptyFunc##stack;
70 GLFUNCS_MACRO
71 #undef X
72
73 teb->glTable = dispatchTable;
74 TlsSetValue( OPENGL32_tls, lpData );
75
76 return TRUE;
77 }
78
79
80 static void
81 OPENGL32_ThreadDetach()
82 {
83 GLTHREADDATA* lpData = NULL;
84 PROC *dispatchTable = NULL;
85
86 rosglMakeCurrent( NULL, NULL );
87
88 lpData = (GLTHREADDATA*)TlsGetValue( OPENGL32_tls );
89 if (lpData != NULL)
90 {
91 if (!HeapFree( GetProcessHeap(), 0, lpData ))
92 DBGPRINT( "Warning: HeapFree() on GLTHREADDATA failed (%d)",
93 GetLastError() );
94 }
95
96 dispatchTable = NtCurrentTeb()->glTable;
97 if (dispatchTable != NULL)
98 {
99 if (!HeapFree( GetProcessHeap(), 0, dispatchTable ))
100 DBGPRINT( "Warning: HeapFree() on dispatch table failed (%d)",
101 GetLastError() );
102 }
103 }
104
105
106 static BOOL
107 OPENGL32_ProcessAttach()
108 {
109 SECURITY_ATTRIBUTES attrib = { sizeof (SECURITY_ATTRIBUTES), /* nLength */
110 NULL, /* lpSecurityDescriptor */
111 TRUE /* bInheritHandle */ };
112
113 OPENGL32_tls = TlsAlloc();
114 if (-1 == OPENGL32_tls)
115 return FALSE;
116
117 memset( &OPENGL32_processdata, 0, sizeof (OPENGL32_processdata) );
118
119 /* create driver, glrc & dcdata list mutex */
120 OPENGL32_processdata.driver_mutex = CreateMutex( &attrib, FALSE, NULL );
121 if (OPENGL32_processdata.driver_mutex == NULL)
122 {
123 DBGPRINT( "Error: Couldn't create driver_list mutex (%d)",
124 GetLastError() );
125 return FALSE;
126 }
127 OPENGL32_processdata.glrc_mutex = CreateMutex( &attrib, FALSE, NULL );
128 if (OPENGL32_processdata.glrc_mutex == NULL)
129 {
130 DBGPRINT( "Error: Couldn't create glrc_list mutex (%d)",
131 GetLastError() );
132 return FALSE;
133 }
134 OPENGL32_processdata.dcdata_mutex = CreateMutex( &attrib, FALSE, NULL );
135 if (OPENGL32_processdata.dcdata_mutex == NULL)
136 {
137 DBGPRINT( "Error: Couldn't create dcdata_list mutex (%d)",
138 GetLastError() );
139 return FALSE;
140 }
141
142 return TRUE;
143 }
144
145
146 static void
147 OPENGL32_ProcessDetach()
148 {
149 GLDRIVERDATA *icd, *icd2;
150 GLDCDATA *dcdata, *dcdata2;
151 GLRC *glrc, *glrc2;
152
153 /* free lists */
154 for (dcdata = OPENGL32_processdata.dcdata_list; dcdata != NULL;)
155 {
156 dcdata2 = dcdata;
157 dcdata = dcdata->next;
158 if (!HeapFree( GetProcessHeap(), 0, dcdata2 ))
159 DBGPRINT( "Warning: HeapFree() on DCDATA 0x%08x failed (%d)",
160 dcdata2, GetLastError() );
161 }
162
163 for (glrc = OPENGL32_processdata.glrc_list; glrc != NULL;)
164 {
165 glrc2 = glrc;
166 glrc = glrc->next;
167 if (!HeapFree( GetProcessHeap(), 0, glrc2 ))
168 DBGPRINT( "Warning: HeapFree() on GLRC 0x%08x failed (%d)",
169 glrc2, GetLastError() );
170 }
171
172 for (icd = OPENGL32_processdata.driver_list; icd != NULL;)
173 {
174 icd2 = icd;
175 icd = icd->next;
176 if (!HeapFree( GetProcessHeap(), 0, icd2 ))
177 DBGPRINT( "Warning: HeapFree() on DRIVERDATA 0x%08x failed (%d)",
178 icd2, GetLastError() );
179 }
180
181 /* free mutexes */
182 if (OPENGL32_processdata.driver_mutex != NULL)
183 CloseHandle( OPENGL32_processdata.driver_mutex );
184 if (OPENGL32_processdata.glrc_mutex != NULL)
185 CloseHandle( OPENGL32_processdata.glrc_mutex );
186 if (OPENGL32_processdata.dcdata_mutex != NULL)
187 CloseHandle( OPENGL32_processdata.dcdata_mutex );
188
189 /* free TLS */
190 if (OPENGL32_tls != -1)
191 TlsFree(OPENGL32_tls);
192 }
193
194
195 BOOL WINAPI
196 DllMain(HINSTANCE hInstance, DWORD Reason, LPVOID Reserved)
197 {
198 DBGPRINT( "Info: Called!" );
199 switch ( Reason )
200 {
201 /* The DLL is loading due to process
202 * initialization or a call to LoadLibrary.
203 */
204 case DLL_PROCESS_ATTACH:
205 DBGTRACE( "Process attach" );
206 if (!OPENGL32_ProcessAttach())
207 return FALSE;
208 /* No break: Initialize the index for first thread. */
209
210 /* The attached process creates a new thread. */
211 case DLL_THREAD_ATTACH:
212 DBGTRACE( "Thread attach" );
213 if (!OPENGL32_ThreadAttach())
214 return FALSE;
215 break;
216
217 /* The thread of the attached process terminates. */
218 case DLL_THREAD_DETACH:
219 DBGTRACE( "Thread detach" );
220 /* Release the allocated memory for this thread.*/
221 OPENGL32_ThreadDetach();
222 break;
223
224 /* DLL unload due to process termination or FreeLibrary. */
225 case DLL_PROCESS_DETACH:
226 DBGTRACE( "Process detach" );
227 OPENGL32_ThreadDetach();
228 OPENGL32_ProcessDetach();
229 break;
230 }
231
232 return TRUE;
233 }
234
235
236 /*! \brief Append ICD to linked list.
237 *
238 * \param icd GLDRIVERDATA to append to list
239 *
240 * \note Only call this when you hold the driver_mutex.
241 */
242 static void
243 OPENGL32_AppendICD( GLDRIVERDATA *icd )
244 {
245 if (OPENGL32_processdata.driver_list == NULL)
246 OPENGL32_processdata.driver_list = icd;
247 else
248 {
249 GLDRIVERDATA *p = OPENGL32_processdata.driver_list;
250 while (p->next != NULL)
251 p = p->next;
252 p->next = icd;
253 }
254 }
255
256
257 /*! \brief Remove ICD from linked list.
258 *
259 * \param icd GLDRIVERDATA to remove from list
260 *
261 * \note Only call this when you hold the driver_mutex.
262 */
263 static void
264 OPENGL32_RemoveICD( GLDRIVERDATA *icd )
265 {
266 if (icd == OPENGL32_processdata.driver_list)
267 OPENGL32_processdata.driver_list = icd->next;
268 else
269 {
270 GLDRIVERDATA *p = OPENGL32_processdata.driver_list;
271 while (p != NULL)
272 {
273 if (p->next == icd)
274 {
275 p->next = icd->next;
276 return;
277 }
278 p = p->next;
279 }
280 DBGPRINT( "Error: ICD 0x%08x not found in list!", icd );
281 }
282 }
283
284
285 /*! \brief Load an ICD.
286 *
287 * \param driver Name of installable client driver.
288 *
289 * \return Pointer to an allocated GLDRIVERDATA struct.
290 * \retval NULL Failure.
291 *
292 * \todo Call SetLastError() where appropriate.
293 */
294 static GLDRIVERDATA *
295 OPENGL32_LoadDriver( LPCWSTR driver )
296 {
297 LONG ret;
298 GLDRIVERDATA *icd;
299
300 DBGPRINT( "Info: Loading driver %ws...", driver );
301
302 /* allocate driver data */
303 icd = (GLDRIVERDATA*)HeapAlloc( GetProcessHeap(),
304 HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY,
305 sizeof (GLDRIVERDATA) );
306 if (icd == NULL)
307 {
308 DBGPRINT( "Error: Couldn't allocate GLDRIVERDATA! (%d)", GetLastError() );
309 return NULL;
310 }
311
312 ret = OPENGL32_RegGetDriverInfo( driver, icd );
313 if (ret != ERROR_SUCCESS)
314 {
315 DBGPRINT( "Error: Couldn't query driver information (%d)", ret );
316 if (!HeapFree( GetProcessHeap(), 0, icd ))
317 DBGPRINT( "Error: HeapFree() returned false, error code = %d",
318 GetLastError() );
319 return NULL;
320 }
321
322 DBGPRINT( "Info: Dll = %ws", icd->dll );
323 DBGPRINT( "Info: Version = 0x%08x", icd->version );
324 DBGPRINT( "Info: DriverVersion = 0x%08x", icd->driver_version );
325 DBGPRINT( "Info: Flags = 0x%08x", icd->flags );
326
327 /* load/initialize ICD */
328 ret = OPENGL32_InitializeDriver( icd );
329 if (ret != ERROR_SUCCESS)
330 {
331 DBGPRINT( "Error: Couldnt initialize ICD!" );
332 if (!HeapFree( GetProcessHeap(), 0, icd ))
333 DBGPRINT( "Error: HeapFree() returned false, error code = %d",
334 GetLastError() );
335 return NULL;
336 }
337
338 /* append ICD to list */
339 OPENGL32_AppendICD( icd );
340 DBGPRINT( "Info: ICD loaded." );
341
342 return icd;
343 }
344
345
346 /*! \brief Initialize a driver (Load DLL and DrvXxx procs)
347 *
348 * \param icd ICD to initialize with the dll, version, driverVersion
349 * and flags already filled.
350 * \return Error code.
351 * \retval ERROR_SUCCESS Success
352 */
353 #define LOAD_DRV_PROC( icd, proc, required ) \
354 *(char**)&icd->proc = (char*)GetProcAddress( icd->handle, #proc ); \
355 if (required && icd->proc == NULL) { \
356 DBGPRINT( "Error: GetProcAddress(\"%s\") failed!", #proc ); \
357 FreeLibrary( icd->handle ); \
358 return GetLastError(); \
359 }
360
361 static DWORD
362 OPENGL32_InitializeDriver( GLDRIVERDATA *icd )
363 {
364 /* check version */
365 if (icd->version > 2)
366 DBGPRINT( "Warning: ICD version > 2 (%d)", icd->version );
367
368 /* load dll */
369 icd->handle = LoadLibraryW( icd->dll );
370 if (icd->handle == NULL)
371 {
372 DWORD err = GetLastError();
373 DBGPRINT( "Error: Couldn't load DLL! (%d)", err );
374 return err;
375 }
376
377 /* validate version */
378 if (icd->driver_version > 1)
379 {
380 LOAD_DRV_PROC(icd, DrvValidateVersion, FALSE);
381 if (icd->DrvValidateVersion != NULL)
382 {
383 if (!icd->DrvValidateVersion( icd->driver_version ))
384 {
385 DBGPRINT( "Error: DrvValidateVersion failed!" );
386 DBGBREAK();
387 FreeLibrary( icd->handle );
388 return ERROR_INVALID_FUNCTION; /* FIXME: use better error code */
389 }
390 }
391 else
392 DBGPRINT( "Info: DrvValidateVersion not exported by ICD" );
393 }
394
395 /* load DrvXXX procs */
396 LOAD_DRV_PROC(icd, DrvCopyContext, TRUE);
397 LOAD_DRV_PROC(icd, DrvCreateContext, FALSE);
398 LOAD_DRV_PROC(icd, DrvCreateLayerContext, FALSE);
399 LOAD_DRV_PROC(icd, DrvDeleteContext, TRUE);
400 LOAD_DRV_PROC(icd, DrvDescribeLayerPlane, TRUE);
401 LOAD_DRV_PROC(icd, DrvDescribePixelFormat, TRUE);
402 LOAD_DRV_PROC(icd, DrvGetLayerPaletteEntries, TRUE);
403 LOAD_DRV_PROC(icd, DrvGetProcAddress, TRUE);
404 LOAD_DRV_PROC(icd, DrvReleaseContext, TRUE);
405 LOAD_DRV_PROC(icd, DrvRealizeLayerPalette, TRUE);
406 LOAD_DRV_PROC(icd, DrvSetContext, TRUE);
407 LOAD_DRV_PROC(icd, DrvSetLayerPaletteEntries, TRUE);
408 LOAD_DRV_PROC(icd, DrvSetPixelFormat, TRUE);
409 LOAD_DRV_PROC(icd, DrvShareLists, TRUE);
410 LOAD_DRV_PROC(icd, DrvSwapBuffers, TRUE);
411 LOAD_DRV_PROC(icd, DrvSwapLayerBuffers, TRUE);
412
413 /* we require at least one of DrvCreateContext and DrvCreateLayerContext */
414 if (icd->DrvCreateContext == NULL && icd->DrvCreateLayerContext == NULL)
415 {
416 DBGPRINT( "Error: One of DrvCreateContext/DrvCreateLayerContext is required!" );
417 FreeLibrary( icd->handle );
418 return ERROR_INVALID_FUNCTION; /* FIXME: use better error code... */
419 }
420
421 return ERROR_SUCCESS;
422 }
423
424
425 /*! \brief Unload ICD.
426 *
427 * \retval TRUE Success.
428 * \retval FALSE Failure.
429 */
430 static BOOL
431 OPENGL32_UnloadDriver( GLDRIVERDATA *icd )
432 {
433 BOOL allOk = TRUE;
434
435 DBGPRINT( "Info: Unloading driver %ws...", icd->driver_name );
436 if (icd->refcount != 0)
437 DBGPRINT( "Warning: ICD refcount = %d (should be 0)", icd->refcount );
438
439 /* unload dll */
440 if (!FreeLibrary( icd->handle ))
441 {
442 allOk = FALSE;
443 DBGPRINT( "Warning: FreeLibrary on ICD %ws failed! (%d)", icd->dll,
444 GetLastError() );
445 }
446
447 /* free resources */
448 OPENGL32_RemoveICD( icd );
449 if (!HeapFree( GetProcessHeap(), 0, icd ))
450 {
451 allOk = FALSE;
452 DBGPRINT( "Warning: HeapFree() returned FALSE, error code = %d",
453 GetLastError() );
454 }
455
456 return allOk;
457 }
458
459
460 /*! \brief Load ICD (shared ICD data)
461 *
462 * \return Pointer to an allocated GLDRIVERDATA on success.
463 * \retval NULL Failure.
464 */
465 GLDRIVERDATA *
466 OPENGL32_LoadICD( LPCWSTR driver )
467 {
468 GLDRIVERDATA *icd;
469
470 /* synchronize */
471 if (WaitForSingleObject( OPENGL32_processdata.driver_mutex, INFINITE ) ==
472 WAIT_FAILED)
473 {
474 DBGPRINT( "Error: WaitForSingleObject() failed (%d)", GetLastError() );
475 return NULL; /* FIXME: do we have to expect such an error and handle it? */
476 }
477
478 /* look if ICD is already loaded */
479 for (icd = OPENGL32_processdata.driver_list; icd; icd = icd->next)
480 {
481 if (!_wcsicmp( driver, icd->driver_name )) /* found */
482 {
483 icd->refcount++;
484
485 /* release mutex */
486 if (!ReleaseMutex( OPENGL32_processdata.driver_mutex ))
487 DBGPRINT( "Error: ReleaseMutex() failed (%d)", GetLastError() );
488
489 return icd;
490 }
491 }
492
493 /* not found - try to load */
494 icd = OPENGL32_LoadDriver( driver );
495 if (icd != NULL)
496 icd->refcount = 1;
497
498 /* release mutex */
499 if (!ReleaseMutex( OPENGL32_processdata.driver_mutex ))
500 DBGPRINT( "Error: ReleaseMutex() failed (%d)", GetLastError() );
501
502 return icd;
503 }
504
505
506 /*! \brief Unload ICD (shared ICD data)
507 *
508 * \retval TRUE Success.
509 * \retval FALSE Failure.
510 */
511 BOOL
512 OPENGL32_UnloadICD( GLDRIVERDATA *icd )
513 {
514 BOOL ret = TRUE;
515
516 /* synchronize */
517 if (WaitForSingleObject( OPENGL32_processdata.driver_mutex, INFINITE ) ==
518 WAIT_FAILED)
519 {
520 DBGPRINT( "Error: WaitForSingleObject() failed (%d)", GetLastError() );
521 return FALSE; /* FIXME: do we have to expect such an error and handle it? */
522 }
523
524 icd->refcount--;
525 if (icd->refcount == 0)
526 // if (0)
527 ret = OPENGL32_UnloadDriver( icd );
528 /* FIXME: InitializeICD crashes when called a second time */
529
530 /* release mutex */
531 if (!ReleaseMutex( OPENGL32_processdata.driver_mutex ))
532 DBGPRINT( "Error: ReleaseMutex() failed (%d)", GetLastError() );
533
534 return ret;
535 }
536
537
538 /*! \brief Enumerate OpenGLDrivers (from registry)
539 *
540 * \param idx Index of the driver to get information about.
541 * \param name Pointer to an array of WCHARs (can be NULL)
542 * \param cName Pointer to a DWORD. Input is len of name array.
543 * Output is length of the drivername.
544 * Can be NULL if name is NULL.
545 *
546 * \return Error code
547 * \retval ERROR_NO_MORE_ITEMS End of driver list.
548 * \retval ERROR_SUCCESS Success.
549 */
550 #if 0 /* unused */
551 DWORD
552 OPENGL32_RegEnumDrivers( DWORD idx, LPWSTR name, LPDWORD cName )
553 {
554 HKEY hKey;
555 LPCWSTR subKey = OPENGL_DRIVERS_SUBKEY;
556 LONG ret;
557 DWORD size;
558 WCHAR driver[256];
559
560 if (name == NULL)
561 return ERROR_SUCCESS; /* nothing to do */
562
563 if (cName == NULL)
564 return ERROR_INVALID_FUNCTION; /* we need cName when name is given */
565
566 /* open OpenGLDrivers registry key */
567 ret = RegOpenKeyExW( HKEY_LOCAL_MACHINE, subKey, 0, KEY_READ, &hKey );
568 if (ret != ERROR_SUCCESS)
569 {
570 DBGPRINT( "Error: Couldn't open registry key '%ws'", subKey );
571 return ret;
572 }
573
574 /* get subkey name */
575 size = sizeof (driver) / sizeof (driver[0]);
576 ret = RegEnumKeyW( hKey, idx, name, *cName );
577 if (ret != ERROR_SUCCESS)
578 {
579 DBGPRINT( "Error: Couldn't get OpenGLDrivers subkey name (%d)", ret );
580 RegCloseKey( hKey );
581 return ret;
582 }
583 *cName = wcslen( name );
584
585 /* close key */
586 RegCloseKey( hKey );
587 return ERROR_SUCCESS;
588 }
589 #endif /* 0 -- unused */
590
591
592 /*! \brief Get registry values for a driver given a name.
593 *
594 * \param driver Name of the driver to get information about.
595 * \param icd Pointer to GLDRIVERDATA.
596 *
597 * \return Error code.
598 * \retval ERROR_SUCCESS Success.
599 *
600 * \note On success the following fields of \a icd are filled: \a driver_name,
601 * \a dll, \a version, \a driver_version and \a flags.
602 */
603 static DWORD
604 OPENGL32_RegGetDriverInfo( LPCWSTR driver, GLDRIVERDATA *icd )
605 {
606 HKEY hKey;
607 WCHAR subKey[1024] = OPENGL_DRIVERS_SUBKEY"\\";
608 LONG ret;
609 DWORD type, size;
610
611 /* drivers registry values */
612 DWORD version = 1, driverVersion = 0, flags = 0;
613 WCHAR dll[256];
614
615 /* open driver registry key */
616 wcsncat( subKey, driver, 1024 );
617 ret = RegOpenKeyExW( HKEY_LOCAL_MACHINE, subKey, 0, KEY_READ, &hKey );
618 if (ret != ERROR_SUCCESS)
619 {
620 DBGPRINT( "Error: Couldn't open registry key '%ws'", subKey );
621 return ret;
622 }
623
624 /* query values */
625 size = sizeof (dll);
626 ret = RegQueryValueExW( hKey, L"Dll", 0, &type, (LPBYTE)dll, &size );
627 if (ret != ERROR_SUCCESS || type != REG_SZ)
628 {
629 DBGPRINT( "Error: Couldn't query Dll value or not a string" );
630 RegCloseKey( hKey );
631 return ret;
632 }
633
634 size = sizeof (DWORD);
635 ret = RegQueryValueExW( hKey, L"Version", 0, &type, (LPBYTE)&version, &size );
636 if (ret != ERROR_SUCCESS || type != REG_DWORD)
637 DBGPRINT( "Warning: Couldn't query Version value or not a DWORD" );
638
639 size = sizeof (DWORD);
640 ret = RegQueryValueExW( hKey, L"DriverVersion", 0, &type,
641 (LPBYTE)&driverVersion, &size );
642 if (ret != ERROR_SUCCESS || type != REG_DWORD)
643 DBGPRINT( "Warning: Couldn't query DriverVersion value or not a DWORD" );
644
645 size = sizeof (DWORD);
646 ret = RegQueryValueExW( hKey, L"Flags", 0, &type, (LPBYTE)&flags, &size );
647 if (ret != ERROR_SUCCESS || type != REG_DWORD)
648 DBGPRINT( "Warning: Couldn't query Flags value or not a DWORD" );
649
650 /* close key */
651 RegCloseKey( hKey );
652
653 /* output data */
654 /* FIXME: NUL-terminate strings? */
655 wcsncpy( icd->driver_name, driver,
656 sizeof (icd->driver_name) / sizeof (icd->driver_name[0]) - 1 );
657 wcsncpy( icd->dll, dll,
658 sizeof (icd->dll) / sizeof (icd->dll[0]) );
659 icd->version = version;
660 icd->driver_version = driverVersion;
661 icd->flags = flags;
662
663 return ERROR_SUCCESS;
664 }
665
666 /* EOF */
667