Release 980329
[wine/multimedia.git] / multimedia / mmio.c
blobc7117a01f359f30fa550cc9c5c92d73748f0aebe
1 /*
2 * MMIO functions
4 * Copyright 1998 Andrew Taylor
6 * NOTES: I/O is still unbuffered; mmioSetBuffer must be implemented
7 * and mmio{Read,Write,Seek,others?} need buffer support.
8 * Buffering should almost give us memory files for free.
9 */
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 #include "windows.h"
17 #include "win.h"
18 #include "heap.h"
19 #include "user.h"
20 #include "file.h"
21 #include "mmsystem.h"
22 #include "debug.h"
23 #include "xmalloc.h"
25 /**************************************************************************
26 * mmioDosIOProc [internal]
28 static LRESULT mmioDosIOProc(LPMMIOINFO16 lpmmioinfo, UINT16 uMessage, LPARAM lParam1, LPARAM lParam2) {
29 TRACE(mmio, "(%p, %X, %ld, %ld);\n", lpmmioinfo, uMessage, lParam1, lParam2);
31 switch (uMessage) {
33 case MMIOM_OPEN: {
34 /* Parameters:
35 * lParam1 = szFileName parameter from mmioOpen
36 * lParam2 = unused
37 * Returns: zero on success, error code on error
38 * NOTE: lDiskOffset automatically set to zero
41 OFSTRUCT ofs;
42 LPSTR szFileName = (LPSTR) lParam1;
44 if (lpmmioinfo->dwFlags & MMIO_GETTEMP) {
45 FIXME(mmio, "MMIO_GETTEMP not implemented\n");
46 return MMIOERR_CANNOTOPEN;
49 /* if filename NULL, assume open file handle in adwInfo[0] */
50 if (!szFileName)
51 return 0;
53 lpmmioinfo->adwInfo[0] =
54 (DWORD) OpenFile32(szFileName, &ofs, lpmmioinfo->dwFlags);
55 if (lpmmioinfo->adwInfo[0] == -1)
56 return MMIOERR_CANNOTOPEN;
58 return 0;
61 case MMIOM_CLOSE: {
62 /* Parameters:
63 * lParam1 = wFlags parameter from mmioClose
64 * lParam2 = unused
65 * Returns: zero on success, error code on error
68 UINT16 uFlags = (UINT16) lParam1;
70 if (uFlags & MMIO_FHOPEN)
71 return 0;
73 _lclose32((HFILE32)lpmmioinfo->adwInfo[0]);
74 return 0;
78 case MMIOM_READ: {
79 /* Parameters:
80 * lParam1 = huge pointer to read buffer
81 * lParam2 = number of bytes to read
82 * Returns: number of bytes read, 0 for EOF, -1 for error (error code
83 * in wErrorRet)
84 * NOTE: lDiskOffset should be updated
87 HPSTR pch = (HPSTR) lParam1;
88 LONG cch = (LONG) lParam2;
89 LONG count;
91 count = _lread32((HFILE32)lpmmioinfo->adwInfo[0], pch, cch);
92 if (count != -1)
93 lpmmioinfo->lDiskOffset += count;
95 return count;
98 case MMIOM_WRITE:
99 case MMIOM_WRITEFLUSH: {
100 /* no internal buffering, so WRITEFLUSH handled same as WRITE */
102 /* Parameters:
103 * lParam1 = huge pointer to write buffer
104 * lParam2 = number of bytes to write
105 * Returns: number of bytes written, -1 for error (error code in
106 * wErrorRet)
107 * NOTE: lDiskOffset should be updated
110 HPSTR pch = (HPSTR) lParam1;
111 LONG cch = (LONG) lParam2;
112 LONG count;
114 count = _hwrite16((HFILE32)lpmmioinfo->adwInfo[0], pch, cch);
115 if (count != -1)
116 lpmmioinfo->lDiskOffset += count;
118 return count;
121 case MMIOM_SEEK: {
122 /* Parameters:
123 * lParam1 = new position
124 * lParam2 = from whence to seek (SEEK_SET, SEEK_CUR, SEEK_END)
125 * Returns: new file postion, -1 on error
126 * NOTE: lDiskOffset should be updated
129 LONG Offset = (LONG) lParam1;
130 LONG Whence = (LONG) lParam2;
131 LONG pos;
133 pos = _llseek32((HFILE32)lpmmioinfo->adwInfo[0], Offset, Whence);
134 if (pos != -1)
135 lpmmioinfo->lDiskOffset = pos;
137 return pos;
140 case MMIOM_RENAME: {
141 /* Parameters:
142 * lParam1 = old name
143 * lParam2 = new name
144 * Returns: zero on success, non-zero on failure
147 FIXME(mmio, "MMIOM_RENAME unimplemented\n");
148 return MMIOERR_FILENOTFOUND;
151 default:
152 WARN(mmio, "unexpected message %u\n", uMessage);
153 return 0;
156 return 0;
159 /**************************************************************************
160 * mmioDosIOProc [internal]
162 static LRESULT mmioMemIOProc(LPMMIOINFO16 lpmmioinfo, UINT16 uMessage, LPARAM lParam1, LPARAM lParam2) {
163 return 0;
166 /**************************************************************************
167 * mmioOpenW [WINMM.123]
169 HMMIO32 WINAPI mmioOpen32W(LPWSTR szFileName, MMIOINFO32 * lpmmioinfo,
170 DWORD dwOpenFlags)
172 LPSTR szFn = HEAP_strdupWtoA(GetProcessHeap(),0,szFileName);
173 HMMIO32 ret = mmioOpen16(szFn,(LPMMIOINFO16)lpmmioinfo,dwOpenFlags);
175 HeapFree(GetProcessHeap(),0,szFn);
176 return ret;
179 /**************************************************************************
180 * mmioOpenA [WINMM.122]
182 HMMIO32 WINAPI mmioOpen32A(LPSTR szFileName, MMIOINFO32 * lpmmioinfo,
183 DWORD dwOpenFlags)
185 return mmioOpen16(szFileName,(LPMMIOINFO16)lpmmioinfo,dwOpenFlags);
188 /**************************************************************************
189 * mmioOpen [MMSYSTEM.1210]
191 HMMIO16 WINAPI mmioOpen16(LPSTR szFileName, MMIOINFO16 * lpmmioinfo,
192 DWORD dwOpenFlags)
194 LPMMIOINFO16 lpmminfo;
195 HMMIO16 hmmio;
196 UINT16 result;
198 TRACE(mmio, "('%s', %p, %08lX);\n", szFileName, lpmmioinfo, dwOpenFlags);
200 hmmio = GlobalAlloc16(GHND, sizeof(MMIOINFO16));
201 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
202 if (lpmminfo == NULL)
203 return 0;
204 memset(lpmminfo, 0, sizeof(MMIOINFO16));
206 /* assume DOS file if not otherwise specified */
207 if (!lpmmioinfo ||
208 (lpmmioinfo->fccIOProc == 0 && lpmmioinfo->pIOProc == NULL)) {
210 lpmminfo->fccIOProc = mmioFOURCC('D', 'O', 'S', ' ');
211 lpmminfo->pIOProc = (LPMMIOPROC16) mmioDosIOProc;
213 /* if just the four character code is present, look up IO proc */
214 else if (lpmmioinfo->pIOProc == NULL) {
216 lpmminfo->fccIOProc = lpmmioinfo->fccIOProc;
217 lpmminfo->pIOProc = mmioInstallIOProc16(lpmmioinfo->fccIOProc, NULL, MMIO_FINDPROC);
220 /* if IO proc specified, use it and specified four character code */
221 else {
223 lpmminfo->fccIOProc = lpmmioinfo->fccIOProc;
224 lpmminfo->pIOProc = lpmmioinfo->pIOProc;
227 if (dwOpenFlags & MMIO_ALLOCBUF) {
228 if ((result = mmioSetBuffer(hmmio, NULL, MMIO_DEFAULTBUFFER, 0))) {
229 if (lpmmioinfo)
230 lpmmioinfo->wErrorRet = result;
231 return 0;
235 lpmminfo->hmmio = hmmio;
237 /* call IO proc to actually open file */
238 result = (UINT16) mmioSendMessage(hmmio, MMIOM_OPEN, (LPARAM) szFileName, (LPARAM) 0);
240 GlobalUnlock16(hmmio);
242 if (result != 0) {
243 GlobalFree16(hmmio);
244 return 0;
247 return hmmio;
251 /**************************************************************************
252 * mmioClose [MMSYSTEM.1211]
254 UINT16 WINAPI mmioClose(HMMIO16 hmmio, UINT16 uFlags)
256 LPMMIOINFO16 lpmminfo;
257 UINT16 result;
259 TRACE(mmio, "(%04X, %04X);\n", hmmio, uFlags);
261 lpmminfo = (LPMMIOINFO16) GlobalLock16(hmmio);
262 if (lpmminfo == NULL)
263 return 0;
265 /* flush the file - if error reported, ignore */
266 if (mmioFlush(hmmio, MMIO_EMPTYBUF) != 0)
267 lpmminfo->dwFlags &= ~MMIO_DIRTY;
269 result = (UINT16) mmioSendMessage(hmmio, MMIOM_CLOSE, (LPARAM) uFlags, (LPARAM) 0);
271 mmioSetBuffer(hmmio, NULL, 0, 0);
273 GlobalUnlock16(hmmio);
274 GlobalFree16(hmmio);
276 return result;
281 /**************************************************************************
282 * mmioRead [MMSYSTEM.1212]
284 LONG WINAPI mmioRead(HMMIO16 hmmio, HPSTR pch, LONG cch)
286 LONG count;
287 LPMMIOINFO16 lpmminfo;
289 TRACE(mmio, "(%04X, %p, %ld);\n", hmmio, pch, cch);
291 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
292 if (lpmminfo == NULL)
293 return -1;
295 count = mmioSendMessage(hmmio, MMIOM_READ, (LPARAM) pch, (LPARAM) cch);
297 GlobalUnlock16(hmmio);
298 TRACE(mmio, "count=%ld\n", count);
299 return count;
304 /**************************************************************************
305 * mmioWrite [MMSYSTEM.1213]
307 LONG WINAPI mmioWrite(HMMIO16 hmmio, HPCSTR pch, LONG cch)
309 LONG count;
310 LPMMIOINFO16 lpmminfo;
312 TRACE(mmio, "(%04X, %p, %ld);\n", hmmio, pch, cch);
314 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
315 if (lpmminfo == NULL)
316 return -1;
318 count = mmioSendMessage(hmmio, MMIOM_WRITE, (LPARAM) pch, (LPARAM) cch);
320 GlobalUnlock16(hmmio);
321 TRACE(mmio, "count=%ld\n", count);
322 return count;
325 /**************************************************************************
326 * mmioSeek [MMSYSTEM.1214]
328 LONG WINAPI mmioSeek(HMMIO16 hmmio, LONG lOffset, int iOrigin)
330 int offset;
331 LPMMIOINFO16 lpmminfo;
333 TRACE(mmio, "(%04X, %08lX, %d);\n", hmmio, lOffset, iOrigin);
335 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
336 if (lpmminfo == NULL)
337 return 0;
339 offset = mmioSendMessage(hmmio, MMIOM_SEEK, (LPARAM) lOffset, (LPARAM) iOrigin);
341 GlobalUnlock16(hmmio);
342 return offset;
345 /**************************************************************************
346 * mmioGetInfo [MMSYSTEM.1215]
348 UINT16 WINAPI mmioGetInfo(HMMIO16 hmmio, MMIOINFO16 * lpmmioinfo, UINT16 uFlags)
350 LPMMIOINFO16 lpmminfo;
351 TRACE(mmio, "mmioGetInfo\n");
352 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
353 if (lpmminfo == NULL) return 0;
354 memcpy(lpmmioinfo, lpmminfo, sizeof(MMIOINFO16));
355 GlobalUnlock16(hmmio);
356 return 0;
359 /**************************************************************************
360 * mmioSetInfo [MMSYSTEM.1216]
362 UINT16 WINAPI mmioSetInfo(HMMIO16 hmmio, const MMIOINFO16 * lpmmioinfo, UINT16 uFlags)
364 LPMMIOINFO16 lpmminfo;
365 TRACE(mmio, "mmioSetInfo\n");
366 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
367 if (lpmminfo == NULL) return 0;
368 GlobalUnlock16(hmmio);
369 return 0;
372 /**************************************************************************
373 * mmioSetBuffer [MMSYSTEM.1217]
375 UINT16 WINAPI mmioSetBuffer(HMMIO16 hmmio, LPSTR pchBuffer,
376 LONG cchBuffer, UINT16 uFlags)
378 FIXME(mmio, "empty stub \n");
379 return 0;
382 /**************************************************************************
383 * mmioFlush [MMSYSTEM.1218]
385 UINT16 WINAPI mmioFlush(HMMIO16 hmmio, UINT16 uFlags)
387 LPMMIOINFO16 lpmminfo;
388 TRACE(mmio, "(%04X, %04X)\n", hmmio, uFlags);
389 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
390 if (lpmminfo == NULL) return 0;
391 GlobalUnlock16(hmmio);
392 return 0;
395 /**************************************************************************
396 * mmioAdvance [MMSYSTEM.1219]
398 UINT16 WINAPI mmioAdvance(HMMIO16 hmmio, MMIOINFO16 * lpmmioinfo, UINT16 uFlags)
400 int count = 0;
401 LPMMIOINFO16 lpmminfo;
402 TRACE(mmio, "mmioAdvance\n");
403 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
404 if (lpmminfo == NULL) return 0;
405 if (uFlags == MMIO_READ) {
406 count = _lread32(LOWORD(lpmminfo->adwInfo[0]),
407 lpmmioinfo->pchBuffer, lpmmioinfo->cchBuffer);
409 if (uFlags == MMIO_WRITE) {
410 count = _lwrite32(LOWORD(lpmminfo->adwInfo[0]),
411 lpmmioinfo->pchBuffer, lpmmioinfo->cchBuffer);
413 lpmmioinfo->pchNext += count;
414 GlobalUnlock16(hmmio);
415 lpmminfo->lDiskOffset = _llseek32((HFILE32)lpmminfo->adwInfo[0], 0, SEEK_CUR);
416 return 0;
419 /**************************************************************************
420 * mmioStringToFOURCCA [WINMM.131]
422 FOURCC WINAPI mmioStringToFOURCC32A(LPCSTR sz, UINT32 uFlags)
424 return mmioStringToFOURCC16(sz,uFlags);
427 /**************************************************************************
428 * mmioStringToFOURCCW [WINMM.132]
430 FOURCC WINAPI mmioStringToFOURCC32W(LPCWSTR sz, UINT32 uFlags)
432 LPSTR szA = HEAP_strdupWtoA(GetProcessHeap(),0,sz);
433 FOURCC ret = mmioStringToFOURCC32A(szA,uFlags);
435 HeapFree(GetProcessHeap(),0,szA);
436 return ret;
439 /**************************************************************************
440 * mmioStringToFOURCC [MMSYSTEM.1220]
442 FOURCC WINAPI mmioStringToFOURCC16(LPCSTR sz, UINT16 uFlags)
444 FIXME(mmio, "empty stub \n");
445 return 0;
448 /**************************************************************************
449 * mmioInstallIOProc16 [MMSYSTEM.1221]
451 LPMMIOPROC16 WINAPI mmioInstallIOProc16(FOURCC fccIOProc,
452 LPMMIOPROC16 pIOProc, DWORD dwFlags)
454 TRACE(mmio, "(%ld, %p, %08lX)\n",
455 fccIOProc, pIOProc, dwFlags);
457 if (dwFlags & MMIO_GLOBALPROC) {
458 FIXME(mmio, " global procedures not implemented\n");
461 /* just handle the known procedures for now */
462 switch(dwFlags & (MMIO_INSTALLPROC|MMIO_REMOVEPROC|MMIO_FINDPROC)) {
463 case MMIO_INSTALLPROC:
464 return NULL;
465 case MMIO_REMOVEPROC:
466 return NULL;
467 case MMIO_FINDPROC:
468 if (fccIOProc == FOURCC_DOS)
469 return (LPMMIOPROC16) mmioDosIOProc;
470 else if (fccIOProc == FOURCC_MEM)
471 return (LPMMIOPROC16) mmioMemIOProc;
472 else
473 return NULL;
474 default:
475 return NULL;
479 /**************************************************************************
480 * mmioInstallIOProc32A [WINMM.120]
482 LPMMIOPROC32 WINAPI mmioInstallIOProc32A(FOURCC fccIOProc,
483 LPMMIOPROC32 pIOProc, DWORD dwFlags)
485 FIXME(mmio, "(%c%c%c%c,%p,0x%08lx) -- empty stub \n",
486 (char)((fccIOProc&0xff000000)>>24),
487 (char)((fccIOProc&0x00ff0000)>>16),
488 (char)((fccIOProc&0x0000ff00)>> 8),
489 (char)(fccIOProc&0x000000ff),
490 pIOProc, dwFlags );
491 return 0;
494 /**************************************************************************
495 * mmioSendMessage [MMSYSTEM.1222]
497 LRESULT WINAPI mmioSendMessage(HMMIO16 hmmio, UINT16 uMessage,
498 LPARAM lParam1, LPARAM lParam2)
500 LPMMIOINFO16 lpmminfo;
501 LRESULT result;
502 const char *msg = NULL;
504 #ifdef DEBUG_RUNTIME
505 switch (uMessage) {
506 #define msgname(x) case x: msg = #x; break;
507 msgname(MMIOM_OPEN);
508 msgname(MMIOM_CLOSE);
509 msgname(MMIOM_READ);
510 msgname(MMIOM_WRITE);
511 msgname(MMIOM_WRITEFLUSH);
512 msgname(MMIOM_SEEK);
513 msgname(MMIOM_RENAME);
514 #undef msgname
516 #endif
518 if (msg)
519 TRACE(mmio, "(%04X, %s, %ld, %ld)\n",
520 hmmio, msg, lParam1, lParam2);
521 else
522 TRACE(mmio, "(%04X, %u, %ld, %ld)\n",
523 hmmio, uMessage, lParam1, lParam2);
525 lpmminfo = (LPMMIOINFO16)GlobalLock16(hmmio);
527 if (lpmminfo && lpmminfo->pIOProc)
528 result = (*lpmminfo->pIOProc)((LPSTR)lpmminfo, uMessage, lParam1, lParam2);
529 else
530 result = MMSYSERR_INVALPARAM;
532 GlobalUnlock16(hmmio);
534 return result;
537 /**************************************************************************
538 * mmioDescend [MMSYSTEM.1223]
540 UINT16 WINAPI mmioDescend(HMMIO16 hmmio, MMCKINFO * lpck,
541 const MMCKINFO * lpckParent, UINT16 uFlags)
543 DWORD dwfcc, dwOldPos;
545 TRACE(mmio, "(%04X, %p, %p, %04X);\n",
546 hmmio, lpck, lpckParent, uFlags);
548 if (lpck == NULL)
549 return 0;
551 dwfcc = lpck->ckid;
552 TRACE(mmio, "dwfcc=%08lX\n", dwfcc);
554 dwOldPos = mmioSeek(hmmio, 0, SEEK_CUR);
555 TRACE(mmio, "dwOldPos=%ld\n", dwOldPos);
557 if (lpckParent != NULL) {
558 TRACE(mmio, "seek inside parent at %ld !\n", lpckParent->dwDataOffset);
559 dwOldPos = mmioSeek(hmmio, lpckParent->dwDataOffset, SEEK_SET);
563 It seems to be that FINDRIFF should not be treated the same as the
564 other FINDxxx so I treat it as a MMIO_FINDxxx
566 if ((uFlags & MMIO_FINDCHUNK) || (uFlags & MMIO_FINDRIFF) ||
567 (uFlags & MMIO_FINDLIST)) {
569 if ((uFlags & MMIO_FINDCHUNK) || (uFlags & MMIO_FINDLIST)) {
570 TRACE(mmio, "MMIO_FINDxxxx dwfcc=%08lX !\n", dwfcc);
571 while (TRUE) {
572 LONG ix;
574 ix = mmioRead(hmmio, (LPSTR)lpck, sizeof(MMCKINFO));
575 TRACE(mmio, "after _lread32 ix = %ld req = %d, errno = %d\n",ix,sizeof(MMCKINFO),errno);
576 if (ix < sizeof(MMCKINFO)) {
578 mmioSeek(hmmio, dwOldPos, SEEK_SET);
579 WARN(mmio, "return ChunkNotFound\n");
580 return MMIOERR_CHUNKNOTFOUND;
582 TRACE(mmio, "dwfcc=%08lX ckid=%08lX cksize=%08lX !\n",
583 dwfcc, lpck->ckid, lpck->cksize);
584 if (dwfcc == lpck->ckid)
585 break;
587 dwOldPos += lpck->cksize + 2 * sizeof(DWORD);
588 if (lpck->ckid == FOURCC_RIFF || lpck->ckid == FOURCC_LIST)
589 dwOldPos += sizeof(DWORD);
590 mmioSeek(hmmio, dwOldPos, SEEK_SET);
593 else {
594 if (mmioRead(hmmio, (LPSTR)lpck, sizeof(MMCKINFO)) < sizeof(MMCKINFO)) {
595 mmioSeek(hmmio, dwOldPos, SEEK_SET);
596 WARN(mmio, "return ChunkNotFound 2nd\n");
597 return MMIOERR_CHUNKNOTFOUND;
600 lpck->dwDataOffset = dwOldPos + 2 * sizeof(DWORD);
601 if (lpck->ckid == FOURCC_RIFF || lpck->ckid == FOURCC_LIST)
602 lpck->dwDataOffset += sizeof(DWORD);
603 mmioSeek(hmmio, lpck->dwDataOffset, SEEK_SET);
605 TRACE(mmio, "lpck->ckid=%08lX lpck->cksize=%ld !\n",
606 lpck->ckid, lpck->cksize);
607 TRACE(mmio, "lpck->fccType=%08lX !\n", lpck->fccType);
609 return 0;
612 /**************************************************************************
613 * mmioAscend [MMSYSTEM.1224]
615 UINT16 WINAPI mmioAscend(HMMIO16 hmmio, MMCKINFO * lpck, UINT16 uFlags)
617 FIXME(mmio, "empty stub !\n");
618 return 0;
621 /**************************************************************************
622 * mmioCreateChunk [MMSYSTEM.1225]
624 UINT16 WINAPI mmioCreateChunk(HMMIO16 hmmio, MMCKINFO * lpck, UINT16 uFlags)
626 FIXME(mmio, "empty stub \n");
627 return 0;
631 /**************************************************************************
632 * mmioRename [MMSYSTEM.1226]
634 UINT16 WINAPI mmioRename(LPCSTR szFileName, LPCSTR szNewFileName,
635 MMIOINFO16 * lpmmioinfo, DWORD dwRenameFlags)
637 UINT16 result;
638 LPMMIOINFO16 lpmminfo;
639 HMMIO16 hmmio;
641 TRACE(mmio, "('%s', '%s', %p, %08lX);\n",
642 szFileName, szNewFileName, lpmmioinfo, dwRenameFlags);
644 hmmio = GlobalAlloc16(GHND, sizeof(MMIOINFO16));
645 lpmminfo = (LPMMIOINFO16) GlobalLock16(hmmio);
647 if (lpmmioinfo)
648 memcpy(lpmminfo, lpmmioinfo, sizeof(MMIOINFO16));
650 /* assume DOS file if not otherwise specified */
651 if (lpmminfo->fccIOProc == 0 && lpmminfo->pIOProc == NULL) {
653 lpmminfo->fccIOProc = mmioFOURCC('D', 'O', 'S', ' ');
654 lpmminfo->pIOProc = (LPMMIOPROC16) mmioDosIOProc;
657 /* if just the four character code is present, look up IO proc */
658 else if (lpmminfo->pIOProc == NULL) {
660 lpmminfo->pIOProc = mmioInstallIOProc16(lpmminfo->fccIOProc, NULL, MMIO_FINDPROC);
663 /* (if IO proc specified, use it and specified four character code) */
665 result = (UINT16) mmioSendMessage(hmmio, MMIOM_RENAME, (LPARAM) szFileName, (LPARAM) szNewFileName);
667 GlobalUnlock16(hmmio);
668 GlobalFree16(hmmio);
670 return result;