I have a program that calls a DLL stored in the same folder that the program is stored in. When I run the calling code, either from the IDE or the program directly, it finds the DLL and gives no errors. But, when the client runs the program, it gives the error "Unable To Load DllSendOrder2018.dll". This behavior just started. On old versions of the code, it loads with no errors (other than another problem I need to fix).
procedure TFrmMain.BtnSendOrderClick(Sender: TObject);
var
SendOrders : procedure; stdcall;
begin
DLLHandleSend := LoadLibrary('DllSendOrder2018.dll');
if DLLHandleSend <> 0 then
begin
#SendOrders := GetProcAddress(DLLHandleSend,'SendOrders');
try
SendOrders;
except
on E: Exception do
ShowMessage(E.Message);
end;
FreeLibrary(DLLHandleSend);
end
else
MessageDlg('Unable To Load DllSendOrder2018.dll',mtError,[mbOk],0);
FldLookup.SetFocus;
end;
Is there a better place to put the DLL?
Related
I wrote an unattended HTTPS upload client which shouldn't require user interaction.
Everything was flawless until I deployed it blindly to a remote system that I do not have a remote desktop access to. The tool reported in logs that SSL libraries are missing.
Ok, I deployed LIBEAY32.dll and SSLEAY32.dll to the application folder on the remote system, but then the tool hung and I couldn't figure out what was going on until I wrote a tool which makes a remote screenshot.
On the screenshot I see a modal window from csrss.exe process with a message:
The program can't start because MSVCR120.dll is missing from your computer.
The window appeared despite of having a lot of try except blocks and Application.OnException handler.
I would like that in such cases application would not be hold up so it can report failure to it's log.
How to achieve this?
In a current implementation TIdHttp.Post call just hangs.
P.S. I solved the absence of DLL by copying it to app folder, but my question is about catching such errors.
To avoid this error, you can use the OpenSSL DLLs available on https://indy.fulgan.com/SSL/
They do not have this dependency on MSVCRT.
Or, use TNetHTTPClient.
From this answer:
TNetHTTPClient was introduced in Delphi XE8.
The most important benefit of TNetHTTPClient is that it allows your
application to support HTTPS without having to provide your own
support for SSL/TLS. TNetHTTPClient relies on the SSL/TLS support
provided by the operating system.
Using the information from Remy Lebeau's comment, I experimented with SetErrorMode function in Delphi 6.
The setup consists of three projects:
Host application, that links caller DLL dynamically,
caller DLL, that links worker DLL statically,
and a worker DLL.
At first I place both DLLs in host app folder, test that everything works, and then delete worker DLL and test without it.
Here is some code:
Host application
program MyHost;
uses
Windows, SysUtils;
var
hLib: HMODULE;
procedure Box(msg: WideString);
begin
MessageBoxW(0, PWideChar(msg), 'MyHost Message', 0);
end;
procedure ShowLastError();
begin
Box('LastError: ' + SysErrorMessage(GetLastError()));
end;
type
TDoWork = procedure();
var
DoWork: TDoWork;
begin
SetErrorMode(SEM_FAILCRITICALERRORS);
try
{Without SetErrorMode it displays modal dialog.
No exception is generated.
After clicking at [Ok], it goes to "if hLib = 0".
With SetErrorMode it just goes to "if hLib = 0"}
hLib := LoadLibrary('CallerLib.dll');
if hLib = 0 then begin
ShowLastError();
Halt(1);
end;
try
#DoWork := GetProcAddress(hLib, 'DoWork');
if #DoWork <> nil then DoWork();
finally
FreeLibrary(hLib);
end;
except
on ex: Exception do Box(ex.ClassName + ': ' + ex.Message);
end;
end.
Caller DLL
library CallerLib;
uses
Windows;
//Static linking
procedure Function1(); stdcall; external 'MyLib.dll';
procedure Function2(); stdcall; external 'MyLib.dll';
//To be dynamically linked
procedure DoWork(); stdcall; export;
begin
Function1();
Function2();
end;
exports
DoWork;
end.
Worker DLL
library MyLib;
uses
Windows;
procedure Function1(); stdcall; export;
begin
MessageBox(0, 'MyDLL.Function1', 'MyDLL', 0);
end;
procedure Function2(); stdcall; export;
begin
MessageBox(0, 'MyDLL.Function2', 'MyDLL', 0);
end;
exports
Function1, Function2;
end.
Beware of Exit command usage in inline functions! I have been using Delphi XE3 here.
Symptom
In certain circumstances, when a call is made to an inline function that contains Exit command, and the return value of the inline function is used directly in WriteLn(), the compiler reports an error message,
"dcc" exited with code 1.
or even worst, the Delphi IDE terminates without any confirmation.
function ProcessNumber(const iNumber: Integer): Boolean; inline;
begin
if iNumber = 0 then begin
Result := False;
Exit;
end;
// some code here ...
Result := True;
end;
procedure Test;
begin
writeln( ProcessNumber(0) );
end;
begin
Test;
ReadLn;
end.
However, if the return value of the inline function is stored in a variable, and then the variable is used in WriteLn(), the problem does not occur.
procedure Test;
var
b: Boolean;
begin
b := ProcessNumber(0);
writeln(b);
end;
Questions
Is this a compiler bug?
If this a bug, is there a workaround to safely exit from an inline function?
This is certainly a bug. It occurs in all the IDE versions that I tested, XE3, XE7 and XE8. I honestly don't think there's a lot you can do. For me the IDE terminates on compilation every time. I think you'll just have to write the code in a way that does not lead to IDE crashes.
You can use the IDE option that forces compilation to use msbuild. This puts the compilation into a separate process and so ensures that the IDE won't crash. It won't help you much though because although your IDE will not keep dying, you still won't be able to compile your program!
When you build with msbuild, you get an error of this form:
error F2084: Internal Error: GPFC00000FD-004D3F34-0
The GPF stands for General Protection Fault, that is an memory access violation. This presumably is an unhandled exception that is killing the IDE when the compilation is performed in process.
My advice is that you submit a bug report to Quality Portal. That is the only way to get the defect fixed. Although do not expect a fix ever to come to XE3.
One workaround that you could use here is to reverse the if conditional implementation and thus avoid using of Exit command altogether.
So instead of using
function ProcessNumber(const iNumber: Integer): Boolean; inline;
begin
if iNumber = 0 then begin
Result := False;
Exit;
end;
// some code here ...
Result := True;
end;
use
function ProcessNumber(const iNumber: Integer): Boolean; inline;
begin
if iNumber <> 0 then begin
// some code here
Result := True;
end;
else
Result := False;
//No exit needed here as this is already at the end of your method
end;
Who is responsible for error checking and handling?
I don't have any of the expensive component libraries such as DevExpress or TMS Components etc so I cannot look at source to get an idea of how most components manage error handling.
Specifically what I am wanting to know is should there be a limit to how many errors and warnings component developers should try to capture? Is there a balance between having meaningful error checking and just making it too easy for developers using your component?
Here is an example using a few scenarios:
Note these are directly from the components source (made up for example purposes)
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
or
procedure TMyComponent.AddFromFile(FileName: string);
begin
if FileExists(FileName) then
begin
FBitmap.LoadFromFile(FileName);
end
else
raise Exception.Create(FileName + ' does not exist.');
end;
And these last two are using an instance of the component at runtime:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
or
procedure TForm1.FormCreate(Sender: TObject);
begin
if FileExists('D:\Test.bmp') then
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end
else
raise Exception.Create('D:\Test.bmp does not exist.');
end;
I guess it comes down to who should error check and handle what? Is the component developer responsible for handling these types of checking or the user of the component?
As I am writing this I believe both component developer and user should handle such checking but I am unsure, so I am looking for what the general consensus amongst developers is.
Thanks.
To answer your specific queestion:
Specifically what I am wanting to know is should there be a limit to how many errors and warnings component developers should try to capture? Is there a balance between having meaningful error checking and just making it too easy for developers using your component?
The general rule about exception handling is that you should only catch exceptions you know how to handle, and let others propagate to higher code that may know how to handle it. If an exception is raised inside of your component, the component needs to decide whether to:
handle that particular exception internally and gracefully move on to other things without notifying the caller at all.
re-throw the exception (maybe with tweaks made to it), or re-throw a whole new exception, to allow the caller to identify and handle that specific failure, if desired.
ignore the exception (don't catch it at all) and just let it propagate as-is.
If an API used by your component returns an error code instead of raising an exception, the component needs to decide how to handle that as well. Whether to ignore the error and move on, or raise an exception to make it more apparent.
In your particular example, I prefer the following approach:
type
EMyComponentAddError = class(Exception)
private
FFileName: String;
begin
constructor CreateWithFileName(const AFileName: string);
property FileName: string read FFileName;
end;
constructor EMyComponentAddError.CreateWithFileName(const AFileName: string);
begin
inherited CreateFmt('Unable to add file: %s', [AFileName]);
FFileName := AFileName;
end;
procedure TMyComponent.AddFromFile(FileName: string);
begin
try
FBitmap.LoadFromFile(FileName);
except
Exception.RaiseOuterException(EMyComponentAddError.CreateWithFileName(FileName));
end;
end;
This allows your component to recognize that an error occurred, act on it as needed, and still report component-specific information to the caller without losing the original error that caused the actual failure. If the caller is interested in the details, it can catch the exception, look at its InnerException property, access custom properties if present, etc.
For example:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
Let's assume MyComponent1.AddFromFile('D:\Test.bmp'); fails. The default exception handler will catch it and display a popup message that says:
Unable to add file: D:\Test.bmp
Useful, but little details, as it could have failed for any number of reasons. Maybe the file could not be opened, but why? Non-existant vs no permission? Maybe the file was opened but corrupted? Maybe memory could not be allocated? And so on.
The caller could catch it and display more useful info, if so desired (it is not required - the component provides the info, the caller decides whether to use it or not):
procedure TForm1.FormCreate(Sender: TObject);
begin
try
MyComponent1.AddFromFile('D:\Test.bmp');
except
on E: EMyComponentAddError do
begin
ShowMessage('There was a problem adding a file:'+sLineBreak+E.FileName+sLineBreak+sLineBreak+E.InnerException.Message);
Sysutils.Abort;
end;
end;
end;
Or:
procedure TForm1.FormCreate(Sender: TObject);
begin
try
MyComponent1.AddFromFile('D:\Test.bmp');
except
on E: EMyComponentAddError do
begin
raise Exception.CreateFmt('There was a problem adding a file:'#10'%s'#10#10'%s', [E.FileName, E.InnerException.Message]);
end;
end;
end;
Either of which would display:
There was a problem adding a file:
D:\Test.bmp
The file was not found
As David said we only need this
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
This will check that
there is an existing file
in this file is a valid bitmap
Now it depends on the application, how important is this for the application. If this TForm1 is the Application.MainForm, every exception you did not catch inside the creation process will terminate the application. This is sometimes a valid behavior.
Very important, the application cannot run without
procedure TForm1.Form1Create(Sender:TObject);
begin
MyComponent.AddFromFile( 'D:\Test.bmp' );
end;
or wrap the exception for a user-friendly message
procedure TForm1.Form1Create(Sender:TObject);
begin
try
MyComponent.AddFromFile( 'D:\Test.bmp' );
except
on E: Exception do
raise Exception.Create( 'Sorry, I cannot run, because of: ' + E.Message );
end;
end;
Very important, but we have a fallback to handle this, maybe
procedure TForm1.Form1Create(Sender:TObject);
var
LBitmapFiles : TStringList;
LBitmapIdx : Integer;
LBitmapLoaded : Boolean;
LErrorStore : TStringList;
begin
LBitmapFiles := nil;
LErrorStore := nil;
try
LBitmapFiles := TStringList.Create;
LErrorStore := TStringList.Create;
LBitmapFiles.Add( 'D:\Test.bmp' );
LBitmapFiles.Add( 'D:\Fallback.bmp' );
LBitmapLoaded := False;
while not LBitmapLoaded and ( LBitmapIdx < LBitmapFiles.Count ) do
try
MyComponent.AddFromFile( LBitmapFiles[LBitmapIdx] );
LBitmapLoaded := True;
except
on E: Exception do
begin
LErrorStore.Add( LBitmapFiles[LBitmapIdx] + ': ' + E.Message );
Inc( LBitmapIdx );
end;
end;
if not LBitmapLoaded then
raise Exception.Create( 'Sorry, I cannot run, because of: ' + LErrorStore.Text );
finally
LErrorStore.Free;
LBitmapFiles.Free;
end;
end;
There are other fallbacks possible and this also depends on the application (f.i. set a dummy bitmap to the component) to get the application to work properly.
Not important, if we have no image ... we have no image, who cares
procedure TForm1.Form1Create(Sender:TObject);
const
CBitmapFile = 'D:\Test.bmp';
begin
// check, if there is a file
if FileExists( CBitmapFile ) then
try
MyComponent.AddFromFile( CBitmapFile );
except
on E: Exception do
begin
// Maybe log the exception
SomeLogger.Log( E );
// Maybe set some extra parameters for the application to know, this has failed
RunningWithoutBitmap();
end;
end
else
// Maybe set some extra parameters for the application to know, this has failed
RunningWithoutBitmap();
end;
Component
procedure TMyComponent.AddFromFile(FileName: string);
begin
FBitmap.LoadFromFile(FileName);
end;
This is all you need. If the bitmap object cannot load the file, for whatever reason, it will raise an exception. Let that exception propagate to the consumer of the code.
There's really no point trying to test whether or not the file exists. What if the file exists and it is not a bitmap file? What if the file exists, is a bitmap file, but the disk has a duff sector and the file read fails? If you attempt to check for all error conditions, you will just be repeating the checks that the LoadFromFile method already does.
Some error conditions cannot possibly be checked from the outside. An error that only becomes apparent part way through reading the file cannot reasonably be checked from the outside.
One very common consequence of over-zealous, duplicate error checking is that you end up with code that produces errors in scenarios where there should be none. If you get your error checking wrong you could end up reporting an error that would not have occurred had you let the underlying code run.
Consumer
procedure TForm1.FormCreate(Sender: TObject);
begin
MyComponent1.AddFromFile('D:\Test.bmp');
end;
At this point the decision is more difficult. I would typically expect the following question to be the driver of the decision:
Is it an expected, and reasonable event, for the file not to be present?
If the answer to that question is yes, then you should consider handling the exception in the FormCreate method. Again, testing FileExists() catches just one failure mode, albeit a common one. Perhaps you should use a try/except block to catch the error.
If the answer to the question is no, let the error propagate.
That said, you should also consider whether or not you want an exception to be thrown from your form's OnCreate event handler. That may be perfectly reasonable, but it is certainly conceivable that you will not wish to do this.
I wrote a program using IdHTTP and IdFTP but I have a problem. I wrote it on windows XP 32 bit (using Delphi XE4) and the exe file (Project1.exe) works fine. When I try to open it on windows 7 64 bit, the computer gives me an error.
Picture:
It means "You cannot acces to the path or the specified file. Maybe you don't have enough permissions". I've never seen this error before. Here you can see a picture of the folder with source code.
How can I solve my problem?
Here's the code:
function downloadSrc(var aUrl:ansiString):ansiString;
begin
with tIdHttp.create(nil) do begin //Create Indy http object
request.userAgent:=INET_USERAGENT; //Custom user agent string
redirectMaximum:=INET_REDIRECT_MAX; //Maximum redirects
handleRedirects:=INET_REDIRECT_MAX<>0; //Handle redirects
readTimeOut:=INET_TIMEOUT_SECS*1000; //Read timeout msec
try //Catch errors
result:=get(aUrl); //Do the request
if url.port='80' then url.port:=''; //Remove port 80 from final URL
aUrl:=url.getFullURI //Return final URL
except result:='error' end; //Return an error message if failed
free //Free the http object
end
end;
procedure TForm1.FormCreate(Sender: TObject);
var i:integer;
begin
if not(DirectoryExists('C:\mk7vrlist')) then
begin
CreateDir('C:\mk7vrlist');
end;
ComboBox1.Items.BeginUpdate;
for i := 0 to 59 do
begin
ComboBox1.AddItem(IntToStr(40000+i*1000), nil);
end;
ComboBox1.AddItem('99999', nil);
ComboBox1.Items.EndUpdate;
end;
procedure TForm1.Label5Click(Sender: TObject);
begin
ShellExecute(self.WindowHandle,'open',PChar('http://www.mk7vrlist.altervista.org'),nil,nil,SW_SHOWNORMAL);
end;
procedure TForm1.SpeedButton1Click(Sender: TObject);
var s:ansiString;
begin
IdFTP1.Host:= 'mk7vrlist.altervista.org';
IdFTP1.Username:='mk7vrlist';
IdFTP1.Password:=pass;
IdFTP1.Connect;
s:='http://www.mk7vrlist.altervista.org/databases/test.txt';
Memo1.Lines.Add(Edit1.Text+':'+ComboBox1.Text+':'+Edit2.Text);
Memo1.Lines.Add(downloadSrc(s));
Memo1.Lines.SaveToFile('C:\mk7vrlist\test.txt');
IdFTP1.ChangeDir('databases/');
IdFTP1.Put('C:\mk7vrlist\test.txt');
IdFTP1.Quit;
IdFTP1.Disconnect;
Label10.Visible:=True;
Beep;
end;
Assuming your account has administrative rights, right click on your program's icon, and invoke 'run as administrator' - you will get prompted/warned - click yes.
I don't believe the problem is 32->64 bit, but going from XP to Win 7, which by default is more particular about access.
You can set up a shortcut configured always to run it as admin, to avoid the right click step, but you will still get prompted/warned.
CreateDir('C:\mk7vrlist)
Normal users do not have that kind of access to c:\
Do something like this instead (pseudo code):
CreateDir('%temp%\mk7vrlist)
I have created a procedure in a dll that opens a form and then prints a report.
This procedure works perfectly from an exe.
I have wrapped the unit that contains this procedure and forms in a dll and exported the procedure as follows:
{$R *.res}
Procedure PrintTopSellers; stdcall;
begin
Form1 := TForm1.create(nil);
GetMonth := TGetMonth.create(nil);
Form1.PrintTopSellers;
end;
exports PrintTopSellers;
begin
end.
Now I call this procedure PrintTopSellers from an exe as follows:
procedure TForm1.Button5Click(Sender: TObject);
type
TRead_iButton = function :integer;
var
DLL_Handle: THandle;
Read_iButton: TRead_iButton;
Begin
DLL_Handle := LoadLibrary('c:\Catalog.dll');
if DLL_Handle <> 0 then
begin
#Read_iButton:= GetProcAddress(DLL_Handle, 'PrintTopSellers');
Read_iButton;
end;
application.ProcessMessages;
FreeLibrary(DLL_Handle);
end;
The call to the procedure works perfectly. However, after I close the calling exe, I get an access violation - "Access violation at address 00BAC89C. Read of address 00BAC89C."
Appreciate any assistance. I am using Delphi 7.
Thanks
You are creating Form1, a windowed control, in the DLL. But you never destroy it. Then you unload the DLL which unloads the code that implements the window procedures for all windows created by the DLL. Presumably when the process shuts down, the window procedures are called, but there is no code there anymore.
Fix the problem by destroying all objects that the DLL creates. It looks to me like the best approach is to do that when PrintTopSellers terminates.
Procedure PrintTopSellers; stdcall;
begin
Form1 := TForm1.create(nil);
try
GetMonth := TGetMonth.create(nil);
try
Form1.PrintTopSellers;
finally
GetMonth.Free;
end;
finally
Form1.Free;
end;
end;
In the code that loads the DLL, TRead_iButton is declared incorrectly. It should be
TRead_iButton = procedure; stdcall;
But that doesn't actually explain the problem here since the signature mismatch is benign for a parameterless procedure.
"TRead_iButton = function: integer; register;"
"Procedure PrintTopSellers; stdcall;"
Absolutely different conventions/types, ain't them ?
Make them the same.
And better ditch DLL and use packages (BPL), then compiler would make you safe from such errors
We also don't see the code neither in Form1.PrintTopSellers nor in TGetMonth. The all can leave some dangling pointers in the host exe, that would get accesses after DLL unloaded.
Show exactly chain of function calls leading to AV - it is called stack trace.
Debug info + some excaption interrupt like Jedi CodeLibrary (used by Delphi IDE) madExcept, EurekaLog, synopse log and a lot of other exist.
Display the call stack in a Delphi Win32 application
Does DLL or EXE use Runtime packages ?