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