Research on Delphi7 memory management and fastmm (useful for EXE and dll Memory Sharing)

Source: Internet
Author: User
[Switch] Delphi7 memory management and fastmm Research (useful for EXE and dll Memory Sharing) Late Autumn published on Views (32)
Comments (0) category: My diaries
Report

From: http://hi.baidu.com/yehe2316/blog/item/d66d3b03b4440a064afb51f0.html

Research on Delphi7 memory management and fastmm [go]
Author: Liu Guohui
I. Introduction
Fastmm is a third-party Memory Manager for Delphi. It is already well-known abroad, and many people in China are using or wishing to use it, even Borland abandoned its original accused Memory Manager in delphi2007 and switched to fastmm.
However, the complexity of memory management and the lack of fastmm Chinese documents have caused many users in China to encounter many problems during use, and some users have abandoned the use, I used fastmm in a recent project, so I encountered many problems. After exploration and research, I finally solved these problems.

Ii. Why fastmm?
The first reason is that fastmm's performance is nearly twice that of Delphi's default memory manager. You can perform a simple test and run the following code:
VaR
I: integer;
TIC: Cardinal;
S: string;
Begin
TIC: = gettickcount;
Try
For I: = 0 to 100000 do
Begin
Setlength (S, I + 100 );
Edt1.text: = s;
End;
Finally
Setlength (S, 0 );
TIC: = gettickcount-tic;
Messagedlg ('Tic = '+ inttostr (TIC), mtinformation, [mbok], 0 );
End;
End;
In my IBM T23 notebook, fastmm4 (the latest fastmm version) takes about 3300 ms, and the default Memory Manager is about 6200 Ms, fastmm4 delivers performance up to 88%.
The second reason is that the shared memory manager function of fastmm is simple and reliable. When an application is composed of multiple modules (exe and DLL), the transmission of dynamic memory variables between modules, such as string, is a big problem. By default, each module is configured with its own memory manager. The memory allocated by a memory manager must also be stored in the memory manager to be safely released. Otherwise, a memory error occurs, in this way, if another module is released within the module allocation, a memory error occurs. To solve this problem, you need to use the shared memory manager so that each module uses the same memory manager. Delphi's default shared memory manager is borlndmm. dll. This memory manager is unreliable and often suffers from problems. In addition, it must be released along with this DLL when the program is released. Fastmm shared memory manager does not require DLL support and is more reliable.
The third reason is that fastmm also has some auxiliary functions that help program development, such as the memory leak detection function, which can detect whether the program has improperly released memory.

Iii. Problems
If we develop an application with only one EXE module, it is very simple to use fastmm. PAS (fastmm4.pas in the latest version) is the first uses unit in the project file, as shown in the following figure:

Program test;
Uses
Fastmm4,
...
However, in general, our applications are composed of an EXE module and multiple DLL modules. In this way, when dynamic memory variables such as string variables are transferred across modules, for example, the following test program is composed of an EXE and a DLL:

Library test; // test. dll
Uses
Fastmm4 ,...;
Procedure getstr (var s: string; const Len: integer); stdcall;
Begin
Setlength (S, Len); // allocate memory
Fillchar (s [1], Len, 'A ');
End;
Exports
Getstr;
-------------------------------------
Program testprj;
Uses
Fastmm4 ,...;
//------------------
Unit mMain; // test interface
...
Procedure tform1.btndoclick (Sender: tobject );
VaR
I: integer;
S: string;
Begin
Try
For I: = 1 to 10000 do
Begin
Getstr (S, I + 1 );
Edt1.text: = s;
Application. processmessages;
End;
Finally
Setlength (S, 0 );
End;
End;

