我目前正在使用 Win32ShellFolderManager2ShellFolder.getLinkLocation 解决Java中的Windows快捷方式。不幸的是,如果Java程序在Vista下作为服务运行, getLinkLocation, ,这不起作用。具体来说,我收到一个异常,指出“无法获取 shell 文件夹 ID 列表”。

搜索网络确实会提到此错误消息,但始终与 JFileChooser. 。我没有使用 JFileChooser, ,我只需要解决一个 .lnk 文件到其目的地。

有谁知道第三方解析器 .lnk 我可以使用用 Java 编写的文件吗?

我已经找到了 .lnk 格式的非官方文档 这里, ,但如果有人以前做过的话,我宁愿不必做这项工作,因为格式相当可怕。

有帮助吗?

解决方案

添加了评论(一些解释以及到目前为止每个贡献者的信用),额外检查文件魔术,快速测试以查看给定文件是否可能是有效链接(不读取所有字节),a修复了使用适当的消息而不是ArrayIndexOutOfBoundsException抛出ParseException,如果文件太小,做了一些常规清理。

来源此处(如果您有任何更改,将它们推送到GitHub repo / 项目

package org.stackoverflowusers.file;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;

/**
 * Represents a Windows shortcut (typically visible to Java only as a '.lnk' file).
 *
 * Retrieved 2011-09-23 from http://stackoverflow.com/questions/309495/windows-shortcut-lnk-parser-in-java/672775#672775
 * Originally called LnkParser
 *
 * Written by: (the stack overflow users, obviously!)
 *   Apache Commons VFS dependency removed by crysxd (why were we using that!?) https://github.com/crysxd
 *   Headerified, refactored and commented by Code Bling http://stackoverflow.com/users/675721/code-bling
 *   Network file support added by Stefan Cordes http://stackoverflow.com/users/81330/stefan-cordes
 *   Adapted by Sam Brightman http://stackoverflow.com/users/2492/sam-brightman
 *   Based on information in 'The Windows Shortcut File Format' by Jesse Hager <jessehager@iname.com>
 *   And somewhat based on code from the book 'Swing Hacks: Tips and Tools for Killer GUIs'
 *     by Joshua Marinacci and Chris Adamson
 *     ISBN: 0-596-00907-0
 *     http://www.oreilly.com/catalog/swinghks/
 */
public class WindowsShortcut
{
    private boolean isDirectory;
    private boolean isLocal;
    private String real_file;

    /**
     * Provides a quick test to see if this could be a valid link !
     * If you try to instantiate a new WindowShortcut and the link is not valid,
     * Exceptions may be thrown and Exceptions are extremely slow to generate,
     * therefore any code needing to loop through several files should first check this.
     *
     * @param file the potential link
     * @return true if may be a link, false otherwise
     * @throws IOException if an IOException is thrown while reading from the file
     */
    public static boolean isPotentialValidLink(File file) throws IOException {
        final int minimum_length = 0x64;
        InputStream fis = new FileInputStream(file);
        boolean isPotentiallyValid = false;
        try {
            isPotentiallyValid = file.isFile()
                && file.getName().toLowerCase().endsWith(".lnk")
                && fis.available() >= minimum_length
                && isMagicPresent(getBytes(fis, 32));
        } finally {
            fis.close();
        }
        return isPotentiallyValid;
    }

    public WindowsShortcut(File file) throws IOException, ParseException {
        InputStream in = new FileInputStream(file);
        try {
            parseLink(getBytes(in));
        } finally {
            in.close();
        }
    }

    /**
     * @return the name of the filesystem object pointed to by this shortcut
     */
    public String getRealFilename() {
        return real_file;
    }

    /**
     * Tests if the shortcut points to a local resource.
     * @return true if the 'local' bit is set in this shortcut, false otherwise
     */
    public boolean isLocal() {
        return isLocal;
    }

    /**
     * Tests if the shortcut points to a directory.
     * @return true if the 'directory' bit is set in this shortcut, false otherwise
     */
    public boolean isDirectory() {
        return isDirectory;
    }

    /**
     * Gets all the bytes from an InputStream
     * @param in the InputStream from which to read bytes
     * @return array of all the bytes contained in 'in'
     * @throws IOException if an IOException is encountered while reading the data from the InputStream
     */
    private static byte[] getBytes(InputStream in) throws IOException {
        return getBytes(in, null);
    }

    /**
     * Gets up to max bytes from an InputStream
     * @param in the InputStream from which to read bytes
     * @param max maximum number of bytes to read
     * @return array of all the bytes contained in 'in'
     * @throws IOException if an IOException is encountered while reading the data from the InputStream
     */
    private static byte[] getBytes(InputStream in, Integer max) throws IOException {
        // read the entire file into a byte buffer
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        byte[] buff = new byte[256];
        while (max == null || max > 0) {
            int n = in.read(buff);
            if (n == -1) {
                break;
            }
            bout.write(buff, 0, n);
            if (max != null)
                max -= n;
        }
        in.close();
        return bout.toByteArray();
    }

    private static boolean isMagicPresent(byte[] link) {
        final int magic = 0x0000004C;
        final int magic_offset = 0x00;
        return link.length >= 32 && bytesToDword(link, magic_offset) == magic;
    }

    /**
     * Gobbles up link data by parsing it and storing info in member fields
     * @param link all the bytes from the .lnk file
     */
    private void parseLink(byte[] link) throws ParseException {
        try {
            if (!isMagicPresent(link))
                throw new ParseException("Invalid shortcut; magic is missing", 0);

            // get the flags byte
            byte flags = link[0x14];

            // get the file attributes byte
            final int file_atts_offset = 0x18;
            byte file_atts = link[file_atts_offset];
            byte is_dir_mask = (byte)0x10;
            if ((file_atts & is_dir_mask) > 0) {
                isDirectory = true;
            } else {
                isDirectory = false;
            }

            // if the shell settings are present, skip them
            final int shell_offset = 0x4c;
            final byte has_shell_mask = (byte)0x01;
            int shell_len = 0;
            if ((flags & has_shell_mask) > 0) {
                // the plus 2 accounts for the length marker itself
                shell_len = bytesToWord(link, shell_offset) + 2;
            }

            // get to the file settings
            int file_start = 0x4c + shell_len;

            final int file_location_info_flag_offset_offset = 0x08;
            int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset];
            isLocal = (file_location_info_flag & 2) == 0;
            // get the local volume and local system values
            //final int localVolumeTable_offset_offset = 0x0C;
            final int basename_offset_offset = 0x10;
            final int networkVolumeTable_offset_offset = 0x14;
            final int finalname_offset_offset = 0x18;
            int finalname_offset = link[file_start + finalname_offset_offset] + file_start;
            String finalname = getNullDelimitedString(link, finalname_offset);
            if (isLocal) {
                int basename_offset = link[file_start + basename_offset_offset] + file_start;
                String basename = getNullDelimitedString(link, basename_offset);
                real_file = basename + finalname;
            } else {
                int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start;
                int shareName_offset_offset = 0x08;
                int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset]
                    + networkVolumeTable_offset;
                String shareName = getNullDelimitedString(link, shareName_offset);
                real_file = shareName + "\\" + finalname;
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new ParseException("Could not be parsed, probably not a valid WindowsShortcut", 0);
        }
    }

    private static String getNullDelimitedString(byte[] bytes, int off) {
        int len = 0;
        // count bytes until the null character (0)
        while (true) {
            if (bytes[off + len] == 0) {
                break;
            }
            len++;
        }
        return new String(bytes, off, len);
    }

    /*
     * convert two bytes into a short note, this is little endian because it's
     * for an Intel only OS.
     */
    private static int bytesToWord(byte[] bytes, int off) {
        return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
    }

    private static int bytesToDword(byte[] bytes, int off) {
        return (bytesToWord(bytes, off + 2) << 16) | bytesToWord(bytes, off);
    }

}

