用Delphi创建DLL在LoadRunner中使用

参考:

Creating a DLL with Delphi for LoadRunner

http://ptfrontline.wordpress.com/2010/04/13/creating-a-dll-with-delphi-for-loadrunner/

This article extends my previous post on Using a Custom DLL in LoadRunner and now shows by example how a DLL is actually created in Delphi and used from LoadRunner.

The DLL I’ll create here is a simple stub with a few basic functions and can serve as a template for any type of DLL that you want to create.

I’ll show how to avoid the most common problems of sending/retrieving strings to/from the DLL as well as how to handle the Unicode situation with D2009 and D2010 (LR is not Unicode enabled). 

Some of the things a LR DLL developer needs to remember are:

  • LR is Muti-Threaded and Multi-processed, make the DLL thread-safe
  • When using LoadGenerators the DLL must be distributed to several machines
  • When using Performance Center the lr_load_dll() function needs to be white-listed on every LoadGenerator. The file to manipulate is “C:\Program Files\HP\Load Generator\merc_asl\lrun_api.asl”
  • The DLL may be unloaded at any time, if the script fails/aborts (=> YOU must release allocated memory)
  • If eternal loops or lockups inside the DLL occur, the whole controller/loadgenerator is lost until rebooted (really bad when under PerfCenter)

Keeping the above in mind, also remember the following general rules

  • LR script makers make mistakes too by sending INVALID input to functions or using them in an unintended way. This means that you as a DLL developer NEED TO VERIFY the input params for every function call!
  • The DLL may be installed on a variety of windows platforms (Win2000->Win7) so make sure you check the OS Version if you are using Windows API calls that are version dependent
  • There is no guarantee that the DLL has Administrator privilidges!
  • The DLL is restricted to the same Win32 memory as the host process (2 Gig max allocation)

Creating the DLL in Delphi

Close all projects and then choose “FILE | OTHER …“, select “DELPHI PROJECTS” and then “Dynamic Link Library“. You should now have the following skeleton (excluding all comments):

1 library Project1;
2 uses
3   SysUtils,
4   Classes;
5 {$R *.res}
6 begin
7 end.

Now copy & paste the following instead of the above:

