Delphi dll files designed for loader locks?

There is a DLL loaded dynamically by the main (desktop) application through Windows.LoadLibrary . This is because there are many similar DLLs, and only a few should be loaded at runtime. Therefore, static binding is not an option.

The problem is that from time to time the main application freezes when loading one of these DLLs. Please note that a problem can occur with each of them. Probably because they have a lot of common code.

The problem is blocking the bootloader ( see this SO answer for what it is ). I found a piece of common code that all DLLs use when starting up in the begin...end section of the library -unit section (i.e. project.dpr ), where GetModuleHandle and GetProcAddress .

I found out that this is completely DONT with the DLL, since the begin...end development of the DLL project file is actually a function of the DllMain library, and calling such functions can lead to deadlocks (named loader lock). I read that in this Microsoft Best Practice Guide .

So, I am rebuilding my code so that these calls are called later when the Windows.LoadLibrary call has been completed.

Unfortunately, the problem is hanging .: - (

Then I started the debugger, going through each part of the initialization, which is called before one line of my code is executed. I determined that a lot of third-party code contradicts the guide on what to do and what not to do in the DLL initialization code:

  • TMS component package
  • JEDI Component Library
  • OmniThreadLibrary
  • Indy components

All of the above dynamically loads other DLLs into the initialization code or requests procedure pointers via GetProcAddress . I think these calls are freezing when my dlls load.

Are Delphi developers aware of the dangers of initialization ? What can I do about this?

+6
source share
3 answers

This is a common problem that, it seems to me, is not particularly specific to Delphi programmers. If you have code that calls LoadLibrary in the initialization section or even FreeLibrary in the finalization section, this code is unsafe for use in the library.

Please note that I am not familiar with all the libraries you mentioned and do not confirm at all that they have an initialization section code that is unsafe for use in the library. I think there is something for you to confirm - I would like to stick with the concepts in this answer and not comment on specific Delphi libraries.

I would say that the calls to GetModuleHandle and GetProcAddress in order from DllMain . I say this because you specifically mention GetProcAddress . It is absolutely good to get a module descriptor, for example, a call to GetModuleHandle , and then get the address of the function by calling GetProcAddress . Therefore, if some of the suspicious libraries do this and do not call LoadLibrary , then they may be fine.

In any case, in accordance with the above caveats, you need to establish that any code that will be called from DllMain that violates the rules set by Microsoft is called at a safe time, and not from DllMain . Unfortunately, these rules are unclear at best. Microsoft say the following in the DllMain documentation :

The entry point function should only perform simple initialization or completion tasks. It should not call the LoadLibrary or LoadLibraryEx functions (or the function that calls these functions), as this can create dependency loops in the order in which the DLL loads. This may cause the DLL to be used before the system has executed its initialization code. Similarly, the entry-point function should not call the FreeLibrary function (or the function that calls FreeLibrary) during the process, as this may cause the DLL to be used after the system has executed its exit code.

Since Kernel32.dll is guaranteed to be loaded into the process address space when the entry point function is called, the calling functions in Kernel32.dll do not cause the DLL to be used before the initialization code has been executed. Therefore, a function entry point can call functions in Kernel32.dll that do not load other DLLs. For example, DllMain can create synchronization objects, such as critical sections and mutexes, as well as use TLS. Unfortunately, there is not a complete list of safe functions in Kernel32.dll.

The last paragraph leaves you with a little guidance. To make sure your library is reliable, you will need to do something in the following lines:

  • Arrange that each initialization section of any unit whose source code you control registers the initialization and completion procedure with the central registry.
  • In an executable project, you invoke the initialization routines when they are registered, and invoke the termination routines in the reverse order when the program terminates.
  • In a library project, you put off calling these initialization and completion procedures. Export a couple of functions from the DLL that the consumer of the DLL can call to request that these initialization and termination procedures be called.

This is the approach I used with my libraries, and it has served me well for many years.

This approach involves quite a bit of work and has the disadvantage that you are modifying third-party libraries. However, if these libraries do not work correctly when used as supplied ones, what is your alternative?

Perhaps, at a slower time, you can contact the developers of any libraries that, in your opinion, are incompatible with use in the library. Try to convince them to change their code so that it is compatible with use in the library. As you can see from the Remy comment on your question, it is entirely possible that the library developers may not be aware of this problem and really want to make changes.

+6
source

The idea from the Russian blog: http://www.gunsmoker.ru/

You can create a Dll in Delphi that does nothing in your DllMain. To do this, you need to create a new package, for example:

 package Plugin1; //... {$E dll} contains InitHook, //... ; end. 

And InitHook:

 unit InitHook; interface implementation function GetProcAddress(hModule: HMODULE; lpProcName: PAnsiChar): Pointer; stdcall; external 'kernel32.dll' name 'GetProcAddress'; procedure Done; stdcall; type TPackageUnload = procedure; var PackageUnload: TPackageUnload; begin @PackageUnload := GetProcAddress(HInstance, 'Finalize'); //Do not localize PackageUnload; end; procedure Init; stdcall; type TPackageLoad = procedure; var PackageLoad: TPackageLoad; begin @PackageLoad := GetProcAddress(HInstance, 'Initialize'); //Do not localize PackageLoad; end; exports Init, Done; end. 

Now you can put any code that you want inside Dll inside this package. But you will have to call Init before calling any other function from this DLL and call Done before unloading it.

Initialization and termination are procedures that the compiler automatically creates in batches. These procedures perform all sections of initialization and completion in all units of the package.

+1
source

If you perform dynamic loading inside your program, then Windows DLL files cannot cause loader locks, since they are already loaded when the first code in your program gets the opportunity to execute. Thus, loader locks can only be caused between your libraries. In this case, you will need to determine the correct boot order. If you have documentation, then search it.

If all your libraries are Delphi / C ++ Builder libraries and compiled in the same release of RAD Studio, I suggest you include runtime packages for all of them. This will reduce code duplication and blocking because two instances of the same type as the application are trying to work simultaneously. Or even better, convert your libraries to packages. This eliminates the possibility of blocking.

0
source

All Articles