Search code examples
javajna

Get IShellFolder with jna


I'm new in java jna API and I have a problem with Win32 API method. I need get IShellFolder object from Win32 API because I need the IContextMenu object of the file. But when I called Win32 API method from jna ParseDisplayName, I have get the same result error: "The system cannot find the file specified". I have attached my code below.

        String directory = "c:\\Users";

        IntByReference pchEaten = new IntByReference();
        pchEaten.setValue(0);
        PointerByReference ppidl = new PointerByReference();
        IntByReference pdwAttributes = new IntByReference();
        pdwAttributes.setValue(0);
        PointerByReference desktopFolder = new PointerByReference();
        WinNT.HRESULT hResult = Shell32.INSTANCE.SHGetDesktopFolder(desktopFolder);
        IShellFolder shellFolder = IShellFolder.Converter.PointerToIShellFolder(desktopFolder);
        if (COMUtils.SUCCEEDED(hResult) && shellFolder != null) {
            hResult = shellFolder.ParseDisplayName(new WinDef.HWND(Pointer.NULL), Pointer.NULL, directory, pchEaten, ppidl, pdwAttributes);
            if (COMUtils.SUCCEEDED(hResult)) {
                return true;
            }
            else {
                return false;
            }
        }
        else {
            return false;
        }

I have the same error with every directories which I have tested. Thank you very much.


Solution

  • Welcome to StackOverflow!

    As I mentioned in the comment, the documentation for iShellFolder::ParseDisplayName says the third argument, pszDisplayName, is "A null-terminated Unicode string with the display name." When encountering "Unicode" in Windows docs, the "UTF-16" charset is impled.

    The proper mapping for a Windows Unicode string in JNA is WString. Unfortunately, JNA has mapped this argument as a String which is a single-byte UTF-8 encoding (even though Java stores Strings internally in UTF-16).

    You can work around the issue by creating a longer String that converts the UTF-8 to UTF-16 (little endian) encoding (in bytes). I previously posted a solution for ASCII that took advantage of its known encoding, but a better solution is to use the String's methods to extract the bytes as UTF-16LE, and then create a String object out of those bytes. This code makes the conversion:

    String test = "müller_test.pdf";
    byte[] utf16 = test.getBytes("UTF-16LE");
    System.out.println("utf16 that we need to pass:\n" + Arrays.toString(utf16));
    String wString = new String(utf16);
    System.out.println("Pass this to Windows:\n" + wString);
    

    That produces this output:

    utf16 (little endian) that we need to pass:
    [99, 0, 58, 0, 92, 0, 85, 0, 115, 0, 101, 0, 114, 0, 115, 0]
    Pass this to Windows:
    m � l l e r _ t e s t . p d f
    

    Unfortunately by inspecting the bytes I'm not sure if it includes the null terminator, so you may want to add the '\u0000' character to the end of your string before doing this manipulation.

    So ultimately you would have this one-liner:

    String wstring = new String((test + '\u0000').getBytes("UTF-16LE"));
    

    Pass wstring to Windows where it expects the Unicode string and you're all set.