2

How can I unlock or delete a file that is in use, so that I can delete it? The file in question is used by my own application.

More specifically, my application is using the freeware Zeos Lib. When opening and saving my database the sqlite3.dll file must reside in the same directory as my application to work correctly.

I want my application to be 100% standalone, so I have added the sqlite3.dll as RC_DATA to my project, and whenever I need to use it (ie, open or save database), I extract it to the same folder as my application. Once the open or save operation has completed, I would like to delete the sqlite3.dll file, and no one would even know it was there or have to worry about missing libraries etc. (While I can appreciate that some of you may not like the idea of storing the libraries inside the application, I have my reasons for doing this: I don't want my end-users knowing what is behind the functioning of my application (SQL), and they also don't need to worry about missing dynamic link libraries.)

The problem is, I can successfully extract the sqlite3.dll and use it for my operations, but the file becomes locked by my application (until I close my application), which brings me to either:

  1. Force unlock the sqlite3.dll file, without closing my application

  2. Force delete the sqlite3.dll file

Unless of course there is another suggestion?

Here is a sample of extracting and using it. No direct calls need to be made such as LoadLibrary etc from my part; the Zeos Lib units must take care of this, so long as the sqlite3.dll is in the same directory as the application.

procedure ExtractResource(ResName: String; Filename: String);
var
  ResStream: TResourceStream;
begin
  ResStream:= TResourceStream.Create(HInstance, ResName, RT_RCDATA);
  try
    ResStream.Position:= 0;
    ResStream.SaveToFile(Filename);
  finally
    ResStream.Free;
  end;
end;

//Open procedure
var
  sFileName: String = ExtractFilePath(ParamStr(0)) + 'Sqlite3.dll';    

if OpenDialog1.Execute then
begin
  ExtractResource('RES_SQLITE3', sFileName);
  ... //process my database
  ...
  ... // finished opening database
  if FileExists(sFileName) then
        DeleteFile(sFileName);
end;

EDIT

I think what I am attempting to do is not very practical, it is not a good idea to this as STATUS_ACCESS_DENIED earlier commented on. I have decided it is best not to proceed with what I set out to do.

  • 1
    @Craig maybe you can't delete the file because is still *used* for you application , if you are using `LoadLibrary` to load the dll, remember call `FreeLibrary` before to delete. – RRUZ May 16 '11 at 16:42
  • Title asks how to delete a locked file. Body asks how to make sure a file is unlocked. The body is the preferable solution. Why do you think anything needs to be *forced* yet? Why is the file still locked, and what have you done to try to unlock it? – Rob Kennedy May 16 '11 at 16:45
  • no I dont need to make any direct calls to the library, as long as it exists in same folder as the Application. I will edit the question to show the code I used. –  May 16 '11 at 16:47
  • How are you linking to the functions in the dll? – Lasse V. Karlsen May 16 '11 at 16:59
  • 1
    Craig, *something* obviously calls it, or else you wouldn't need it at all. How do you use it? I suspect we'll discover that you have SQLite-using code that you didn't write, and it loads the library but doesn't release it. Maybe that's because it never releases it, or maybe it's because you haven't told it to. Show *that* code, please. – Rob Kennedy May 16 '11 at 17:01
  • 1
    I wrote the code, the only thing I figured might of been locking it is TZConnection. But I create this like so: Connector: TZConnection.Create(nil); and disconnect it Connector.Disconnect; and Connector.Free. –  May 16 '11 at 17:10

6 Answers6

2

You should better use static linking of the SQLite3 engine instead of relying on an external dll. By including the .obj into your .dcu SQLite3 unit.

It will also add some nice features like ability to use FastMM4 as the memory manager for the SQlite3 engine also (speed up), and potentially some nice low-level features (like encryption).

See for instance:

Arnaud Bouchez
  • 42,305
  • 3
  • 71
  • 159
1

Simple, don't do it. There is a reason why the file is locked. Usually it's a very good reason, such as some other process (or even your own) still using it. Find out what keeps the file locked (e.g. using Process Explorer) and as long as it's your process make sure you did free everything. E.g. FreeLibrary after LoadLibrary etc ...

If you absolutely must remove the file, try DeleteFile and when that fails call MoveFileEx with MOVEFILE_DELAY_UNTIL_REBOOT to remove the file upon reboot.

You can have another MoveFile or MoveFileEx in between to "rename" the file while it is in use (this usually works on the same partition). But this is only necessary if you rely on the file name and therefore another instance of your program could get blocked if the old (locked) stale file still exists.

There are methods to do what you want, but they should not end up in code that is released to end users. It's basically boiling down to hackery that would - for example - use injected threads to close/unlock files in the entity that holds them locked. But it's bad form. Try to avoid it.

0xC0000022L
  • 20,597
  • 9
  • 86
  • 152
  • Maybe you are correct and I should not do this really. In regards to your comments on moving/deleting the file, as I said to Don doing this after boot kind of defeats the purpose of what I was trying to do. –  May 16 '11 at 17:39
  • @Craig: as I commented there, the important piece of code in between is missing. Basically that which keeps the file locked. And you should post it, because it is relevant. There is likely a `LoadLibrary` and there should be one `FreeLibrary` for each `LoadLibrary`. And when you call `LoadLibrary` multiple times, this can affect the reference count, too. – 0xC0000022L May 16 '11 at 17:41
  • 1
    And one should wait a few seconds after the last freelibrary before trying to delete. (or at least retry a few secs later if the first time fails). Locks sometimes linger, specially on network drives. – Marco van de Voort May 21 '11 at 15:28
