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