Can Delphi only use a .dll if required?
Asked Answered
I

2

6

I have added these two methods to the 1st unit of my Delphi 5 application.

function Inp(PortAddress: Integer): Integer; stdcall; external 'inpout32.dll' name 'Inp32';

procedure Output(PortAddress, Value: Integer); stdcall; external 'inpout32.dll' name 'Out32';

However I don't want to have to issue the inpout32 library with the software unless they explicitly need it. Currently the program says "Not Found" upon executing unless they're present in the root or System32.

Users will only call these methods if they have a specific option set, but this is not gathered from the .ini file until after the inpout library is used.

Is there a way to only use this library when required like some components do, rather than declaring it the way I have?

Interventionist answered 13/2, 2012 at 16:3 Comment(1)
Yes, you can do 'dynamic loading'. Use LoadLibrary and GetProcAddress, as discussed here.Cordes
C
17

In Delphi versions prior to 2010, you have to use classic dynamic loading. Consider this typical (and simple) example calling the Beep function from Kernel32.dll (which you should not hardcode the path to in real code, of course!):

type
  TBeepFunc = function(dwFreq: DWORD; dwDuration: DWORD): BOOL; stdcall;

procedure TForm4.FormClick(Sender: TObject);
var
  lib: HMODULE;
  prc: TBeepFunc;
begin

  lib := LoadLibrary('C:\WINDOWS\System32\Kernel32.dll');
  if lib = 0 then RaiseLastOSError;
  try
    @prc := GetProcAddress(lib, 'Beep');
    if Assigned(prc) then
      prc(400, 2000)
    else
      ShowMessage('WTF? No Beep in Kernel32.dll?!');
  finally
    FreeLibrary(lib);
  end;
end;
Cordes answered 13/2, 2012 at 16:17 Comment(4)
Thanks @Andreas this looks like the easiest solution. RaiseLastOSError wasn't recognised in my Delphi5 though. Am I safe to remove the If statement as the error will always be caught by the second message "WTF? Either no process or library not loaded?!"Interventionist
@notidaho: No, if lib = 0 you must not enter the try block. You can replace RaiseLastOSError with any exception-raising code, such as raise Exception.Create('Couldn't load library.'). Of course, you can also replace the line in questoin with if lib <> 0 then, so that the entire try..finally..end will be run only if the lib is valid. But it is probably better to display an error message.Cordes
RaiseLastOSError = RaiseLastWin32Error in D5Numismatist
This method has worked an absolute treat thanks. I issued a user friendly message as I'm not sure how useful the last raised error is? As a point of interest even though I just try to LoadLibrary('input.dll') from the root it still works if the library is in System32.Interventionist
R
17

This facility, known as delay loading, was added in Delphi 2010.

Using your code as an example you could write your import like this:

function Inp(PortAddress: Integer): Integer; stdcall; 
    external 'inpout32.dll' name 'Inp32' delayed;

The binding to this external function will be performed only when the function is first called. If the binding fails then an exception is raised at runtime.

You can use SetDliNotifyHook and SetDliFailureHook to customise the delay loading behaviour should you need even more fine-grained control.

Some blog articles to supplement the product documentation:


On older versions of Delphi you can use LoadLibrary and GetProcAddress. Or, if you want something a little slicker I can heartily recommend Hallvard Vassbotn's delay load class which he describes in this blog article. This code wraps up all the boiler plate of calling LoadLibrary and GetProcAddress and is only slightly more cumbersome to use than the new Delphi 2010 built-in feature.

I successfully used Hallvard's library for many years. One minor word of caution is that it is not threadsafe so if multiple threads attempt to bind to a function at the same time then the code can fail. This is easy enough to fix by adding internal locks to Hallvard's code.

Reek answered 13/2, 2012 at 16:6 Comment(5)
Oh, so there is no need to GetProcAddress and all that anymore? Too bad, that was fun...Cordes
@Andreas Personally I'm still using GetProcAddress and my own class to handle delay loading, but no, they added this new feature in D2010. Too late for you I fear.Reek
Thanks David seems like a good idea but is there any way I can do this in Delphi5?Interventionist
Gah, I just saw the D5 in your text. Missed it on first read. Better off in a tag FWIW. In D5 I would steer you to Hallvard Vassbotn's delay load library - see my updated answer.Reek
Not to worry David maybe it'll be useful to some one else. Personally I think I'm gonna go down LoadLibrary and GetProcAddress route. cheersInterventionist
C
17

In Delphi versions prior to 2010, you have to use classic dynamic loading. Consider this typical (and simple) example calling the Beep function from Kernel32.dll (which you should not hardcode the path to in real code, of course!):

type
  TBeepFunc = function(dwFreq: DWORD; dwDuration: DWORD): BOOL; stdcall;

procedure TForm4.FormClick(Sender: TObject);
var
  lib: HMODULE;
  prc: TBeepFunc;
begin

  lib := LoadLibrary('C:\WINDOWS\System32\Kernel32.dll');
  if lib = 0 then RaiseLastOSError;
  try
    @prc := GetProcAddress(lib, 'Beep');
    if Assigned(prc) then
      prc(400, 2000)
    else
      ShowMessage('WTF? No Beep in Kernel32.dll?!');
  finally
    FreeLibrary(lib);
  end;
end;
Cordes answered 13/2, 2012 at 16:17 Comment(4)
Thanks @Andreas this looks like the easiest solution. RaiseLastOSError wasn't recognised in my Delphi5 though. Am I safe to remove the If statement as the error will always be caught by the second message "WTF? Either no process or library not loaded?!"Interventionist
@notidaho: No, if lib = 0 you must not enter the try block. You can replace RaiseLastOSError with any exception-raising code, such as raise Exception.Create('Couldn't load library.'). Of course, you can also replace the line in questoin with if lib <> 0 then, so that the entire try..finally..end will be run only if the lib is valid. But it is probably better to display an error message.Cordes
RaiseLastOSError = RaiseLastWin32Error in D5Numismatist
This method has worked an absolute treat thanks. I issued a user friendly message as I'm not sure how useful the last raised error is? As a point of interest even though I just try to LoadLibrary('input.dll') from the root it still works if the library is in System32.Interventionist

© 2022 - 2024 — McMap. All rights reserved.