4

When my program opens, before any of my code actually runs, it will automatically attempt to load various DLLs whose functions it imports. It looks in the folder that the app is in, and then in a few specific places like \Windows and \Windows\System32.

If I want to use some custom DLLs, but I don't want to clutter up the app's folder with them, is there a way to install them to a subfolder and then put something into the EXE that tells it where to look?

Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • What am I missing here Mason? You are already looking in various locations yourself. One more shouldn't be a problem then? After all most plugin systems require plugin writers to install their dlls in a prescribed folder of the main app? Or do you want the folder name to be free? In that case all "normal" ways of configuring options are open to you? Why would you want to "put something into the EXE"? Mystified... :-) – Marjan Venema Oct 27 '10 at 18:22
  • 1
    @Marjan: What you're missing is that these are built-in dependencies that get loaded *by Windows at load time,* before the first line of the DPR, not dynamically-loaded plugins that get loaded *by the application after it's started up.* – Mason Wheeler Oct 27 '10 at 18:30
  • 1
    In that case you are limited to the windows path and will either have to put your dll's in a folder that is on there, or add your dll folder to the windows' environment path... If you don't want to mess with the machine's windows' path: you could run your app from a bat/cmd file and change the path just before starting the app, that should limit the change to the environment settings for the (duration of the) cmd instance I think. – Marjan Venema Oct 27 '10 at 18:54

4 Answers4

3

You must change PATH environment variable. Try using SetDllDirectory() function. Otherwise, you will have to dynamically load your DLLs.

Also see this question to avoid some more possible problems.

Community
  • 1
  • 1
ffriend
  • 27,562
  • 13
  • 91
  • 132
3

Here's everything you need to know about DLL search order. Be careful that you don't introduce a security problem. If you search an insecure location, an attacker can put a malicious DLL there and get your program to load and execute it.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
1

I actually like dynamically loaded DLLS, and just have a wrapper unit so I can call the DLL functions as if they were compiled right in.
I avoid the performance hit of loading/unloading all the time by having every wrapper function call my local LoadDLLLibrary, like this:

function LoadDLLLibrary: Boolean;
begin
  if MyDLLLib = 0 then
    MyDLLLib := LoadLibrary('path to my dll');   // Only load it once.
  Result := MyDLLLib <> 0;
end;

Each of the wrappers calls LoadDLLLibrary (which only actually does anything one time) and then calls GetProcAddress. It's like this:

procedure DoSomeDLLStuff;
var
  DLLProc: TExecuteDoSomeDLLStuffProc;
begin
  LoadDLLLibrary;
  try
    if MyDLLLib <> 0 then
    begin
      DLLProc := GetProcAddress(MyDLLLib , PROC_SomeDLLSTuff);
      DLLProc;  // run it
    end;
  finally
    // No need to unload, it'll get unloaded in finalization.
  end;
end;

And way down at the bottom.....

initialization
  MyDLLLib := 0;

finalization
  UnLoadDLLLibrary;  // Final unload, as we let it stick around instead of freeing it all the time.
end.

So the end result is that I only load the DLL once, and unload it once. Very handy for DLLs that are loaded dynamically, but are executed a lot.

Chris Thornton
  • 15,620
  • 5
  • 37
  • 62
  • Yeah, but that doesn't work so well when the DLLs in question are BPL runtime packages. – Mason Wheeler Oct 27 '10 at 18:21
  • @Mason, then you should re-word your question. Alas, I have the perfect answer to some other question.... – Chris Thornton Oct 27 '10 at 18:23
  • Well, I did specify in the question that these are loaded at load time, before the app has started up... – Mason Wheeler Oct 27 '10 at 18:31
  • @Mason: Well actually, bpl compiled with runtime packages can be dynamically loaded as well. Trying to dig up an old example... – Marjan Venema Oct 27 '10 at 18:31
  • Since when are BPL:s considered DLL:s? I suppose @Mason reads "DLL" as "dynamically-loaded library", in a general sense, whereas I (and probably a quite few others too) interpret it as "Windows *.DLL files"... – Andreas Rejbrand Oct 27 '10 at 20:10
  • (Sorry for the Swedish colon notation used above... Too late to change.) – Andreas Rejbrand Oct 27 '10 at 20:18
  • @Andreas: BPLs *are* Windows DLL files. They can be loaded with LoadLibrary and everything. (In fact, SysUtils.LoadPackage calls LoadLibrary internally.) They just have some special extra features that normal DLLs don't. – Mason Wheeler Oct 27 '10 at 20:23
  • @Andreas: I basically just wrote it that way so I could get answers from people who know how Windows loads things, even if they don't necessarily know much about Delphi BPLs. – Mason Wheeler Oct 27 '10 at 20:45
1

As I said in my comments to the question, if you are relying on static dependencies and thus loading by Windows, then you are stuck with using the standard ways in which Windows searches for dlls. And if you do not want to change the windows' path permanently, you could try running your app from a bat/cmd file and change the path just before starting your app. AFAIK that should limit the change of the path to the (duration of the) cmd instance started to execute the bat/cmd file.

More flexibility can be obtained though if you are able to change to using dynamic dependencies (remove your bpls from the required list?). As with LoadLibrary, bpls compiled to use runtime packages can be loaded dynamically as well. It is what most delphi bpl based plugin systems rely on.

(Un)Loading bpls dynamically is done using (Un)LoadPackage. LoadPackage loads the package specified by the Name parameter (using SafeLoadLibrary), checks for duplicate units, and calls the initialization blocks of all units contained in the package.

To make sure all Register procedures in a dynamically loaded bpl are called, you need to enumerate the units using a GetPackageInfo call providing a call back function.

BTW: Code samples are excerpts from a plugin system developed during a dynamic applications workshop by Mark Miller (CodeRush's developer/architect) during a 2001 conference. The code used to be online, but I can no longer find it there...

var
  localModuleHandle: HModule;
begin
  try
    localModuleHandle := LoadPackage(packageName);

    //GetPackageInfo accesses the given package's info table and enumerates
    //  all the contained units and required packages 
    Flags := ufAllUnits;
    GetPackageInfo(localModuleHandle, Pointer(localModuleHandle), Flags, PackageIsLoadingProc);
  except
    on e: Exception do
      Application.MessageBox(PChar(e.Message), PChar(sError), MB_OK + MB_ICONWARNING);
  end;  
end;

procedure PackageIsLoadingProc(const Name: string; NameType: TNameType;
                               Flags: Byte; Param: Pointer);
type
  TRegisterProc = procedure;
var
  RegisterProc: TRegisterProc;
  localName: String;
begin
//  Flags:
//  ufMainUnit = $01;
//  ufPackageUnit = $02;
//  ufWeakUnit = $04;
//  ufOrgWeakUnit = $08;
//  ufImplicitUnit = $10;
//  ufWeakPackageUnit = ufPackageUnit or ufWeakUnit;

  if NameType = ntContainsUnit then
    begin
      localName := LowerCase(Name);
      if Length(localName) > 0 then
        localName[1] := UpCase(localName[1]);

      @RegisterProc := GetProcAddress(HModule(Param), 
                                      PChar('@' + localName + '@Register$qqrv'));
      if @RegisterProc <> nil then
        RegisterProc;
    end;
end;
Marjan Venema
  • 19,136
  • 6
  • 65
  • 79