其他提示

萨姆·布莱曼的解决方案 仅适用于本地文件。我添加了对网络文件的支持:

  • Java 中的 Windows 快捷方式 (.lnk) 解析器?
  • http://code.google.com/p/8bits/downloads/detail?name=The_Windows_Shortcut_File_Format.pdf
  • http://www.javafaq.nu/java-example-code-468.html

    public class LnkParser {
    
    public LnkParser(File f) throws IOException {
        parse(f);
    }
    
    private boolean isDirectory;
    private boolean isLocal;
    
    public boolean isDirectory() {
        return isDirectory;
    }
    
    private String real_file;
    
    public String getRealFilename() {
        return real_file;
    }
    
    private void parse(File f) throws IOException {
        // read the entire file into a byte buffer
        FileInputStream fin = new FileInputStream(f);
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        byte[] buff = new byte[256];
        while (true) {
            int n = fin.read(buff);
            if (n == -1) {
                break;
            }
            bout.write(buff, 0, n);
        }
        fin.close();
        byte[] link = bout.toByteArray();
    
        parseLink(link);
    }
    
    private void parseLink(byte[] link) {
        // get the flags byte
        byte flags = link[0x14];
    
        // get the file attributes byte
        final int file_atts_offset = 0x18;
        byte file_atts = link[file_atts_offset];
        byte is_dir_mask = (byte)0x10;
        if ((file_atts & is_dir_mask) > 0) {
            isDirectory = true;
        } else {
            isDirectory = false;
        }
    
        // if the shell settings are present, skip them
        final int shell_offset = 0x4c;
        final byte has_shell_mask = (byte)0x01;
        int shell_len = 0;
        if ((flags & has_shell_mask) > 0) {
            // the plus 2 accounts for the length marker itself
            shell_len = bytes2short(link, shell_offset) + 2;
        }
    
        // get to the file settings
        int file_start = 0x4c + shell_len;
    
        final int file_location_info_flag_offset_offset = 0x08;
        int file_location_info_flag = link[file_start + file_location_info_flag_offset_offset];
        isLocal = (file_location_info_flag & 2) == 0;
        // get the local volume and local system values
        //final int localVolumeTable_offset_offset = 0x0C;
        final int basename_offset_offset = 0x10;
        final int networkVolumeTable_offset_offset = 0x14;
        final int finalname_offset_offset = 0x18;
        int finalname_offset = link[file_start + finalname_offset_offset] + file_start;
        String finalname = getNullDelimitedString(link, finalname_offset);
        if (isLocal) {
            int basename_offset = link[file_start + basename_offset_offset] + file_start;
            String basename = getNullDelimitedString(link, basename_offset);
            real_file = basename + finalname;
        } else {
            int networkVolumeTable_offset = link[file_start + networkVolumeTable_offset_offset] + file_start;
            int shareName_offset_offset = 0x08;
            int shareName_offset = link[networkVolumeTable_offset + shareName_offset_offset]
                    + networkVolumeTable_offset;
            String shareName = getNullDelimitedString(link, shareName_offset);
            real_file = shareName + "\\" + finalname;
        }
    }
    
    private static String getNullDelimitedString(byte[] bytes, int off) {
        int len = 0;
        // count bytes until the null character (0)
        while (true) {
            if (bytes[off + len] == 0) {
                break;
            }
            len++;
        }
        return new String(bytes, off, len);
    }
    
    /*
     * convert two bytes into a short note, this is little endian because it's
     * for an Intel only OS.
     */
    private static int bytes2short(byte[] bytes, int off) {
        return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
    }
    
    /**
     * Returns the value of the instance variable 'isLocal'.
     *
     * @return Returns the isLocal.
     */
    public boolean isLocal() {
        return isLocal;
    }
    }
    

