source: trunk/src/kernel32/virtual.cpp@ 1153

Last change on this file since 1153 was 1153, checked in by phaller, 26 years ago

Fix: VirtualFree not freeing memory fixed

File size: 20.1 KB
Line 
1/* $Id: virtual.cpp,v 1.15 1999-10-06 10:02:34 phaller Exp $ */
2
3/*
4 * Win32 virtual memory functions
5 *
6 * Copyright 1998-1999 Sander van Leeuwen (sandervl@xs4all.nl)
7 * Copyright 1998 Knut St. Osmundsen
8 * Copyright 1998 Peter FitzSimmons
9 *
10 * Parts (VIRTUAL_MapFileA/W) based on Wine code (memory\virtual.c):
11 *
12 * Copyright 1997 Alexandre Julliard
13 *
14 * Project Odin Software License can be found in LICENSE.TXT
15 *
16 */
17
18#include <odin.h>
19#include <odinwrap.h>
20
21#include <os2win.h>
22#include <stdlib.h>
23#include <string.h>
24#include <win\virtual.h>
25#include <heapstring.h>
26#include <handlemanager.h>
27#include "mmap.h"
28#include "oslibdos.h"
29
30
31ODINDEBUGCHANNEL(KERNEL32-VIRTUAL)
32
33
34/***********************************************************************
35 * CreateFileMapping32A (KERNEL32.46)
36 * Creates a named or unnamed file-mapping object for the specified file
37 *
38 * RETURNS
39 * Handle: Success
40 * 0: Mapping object does not exist
41 * NULL: Failure
42 */
43HANDLE WINAPI CreateFileMappingA(
44 HFILE hFile, /* [in] Handle of file to map */
45 SECURITY_ATTRIBUTES *sa, /* [in] Optional security attributes*/
46 DWORD protect, /* [in] Protection for mapping object */
47 DWORD size_high, /* [in] High-order 32 bits of object size */
48 DWORD size_low, /* [in] Low-order 32 bits of object size */
49 LPCSTR name /* [in] Name of file-mapping object */ )
50{
51 return HMCreateFileMapping(hFile, sa, protect, size_high, size_low, name);
52}
53
54
55/***********************************************************************
56 * CreateFileMapping32W (KERNEL32.47)
57 * See CreateFileMapping32A
58 */
59HANDLE WINAPI CreateFileMappingW( HFILE hFile, LPSECURITY_ATTRIBUTES attr,
60 DWORD protect, DWORD size_high,
61 DWORD size_low, LPCWSTR name )
62{
63 LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
64 HANDLE ret = CreateFileMappingA( hFile, attr, protect,
65 size_high, size_low, nameA );
66 HeapFree( GetProcessHeap(), 0, nameA );
67 return ret;
68}
69
70
71/***********************************************************************
72 * OpenFileMapping32A (KERNEL32.397)
73 * Opens a named file-mapping object.
74 *
75 * RETURNS
76 * Handle: Success
77 * NULL: Failure
78 */
79HANDLE WINAPI OpenFileMappingA(
80 DWORD access, /* [in] Access mode */
81 BOOL inherit, /* [in] Inherit flag */
82 LPCSTR name ) /* [in] Name of file-mapping object */
83{
84 dprintf(("OpenFileMappingA: %x %d %s", access, inherit, name));
85 return HMOpenFileMapping(access, inherit, name);
86}
87
88
89/***********************************************************************
90 * OpenFileMapping32W (KERNEL32.398)
91 * See OpenFileMapping32A
92 */
93HANDLE WINAPI OpenFileMappingW( DWORD access, BOOL inherit, LPCWSTR name)
94{
95 LPSTR nameA = HEAP_strdupWtoA( GetProcessHeap(), 0, name );
96 HANDLE ret = OpenFileMappingA( access, inherit, nameA );
97 HeapFree( GetProcessHeap(), 0, nameA );
98 return ret;
99}
100
101
102/***********************************************************************
103 * MapViewOfFile (KERNEL32.385)
104 * Maps a view of a file into the address space
105 *
106 * RETURNS
107 * Starting address of mapped view
108 * NULL: Failure
109 */
110LPVOID WINAPI MapViewOfFile(
111 HANDLE mapping, /* [in] File-mapping object to map */
112 DWORD access, /* [in] Access mode */
113 DWORD offset_high, /* [in] High-order 32 bits of file offset */
114 DWORD offset_low, /* [in] Low-order 32 bits of file offset */
115 DWORD count /* [in] Number of bytes to map */
116)
117{
118 return MapViewOfFileEx( mapping, access, offset_high,
119 offset_low, count, NULL );
120}
121
122
123/***********************************************************************
124 * MapViewOfFileEx (KERNEL32.386)
125 * Maps a view of a file into the address space
126 *
127 * RETURNS
128 * Starting address of mapped view
129 * NULL: Failure
130 */
131LPVOID WINAPI MapViewOfFileEx(
132 HANDLE handle, /* [in] File-mapping object to map */
133 DWORD access, /* [in] Access mode */
134 DWORD offset_high, /* [in] High-order 32 bits of file offset */
135 DWORD offset_low, /* [in] Low-order 32 bits of file offset */
136 DWORD count, /* [in] Number of bytes to map */
137 LPVOID addr /* [in] Suggested starting address for mapped view */
138)
139{
140 return HMMapViewOfFileEx(handle, access, offset_high, offset_low, count, addr);
141}
142
143
144/***********************************************************************
145 * FlushViewOfFile (KERNEL32.262)
146 * Writes to the disk a byte range within a mapped view of a file
147 *
148 * RETURNS
149 * TRUE: Success
150 * FALSE: Failure
151 */
152BOOL WINAPI FlushViewOfFile(
153 LPCVOID base, /* [in] Start address of byte range to flush */
154 DWORD cbFlush /* [in] Number of bytes in range */
155)
156{
157 Win32MemMap *map;
158 DWORD offset;
159
160 if (!base)
161 {
162 SetLastError( ERROR_INVALID_PARAMETER );
163 return FALSE;
164 }
165 map = Win32MemMapView::findMapByView((ULONG)base, &offset, MEMMAP_ACCESS_READ);
166 if(map == NULL) {
167 SetLastError( ERROR_FILE_NOT_FOUND );
168 return FALSE;
169 }
170 return map->flushView(offset, cbFlush);
171}
172
173
174/***********************************************************************
175 * UnmapViewOfFile (KERNEL32.540)
176 * Unmaps a mapped view of a file.
177 *
178 * NOTES
179 * Should addr be an LPCVOID?
180 *
181 * RETURNS
182 * TRUE: Success
183 * FALSE: Failure
184 */
185BOOL WINAPI UnmapViewOfFile(LPVOID addr /* [in] Address where mapped view begins */
186)
187{
188 Win32MemMap *map;
189 Win32MemMapView *view;
190
191 DWORD offset;
192
193 if (!addr)
194 {
195 SetLastError( ERROR_INVALID_PARAMETER );
196 return FALSE;
197 }
198 map = Win32MemMapView::findMapByView((ULONG)addr, &offset, MEMMAP_ACCESS_READ, &view);
199 if(map == NULL) {
200 SetLastError( ERROR_FILE_NOT_FOUND );
201 return FALSE;
202 }
203 return map->unmapViewOfFile(view);
204}
205
206/***********************************************************************
207 * VIRTUAL_MapFileW
208 *
209 * Helper function to map a file to memory:
210 * name - file name
211 * [RETURN] ptr - pointer to mapped file
212 */
213HANDLE WINAPI VIRTUAL_MapFileW( LPCWSTR name , LPVOID *lpMapping)
214{
215 HANDLE hFile, hMapping = -1;
216
217 hFile = CreateFileW( name, GENERIC_READ, FILE_SHARE_READ, NULL,
218 OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
219 if (hFile != INVALID_HANDLE_VALUE)
220 {
221 hMapping = CreateFileMappingA( hFile, NULL, PAGE_READONLY, 0, 0, NULL );
222 CloseHandle( hFile );
223 if (hMapping != INVALID_HANDLE_VALUE)
224 {
225 *lpMapping = MapViewOfFile( hMapping, FILE_MAP_READ, 0, 0, 0 );
226 }
227 }
228 return hMapping;
229}
230
231/***********************************************************************
232 * VIRTUAL_MapFileA
233 *
234 * Helper function to map a file to memory:
235 * name - file name
236 * [RETURN] ptr - pointer to mapped file
237 */
238HANDLE WINAPI VIRTUAL_MapFileA( LPCSTR name , LPVOID *lpMapping)
239{
240 HANDLE hFile, hMapping = -1;
241
242 hFile = CreateFileA(name, GENERIC_READ, FILE_SHARE_READ, NULL,
243 OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
244 if (hFile != INVALID_HANDLE_VALUE)
245 {
246 hMapping = CreateFileMappingA( hFile, NULL, PAGE_READONLY, 0, 0, NULL );
247 CloseHandle( hFile );
248 if (hMapping != INVALID_HANDLE_VALUE)
249 {
250 *lpMapping = MapViewOfFile( hMapping, FILE_MAP_READ, 0, 0, 0 );
251 }
252 }
253 return hMapping;
254}
255
256//******************************************************************************
257//******************************************************************************
258LPVOID WIN32API VirtualAlloc(LPVOID lpvAddress, DWORD cbSize, DWORD fdwAllocationType,
259 DWORD fdwProtect)
260{
261 PVOID Address = lpvAddress;
262 ULONG flag = 0, base;
263 DWORD rc;
264
265 dprintf(("VirtualAlloc at %X; %d bytes, fAlloc %d, fProtect %d\n", (int)lpvAddress, cbSize, fdwAllocationType, fdwProtect));
266
267 if (cbSize > 0x7fc00000) /* 2Gb - 4Mb */
268 {
269 dprintf(("VirtualAlloc: size too large"));
270 SetLastError( ERROR_OUTOFMEMORY );
271 return NULL;
272 }
273
274 if (!(fdwAllocationType & (MEM_COMMIT | MEM_RESERVE)) ||
275 (fdwAllocationType & ~(MEM_COMMIT | MEM_RESERVE)))
276 {
277 dprintf(("VirtualAlloc: Invalid parameter"));
278 SetLastError( ERROR_INVALID_PARAMETER );
279 return NULL;
280 }
281
282 if(fdwAllocationType & MEM_COMMIT) {
283 dprintf(("VirtualAlloc: commit\n"));
284 flag = PAG_COMMIT;
285 }
286 if(fdwProtect & PAGE_READONLY) flag |= PAG_READ;
287 if(fdwProtect & PAGE_READWRITE) flag |= (PAG_READ | PAG_WRITE);
288 if(fdwProtect & PAGE_WRITECOPY) flag |= (PAG_READ | PAG_WRITE);
289
290 if(fdwProtect & PAGE_EXECUTE_READWRITE) flag |= (PAG_EXECUTE | PAG_WRITE | PAG_READ);
291 if(fdwProtect & PAGE_EXECUTE_READ) flag |= (PAG_EXECUTE | PAG_READ);
292 if(fdwProtect & PAGE_EXECUTE) flag |= PAG_EXECUTE;
293
294 if(fdwProtect & PAGE_GUARD) flag |= PAG_GUARD;
295
296 //just do this if other options are used
297 if(!(flag & (PAG_READ | PAG_WRITE | PAG_EXECUTE)) || flag == 0)
298 {
299 dprintf(("VirtualAlloc: Unknown protection flags, default to read/write"));
300 flag |= PAG_READ | PAG_WRITE;
301 }
302
303 if(fdwAllocationType & MEM_COMMIT && lpvAddress != NULL)
304 {
305 Address = lpvAddress;
306
307 rc = OSLibDosSetMem(lpvAddress, cbSize, flag);
308
309 //might try to commit larger part with same base address
310 if(rc == OSLIB_ERROR_ACCESS_DENIED && cbSize > 4096 )
311 { //knut: AND more than one page
312 char *newbase = (char *)lpvAddress + ((cbSize-1) & 0xFFFFF000); //knut: lets not start after the last page!
313 ULONG size, os2flags;
314
315 while(newbase >= (char *)lpvAddress)
316 { //knut: should check first page to!!
317 size = 4096;
318 os2flags = 0;
319 rc = OSLibDosQueryMem(newbase, &size, &os2flags);
320 if(rc)
321 break;
322
323 if(os2flags & PAG_COMMIT)
324 {
325 newbase += 4096;
326 break;
327 }
328 newbase -= 4096;
329 }
330
331 if(rc == 0)
332 {
333 //In case it wants to commit bytes that fall into the last
334 //page of the previous commit command
335 if(cbSize > ((int)newbase - (int)lpvAddress))
336 rc = OSLibDosSetMem(newbase, cbSize - ((int)newbase - (int)lpvAddress), flag);
337 }
338 else return(NULL);
339
340 }
341 else
342 {
343 if(rc == OSLIB_ERROR_INVALID_ADDRESS) {
344 rc = OSLibDosAllocMem(&Address, cbSize, flag );
345 }
346 else
347 if(rc) dprintf(("Unexpected DosSetMem error %x", rc));
348 }
349 }
350 else
351 {
352 rc = OSLibDosAllocMem(&Address, cbSize, flag);
353 }
354
355 if(rc)
356 {
357 dprintf(("DosSetMem returned %d\n", rc));
358 SetLastError( ERROR_OUTOFMEMORY );
359 return(NULL);
360 }
361
362 dprintf(("VirtualAlloc returned %X\n", Address));
363 return(Address);
364}
365//******************************************************************************
366//******************************************************************************
367ODINFUNCTION3(BOOL, VirtualFree, LPVOID, lpvAddress,
368 DWORD, cbSize,
369 DWORD, FreeType)
370{
371 DWORD rc;
372
373 // verify parameters
374 if ( (lpvAddress == NULL) ||
375 ( (FreeType & MEM_RELEASE) &&
376 (cbSize != 0) )
377 )
378 {
379 SetLastError(ERROR_INVALID_PARAMETER);
380 return(FALSE);
381 }
382
383 if(FreeType & MEM_DECOMMIT)
384 rc = OSLibDosSetMem(lpvAddress, cbSize, PAG_DECOMMIT);
385 else
386 rc = OSLibDosFreeMem(lpvAddress); //MEM_RELEASE, cbSize == 0 (or should be)
387
388 if(rc)
389 {
390 dprintf(("KERNEL32:VirtualFree rc = #%d\n",
391 rc));
392 SetLastError(ERROR_GEN_FAILURE);
393 return(FALSE);
394 }
395
396 return(TRUE);
397}
398//******************************************************************************
399//LPVOID lpvAddress; /* address of region of committed pages */
400//DWORD cbSize; /* size of the region */
401//DWORD fdwNewProtect; /* desired access protection */
402//PDWORD pfdwOldProtect; /* address of variable to get old protection */
403//TODO: Not 100% complete
404//TODO: SetLastError on failure
405//******************************************************************************
406BOOL WIN32API VirtualProtect(LPVOID lpvAddress, DWORD cbSize, DWORD fdwNewProtect,
407 DWORD *pfdwOldProtect)
408{
409 DWORD rc;
410 ULONG pageFlags = 0;
411 int npages;
412
413 dprintf(("VirtualProtect %X; %d bytes, new flags %X (%X)\n", (int)lpvAddress, cbSize, fdwNewProtect, pfdwOldProtect));
414 if(pfdwOldProtect == NULL)
415 return(FALSE);
416
417 rc = OSLibDosQueryMem(lpvAddress, &cbSize, &pageFlags);
418 if(rc) {
419 dprintf(("DosQueryMem returned %d\n", rc));
420 return(FALSE);
421 }
422 dprintf(("Old memory flags %X\n", pageFlags));
423 *pfdwOldProtect = 0;
424 if(pageFlags & PAG_READ && !(pageFlags & PAG_WRITE))
425 *pfdwOldProtect |= PAGE_READONLY;
426 if(pageFlags & (PAG_WRITE))
427 *pfdwOldProtect |= PAGE_READWRITE;
428
429 if((pageFlags & (PAG_WRITE | PAG_EXECUTE)) == (PAG_WRITE | PAG_EXECUTE))
430 *pfdwOldProtect |= PAGE_EXECUTE_READWRITE;
431 else
432 if(pageFlags & PAG_EXECUTE)
433 *pfdwOldProtect |= PAGE_EXECUTE_READ;
434
435 if(pageFlags & PAG_GUARD)
436 *pfdwOldProtect |= PAGE_GUARD;
437 pageFlags = 0;
438
439 if(fdwNewProtect & PAGE_READONLY) pageFlags |= PAG_READ;
440 if(fdwNewProtect & PAGE_READWRITE) pageFlags |= (PAG_READ | PAG_WRITE);
441 if(fdwNewProtect & PAGE_WRITECOPY) pageFlags |= (PAG_READ | PAG_WRITE);
442 if(fdwNewProtect & PAGE_EXECUTE_READ) pageFlags |= (PAG_EXECUTE | PAG_READ);
443 if(fdwNewProtect & PAGE_EXECUTE_READWRITE)
444 pageFlags |= (PAG_EXECUTE | PAG_WRITE | PAG_READ);
445 if(fdwNewProtect & PAGE_EXECUTE_WRITECOPY)
446 pageFlags |= (PAG_EXECUTE | PAG_WRITE | PAG_READ);
447 if(fdwNewProtect & PAGE_GUARD) pageFlags |= PAG_GUARD;
448//Not supported in OS/2??
449// if(fdwNewProtect & PAGE_NOACCESS)
450
451 dprintf(("New memory flags %X\n", pageFlags));
452 if(pageFlags == 0) {
453 dprintf(("pageFlags == 0\n"));
454 return(TRUE); //nothing to do
455 }
456 ULONG offset = ((ULONG)lpvAddress & 0xFFF);
457 npages = (cbSize >> 12);
458 if(cbSize & 0xFFF + offset) {
459 npages++;
460 }
461
462 lpvAddress = (LPVOID)((int)lpvAddress & ~0xFFF);
463 cbSize = npages*4096;
464 dprintf(("lpvAddress = %X, cbSize = %d\n", lpvAddress, cbSize));
465
466 rc = OSLibDosSetMem(lpvAddress, cbSize, pageFlags);
467 if(rc) {
468 dprintf(("DosSetMem returned %d\n", rc));
469 return(FALSE);
470 }
471 return(TRUE);
472}
473//******************************************************************************
474//******************************************************************************
475DWORD WIN32API VirtualQuery(LPCVOID lpvAddress, LPMEMORY_BASIC_INFORMATION pmbiBuffer,
476 DWORD cbLength)
477{
478 ULONG cbRangeSize, dAttr;
479 DWORD rc;
480
481 if(lpvAddress == NULL || pmbiBuffer == NULL || cbLength == 0) {
482 return 0;
483 }
484
485 cbRangeSize = cbLength & ~0xFFF;
486 if(cbLength & 0xFFF) {
487 cbRangeSize += PAGE_SIZE;
488 }
489 rc = OSLibDosQueryMem((LPVOID)lpvAddress, &cbRangeSize, &dAttr);
490 if(rc) {
491 dprintf(("VirtualQuery - DosQueryMem %x %x returned %d\n", lpvAddress, cbLength, rc));
492 return 0;
493 }
494 memset(pmbiBuffer, 0, sizeof(MEMORY_BASIC_INFORMATION));
495 pmbiBuffer->BaseAddress = (LPVOID)lpvAddress;
496 pmbiBuffer->RegionSize = cbRangeSize;
497 if(dAttr & PAG_READ && !(dAttr & PAG_WRITE))
498 pmbiBuffer->Protect |= PAGE_READONLY;
499 if(dAttr & PAG_WRITE)
500 pmbiBuffer->Protect |= PAGE_READWRITE;
501
502 if((dAttr & (PAG_WRITE | PAG_EXECUTE)) == (PAG_WRITE | PAG_EXECUTE))
503 pmbiBuffer->Protect |= PAGE_EXECUTE_READWRITE;
504 else
505 if(dAttr & PAG_EXECUTE)
506 pmbiBuffer->Protect |= PAGE_EXECUTE_READ;
507
508 if(dAttr & PAG_GUARD)
509 pmbiBuffer->Protect |= PAGE_GUARD;
510
511 if(dAttr & PAG_FREE)
512 pmbiBuffer->State = MEM_FREE;
513 else
514 if(dAttr & PAG_COMMIT)
515 pmbiBuffer->State = MEM_COMMIT;
516 else pmbiBuffer->State = MEM_RESERVE;
517
518 if(!(dAttr & PAG_SHARED))
519 pmbiBuffer->Type = MEM_PRIVATE;
520
521 //TODO: This is not correct: AllocationProtect should contain the protection
522 // flags used in the initial call to VirtualAlloc
523 pmbiBuffer->AllocationProtect = pmbiBuffer->Protect;
524 if(dAttr & PAG_BASE) {
525 pmbiBuffer->AllocationBase = (LPVOID)lpvAddress;
526 }
527 else {
528 while(lpvAddress > 0) {
529 rc = OSLibDosQueryMem((LPVOID)lpvAddress, &cbRangeSize, &dAttr);
530 if(rc) {
531 dprintf(("VirtualQuery - OSLibDosQueryMem %x %x returned %d\n", lpvAddress, cbLength, rc));
532 break;
533 }
534 if(dAttr & PAG_BASE) {
535 pmbiBuffer->AllocationBase = (LPVOID)lpvAddress;
536 break;
537 }
538 lpvAddress = (LPVOID)((ULONG)lpvAddress - PAGE_SIZE);
539 }
540 }
541 return sizeof(MEMORY_BASIC_INFORMATION);
542}
543//******************************************************************************
544//******************************************************************************
545BOOL WIN32API VirtualLock( LPVOID lpAddress, DWORD dwSize )
546{
547 dprintf(("VirtualLock at %d; %d bytes - stub (TRUE)\n", (int)lpAddress, dwSize));
548 return TRUE;
549}
550
551//******************************************************************************
552BOOL WIN32API VirtualUnlock( LPVOID lpAddress, DWORD dwSize )
553{
554 dprintf(("VirtualUnlock at %d; %d bytes - stub (TRUE)\n", (int)lpAddress, dwSize));
555 return TRUE;
556}
557
558/*****************************************************************************
559 * Name : BOOL VirtualProtectEx
560 * Purpose : The VirtualProtectEx function changes the access protection on
561 * a region of committed pages in the virtual address space of a specified
562 * process. Note that this function differs from VirtualProtect,
563 * which changes the access protection on the calling process only.
564 * Parameters: HANDLE hProcess handle of process
565 * LPVOID lpvAddress address of region of committed pages
566 * DWORD cbSize size of region
567 * DWORD fdwNewProtect desired access protection
568 * PDWORD pfdwOldProtect address of variable to get old protection
569 * Variables :
570 * Result : size of target buffer
571 * Remark :
572 * Status : UNTESTED STUB
573 *
574 * Author : Patrick Haller [Mon, 1998/06/15 08:00]
575 *****************************************************************************/
576
577BOOL WIN32API VirtualProtectEx(HANDLE hProcess,
578 LPVOID lpvAddress,
579 DWORD cbSize,
580 DWORD fdwNewProtect,
581 LPDWORD pfdwOldProtect)
582{
583 dprintf(("KERNEL32: VirtualProtectEx(%08x,%08xh,%08xh,%08xh,%08xh) not implemented for different processes.\n",
584 hProcess,
585 lpvAddress,
586 cbSize,
587 fdwNewProtect,
588 pfdwOldProtect));
589
590 return VirtualProtect(lpvAddress, cbSize, fdwNewProtect, pfdwOldProtect);
591}
592
593
594/*****************************************************************************
595 * Name : DWORD VirtualQueryEx
596 * Purpose : The VirtualQueryEx function provides information about a range
597 * of pages within the virtual address space of a specified process.
598 * Parameters: HANDLE hProcess handle of process
599 * LPCVOID lpvAddress address of region
600 * LPMEMORY_BASIC_INFORMATION pmbiBuffer address of information buffer
601 * DWORD cbLength size of buffer
602 * Variables :
603 * Result : number of bytes returned in buffer
604 * Remark :
605 * Status : UNTESTED STUB
606 *
607 * Author : Patrick Haller [Mon, 1998/06/15 08:00]
608 *****************************************************************************/
609
610DWORD WIN32API VirtualQueryEx(HANDLE hProcess,
611 LPCVOID lpvAddress,
612 LPMEMORY_BASIC_INFORMATION pmbiBuffer,
613 DWORD cbLength)
614{
615 dprintf(("KERNEL32: VirtualQueryEx(%08x,%08xh,%08xh,%08xh) not implemented for different processes.\n",
616 hProcess,
617 lpvAddress,
618 pmbiBuffer,
619 cbLength));
620
621 return VirtualQuery(lpvAddress, pmbiBuffer, cbLength);
622}
623//******************************************************************************
624//******************************************************************************
Note: See TracBrowser for help on using the repository browser.