如何从 Perl 创建然后使用长的 Windows 路径? [英] How do I create then use long Windows paths from Perl?

查看:21
本文介绍了如何从 Perl 创建然后使用长的 Windows 路径?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我参与了在 Windows 中创建长得可怕的路径的构建过程.这不是我的错.有好几个目录,目录名都没有异常长;它们的长度和数量足以使其超过 MAX_PATH(260 个字符).在这些名称中,我没有使用除 ASCII 以外的任何其他内容.

I have part of a build process that creates a hideously long paths in Windows. It's not my fault. It's several directories deep, and none of the directory names are abnormally long; they're just long and numerous enough to make it over MAX_PATH (260 chars). I'm not using anything other than ASCII in these names.

最大的问题是爆炸发生在Module::Builddist 目标期间,虽然我认为构建系统并不重要,因为它们会创建相同的目录.

The big problem is that the blow-up happens deep in the guts of Module::Build during the dist target, although I figure the build system doesn't matter because they'd make the same directories.

使用 File::Path 创建这些过长目录之一失败:

Creating one of these overly-long directories with File::Path fails:

 use File::Path qw( make_path );

 make_path( 'C:\.....' ); # fails if path is over 260 chars

同样,一旦绝对路径超过MAX_PATH,手动构建每个目录级别就会失败.

Similarly, constructing each directory level by hand fails once the absolute path would go over MAX_PATH.

这并不新鲜,也不是 Perl 的错,Microsoft 将其记录在 命名文件、路径和命名空间.他们的修复建议在任何路径前添加 \? 以访问 Unicode 文件名 API.然而,这似乎不是 Perl 脚本的完整修复,因为它仍然失败:

This isn't new, isn't Perl's fault, and Microsoft documents it in Naming Files, Paths, and Namespaces. Their fix suggests adding the \? in front of any path to access the Unicode filename API. However, that doesn't seem to be the full fix for a Perl script because it still fails:

 use File::Path qw( make_path );

 make_path( '\\?\C:\.....' );  # still fails if path is over MAX_PATH, works otherwise

这可能是因为 make_path 将其参数拉开,然后一次遍历目录一级,所以 \? 仅适用于顶级,位于 MAX_PATH 内.

This might be because make_path pulls apart its argument and then goes through the directories one level at a time, so \? only applies to the top-level, which is within MAX_PATH.

我找到了一份 错误报告给 ActiveState,表明我还有其他事情需要修复以获取 Unicode 文件名,Jan Dubois 在 Re:Windows 2K/XP 上的长"文件名,虽然我不确定它是否适用(并且非常旧).perlrun 提到这是 -C 开关的工作,但显然那部分被放弃了.perl RT 队列有一个更新的错误 60888: Win32: support full文件名中的 unicode(使用宽系统调用).

I dug up a bug report to ActiveState that suggests there's something else I need to fix up to get to the Unicode filenames, and Jan Dubois gives a bit more details in Re: "long" filenames on Windows 2K/XP, although I'm not sure it applies (and is extremely old). perlrun mentions that this use to be the job of the -C switch, but apparently that part was abandoned. The perl RT queue has a more recent bug 60888: Win32: support full unicode in filenames (use Wide-system calls).

Miyagawa 注意到一些 Unicode 文件名问题Win32API::File 没有特别提及长路径.然而,Win32API::File CPAN 论坛条目似乎只是表示恐惧,导致愤怒,这会导致仇恨,等等.Perlmonks 帖子中有一个例子 如何在 Windows 中使用 Unicode (UTF16-LE) 文件名统计文件?.似乎 Win32::CreateDirectory 是答案,下次我靠近 Windows 机器时我会尝试.

Miyagawa notes some Unicode filename issues and Win32API::File without specifically mentioning long paths. However, the Win32API::File CPAN Forum entry seems to indicate only fear, which leads to anger, which leads to hate, and so on. There's an example in the Perlmonks post How to stat a file with a Unicode (UTF16-LE) filename in Windows?. It seems the Win32::CreateDirectory is the answer, and I'll try that the next time I get next to a Windows machine.

