A dynamic link library (DLL) is a pre-compiled and executable collection of functions or routines that can be loaded by an application program during execution of the program. A DLL provides a list of names of sharable symbols (e.g., functions, routines, variables) that can be exported and used by other application programs or DLLs. The application program and its DLLs can implicitly or explicitly import (access) sharable symbols of other DLLs.
Implicit access is generally achieved by statically linking to the DLLs. The names of symbols imported by applications or DLLs are listed in header files used during compilation of the applications and DLLs. Statically linked DLLs are loaded into memory upon initiation of the application program so that the sharable functions of the DLLs become available. The addresses of the sharable functions required by the application program for accessing the functions are fixed at initiation and do not change during execution of the application program. The loaded DLLs generally are removed from the memory when the application program ends.
In one example, the simulator program 16 is configured to simulate and test different processor models. A corresponding processor DLL 24a includes parameters of a particular processor model. The processor can be, e.g., a central processing unit, a microcontroller, a network processor, or another processor type. Each processor DLL 24a exports a number of sharable functions. Examples of the exported functions of the processor DLL 24a include functions for accessing and updating processor simulated states, functions for printing messages on a console associated with the processor DLL 24a, and functions for validating the application programming interface versions.
Each processor DLL 24a is associated with a corresponding set of device DLLs 24b and a set of simulation DLLs 24c. Each device DLL 24b includes parameters of device models that represent devices on a circuit board on which the processor is mounted. Examples of the devices include memory devices, bus interfaces, clock devices, other logic circuits and interconnects. The processor DLL 24a models the operations of a processor, which depend on conditions of the external devices (i.e., devices external to the processor). Each simulation DLL 24c includes, e.g., control functions for managing resources and controlling the simulation process, initialization of the device models, in conjunction with the processor DLL 24a and the device DLL 24b. The processor DLL 24a, the device DLLs 24b, and the simulation DLLs 24c communicate with one another to update internal states based on information provided by the other DLLs.
Each of the device DLLs 24b and the simulation DLLs 24c includes code for an initialization routine 60, an exit routine 90, and a proxy get address function 110, to be described later.
The simulator program 16 instructs 32 the processor DLL 24a to run an initialization script to load and link to the other DLLs. The initialization script includes an instruction that requests the processor DLL 24a to explicitly load a device DLL 24b. The processor DLL 24a loads 34 the device DLL 24b so that exported functions of the device DLL 24b are accessible to the processor DLL 24a. An address of the device DLL 24b and a data structure describing the device DLL 24b are returned to the processor DLL 24a. A structure including the name of the device DLL 24b and a handle pointing to the device DLL 24b is added to a loaded DLL list maintained by the processor DLL 24a.
The processor DLL 24a calls 36 the initialization routine 60 of the device DLL 24b to dynamically link the processor DLL 24a to the device DLL 24b. The initialization routine 60 is inserted in the device DLL 24b when the device DLL 24b was initially generated. Each DLL that is to be loaded by the processor DLL 24a includes an initialization routine that is similar to the initialization routine 60, which is shown in
The processor DLL 24a call 36 to the initialization routine 60 is used to request the initialization routine 60 to load 38 the already loaded processor DLL 24a. A handle referencing the already loaded processor DLL 24a is returned to the device DLL 24b so that the device DLL 24b can access the exported functions of the processor DLL 24a. A structure including the name of the processor DLL 24a and the handle referencing the processor DLL 24a is added to a loaded DLL list maintained by the device DLL 24b. The initialization routine 60 (discussed in
The simulator program 16 instructs 42 the processor DLL 24a to explicitly load a simulation DLL 24c. The processor DLL 24a explicitly loads 44 the simulation DLL 24c so that exported functions of the simulation DLL 24c are accessible to the processor DLL 24a. An address of the simulation DLL 24c and a data structure describing the simulation DLL 24c are returned to the processor DLL 24a. A structure that includes the name of the simulation DLL 24c and the handle referencing the simulation DLL 24c is placed on the loaded DLL list maintained by the processor DLL 24a.
The processor DLL 24a calls 46 the initialization routine 60 of the simulation DLL 24c to dynamically link the processor DLL 24a to the simulation DLL 24c. The initialization routine 60 is inserted into the simulation DLL 24c when the simulation DLL 24c is generated. The processor DLL 24a instructs the initialization routine 60 to load 48 the already loaded processor DLL 24a. A handle referencing the already loaded processor DLL 24a is returned to the simulation DLL 24c so that the simulation DLL 24c can access the exported functions of the processor DLL 24a. A structure that includes the name of the loaded processor DLL 24a and the handle referencing the processor DLL 24a is added to a loaded DLL list that is maintained by the simulation DLL 24c. The initialization routine 60 when executed allows the simulation DLL 24c to have transparent access to shared resources of the processor DLL 24a.
After linking the processor DLL 24a to the simulation DLL 24c, the processor DLL 24a calls 52 the initialization routine 60 of the device DLL 24b and passes the name of the simulation DLL 24c to the device DLL 24b. The device DLL 24b loads 54 the simulation DLL 24c to make the exported functions of the simulation DLL 24c accessible to the device DLL 24b. A structure that includes the name of the simulation DLL 24c and a handle referencing the simulation DLL 24c is added to the loaded DLL list maintained by the device DLL 24b.
The processor DLL 24a calls 56 the initialization routine 60 of the simulation DLL 24c and passes the name of the device DLL 24b to the simulation DLL 24c. The simulation DLL 24c loads 58 the device DLL 24b to make the exported functions of the device DLL 24b accessible to the simulation DLL 24c. A structure that includes the name of the device DLL 24b and a handle referencing the device DLL 24b is added to the loaded DLL list maintained by the simulation DLL 24c.
In this way, the exported functions of each of the processor DLL 24a, the device DLLs 24b, and the simulation DLLs 24c are accessible to the other DLLs. Each of the DLLs 24a, 24b, and 24c can access the exported functions of the other DLLs. Each DLL maintains a list of the names of the other DLLs and handles pointing to the other DLLs.
In process 20, the steps 34, 36, and 38 are similar to steps 44, 46, and 48, respectively. The step 52 is similar to the step 36, except that in step 36, the name and file path of the processor DLL 24a is passed to the initialization routine 60 of the device DLL 24b, whereas in step 52, the name and file path of the simulation DLL 24c is passed to the initialization routine 60 of the device DLL 24b. The step 56 is similar to the step 52, except that in step 52, the name and file path of the simulation DLL 24c is passed to the initialization routine 60 of the device DLL 24b, whereas in step 56, the name and file path of the device DLL 24b is passed to the initialization routine 60 of the simulation DLL 24c.
The processor DLL 24a may load more than one device DLL 24b and more than one simulation DLL 24c. To load a new DLL so that the exported functions of the new DLL can be accessed by previously loaded DLLs, and that the exported functions of the previously loaded DLLs can be accessed by the new DLL, steps similar to those described above are repeated.
The simulator program 16 instructs the processor DLL 24a to explicitly load the new DLL so that exported functions of the new DLL is accessible to the processor DLL 24a. The processor DLL 24a calls an initialization routine 60 of the new DLL to dynamically link the processor DLL 24a to the new DLL. The initialization routine 60 is inserted into the new DLL when the new DLL was generated. The processor DLL 24a call to the initialization routine 60 of the new DLL is used to request the initialization routine 60 to load the processor DLL 24a so that the exported functions of the processor DLL 24a become accessible to the new DLL.
The processor DLL 24a calls the initialization routine 60 of each previously loaded DLLs to explicitly load the new DLL, and make the exported functions of the new DLL accessible to each of the previously loaded DLLs. The processor DLL 24a calls the initialization routine 60 of the new DLL to explicitly load each of the previously loaded DLLs, and make the exported functions of the previously loaded DLLs accessible to the new DLL. In this way, regardless of how many DLLs are loaded, the exported functions of each DLL are accessible by the other DLLs.
For example, in step 36 of
The returned DLL handle is examined 72 to determine whether it is valid. If the handle is not valid (e.g., it is null), the routine 60 exits 74. If the handle is valid, a structure including the name of the DLL and the handle is added 76 to a loaded DLL list. The loaded DLL list includes the names and handles associated with the DLLs that have been loaded by the DLL in which the initialization routine 60 resides, which in this case is the device DLL 24b.
The initialization routine 60 finds 78 a print function in the loaded DLL to print success or error messages. In one example, the simulator program 16 opens a console (or window) to display text and data. When the initialization routine 60 loads the processor DLL 24a (in step 36), and an error occurs, the error is displayed in the console opened by the simulator program 16.
The initialization routine 60 establishes 78 a DLL exit handler (see
The initialization routine 60 generates 80 a return code, in which specific bits are set (or cleared) if there are errors. For example, bit 1 may indicate that there is an error in finding a print function in the loaded DLL, bit 2 may indicate that there is an error in establishing a DLL exit handler, and bit 3 may indicate that there is an error in verifying the API versions. The return code is evaluated 82 to determine whether it indicates errors. If the return code does not indicate errors, a success message is printed 84, and the routine 60 exits 88. If the return code indicates errors, an error message is printed 86, and the routine 60 exits 88.
The print function in the loaded DLL is listed in a header file of the loaded DLL that lists the exported functions. In one example, if the print function can be found, a DLL exit handler can be established, and the API versions of the loading and loaded DLLs verified, it is assumed that the other exported functions can also be properly accessed.
A get address function (e.g., GetProcAddress, which is a standard Microsoft Windows function) is called 120 to search the requested function in the DLL corresponding to the DLL handle. Each DLL has a symbol table that lists the address of each exported function. The GetProcAddress function searches the symbol table to find a match between an entry in the table and the function whose address is sought. The function pointer is set 120 to the value returned by the GetProcAddress function. The function pointer is tested 122 to determine whether it is valid.
If the function pointer is valid (not NULL), indicating that the function was found in the DLL, the function 110 returns the function pointer 126 and exits 128.
If the function pointer is null, which means that the function was not found in the DLL, the DLL handle is examined 124 to determine whether it is the last handle in the list. If the DLL handle is the last handle in the list, the function pointer is returned 126, and the function 110 exits 128. If the DLL handle is not the last handle in the list, steps 118, 120, 122, and 124 are repeated until the last DLL handle in the list has been processed.
The difference between the proxy get address function 110 and the get address function (e.g., GetProcAddress) is as follows. The proxy get address function 110 searches a list of DLLs to determine whether a specified function is an exported function of one of the DLLs, calls the get address function to obtain the address of the specified function, and returns the address of the specified function. The get address function searches within the symbol table of a specified DLL to find the address of a specified function of the specified DLL.
If this is the first time that Function—01 is called, the calling DLL will not know the address of Function—01. The process 130 jumps 134 to a surrogate function Initial_Proxy_Function—01 through the function pointer Proxy_Function—01. The function pointer Proxy_Function—01 is set to point to the address of the surrogate function when the function pointer is initialized. A Proxy_Get_Proc_Address function (which is similar to the proxy get address function 110 described in
The function pointer Proxy_Function—01 is tested 138 to determine whether it is valid. If the function pointer is not valid, an error message is printed 140. The function pointer Proxy_Function—01 is reset to point to the address of the surrogate function, Initial_Proxy_Function—01, and the process 130 exits 142. If the function pointer is valid, the process 130 jumps 144 to Function—01 through the function pointer Proxy_Function—01, which has been assigned the address of the Function—01 in step 136, and exits 146.
The process 130 provides a mechanism in which an exported function can be accessed by a DLL even when the DLL initially does not know the address of the exported function. The first time the exported function is called, the function pointer Proxy_Function—01 is set to point to the address of a surrogate function, which finds the address of the Function—01, and sets the function pointer to point to the address of Function—01. The next time that the exported function is called, the function pointer has already been set to point to the exported function, thus the surrogate function is not invoked, and the exported function is accessed directly.
When there are several functions that are exported by a DLL, a separate segment of code is provided for each of the exported functions to implement a process for calling the exported function, similar to the process 130. For example, for a DLL to call an exported function Function—02, a process similar to process 130 is executed, except that the function pointer Proxy_Function—01 is replaced by Proxy_Function—02, the surrogate function Initial_Proxy_Function—01 is replaced by Initial_Proxy_Function—02, and the argument passed to Proxy_Get_Proc_Address becomes Function—02.
In one example, the processes (e.g., 130) for calling the exported functions can be implemented in the C++ programming language. The code for implementing the processes can be automatically generated using C++ pre-processor macros that decorate function prototypes in a header file. The following describes how macros can be implemented so that, when expanded by the pre-processor, they produce code to implement the processes that allow calling of exported functions.
A standard C++ prototype for a function that is neither imported nor exported by DLLs can have the following format:
When a function (e.g., Function—01) is to be used in a DLL application (e.g., 24), the prototype shown above can be decorated by a C++ pre-preprocessor macro. The macro prefixes the function name with the return type and compiler directives, which define function characteristics and whether the function is being imported or exported.
For example, to allow an exported function (e.g., Function—01) to be used in a DLL application, a function prototype shown below can be included in a header file that defines functions used in the DLL application:
Here, the string XACTAPI can be defined as a macro similar to the one shown below, which is suitable for applications that do not use DLLs (DLL_USE not defined) and applications that use DLLs (DLL_USE defined with either DLL_IMPORT or DLL_EXPORT defined).
In the above code, the variable DLL_USE indicates whether the application uses DLLs, the variable WIN32 indicates whether this is a Windows environment, the variable DLL_IMPORT indicates whether the function (e.g., Function—01) is an imported function, and the variable DLL_EXPORT indicates whether the function is an exported function.
To generate code for implementing the processes (e.g., 130) for calling the exported functions, macros can be written to further decorate each function prototype declaration as shown below:
Depending on the setting of a pre-processor variable that controls how the function macro is expanded, the XACTAPI_PROXY function macro can (1) generate a function prototype suitable for use in applications that do not use DLLs, (2) generate a function prototype suitable for use in applications that use DLLs, or (3) generate a sequence of code implementing the process 130. To use the XACTAPI_PROXY function macro to generate code that implements the process 130, the preprocessor variable USE_DLL is not defined and the pre-processor variable controlling expansion of XACTAPI_PROXY is defined.
The definition of the XACTAPI_PROXY function macro is written so that, when the C++ preprocessor expands the function macro, the code for implementing the process 130 is automatically generated. The following describes a process 150 that is implemented when the C++ preprocessor expands the XACTAPI_PROXY function macro.
Code for a standard function prototype (one that is neither imported or exported by a DLL) for the named function (e.g., Function—01) is generated 152 based on the macro arguments.
Code for a function prototype for a surrogate function with a unique name (e.g., Initial_Proxy_Function—01) derived from the function named in the second macro argument (e.g., Function—01) is generated 154. The surrogate function has the same return type (e.g., XACTAPI) and formal argument list (e.g., command_string) as the function named by the second macro argument. The surrogate function (e.g., Initial_Proxy_Function—01) is the function that is executed the first time the named function (e.g., Function—01) is called.
Code for a uniquely named function pointer (e.g., Proxy_Function—01) derived from the second macro argument is generated 156. The function pointer points to functions of the type defined in the prototypes generated in steps 152 and 154. The function pointer is initialized in its declaration to the address of the surrogate function (which is named in the prototype generated in step 154) so that when the function (e.g., Function—01) is called for the first time, the process 150 will jump to the surrogate function.
Code for the surrogate function having the name and type declared by the function prototype provided in step 154 is generated 158. The body of the surrogate function contains three statements—an assignment statement, an IF statement with a success clause and an ELSE clause, and a RETURN statement. The generated assignment statement updates the function pointer (e.g., Proxy_Function—01) generated in step 156 with the address returned by the get address function (e.g., Proxy_Get_Proc_Address) described in
The generated IF statement inspects the value of the function pointer assigned in the previous statement for a non-null value. The success clause generated for the IF statement contains assembly code to unwind the stack to the call frame and jump into the function pointed to by the function pointer declared in step 156. The ELSE clause generated for the IF statement contains a statement that attempts to report an error message and a statement that restores the function pointer declared in step 156 to its initial value (which is the address of the surrogate function). The body of the surrogate function includes a RETURN statement.
Code for a function with the name and type declared by the function prototype provided in step 152 is generated 160. The body of the function contains assembly code to unwind the stack to the call frame and then jump into the function pointed to by the function pointer declared in step 156. The body of the function includes a RETURN statement.
A header file is provided with a function macro for each of the functions that are exported by the DLLs 24. For example, if there are three exported functions, Function—01, Function—02, and Function—03, the header file would include the following function prototype declarations:
where macro 2 and macro 3 are macros that can be further expanded, similar to XACTAPI. For example, the declaration
In one example, the simulator program 16 is an IXP Workbench program from Intel Corporation that provides an interactive user interface to allow a user to simulate and test a family of Intel® IXP network processors. The processor DLL 24a is an XACTOR DLL from Intel Corporation that represents a particular IXP network processor chip, e.g., IXP2400, IXP2600, and IXP2800 network processors. The device DLL 24b is a foreign model DLL that include parameters of board level devices, such as external memory devices, clock devices, and other logic circuits. The simulation DLL 24c is an IXP Workbench DLL that manages resources and sets forth simulation conditions of the IXP Workbench.
The process 150, which is implemented by the code defining the function macros described above, such as XACTAPI_PROXY, provides an efficient means of updating, adding, or removing DLLs that are used by the simulator program 16. For example, if new device DLLs 24b or simulation DLLs 24c are added, their exported functions are added to the list of function macros in the header file. When other DLLs import the header file, the preprocessor automatically generate code that allow the DLLs to call those additional exported functions.
The processes described above provide a means for explicitly accessing sharable functions of loaded DLLs. The address of a sharable function is not fixed upon initiation of the application program, and may depend on conditions at the time when the DLL is actually loaded. When several DLLs are loaded, one DLL initially may not know the addresses of the sharable functions or the parameters that need to be passed to the sharable functions of another DLL. The processes described above allow an application program that does not know the names of DLLs that it needs to load until run-time, to obtain the names of DLLs and their exported functions at run-time, explicitly load the DLLs into memory, explicitly link to each sharable function in each loaded DLL, explicitly link each DLL to the other DLLs, and to unload the loaded DLLs.
Although some examples have been discussed above, other implementations and applications are also within the scope of the following claims. For example, the simulator program 16 can be used to simulate devices other than processors. The DLLs may export different types of symbols, such as data values. The simulator program 16 may load each of the DLLs, such as the processor DLL 24a, the device DLLs 24b, and the simulation DLLs 24c, and instruct the DLLs to link to one another, as shown in