When the second btndoclick process is executed, a memory error occurs. Why? The string of Delphi carries the reference count. As with the interface variable, once the reference count is 0, the memory is automatically released. In the btndoclick process, call the getstr process and use setlength to allocate a piece of memory to S. At this time, the reference count of this string is 1, and then run the edt1.text: = s statement, the reference count of the string is 2, and the loop calls getstr to re-allocate memory to S, so that the reference count of the original string is reduced by 1, and then edt1.text: = release is executed, instead of in test. DLL release), but there is no error at this time. After the loop is executed, the reference count of another string is 2, but the setlength (S,
0), the string is referenced by edt1.text, and the reference count is 1. When the second btndoclick is executed and edt1.text: = s is executed, if the previous string reference count with a reference count of 1 is reduced to 0, it will be released, and a memory error will occur.
From this, we can see that releasing the memory allocated by other modules in another module does not necessarily result in a memory error immediately. However, if it is executed frequently, a memory error may occur, this uncertain error is concealed and often does not occur during debugging, but it is difficult to find the cause without careful analysis.
To solve this problem, we need to start from the root cause, which is memory management.
1. Delphi Memory Management
Delphi applications can use three memory zones: global memory zone, heap, and stack. The global memory zone stores global variables, stacks are used to pass parameters and return values, and temporary variables in functions, these two types are automatically managed by the compiler, and strings, objects, and dynamic arrays are all allocated from the heap. memory management refers to the management of heap memory, that is, allocate memory from the heap and release the memory allocated from the heap (hereinafter referred to as the memory allocation and release ).
We know that a process has only one stack, so it is easy to mistakenly think that a process has only one stack, but in fact, in addition to a default heap allocated by the system (default size: 1 MB), a process can also create multiple user heaps, each of which has its own handle, delphi's memory management module manages the self-created heap. Delphi also divides a heap into multiple blocks of varying sizes in the form of a linked list. The actual memory operations are on these blocks.
Delphi defines memory management as get, free, and realloc ). The memory manager is a combination of these three implementations. Delphi defines the Memory Manager tmemorymanager in the system unit:

Pmemorymanager = ^ tmemorymanager;
Tmemorymanager = record
Getmem: function (size: integer): pointer;
Freemem: function (P: pointer): integer;
Reallocmem: function (P: pointer; Size: integer): pointer;
End;
As a result, the memory manager of Delphi is a tmemorymanager record object, which has three fields pointing to memory allocation, release, and reallocation routines respectively.
The System Unit also defines a variable memorymanager:
Memorymanager: tmemorymanager = (
Getmem: sysgetmem;
Freemem: sysfreemem;
Reallocmem: sysreallocmem );
This variable is the memory manager of the Delphi Program. By default, the three fields of this memory manager point to sysgetmem, sysfreemem, and sysreallocmem implemented in getmem. inc respectively. This memory manager variable is only visible in system. Pas, but the system unit provides three routines that can access this variable:

// Read the memory manager, that is, read the memorymanager
Procedure getmemorymanager (VAR memmgr: tmemorymanager );
// Install the Memory Manager (replace the default memory manager with the new Memory Manager)
Procedure setmemorymanager (const memmgr: tmemorymanager );
// Whether the memory manager has been installed (that is, whether the default memory manager has been replaced)
Function ismemorymanagerset: Boolean;

4. Shared Memory Manager
What is the shared memory manager?
The Shared Memory Manager is all modules of an application. Both EXE and DLL use the same memory manager to manage the memory, memory Allocation and release are completed by the same memory manager, so there will be no memory errors.
So how to share the Memory Manager?
From the above analysis, we can know that since we want to use the same memory manager, we can simply create an independent Memory Manager Module (DLL ), all other modules use the Memory Manager of this module to allocate and release memory. Delphi7 adopts this method by default. When we use the Wizard to create a DLL project, the project file will have such a statement:
{Important Note about dll Memory Management: sharemem must be
First unit in your library's uses clause and your project's (select
Project-View Source) uses clause if your DLL exports any procedures or
Functions that pass strings as parameters or function results. This
Applies to all strings passed to and from your DLL -- even those that
Are nested in records and classes. sharemem is the interface unit
The borlndmm. dll Shared Memory Manager, which must be deployed along
With your DLL. To avoid using borlndmm. dll, pass string Information
Using pchar or parameter string parameters .}
In this section, we are prompted that sharemem is the interface unit of the borlndmm. dll shared memory manager. Let's take a look at this sharemem. This unit file is very short and contains such a statement:

Const
Delphimm = 'borlndmm. dll ';
Function sysgetmem (size: integer): pointer;
External delphimm name' @ borlndmm @ sysgetmem $ qqri ';
Function sysfreemem (P: pointer): integer;
External delphimm name' @ borlndmm @ sysfreemem $ qqrpv ';
Function sysreallocmem (P: pointer; Size: integer): pointer;
External delphimm name' @ borlndmm @ sysreallocmem $ qqrpvi ';
These Declarations ensure that borlndmm. dll will be statically loaded.
The initialization code in sharemem is as follows:
If not ismemorymanagerset then
Initmemorymanager;
First, determine whether the memory manager has been installed (that is, whether the default memory manager has been replaced). If not, initialize the Memory Manager, initmemorymanager is also very simple (useless code is removed ):