001 library MyDLL;
002   
003 uses
004   SysUtils, Classes, Windows,
005   VUManager in 'VUManager.pas';
006   
007 Const
008   { Version Constants }
009   DLLVer_Major                 = 1;
010   DLLVer_Minor                 = 0;
011   DLLVer_Copyright             = 'Copyright (C) by Celarius Oy';
012   
013   { DLL Errors - Negatives are errors }
014   ERR_None                     =  0;
015   ERR_Unknown                  = -1;
016   ERR_Unknown_Object           = -2;
017   ERR_Insufficient_Buffer      = -3;
018   
019 ////////////////////////////////////////////////////////////////////////////////
020   
021 Var
022   VUMgr : TVUManager;
023   
024 {$R *.res}
025   
026 ////////////////////////////////////////////////////////////////////////////////
027   
028 function dll_Initialize( VUserID: Cardinal ): Integer; stdcall;
029 // Initialize DLL, Adds the Vuser with the ID
030 //
031 // Returns
032 //    Integer    The Error code
033 begin
034      If VUMgr.AddVUser( VUserID ) then
035         Result := ERR_None
036      Else
037         Result := ERR_Unknown_Object;
038 end;
039   
040 ////////////////////////////////////////////////////////////////////////////////
041   
042 function dll_Finalize( VUserID: Cardinal ): Integer; stdcall;
043 // Finalize DLL, removes the VUser with the ID
044 //
045 // Returns
046 //    Integer    Error code
047 begin
048      If VUMgr.RemoveVUser( VUserID ) then
049         Result := ERR_None
050      Else
051         Result := ERR_Unknown_Object;
052 end;
053   
054 ////////////////////////////////////////////////////////////////////////////////
055   
056 function dll_GetVersion: Integer; stdcall;
057 // Get Library version
058 //
059 // Returns
060 //    Integer    Version Number
061 begin
062      Result := (DLLVer_Major SHL 16) + DLLVer_Minor;
063 end;
064   
065 ////////////////////////////////////////////////////////////////////////////////
066   
067 function dll_GetCopyright( DestStr: PAnsiChar; DestSize: Integer): Integer; stdcall;
068 // Get Library Copyright String
069 //
070 // Returns
071 //    Integer    Version Number
072 begin
073      if (DestSize>Length(DLLVer_Copyright)) then
074      Begin
075           StrCopy( DestStr, PAnsiChar(DLLVer_Copyright) );
076           Result := ERR_None;
077      End Else Result := ERR_Insufficient_Buffer;
078 end;
079   
080 ////////////////////////////////////////////////////////////////////////////////
081   
082 function dll_TrimStr(ID:Cardinal; SourceStr: PAnsiChar; DestStr: PAnsiChar; DestSize: Integer): Integer; stdcall;
083 // Trims a String (removes special chars in beginning/end)
084 //
085 // Returns
086 //    Integer    Error Code
087 Var
088    VU : TVirtualUser;
089    S  : AnsiString;
090 begin
091      { Find the Virtual User }
092      VU := VUMgr.FindVU(ID);
093      if Assigned(VU) then
094      Begin
095           S := VU.TrimString( SourceStr ); // process the string
096   
097           if (DestSize>Length(S)) then
098           Begin
099                StrCopy( DestStr, PAnsiChar(S) );
100                Result := ERR_None;
101           End Else Result := ERR_Insufficient_Buffer;
102   
103      End Else Result := ERR_Unknown_Object;
104 end;
105   
106 ////////////////////////////////////////////////////////////////////////////////
107   
108 { Export functions }
109 exports
110   { General Functions }
111   dll_Initialize       name 'dll_Initialize',
112   dll_Finalize         name 'dll_Finalize',
113   dll_GetVersion       name 'dll_GetVersion',
114   dll_GetCopyright     name 'dll_GetCopyright',
115   dll_TrimStr          name 'dll_TrimStr'
116   ;
117   
118 ////////////////////////////////////////////////////////////////////////////////
119   
120 procedure DLLEntryProc(EntryCode: integer);
121 //
122 // DLL Entry/Exit, Process and Thread Attach/Detach procedures }
123 //
124 Var
125    Buf           : Array[0..MAX_PATH] of Char;
126    LoaderProcess : String;
127    DLLPath       : String;
128 begin
129      Try
130         Case EntryCode of
131              { The DLL has just been loaded with LoadLibrary()               }
132              { either by the main process, or by a vuser - we do not know    }
133              { which one                                                     }
134              DLL_PROCESS_ATTACH:
135              Begin
136                   { Get the Path where the DLL is }
137                   GetModuleFileName(HInstance, buf, Length(buf)) ;
138                   DLLPath := ExtractFilePath(StrPas(buf));
139                   { Get the name of the module that loaded us }
140                   GetModuleFileName(HInstance, buf, Length(buf)) ;
141                   LoaderProcess := StrPas(buf);
142                   { Create handler }
143                   VUMgr:= TVUManager.Create( NIL );
144              End;
145   
146              { The Process Detached the DLL - Once per MMDRV Process }
147              { Application called Unload - Perform Cleanup }
148              DLL_PROCESS_DETACH:
149              Begin
150                   If Assigned(VUMgr) then FreeAndNil(VUMgr);
151              End;
152   
153              { A Thread has just loaded the DLL }
154              DLL_THREAD_ATTACH:
155              Begin
156                   { This occures when a VUser calls lr_load_dll() }
157              End;
158   
159              { A Thread has just unloaded the DLL }
160              DLL_THREAD_DETACH:
161              Begin
162                   { This occures when a VUser exits }
163              End;
164         End;
165      Except
166         // TODO: Write an Exception Handler
167         On E: Exception do ;
168      End;
169 end;
170   
171 ////////////////////////////////////////////////////////////////////////////////
172 //
173 // DLL Initialization - When DLL is loaded the first time by any process }
174 //
175 ////////////////////////////////////////////////////////////////////////////////
176 begin
177 {$IFDEF Debug}
178           { True means Delphi checks & reports memleaks }
179           ReportMemoryLeaksOnShutdown := True;
180 {$ENDIF}
181           { Make the Memory-Manager aware that this DLL is used in Thread-environments }
182           IsMultiThread := True;
183           VUMgr:= NIL;
184   
185           { If we have not already set te DLLProc, do it }
186           if NOT Assigned(DllProc) then
187           begin
188                DLLProc := @DLLEntryProc;         // install custom exit procedure
189                DLLEntryProc(DLL_PROCESS_ATTACH);
190           end;
191 end.

