Page 1 of 1

Filemapping and co

Posted: Wed Jun 06, 2007 12:21 am
by blueznl
More 3.94... should be easy to convert :-) This shows the use of the FileMapping API's, there's another part that I have to clean up which deals with shared memory between apps via filemapping. I'll post that when it's presentable...

Code: Select all

; purebasic survival guide - pb3.94
; filemapping_1.pb - 06.06.2007 ejn (blueznl)
; http://www.xs4all.nl/~bluez/datatalk/purebasic.htm
;
; - file mapping
; - CreateFile_()
; - CloseHandle_()
; - CreateFileMapping_()
; - MapViewOfFile_()
; - UnMapViewOfFile_()
;
; you DEFINITELY want to have the WIN32.HLP file open and at your side, jump to a page called 'File Mapping' and check on
; the subsequent pages
;
; windows offers a mechanism which maps files to (virtual) memory, whenever you change something in that memory,
; that change will be reflected in the file
;
; a program can have multiple views on a (mapped) file, and their contents will all be consistent when accessed through
; the mapping mechanism, all the (mapped) file data will be in memory thus speeding up access, and finally all data
; in that file can be processed as if it was memory
;
; WARNING! Windows NT and Windows 95 have certain restrictions, that may not be covered below (tested on XP only)
;
;
; *** creating a file using winapi calls... 
;
; (simple winapi stuff, but we need it later, so let's first look at this)
;
; create a file for exclusive use
;
file_name.s = "testfile.dat"                                                            ; name of file to create
file_h = CreateFile_(@file_name,#GENERIC_READ|#GENERIC_WRITE,0,0,#CREATE_ALWAYS,0,0)    ; open the file, empty it if it already existed
If file_h = #INVALID_HANDLE_VALUE Or file_h = 0                                         ; things went bonkers...
  Debug "error creating file"
EndIf
;
; now put some data in it
;
bytes_written.l = 0                                                                     ; placeholder in memory where WriteFile_() can store the number of bytes written
For n = 1 To 10360
  buffer.s = "this is line "+RSet(Str(n),4)+Chr(13)+Chr(10)                             ; create some data that we can write to the file
  result = WriteFile_(file_h,@buffer,Len(buffer),@bytes_written,0)                      ; write the data to the file
Next n
;
; and close the file
;
CloseHandle_(file_h)
;
;
; *** granularity
;
; before we delve deeper into filemapping we have to understand a basic building block: system granularity, or
; allocation granularity: this is the smallest block the memory manager can handle or map, and depends on your
; operating system and hardware
;
; certain calls need this information, which can be obtained this way:
;
systeminfo.SYSTEM_INFO
GetSystemInfo_(@systeminfo)
system_granularity = systeminfo\dwAllocationGranularity
Debug "system granularity "+Str(system_granularity)+" bytes"
;
;
; *** filemapping
;
; mapping a file to memory offers us faster access, windows takes care of all caching
;
; let's start by opening an existing file
;
file_name.s = "testfile.dat"                                                            ; name of file to create
file_h = CreateFile_(@file_name,#GENERIC_READ|#GENERIC_WRITE,0,0,#OPEN_EXISTING,0,0)    ; open an existing file
;
; now we map this file to (a part of) (virtual) memory
; 
; when creating a file mapping object, we can specify the maximal size it can cover, this is NOT the buffer size it
; actually uses, it is the MAXIMAL buffer size it ever uses, by setting it to 0 it will be at max the CURRENT size
; of the specified file, check also WIN32.HLP for limitations of NT and 95...
; 
; notice that CreateFile_(), CreateFileMapping_() and MapViewOfFile_() must have matching parameters
;
filemap_name.s = "testmap.dat"                                                          ; we give the mapping a name, though there isn't a real need here
filemap_h = CreateFileMapping_(file_h,0,#PAGE_READWRITE,0,0,@filemap_name)              ; this creates an object that can map memory to the file
;
; with MapViewOfFile_() we specify a 'floating window' that peeks somewhere into the specified file, we can specify where
; this 'view' looks at, and how large it is
;
;   MapViewOfFile_( <object_handle> , <access type> , <offset hi> , <offset lo> , <view size> )
;
; the pointers is 64 bit value, split up over a low and high part, and must be multiples of the allocation granularity
; (aka system granularity)
;
mapview_h = MapViewOfFile_(filemap_h,#FILE_MAP_WRITE,0,0,system_granularity)            ; creates a map which points to the beginning of the file
;
; the returned handle is also the memory address where we can find the mapped data, let's find out what the first 100 bytes
; of the file contained
;
Debug PeekS(mapview_h,100)                                                              ; show 100 bytes
UnmapViewOfFile_(mapview_h)                                                             ; and be done
;
; unfortunately there is no way to 'move' a view, we first have to delete the old one and create a new one
; we have to be carefull that we won't 'overrun' the file, ie. the map should stay within the boundaries of the file
;
; in other words, we 'carve up' the file in multiple blocks of size 'system_granularity', ie. the specified offset
; must be a mutliple of system_granularity
;
; we must also make sure that the last byte of the view stays within the size specified with CreateFileMapping_(),
; this may mean we have to make the view smaller than system_granularity when reaching the end of the file
;
; let's do some fancy graphics :-) the '-' indicate the file, the 'x' indicate the view
;
;   ----- xxxxx ---
;   ----- ----- xxx
; 
; note: whilst sizes specified with CreateFileMapping_() MAY allocate PHYSICAL memory up to the specified size, Windows
; will not keep the data at any cost in memory, and may write it out to the file itself, the view size specified with
; MapViefOfFile_() WILL most likely take up physical memory and certainly take up virtual memory, and is limited by
; the specified virtual memory settings
;
; super large view sizes may slow down your system and may cause problems, yet sizes too small will decrease the
; efficiency of file mapping... you decide what is an appropriate size :-)
;
position = FileSize(file_name)-200                                                      ; let's move to the end of the file
mapoffset = position - (position % system_granularity)                                  ; make sure we start on a multiple of system granularity
viewoffset = position - mapoffset                                                       ; where can we find the requested data in the view?
viewsize = FileSize(file_name) - mapoffset                                              ; how much file is left?
If viewsize > system_granularity                                                        ; please, fred, give us Min() and Max()
  viewsize = system_granularity                                                         
EndIf
mapview_h = MapViewOfFile_(filemap_h,#FILE_MAP_WRITE,0,mapoffset,viewsize)
Debug "view points to position "+Str(mapoffset)+" in file"
Debug "view size "+Str(viewsize)
Debug "requested data starts in file at "+Str(position)
Debug "and in memory (view) at "+Str(viewoffset)
Debug PeekS(mapview_h+viewoffset,100)
;
; ok, we're done, time to clean up
;
UnmapViewOfFile_(mapview_h)
CloseHandle_(filemap_h)                     
CloseHandle_(file_h)
;
; and to verify the result
;
OpenFile(1,file_name)
FileSeek(position)
buffer.s = Space(100)
ReadData(@buffer,100)
Debug buffer
CloseFile(1)
;
Yes. Final twitches of the 3.94 corpse. I need this for some stuff I am working on, so I decided to do the regular 'teach it myself' approach.

Posted: Thu Jun 07, 2007 3:04 pm
by blueznl
And this time with shared memory... or at least the framework to do so...

Code: Select all

; purebasic survival guide - pb3.94
; filemapping_2.pb - 07.06.2007 ejn (blueznl)
; http://www.xs4all.nl/~bluez/datatalk/purebasic.htm
;
; - file mapping in memory (sharing memory between applications)
; - CloseHandle_()
; - CreateFileMapping_()
; - MapViewOfFile_()
; - UnMapViewOfFile_()
;
; you DEFINITELY want to have the WIN32.HLP file open and at your side, jump to a page called 'File Mapping' and check on
; the subsequent pages
;
; check out filemapping_1.pb first, as it explains some of the API calls made here
;
; WARNING! Windows NT and Windows 95 have certain restrictions, that may not be covered below (tested on XP only)
;
;
; it is possible for different applications to share a mapped file, and access that data simultaneously
; interestingly it doesn't have to be a real file but could be just a chunk of memory!
;
;   CreateFileMapping_( <filehandle> , <security attribs> , <protection> , <hi size> , <lo size> , <name> )
;
; by specifying $FFFFFFFF instead of a real handle a block of (virtual) memory is allocated, but there is no associated
; file for it, a name is optional BUT by specifying a name MULTIPLE PROGRAMS CAN ACCESS THE SAME DATA IN MEMORY...
; ...now that's VERY interesting :-)
;
; each program that wants to access that block of data can try to create a filemap using the filemap name
; GetLastError_() can be used to check if the filemapping object already existed or not
;
; let's create a filemap in memory of 1 kilobyte (actually, it would make more sense to use the system granularity
; as that is what's going to be in use anyway, but who cares...)
;
filemap_name.s = "shared_memory"
filemap_h = CreateFileMapping_($FFFFFFFF,0,#PAGE_READWRITE,0,1024,@filemap_name)
lasterror = GetLastError_()
If filemap_h = #INVALID_HANDLE_VALUE Or filemap_h = 0
  Debug "error creating file"
EndIf
If lasterror = #ERROR_ALREADY_EXISTS
  Debug "object already exists"
EndIf
;
; multiple programs can open up the same mappingfile object by using the name, let's open it up for the second time
; CreateFileMapping_() will still return a valid handle, even if it already existed, so we end up with two handles to the
; same object, as we're just fooling around let's close that second one after we found out it already existed :-)
;
filemap2_name.s = "shared_memory"
filemap2_h = CreateFileMapping_($FFFFFFFF,0,#PAGE_READWRITE,0,1024,@filemap2_name)
lasterror = GetLastError_()
If filemap2_h = #INVALID_HANDLE_VALUE Or filemap2_h = 0
  Debug "error creating file"
EndIf
If lasterror = #ERROR_ALREADY_EXISTS
  Debug "object already exists"
  CloseHandle_(filemap2_h)
EndIf
;
; object is there, let's map a view to it
;
mapview_p = MapViewOfFile_(filemap_h,#FILE_MAP_WRITE,0,0,0)
;
; so now we can access 1024 bytes, starting at mapview_p, which we can share with other programs as long as they open
; the same block of memory using CreateFileMapping_()
;
PokeS(mapview_p,"this is a test")
;
; please note: there is no inherent synchronisation mechanism for accessing filemapping objects, for that you have to use
; a mutex (see the examples in mutex_1.pb) or messages (check RegisterWindowMessage_())
;
Debug PeekS(mapview_p)
;
; we're done, so we unmap the view, all data in the filemapping objects stays in there as long as ANY program has that
; object open, but the moment all clients close it... poof
;
CloseHandle_(filemap_h)
;
; next stop: an example of two communicating processes!
;

Posted: Thu Jun 07, 2007 4:05 pm
by Max
Good job.
I'm using a very simple system to send and receive data between programs:

Sender program

Code: Select all

Procedure SndParams(params.s)
  return  = #False
  finame.s = "PARAMS"
  lon = ((len(params)+1023) / 1024) * 1024        ; 1K blocks (not needed)
  MAPfile  = CreateFileMapping_($FFFFFFFF,0,#PAGE_READWRITE,0,lon,finame)
  if MAPfile
    MAPview = MapViewOfFile_(MAPfile,#FILE_MAP_ALL_ACCESS,0,0,lon)
    pokes(MAPview,params)
    UnMapViewOfFile_(MAPview)
    return = #True
  endif
  ProcedureReturn return
EndProcedure
Receiver program:

Code: Select all

Procedure.s RcvParams()
  return.s = ""
  finame.s  = "PARAMS"
  MAPfile   = OpenFileMapping_(#FILE_MAP_ALL_ACCESS,0,finame)
  if MAPfile
    MAPview = MapViewOfFile_(MAPfile,#FILE_MAP_ALL_ACCESS,0,0,0)
    return = peeks(MAPview)
    UnMapViewOfFile_(MAPview)
    CloseHandle_(MAPfile)
  endif
  ProcedureReturn return
EndProcedure
IMPORTANT! Sender program MUST BE active until receiver program has retreived the data. (otherwise, handle of MAPfile is closed).

Posted: Thu Jun 07, 2007 4:57 pm
by blueznl
That's what I was planning to do, but I wanted to understand what I was doing :)

And as I wanted to go one step beyound I needed to know about mutex'es as well...

http://www.purebasic.fr/english/viewtopic.php?t=27439

And why not explain it to others as good as I can, whilst I am learning things myself. Programming is a hobby for me, not a profession :)

(Though in the end, I may use a mix of different mechanisms, that is one mapped file, one mutex, and a message, but I haven't decided yet.)