然后,假设我可以创建长路径路径.现在我必须教 Module::Build,也许还有其他东西,来处理它.如果 Win32::GetANSIPathName() 执行它在罐头上所说的那样,那么使用monkeypatches 可能会立即变得容易.

Then, supposing I can create the long path path. Now I have to teach Module::Build, and maybe other things, to handle it. That might be immediately easy with monkeypatches if Win32::GetANSIPathName() does what it says on the tin.

推荐答案

对于每个需要处理字符串的函数,Windows 有两个单独的系统调用,一个A"使用 ANSI aka Active Code Page 作为编码(例如 cp1252)和W"调用使用 UTF-16le 调用.Perl 使用A"调用,而 \? 仅适用于W";电话.

Windows has two separate system call for each function that needs to deal with strings, an "A" call using the ANSI aka Active Code Page as the encoding (e.g. cp1252) and a "W" call using UTF-16le. Perl uses "A" calls, while \? only works with "W" calls.

您可以使用 Win32::API 访问W"调用如下脚本所示,但 Win32::LongPath 不仅使用"W"调用,但会自动添加 \?!

You can use Win32::API to access the "W" calls as shown in the script below, but Win32::LongPath not only uses the "W" calls, but automatically adds \?!

使用Win32::API调用CreateDirectoryW使用长路径(\?-前缀路径)示例:

Example of using Win32::API to call CreateDirectoryW to use a long path (\?-prefixed path):

#!/usr/bin/perl

use strict;
use warnings;

use Carp;
use Encode qw( encode );
use Symbol;

use Win32;

use Win32API::File qw(
    CreateFileW OsFHandleOpen
    FILE_GENERIC_READ FILE_GENERIC_WRITE
    OPEN_EXISTING CREATE_ALWAYS FILE_SHARE_READ
);

use Win32::API;
use File::Spec::Functions qw(catfile);

Win32::API->Import(
    Kernel32 => qq{BOOL CreateDirectoryW(LPWSTR lpPathNameW, VOID *p)}
);

my %modes = (
    '<' => {
        access => FILE_GENERIC_READ,
        create => OPEN_EXISTING,
        mode   => 'r',
    },
    '>' => {
        access => FILE_GENERIC_WRITE,
        create => CREATE_ALWAYS,
        mode   => 'w',
    },
    # and the rest ...
);

use ex::override open => sub(*;$@) {
    $_[0] = gensym;

    my %mode = %{ $modes{$_[1]} };

    my $os_fh = CreateFileW(
        encode('UCS-2le', "$_[2]"),
        $mode{access},
        FILE_SHARE_READ,
        [],
        $mode{create},
        0,
        [],
    ) or do {$! = $^E; return };

    OsFHandleOpen($_[0], $os_fh, $mode{mode}) or return;
    return 1;
};

my $path = '\\?\' . Win32::GetLongPathName($ENV{TEMP});
my @comps = ('0123456789') x 30;

my $dir = mk_long_dir($path, @comps);
my $file = 'test.txt';
my $str = "This is a test
";

write_test_file($dir, $file, $str);

$str eq read_test_file($dir, $file) or die "Read failure
";

sub write_test_file {
    my ($dir, $file, $str) = @_,

    my $path = catfile $dir, $file;

    open my $fh, '>', $path
        or croak "Cannot open '$path':$!";

    print $fh $str or die "Cannot print: $!";
    close $fh or die "Cannot close: $!";
    return;
}

sub read_test_file {
    my ($dir, $file) = @_,

    my $path = catfile $dir, $file;

    open my $fh, '<', $path
        or croak "Cannot open '$path': $!";

    my $contents = do { local $/; <$fh> };
    close $fh or die "Cannot close: $!";
    return $contents;
}

sub mk_long_dir {
    my ($path, $comps) = @_;

    for my $comp ( @$comps ) {
        $path = catfile $path, $comp;
        my $ucs_path = encode('UCS-2le', "$path");
        CreateDirectoryW($ucs_path, undef)
            or croak "Failed to create directory: '$path': $^E";
    }
    return $path;
}

这篇关于如何从 Perl 创建然后使用长的 Windows 路径?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