Inspect your memory image and see fragmentation

The VirtualQueryEx function can help to inspect the memory of a particular process. It returns information about the various memory pages allocated to a process. If a block is marked as MEM_IMAGE, it’s a loaded module, like an EXE or DLL, so you can use the GetModuleFileName function to find the full path of the loaded module.

Run the code below, which displays 2 instances of a form and graphically maps the process’s memory allocation map onto the forms. The yellow/green blocks indicate free memory. The white is MEM_COMMIT, and the red is a MEM_IMAGE loaded module. Move your mouse around the form and you can see the BROWSE record reflect what memory block you’re over. Between the first and the second form instances, memory is fragmented by allocating some huge strings and freeing every other one. You can see the difference between the two graphs.

To show the modules loaded in the process, try

SELECT DISTINCT filename FROM memmap

Try implementing an interface or calling a VFP COM server via early binding to make VFP allocate some memory using the PAGE_EXECUTE_READWRITE flag

Or try using a multithreaded VFP Com server and hit it with many threads (perhaps using IIS) and perhaps see more PAGE_GUARD attributes: one for each thread’s stack (if each thread’s stack is allowed to grow).

CLEAR ALL

CLEAR

#define PROCESSOR_ARCHITECTURE_INTEL 0

#define PROCESSOR_ARCHITECTURE_MIPS 1

#define PROCESSOR_ARCHITECTURE_ALPHA 2

#define PROCESSOR_ARCHITECTURE_PPC 3

#define PROCESSOR_ARCHITECTURE_SHX 4

#define PROCESSOR_ARCHITECTURE_ARM 5

#define PROCESSOR_ARCHITECTURE_IA64 6

#define PROCESSOR_ARCHITECTURE_ALPHA64 7

#define PROCESSOR_ARCHITECTURE_MSIL 8

#define PROCESSOR_ARCHITECTURE_AMD64 9

#define PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 10

*State:

#define MEM_COMMIT 0x1000

#define MEM_RESERVE 0x2000

#define MEM_FREE 0x10000

*Protect:

#define PAGE_NOACCESS 0x01

#define PAGE_READONLY 0x02

#define PAGE_READWRITE 0x04

#define PAGE_WRITECOPY 0x08

#define PAGE_EXECUTE 0x10

#define PAGE_EXECUTE_READ 0x20

#define PAGE_EXECUTE_READWRITE 0x40

#define PAGE_EXECUTE_WRITECOPY 0x80

#define PAGE_GUARD 0x100

#define PAGE_NOCACHE 0x200

#define PAGE_WRITECOMBINE 0x400

*Type

#define SEC_IMAGE 0x1000000

#define MEM_IMAGE SEC_IMAGE

#define MEM_PRIVATE 0x20000

#define MEM_MAPPED 0x40000

#define MEM_RESET 0x80000

#define MEM_TOP_DOWN 0x100000

#define MEM_LARGE_PAGES 0x20000000

#define MEM_4MB_PAGES 0x80000000

#define SEC_RESERVE 0x4000000

#define PROCESS_DUP_HANDLE (0x0040)

#define PROCESS_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF)

PUBLIC oForm1,oForm2

oForm1=CREATEOBJECT("MemMapForm")

oForm1.Show

oForm1.ReadMem()

*Now do something that takes a lot of memory, like create an In Process COM server

* ox=CREATEOBJECT("t1.c1")

DIMENSION aa(10)

FOR i = 1 TO ALEN(aa)

      aa[i]=SPACE(1.5e7) && make many huge strings

ENDFOR

FOR i = 1 TO ALEN(aa) STEP 2 && now fragment mem

      aa[i]=0

ENDFOR

*Create another instance

oForm2=CREATEOBJECT("MemMapForm")

oForm2.Show

oForm2.ReadMem()

DEFINE CLASS MemMapForm AS form

      AllowOutput=.f.

      height=400

      width=600

      caption=""

      DataSession=2 && private data

      PROCEDURE init

            DECLARE integer VirtualQueryEx IN WIN32API integer hProcess, integer lpAddress,;

                  string @, integer dwLength

            DECLARE GetSystemInfo IN WIN32API string @pSystemInfo

            DECLARE integer GetCurrentProcess IN win32api

            DECLARE integer GetModuleFileName IN WIN32API integer hModule, string @ cBuf, integer nSize

            this.Top=(this.DataSessionId-2) * thisform.Height

      PROCEDURE ReadMem()

            cSystemInfo = SPACE(36) && sizeof(SYSTEM_INFO)

            GetSystemInfo(@cSystemInfo)

            dwPageSize=CTOBIN(SUBSTR(cSystemInfo,1*4+1,4),"4rs")

            IF .f.

                  ?"ProcessorArchitecture ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,0*4+1,4),"4rs"),"@0x")

                  ?"PageSize ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,1*4+1,4),"4rs"),"@0x")

            ?"MinimumApplicationAddress",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,2*4+1,4),"4rs"),"@0x")

            ?"MaximumApplicationAddress",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,3*4+1,4),"4rs"),"@0x")

                  ?"ActiveProcessorMask ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,4*4+1,4),"4rs"),"@0x")

                  ?"NumberOfProcessors ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,5*4+1,4),"4rs"),"@0x")

                  ?"ProcessorType ",CTOBIN(SUBSTR(cSystemInfo,6*4+1,4),"4rs") && 586 = PENTIUM

                  ?"AllocationGranularity ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,7*4+1,4),"4rs"),"@0x")

                  ?"ProcessorLevel ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,8*4+1,2),"2rs"),"@0x")

                  ?"ProcessorRevision ",TRANSFORM(CTOBIN(SUBSTR(cSystemInfo,8*4+1+2,2),"2rs"),"@0x")

            ENDIF

            DECLARE integer GetProcessHeap IN win32api