我可以在GitHub上推荐这个存储库:

https://github.com/BlackOverlord666/mslinks

在那里,我找到了创建快捷方式的简单解决方案:

ShellLink.createLink("path/to/existing/file.txt", "path/to/the/future/shortcut.lnk");

如果您想阅读快捷方式:

File shortcut = ...;
String pathToExistingFile = new ShellLink(shortcut).resolveTarget();

如果您想更改快捷方式的图标,请使用:

ShellLink sl = ...;
sl.setIconLocation("/path/to/icon/file");

您可以编辑快捷链接的大多数属性,例如工作目录,工具提示文本,图标,命令行参数,热键,创建LAN共享文件和目录的链接等等......

希望这可以帮助你:)

亲切的问候 Josua Frank

我在Java中的'.lnk'上也工作过(现在没时间)。我的代码是此处

它有点乱(有些测试垃圾)但本地和网络解析效果很好。也实现了创建链接。请测试并发给我补丁。

解析示例:

Shortcut scut = Shortcut.loadShortcut(new File("C:\\t.lnk"));
System.out.println(scut.toString());

创建新链接:

Shortcut scut = new Shortcut(new File("C:\\temp"));
OutputStream os = new FileOutputStream("C:\\t.lnk");
os.write(scut.getBytes());
os.flush();
os.close();

The code plan9assembler linked to appears to work with minor modification. I think it's just the "& 0xff" to prevent sign extension when bytes are upcast to ints in the bytes2short function that need changing. I've added the functionality described in http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf to concatenate the "final part of the pathname" even though in practice this doesn't seem to be used in my examples. I've not added any error checking to the header or dealt with network shares. Here's what I'm using now:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;