Procedure initmemorymanager;
VaR
Sharedmemorymanager: tmemorymanager;
MM: integer;
Begin
// Force a static reference to borlndmm. dll, so we don't have to loadlibrary
Sharedmemorymanager. getmem: = sysgetmem;
MM: = getmodulehandle (delphimm );
Sharedmemorymanager. getmem: = getprocaddress (mm, '@ borlndmm @ sysgetmem $ qqri ');
Sharedmemorymanager. freemem: = getprocaddress (mm, '@ borlndmm @ sysfreemem $ qqrpv ');
Sharedmemorymanager. reallocmem: = getprocaddress (mm, '@ borlndmm @ sysreallocmem $ qqrpvi ');
Setmemorymanager (sharedmemorymanager );
End;
This function defines a memory manager object, sets the three function implementations whose domain points to borlndmm. dll, and then calls setmemorymanager to replace the default Memory Manager.
In this way, no matter which module, because sharemem must be used as the first uses unit of the project, the initialization of sharemem of each module is first executed, that is, although the Memory Manager objects of each module are different, the three function pointers of the Memory Manager point to borlndmm. DLL function implementation. Therefore, the memory allocation and release of all modules are in borlndmm. the DLL is completed internally, so there will be no problem caused by cross-module release of memory.
Then, how does fastmm implement the shared memory manager?
Fastmm adopts a simple principle, that is, to create a memory manager and put the address of the Memory Manager to the location that can be read by all modules in a process, before creating a memory manager for other modules, check whether another module has put the memory manager in this position. If yes, use the Memory Manager; otherwise, create a new memory manager, put the address in this location. In this way, all modules of the Process use a memory manager to share the Memory Manager.
In addition, the memory manager is not sure which module is created. For all modules, you only need to use fastmm as the first uses unit of the project file, it may be the creator of the Memory Manager. The key is to check the loading sequence of the application. The first loaded module will become the creator of the Memory Manager.
How is fastmm implemented?
Open fastmm4.pas (the latest fastmm version), or check its initialization section:

{Initialize all the Lookup tables, etc. For the Memory Manager}
Initializememorymanager;
{Has another MM been set, or has the Borland mm been used? If so, this file
Is not the first unit in the uses clause of the project's. DPR file .}
If checkcaninstallmemorymanager then
Begin
Installmemorymanager;
End;
Initializememorymanager initializes some variables. After initialization, it calls checkcaninstallmemorymanager to check whether fastmm is the first uses unit of the project. If true is returned, it calls installmemorymanager to install fastmm's memory manager, we extract the key code of the function in order for analysis:
{Build a string identifying the current process}
Lcurrentprocessid: = getcurrentprocessid;
For I: = 0 to 7 do
Uniqueprocessidstring [8-I]: = hextable [(lcurrentprocessid SHR (I * 4) and $ f)];
Mmwindow: = findwindow ('static ', pchar (@ uniqueprocessidstring [1]);
First, obtain the ID of the Process, convert it to a hexadecimal string, and then find the window with the string as the window name.
If no window exists in the process, the mmwindow returns 0, and the window is created:
Mmwindow: = createwindow ('static ', pchar (@ uniqueprocessidstring [1]),
Ws_popup, 0, 0, 0, 0, 0, 0, hinstance, nil );
What is the purpose of creating this window? Continue with the following code:

If mmwindow <> 0 then
Setwindowlong (mmwindow, gwl_userdata, INTEGER (@ newmemorymanager ));
Newmemorymanager. getmem: = fastgetmem;
Newmemorymanager. freemem: = fastfreemem;
Newmemorymanager. reallocmem: = fastreallocmem;
According to msdn, each window has a 32-bit value for the application that creates it. The value can be set by calling setwindowlong with gwl_userdata as the parameter, you can also call getwindowlong with gwl_userdata as the parameter to read data. Therefore, we can clearly understand that fastmm saves the address of the memory manager to be shared to this value, so that other modules can get this value through getwindowlong, to obtain the Shared Memory Manager:

Newmemorymanager: = pmemorymanager (getwindowlong (mmwindow, gwl_userdata) ^;
Through the above analysis, we can see that fastmm is much more clever than Borland in implementing shared memory manager. Borland's implementation method makes it necessary for applications to integrate borlndmm. DLL is released together, while the implementation of fastmm does not require any DLL support.
However, the extracted code above ignores some compilation options. In fact, fastmm's shared memory manager function should be used, you need to enable some compilation options on the fastmm4.pas unit during compilation of each module:
{$ Define sharemm} // enables the shared memory manager function, which is a prerequisite for the other two compilation options to take effect.
{$ Define sharemmiflibrary} // allows a DLL to share its memory manager. If this option is not defined, only the EXE module in an application can create and share its memory manager, the static DLL is always loaded earlier than the exe. Therefore, if a DLL is statically loaded, you must enable this option. Otherwise, an error may occur.
{$ Define attempttousesharedmm} // allows a module to use the memory manager shared by other modules.
These compilation options are stored in fastmm4options in the directory where fastmm4.pas is located. the INC file contains definitions and descriptions, but these definitions are commented out. Therefore, you can cancel the annotations to open these compilation options, or you can create one in your project directory. inc file (such as fastsharemm. INC), write these compilation options into this file, and then, "{$ include fastmm4options. INC} "added" {$ include fastsharemm. INC} ", so that different projects can use different fastsharemm. inc file.

5. multi-thread Memory Management
Is memory management secure in a multi-threaded environment? Obviously, if some measures are not taken, it is definitely not safe. Borland has considered this situation. Therefore, in Delphi's system. the PAS defines a system variable ismultithread, which indicates whether the current multi-threaded environment is used. How does it work? The code that opens the tthread. create function shows that it calls beginthread to create a thread, while beginthread sets ismultithread to true.
Let's take a look at the implementation of sysgetmem, sysfreemem, and sysreallocmem of getmem. Inc. We can see that the statements at the beginning are as follows:
If ismultithread then entercriticalsection (heaplock );
That is to say, in a multi-threaded environment, the allocation and release of memory must be synchronized in the critical section to ensure security.
Fastmm uses a cup command lock for synchronization. The command is used as the prefix of other commands and can lock the bus during the execution of one command. Of course, synchronization is performed only when ismultithread is true.
Ismultithread is defined in system. PAS system variables. Each module (exe or DLL) has its own ismultithread variable, and the default value is fasle. Only the user thread is created in this module, this variable is set to true. Therefore, when we create a thread in EXE, we only set ismultithread in EXE to true, ismultithread in other DLL modules will not be set to true. However, as mentioned earlier, if we use static loaded DLL, these dll will be loaded by the system earlier than exe, at this time, the first loaded dll will create a memory manager and share it. Other modules will use this memory manager. That is to say, the ismultithread variable of EXE does not affect the Memory Manager of the application, the memory manager still thinks that the current multi-threaded environment is not used for synchronization, so there will be a memory error.
To solve this problem, it is necessary to set ismultithread of all modules to true in a multi-threaded environment to ensure that no matter which module actually creates a memory manager, the memory manager knows that the current multi-threaded environment requires synchronization.
Fortunately, Windows provides a mechanism to let our DLL know that the application has created a thread. The dllmain function is the DLL dynamic link library entry function. Delphi encapsulates this entry function and provides a tdllproc function type and a variable dllproc of this type:

Tdllproc = procedure (reason: integer); // defined in system. Pas
// Defined in sysinit. PAS:
VaR
Dllproc: tdllproc;

When the system calls the DLL's dllmain, Delphi will finally call dllproc for processing, and dllproc can be directed to our own tdllproc implementation. When a process creates a new thread, the operating system will call dllmain with the reason = dll_thread_attach parameter. Delphi will call dllproc with this parameter at last, therefore, we only need to implement a new tdllproc to implement thisdllproc and direct the dllproc to thisdllproc. In thisdllproc, set ismultithread to true when you receive dll_thread_attach. The implementation code is as follows:

Library xxx;
VaR
Olddllproc: tdllproc;
Procedure thisdllproc (reason: integer );
Begin
If reason = dll_thread_attach then
Ismultithread: = true;
If assigned (olddllproc) then
Olddllproc (reason );
End;
Begin
Olddllproc: = dllproc;
Dllproc: = thisdllproc;
Thisdllproc (dll_process_attach );

Vi. Summary
This article mainly discusses the following issues:
1. Why fastmm?
2. What are the problems and causes of cross-module transmission of dynamic memory variables?
3. Delphi memory management and Memory Manager
4. Why do we need to share the memory manager? How does Delphi and fastmm share the Memory Manager?
5. How does the Memory Manager synchronize data in a multi-threaded environment?
6. How to Set ismultithread variables across modules in a multi-threaded environment to ensure that the memory manager is synchronized

To use fastmm correctly, you need to do the following during module development:
1. Open the compilation options {$ define sharemm}, {$ define sharemmiflibrary}, and {$ define attempttousesharedmm}
2. Use fastmm (4). PAS as the first uses unit of each project file.
3. If it is a DLL, you need to handle the dllmain call with the dll_thread_attach parameter and set ismultithread to true.

VII. References
Fifth edition of Windows programming, by Charles Petzold, Peking University Press
Delphi source code analysis by Zhou aimin, Electronic Industry Press

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.