You can now save the project as “MyDLL” in a folder of your choice.

Now we’ll need to create the VUManager unit with “FILE | NEW UNIT” and save the new unit as “VUManager.pas” in the same directory as the rest of the Delphi files. Copy & Paste the following into the new unit.

001 unit VUManager;
002   
003 Interface
004   
005 Uses SysUtils, Classes, Windows;
006   
007 Type
008   { Forward Declarations }
009   TVirtualUser = class;
010   TVUManager = class;
011   
012 ////////////////////////////////////////////////////////////////////////////////
013   
014   TVirtualUser = class(TComponent)
015   private
016     { Private declarations }
017   protected
018     { Protected declarations }
019   public
020     { Public declarations }
021     VUserID               : Cardinal;
022     Constructor Create(AOwner:TComponent; ID:Cardinal); ReIntroduce;
023     Destructor Destroy; Override;
024     Function TrimString(InStr: AnsiString): AnsiString;
025   end;
026   
027 ////////////////////////////////////////////////////////////////////////////////
028   
029   TVUManager = class(TComponent)
030   private
031     { Private declarations }
032     fVirtualUsers          : TThreadList;
033   protected
034     { Protected declarations }
035   public
036     { Public declarations }
037     Constructor Create(AOwner:TComponent); Override;
038     Destructor Destroy; Override;
039     { Methods }
040     Function FindVU(ID:Cardinal):TVirtualUser;
041     Function AddVUser(ID: Cardinal): Boolean;
042     Function RemoveVUser(ID: Cardinal): Boolean;
043   end;
044   
045 ////////////////////////////////////////////////////////////////////////////////
046   
047 Implementation
048   
049 ////////////////////////////////////////////////////////////////////////////////
050   
051 { TVirtualUser }
052   
053 constructor TVirtualUser.Create(AOwner: TComponent; ID:Cardinal);
054 begin
055      inherited Create(AOwner);
056      VUserID := ID;
057 end;
058   
059 destructor TVirtualUser.Destroy;
060 begin
061      Try
062         { Cleanup anything that is allocated by the VUser }
063      Finally
064         inherited;
065      End;
066 end;
067   
068 function TVirtualUser.TrimString(InStr: AnsiString): AnsiString;
069 begin
070      Result := Trim(InStr);
071 end;
072   
073 ////////////////////////////////////////////////////////////////////////////////
074   
075 { TVUManager }
076   
077 constructor TVUManager.Create(AOwner: TComponent);
078 begin
079      Try
080         inherited;
081         fVirtualUsers  := TThreadList.Create();
082      Finally
083      End;
084 end;
085   
086 destructor TVUManager.Destroy;
087 Var
088    I : Integer;
089 begin
090      Try
091         { Clear and Free the VUsers List }
092         If Assigned(fVirtualUsers) then
093         Begin
094              With fVirtualUsers.LockList do
095              Try
096                 for I := 0 to Count - 1 do
097                     TVirtualUser(Items[I]).Free;
098                 Clear;
099              Finally
100                 fVirtualUsers.UnLockList;
101              End;
102              FreeAndNil(fVirtualUsers);
103         End;
104      Finally
105         inherited;
106      End;
107 end;
108   
109 function TVUManager.FindVU(ID: Cardinal): TVirtualUser;
110 Var
111    I : Cardinal;
112 begin
113      Result := NIL;
114      With fVirtualUsers.LockList do
115      Try
116         for I := 0 to Count - 1 do
117         Begin
118              if TVirtualUser(Items[I]).VUserID=ID then
119              Begin
120                   Result := TVirtualUser(Items[I]);
121                   Break;
122              End;
123         End;
124      Finally
125         fVirtualUsers.UnlockList;
126      End;
127 end;
128   
129 function TVUManager.AddVUser(ID: Cardinal): Boolean;
130 Var
131    VU : TVirtualUser;
132 begin
133      Result := False;
134      With fVirtualUsers.LockList do
135      Try
136         VU := TVirtualUser.Create(NIL, ID);
137         Add( VU );
138         Result := True;
139      Finally
140         fVirtualUsers.UnlockList;
141      End;end;
142   
143 function TVUManager.RemoveVUser(ID: Cardinal): Boolean;
144 Var
145    I : Cardinal;
146    VU : TVirtualUser;
147 begin
148      Result := False;
149      With fVirtualUsers.LockList do
150      Try
151         VU := NIL;
152         for I := 0 to Count - 1 do
153             if TVirtualUser(Items[I]).VUserID = ID then
154             Begin
155                  VU := TVirtualUser(Items[I]);
156                  Delete(I);
157                  Break;
158             End;
159      Finally
160         fVirtualUsers.UnlockList;
161         if Assigned(VU) then
162         Begin
163              FreeAndNil(VU);
164              Result := True;
165         End;
166      End;
167 end;
168   
169 // END OF SOURCE
170 End.