* ?TRANSFORM(GetProcessHeap (),"@0x")

            *x=CREATEOBJECT("t1.c1")

            CREATE CURSOR MEMMap (BaseAddr i, AllocBase i, AllocProt i, ;

                  RegionSize i, state i, Protect i, Type i,Filename c(200))

            dwPage=1

            DO WHILE .t.

                  cMBI = SPACE(28) && MEMORY_BASIC_INFORMATION

                  VirtualQueryEx(GetCurrentProcess(), dwPage * dwPageSize,@cMBI,LEN(cMBI))

                  IF CTOBIN(SUBSTR(cMBI,0*4+1,4),"4rs")>= 0x7ffe0000

                        EXIT

                  ENDIF

                  cFilename=SPACE(200)

                  INSERT INTO memMap VALUES (;

                        CTOBIN(SUBSTR(cMBI,0*4+1,4),"4rs"),;

                        CTOBIN(SUBSTR(cMBI,1*4+1,4),"4rs"),;

                        CTOBIN(SUBSTR(cMBI,2*4+1,4),"4rs"),;

                        CTOBIN(SUBSTR(cMBI,3*4+1,4),"4rs"),;

                        CTOBIN(SUBSTR(cMBI,4*4+1,4),"4rs"),;

                        CTOBIN(SUBSTR(cMBI,5*4+1,4),"4rs"),;

                        CTOBIN(SUBSTR(cMBI,6*4+1,4),"4rs"),;

                        "")

                  IF BITAND(type,MEM_IMAGE)>0

                        IF AllocBase>0

                              nLen=GetModuleFileName(AllocBase,@cFileName,LEN(cFileName))

                              cFilename=LEFT(cFilename,nLen)

                              REPLACE filename WITH cFilename

                        ENDIF

                  ENDIF

                  dwPage = dwPage + RegionSize/dwPageSize

            ENDDO

            *use the BROWSE to see the data: mouse over the forms and see the current record change

            cBrowName="oBrow"+TRANSFORM(thisform.DataSessionId - 1)

            PUBLIC (cBrowName)

            BROWSE NOWAIT NAME (cBrowName) TITLE "MemMap"+TRANSFORM(thisform.DataSessionId - 1) FIELDS ;

                  BaseAddr=TRANSFORM(BaseAddr,"@0x"),;

                  AllocBase=TRANSFORM(AllocBase,"@0x"),;

                  AllocProt=TRANSFORM(AllocProt,"@0x"),;

                  RegionSize=TRANSFORM(RegionSize,"@0x"),;

            State=IIF(BITAND(state,MEM_FREE)>0,"Free",IIF(BITAND(state,MEM_RESERVE)>0,"Reserve","Commit")),;

                  Protect=TRANSFORM(Protect,"@0x"),;

                  Type=TRANSFORM(Type,"@0x"),;

                  FileName=JUSTFNAME(FileName)

            oBrow = EVALUATE(cBrowName)

            oBrow.height = thisform.Height

            oBrow.Top=thisform.Top

            oBrow.left=thisform.width

            oBrow.width = 800

            *SELECT DISTINCT filename FROM memmap

            dx = thisform.Width && now graph it

            dy = thisform.Height

            nRatio = dx*dy/2^31 && max addr / max pixels

            x0=0

            y0=0

            nPos=0 && from 0 to dx * dy

            SCAN

                  nPos=nPos + RegionSize*nRatio

                  x1= MOD(nPos,dx)

                  y1 = INT(nPos /dx )

                  IF EMPTY(Filename)

                        coff = MOD(RECNO()*41,60)

                        thisform.ForeColor=IIF(BITAND(state,MEM_FREE)>0,0xffff-coff ,0xffffff) && yellow is free

                  ELSE

                        thisform.ForeColor=0xff

                  ENDIF

                  DO WHILE y0 < y1

                        thisform.Line(x0,y0,dx,y0)

                        x0=0

                        y0=y0+1

                  ENDDO

                  thisform.Line(x0,y0,x1,y1)

                  x0=x1

                  y0=y1

            ENDSCAN

      PROCEDURE MouseMove(nButton, nShift, nX, nY)

            dx = thisform.Width

            dy = thisform.Height

            nRatio = dx*dy/2^31 && max addr / max pixels

            nPos = nY * dx+nX

            nAddr = nPos / nRatio

            LOCATE FOR BETWEEN(nAddr, BaseAddr , BaseAddr + RegionSize)

            ACTIVATE WINDOW ("MemMap"+TRANSFORM(thisform.DataSessionId - 1))

            thisform.Caption=TRANSFORM(nAddr,"@0x")+" "+JUSTFNAME(filename)

ENDDEFINE