Hi Laurence,

> So basically we're back to square one. The method to add the package
> tkdnd to the tkkit.dll works until we try to create a standalone
> executable.

I've finally figured it out. :)

You are hitting 2 bugs in PerlApp that cause this behavior, and prevent
a sensible error message.  Fortunately I also found a workaround until
we release an updated PDK.

The bugs
========

The tkkit.dll is a "companion DLL" that is being extracted into the same
directory as Tcl.dll.  The directory name for this extraction is however
only based on the MD5 checksum of Tcl.dll.  So if you have run a Tkx
program on that machine before, then the extraction directory already
exists, but contains a different version of tkkit.dll.

The extraction code will detect this situation and wait a couple of seconds
(assuming that maybe a concurrently running process is in the middle of
extracting the same file to this shared location).  If after waiting
for a little bit the version on disk is still different from the bundled
version, then it gives up and dies with an error message that the file
on disk may be corrupt.

Unfortunately this error is discarded, as the extraction is eval()ed from
C code, and there is no check if the code inside the eval died. Execution
just continues.  Due to this inconsistent state Tcl.pm will later attempt
to load Tcl.dll from the wrong directory (which doesn't even exist), and
the process finally dies, albeit with a rather confusing error message.

So the bugs are:

1) The MD5 checksum does not cover all content of the extraction directory.
2) Extraction errors are not reported and execution continues.

Workarounds
===========

There are several possible workarounds:

1) Use the --clean option.

This will extract the files to a process-specific subdirectory, so there is
no chance of conflict.  This works, but doesn't provide the reduced startup
time you get when the files only need to be extracted the very first time
the program is run and then stay in the temp directory.

Using the --dyndll option will not help, as tkkit.dll cannot be loaded from
memory and must be written to disk so that the Tcl code can access the
embedded VFS.

2) Don't bundle tkkit.dll in your executable but provide it separately.

This avoid the file extraction costs in all cases, but of course is no longer
a single file deployment.

3) Modify Tcl.dll whenever you update tkkit.dll.

That way Tcl.dll will generate a different MD5 checksum and you no longer
get a conflict at extraction time.

All module DLLs build by ActiveState contains the filename of the debugging
symbols at the end of the file (it is part of the PE file format).  Tcl.dll
ends with a string like this  '\blib\arch\auto\Tcl\Tcl.pdb' plus a trailing
"\0" to mark the end of the string.

I've written a little script that replaces the final 5 bytes (".pdb\0")
with "\0" followed by the file modification time of tkkit.dll as a 32-bit
integer:

#!perl
use strict;
use warnings;
use Config qw(%Config);

my $dir = "$Config{privlibexp}\\auto\\Tcl";
die unless -f "$dir\\Tcl.dll" && -f "$dir\\tkkit.dll";
my $mtime = pack "L", (stat("$dir\\tkkit.dll"))[9];

chmod 0666, "$dir\\Tcl.dll";
open(my $fh, "+<", "$dir\\Tcl.dll") or die;
binmode($fh);
$_ = do {local $/; <$fh> };
s/(blib\\arch\\auto\\Tcl\\Tcl).....$/$1\0$mtime/s or die;
seek($fh,0,0) or die;
print $fh $_ or die;
close($fh) or die;
__END__

If you run this script every time you modify tkkit.dll, then you should
no longer run into the bugs explained above.  You will no longer be able
to load Tcl.dll into e.g. Visual Studio and have it look for the debugging
symbols in Tcl.pdb, but it is rather unlikely that you would ever want
to do that anyways, especially in a packaged application.

Cheers,
-Jan


Reply via email to