0

Though you can't delete a file that's in use, you can rename it. Just rename the dll to something like SQLITE3.DELETE.guid. On startup you can have your app try to delete any sqlite.delete.* file so after the file frees it will go away. -don

Don Dickinson
  • 6,200
  • 3
  • 35
  • 30
  • Better yet a delayed removal after renaming the file. I.e. `MoveFile` followed by `MoveFileEx` with `MOVEFILE_DELAY_UNTIL_REBOOT`. I personally prefer `DeleteFile` followed by `MoveFileEx` with `MOVEFILE_DELAY_UNTIL_REBOOT`, though. – 0xC0000022L May 16 '11 at 17:26
  • 1
    Thanks for the tip, but I need it deleting immediately, not scheduled to be deleted after boot. –  May 16 '11 at 17:28
  • @Craig: in this case you have still not posted relevant code that contains the "mystery" as to why the file is locked in the first place. Likely it's as simple as FreeLibrary. – 0xC0000022L May 16 '11 at 17:37
0

It looks like the LibraryLoader in ZPlainSqLite3.pas is what is loading the DLL. You could try running the code from the finalization section before trying to delete the DLL:

  if Assigned(LibraryLoader) then
     LibraryLoader.Free;
jasonpenny
  • 2,999
  • 1
  • 24
  • 23
0

You should first make sure the file isn't still in use. In your case, it is still in use because the database library loaded the DLL and hasn't freed it yet. Since you've already disconnected from the database, it's probably not still in active use, but the OS doesn't know that — if a DLL is loaded, the OS assumes it's still needed and disallows deletion.

When you connect to the database, Zeos finds the corresponding database driver (in this case, TZSQLiteDriver, from ZDbcSqLite.pas) and asks it to load its functions. But when you disconnect from the database, Zeos does not ask the database driver to unload its functions. That would be wasteful in a typical program, where there may be several connections established and destroyed over the lifetime of the program.

If you're sure that there are no open connections to the database, you can unload the functions yourself. The loader for SQLite is in ZPlainSqLite3.pas. Although you could free the loader object entirely, that may cause problems later since there are other parts of Zeos that expect it to still be around when they need it. Instead, just tell it to unload:

ZPlainSqLite3.Loader.FreeNativeLibrary;

That causes the object to set flags indicating that it is unloaded, so if you need to use it again, it will reload everything.


If you really wanted to get fancy, you could try writing your own TZNativeLibraryLoader descendant that automatically fetches the DLL from the resource when loading and deletes the file when unloading. Then you wouldn't need to worry about database-connection lifetimes in the rest of your program. You could just connect and disconnect like everyone else.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • I dont seem to have ZPlainSqLite3.pas, Windows Search does not return the file either? never mind it seems to be in ZPlainSqLiteDriver.pas –  May 16 '11 at 18:02
  • Wrong again, for some reason I dont have the file in my installation?? I can see what it looks like here: http://www.koders.com/delphi/fidB6E96529FE947FE2CFAFEBC741F72FF09BB306F5.aspx?s=hook#L238 but I dont have that unit –  May 16 '11 at 18:11
  • Based on [the tarball I just downloaded half an hour ago](http://zeoslib.cvs.sourceforge.net/), it's in zeosdbo_rework\src\plain. The driver file contains the driver, not the loader. The driver doesn't let you control the loader, which is what you need. Search your source code for "sqlite3.dll" — that's the file that should contain the loader. – Rob Kennedy May 16 '11 at 18:13
  • @Rob Thanks for the information, but following the advise of STATUS_ACCESS_DENIED I believe what I am trying to do is not very practical. I have up voted your comment though from the advice you have also shared. –  May 16 '11 at 18:44
  • 1
    Right. What you were *trying* to do was to delete the file while it was still loaded, or to *forcefully* unload it. My answer gives you a way to avoid doing that. It lets you unload the DLL *gracefully*, in a way that the rest of your program can be fully aware of and handle without the potential for crashing. – Rob Kennedy May 16 '11 at 18:53
  • Well I tried modifying the Zeos source units, rebuilt them but it was still locked by Windows. I changed my approach to my initial problem, instead of extracting and trying to delete the sqlite3.dll when not needed, I just check if the sqlite3.dll file exists at startup and carry on as normal, if it doesn't exist I extract it and use it like normal. problem solved. –  May 16 '11 at 19:04
0

There's a couple solutions available that allow you to compile the SQLite libraries into your exe, rather than using the DLL. I think that'd be a much better approach if you really must have a single file executable. What you are basically trying to duplicate by extracting/deleting the DLL is the functionality of an installer, and you really shouldnt. That approach is just asking for support problems.

Also keep in mind that your app may need to run with administrator rights to be able to extract the dll and save it into the program files directory.

If you can live with a DLL being installed along with the executable, you can rename the DLL file to something else (ie, Database.dll), and make changes in the zeos code so that it points to the new DLL name. Then the SQLite functionality would not be immediately apparent.

GrandmasterB
  • 3,396
  • 1
  • 23
  • 22