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