public class LnkParser {

    public LnkParser(File f) throws Exception {
        parse(f);
    }

    private boolean is_dir;

    public boolean isDirectory() {
        return is_dir;
    }

    private String real_file;

    public String getRealFilename() {
        return real_file;
    }

    private void parse(File f) throws Exception {
        // read the entire file into a byte buffer
        FileInputStream fin = new FileInputStream(f);
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        byte[] buff = new byte[256];
        while (true) {
            int n = fin.read(buff);
            if (n == -1) {
                break;
            }
            bout.write(buff, 0, n);
        }
        fin.close();
        byte[] link = bout.toByteArray();

        // get the flags byte
        byte flags = link[0x14];

        // get the file attributes byte
        final int file_atts_offset = 0x18;
        byte file_atts = link[file_atts_offset];
        byte is_dir_mask = (byte) 0x10;
        if ((file_atts & is_dir_mask) > 0) {
            is_dir = true;
        } else {
            is_dir = false;
        }

        // if the shell settings are present, skip them
        final int shell_offset = 0x4c;
        final byte has_shell_mask = (byte) 0x01;
        int shell_len = 0;
        if ((flags & has_shell_mask) > 0) {
            // the plus 2 accounts for the length marker itself
            shell_len = bytes2short(link, shell_offset) + 2;
        }

        // get to the file settings
        int file_start = 0x4c + shell_len;

        // get the local volume and local system values
        final int basename_offset_offset = 0x10;
        final int finalname_offset_offset = 0x18;
        int basename_offset = link[file_start + basename_offset_offset]
                                + file_start;
        int finalname_offset = link[file_start + finalname_offset_offset]
                                + file_start;
        String basename = getNullDelimitedString(link, basename_offset);
        String finalname = getNullDelimitedString(link, finalname_offset);
        real_file = basename + finalname;
    }

    private static String getNullDelimitedString(byte[] bytes, int off) {
        int len = 0;
        // count bytes until the null character (0)
        while (true) {
            if (bytes[off + len] == 0) {
                break;
            }
            len++;
        }
        return new String(bytes, off, len);
    }

    /*
     * convert two bytes into a short note, this is little endian because it's
     * for an Intel only OS.
     */
    private static int bytes2short(byte[] bytes, int off) {
        return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
    }
}

This short code is really usefull...

But two fixes are needed:

  • the isPotentialValidLink improved not to load file if name doesn't end with ".lnk"

      public static boolean isPotentialValidLink(final File file) {
        final int minimum_length = 0x64;
        boolean isPotentiallyValid = false;
        if (file.getName().toLowerCase().endsWith(".lnk"))
            try (final InputStream fis = new FileInputStream(file)) {
                isPotentiallyValid = file.isFile() && fis.available() >= minimum_length && isMagicPresent(getBytes(fis, 32));
            } catch (Exception e) {
                // forget it
            }
        return isPotentiallyValid;
      }
    
  • the offset has to be computed with 32bits not only a byte...

     final int finalname_offset = bytesToDword(link,file_start + finalname_offset_offset) + file_start;
     final int basename_offset = bytesToDword(link,file_start + basename_offset_offset) + file_start;
    

The given code works well, but has a bug. A java byte is a signed value from -128 to 127. We want an unsigned value from 0 to 255 to get the correct results. Just change the bytes2short function as follows:

static int bytes2short(byte[] bytes, int off) {
    int low = (bytes[off]<0 ? bytes[off]+256 : bytes[off]);
    int high = (bytes[off+1]<0 ? bytes[off+1]+256 : bytes[off+1])<<8;
    return 0 | low | high;
}

The solution of @Code Bling does not work for me for Files in the User directory.
For Example "C:/Users/Username/Filename.txt".
The reason for that is: in The_Windows_Shortcut_File_Format.pdf

that was mentioned by @Stefan Cordes on page 6 it says that only the first 2 bits are important for volumes info. All other bits might be filled with random garbage when the first bit of volumes info is "0".

So if it comes to:

isLocal = (file_location_info_flag & 2) == 0;

then file_location_info_flag might be "3". This file is still local but this line of code assigns false to isLocal.

So i suggest the following adjustment to @Code Bling's code:

isLocal = (file_location_info_flag & 1) == 1;
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top