views:

101

answers:

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?

+2  A: 

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.

Andrei
+2  A: 

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
+1 for the warning about malicious DLLs.
Jeroen Pluimers
+1  A: 

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
Yeah, but that doesn't work so well when the DLLs in question are BPL runtime packages.
Mason Wheeler
@Mason, then you should re-word your question. Alas, I have the perfect answer to some other question....
Chris Thornton
Well, I did specify in the question that these are loaded at load time, before the app has started up...
Mason Wheeler
@Mason: Well actually, bpl compiled with runtime packages can be dynamically loaded as well. Trying to dig up an old example...
Marjan Venema
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
(Sorry for the Swedish colon notation used above... Too late to change.)
Andreas Rejbrand
@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
@Mason: Yes, you are right. I have been awake too long.
Andreas Rejbrand
@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
+1  A: 

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