An short explanation of what’s going on …

The main project file contains the functions that are exported by the DLL. These are available to the LR script. These in turn call methods from the VUManager object where the actual magic of the DLL will happen.

The VUManager has several useful methods (Find/Add/Remove vuser) and then the TVirtualUser object contains the actual methods that perform VUser specific stuff. To extend the DLL one can add methods to the TVirtualUser object and create a small Export interface in the main project file for the method (see the dll_TrimStr() for more details).

The dll_GetVersion() and dll_GetCopyright() functions are there only to provide the script with details of the DLL, and are not mandatory to use when a script uses the DLL.

The dll_Initialize() function

This function is responsible for allocating an instance of a VUser in the VUManager object. It accepts the VUserID that is a unique identifier across all running vusers in a test.

The dll_Finalize() function

The finalize function deallocates (or frees) the VUser instance in the VUManager object. It accepts the VUserID. After this call any dll functions that depend on a valid VUserID will fail with the corresponding error code.

The dll_TrimStr() function

The TrimStr function demonstrates how to “read” a string sent from LoadRunner, manipulate it and then return it to LoadRunner. The PAnsiChar is important here to make Delphi handle the string as an 8-bit null-terminated string, instead of a Unicode string.

How LoadRunner uses the DLL (in theory)

As soon as the MMDRV.EXE process loads the DLL, the DLL creates the VUManager object. This object is responsible for “Managing” the actual VUsers we are going to be servicing. Each VUser Thread also “loads” the DLL but in reality it only gets a copy of it in memory (this is not 100% accurate but good enough for now).

The actual LR script then uses the dll_Initialize(VUserID) function to initialize a VUser in the VUManager, that will allocate an TVirtualUser object for it, with the given ID. This way we can “keep track” of the individual VUsers later when needed. The dll_TrimStr() function takes the ID as the first parameter to identify the VUser.

A LoadRunner script using the DLL

The vuser_init() action:

01 int VuserID; // Script Global Variable
02 vuser_init()
03 {
04     int ret;
05   
06     // Get VUserID (Needs to be created as parameter)
07     VUserID = atoi(lr_eval_string("{VUserID"));
08   
09     // Load the DLL
10     ret = lr_load_dll("MyDLL.dll");
11     if (!ret==0)
12     {
13         lr_error_message("Could not load DLL");
14         lr_abort;
15     }
16   
17     // Initialize the VUser object inside the DLL
18     dll_Initialize( VUserID );
19   
20     return 0;
21 }

The Action() action:

01 Action()
02 {
03     char buf[1024];
04   
05     // Trim a string
06     dll_TrimStr( VUserID, "  this string will be trimmed!!  ", buf, sizeof(buf) );
07   
08     lr_output_message("Trimmed String='%s'", buf);
09   
10     return 0;
11 }

The vuser_end() action:

1 vuser_end()
2 {
3     // Finalize the VUser (free the VUser object inside the DLL)
4     dll_Finalize( VUserID );
5   
6     return 0;
7 }

I hope this brief introduction to DLL writing for LoadRunner is of help to those who seek information regarding this subject.

Enjoy!

posted on 2011-05-22 22:41  preftest  阅读(717)  评论(0)    收藏  举报

导航