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