-2

As a programmer, you have probably had to use or create some kind of string comparison function. Usually, these are pretty simple:

function compare(s1, s1) { return s1.toLowerCase() - s2.toLowerCase(); }

This works great for the vast majority of cases. However, Windows (XP and later) sorts files differently -- and better! -- than a poor ASCII implementation.

How can I create a Minimal, Complete, and Verifiable example of native Windows natural order sorting in a custom program?

Everything I have read points to using the StrCmpLogicalW function in shlwapi.dll. That's great! But how can this function be used inside a custom C/C++ program?

I am not interested in re-implementing the compare function. I have already seen this, this, this, this, and this. These are no doubt very close approximations, but I just want to link or call the Windows API function in my program.

Here are some other things I have researched and tried already:

  • reading the documentation on shlwapi.dll and StrCmpLogicalW from Microsoft
  • finding a (supposedly) complete program posted from an earlier Q&A here on StackOverflow
  • compiling several small code samples for Visual Studio 2010 Express, both C++ and C# versions (fatal error C1190: managed targeted code requires a '/clr' option... really?)
  • compiling several small code samples for Visual Studio 2012 Express, because some article said this would get rid of the earlier compile error about the /clr option, but just got a bunch of different compile errors instead
  • tried compiling several small code samples for Eclipse C++ with MinGW

When I first started looking into this, I thought, "It's just the Windows API, this will be easy!" I have yet to come up with a working program in any language.

I have been doing C/C++ and Unix/DOS/Windows shell scripting for a long time, and using an API has never been so irksome. Shame on you, Microsoft.


Also, I've already read the rants about ASCII sorting, but thank you. These contained some fertile soil for some good thinking.

https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/

http://weblog.masukomi.org/2007/12/10/alphabetical-asciibetical/

JonathanDavidArndt
  • 2,518
  • 13
  • 37
  • 49

2 Answers2

3

C++:

#include <windows.h>
#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

#include <algorithm>
#include <vector>
#include <string>
#include <iostream>

bool str_cmp_logical(std::wstring const &lhs, std::wstring const &rhs)
{
    return StrCmpLogicalW(lhs.c_str(), rhs.c_str()) < 1;
}

int main()
{
    std::vector<std::wstring> foo{
        L"20string", L"2string", L"3string", L"st20ring", L"st2ring",
        L"st3ring", L"string2", L"string20", L"string3"
    };

    for (auto const &f : foo)
        std::wcout << f << L' ';
    std::wcout.put(L'\n');

    std::sort(foo.begin(), foo.end(), str_cmp_logical);

    for (auto const &f : foo)
        std::wcout << f << L' ';
    std::wcout.put(L'\n');
}

Output:

20string 2string 3string st20ring st2ring st3ring string2 string20 string3
2string 3string 20string st2ring st3ring st20ring string2 string3 string20

Trying to compile the code with MinGW failed, because the version of <shlwapi.h> that comes with its package w32api doesn't provide a prototype for StrCmpLogicalW(). When I declared it myself i got

C:\MinGW\bin>"g++.exe" -lshlwapi C:\Users\sword\source\repos\Codefun\main.cpp
C:\Users\sword\AppData\Local\Temp\ccMrmLbD.o:main.cpp:(.text+0x23): undefined reference to `StrCmpLogicalW(wchar_t const*, wchar_t const*)'
collect2.exe: error: ld returned 1 exit status

So the libraries shipped with MinGW don't seem to be aware of StrCmpLogicalW().

It should work with Mingw-w64, though.

Swordfish
  • 12,971
  • 3
  • 21
  • 43
  • This looks great, and matches very closely some of the early examples I found. But it does not compile until VS2010. Eclipse with MinGW. says "ignoring #pragma comment [-Wunknown-pragmas]" – JonathanDavidArndt Sep 30 '18 at 04:04
  • 1
    "But it does not compile until VS2010." Does not compile ... what are the error messages? Why (TF) are you using VS2010 in 2017? "Eclipse with MinGW says "ignoring #pragma comment [-Wunknown-pragmas]" Add `shlwapi.lib` as linker input any other way your compiler/linker understands? – Swordfish Sep 30 '18 at 04:16
  • Trying to add this library is part of the point of this question. The file `shlwapi.h` is easy to find, but the `shlwapi.lib` file does not exist on my system, and I am not familiar with how to include it. My programming background is primarily Linux, and the Windows API ecosystem is somewhat mystifying. – JonathanDavidArndt Sep 30 '18 at 04:35
  • @JonathanDavidArndt You need to install the [Windows Platform SDK](https://developer.microsoft.com/en-US/windows/downloads/windows-10-sdk) to get the files needed to write Windows programs (like `shlwapi.h` and `shlwapi.lib`). When installing, say that you want the files necessary for creating Windows Desktop apps in C++. – Raymond Chen Sep 30 '18 at 04:46
  • @RaymondChen Will those `lib`s be compatible with MinGW? I'm just wondering ... why would MinGW come with its own set of WinAPI headers and libs if it were much simpler to just use the PlatformSDK provided by MS? – Swordfish Sep 30 '18 at 04:50
  • @Swordfish I'm guessing that MinGW includes a custom copy of the header files because they needed to tweak them to work with g++. But they didn't need to make any tweaks to the libraries, so they're expecting you to get the libraries from the Platform SDK. I'M JUST GUESSING. – Raymond Chen Sep 30 '18 at 16:02
  • 1
    @RaymondChen As to http://www.mingw.org/wiki/MinGW it ships with its own set of import libraries. – Swordfish Sep 30 '18 at 21:12
-1

Turns out, DLLs can be called easily with AutoIt or AutoHotKey.

I have distilled this post from the AutoIt forums into a minimal working example:

Func _StrCmpLogicalW($s1, $s2)
   Return DllCall('shlwapi.dll', 'int', 'StrCmpLogicalW', 'wstr', $s1, 'wstr', $s2)[0]
EndFunc

And here is a minimal example distilled from this archive post on the AutoHotkey forums:

_StrCmpLogicalW(s1, s2)
{
   VarSetCapacity(ws1, StrLen(s1)*2+1,0), DllCall("MultiByteToWideChar", "UInt",0, "UInt",0, "UInt",&s1, "Int",-1, "UInt",&ws1, "Int",StrLen(s1)+1)
   VarSetCapacity(ws2, StrLen(s2)*2+1,0), DllCall("MultiByteToWideChar", "UInt",0, "UInt",0, "UInt",&s2, "Int",-1, "UInt",&ws2, "Int",StrLen(s2)+1)
   return DllCall("Shlwapi.dll\StrCmpLogicalW","UInt",&ws1,"UInt",&ws2)
}

That's it! The function takes two stings and returns -1/0/+1 like ever other comparison function in the world.

Combine this with a sorting algorithm (such as _ArrayMultiColSort(), _ArrayCustomSort() for AutoIt) and now you can Quicksort entire lists.

(Please, do not use Bubble Sort. Think of the children.)

JonathanDavidArndt
  • 2,518
  • 13
  • 37
  • 49
  • What kind of bot are you making :). – Ryan Mann Sep 30 '18 at 03:46
  • This was actually inspired by a list of music files that was copied onto a USB player that reads the files sequentially as they were written. My naive ASCII comparison was working great! ... as long as tracks were numbered with leading zeros. There was one very large folder that was playing out of order, and it was because tracks were not numbered, and all the files were named funny. – JonathanDavidArndt Sep 30 '18 at 04:00
  • Ah, I always just sort by last modified date, so I get them in the order they were added to the file system. – Ryan Mann Sep 30 '18 at 04:02
  • 2
    Question makes no mention of AutoIt. Calling a winapi function is indeed trivial. – David Heffernan Sep 30 '18 at 06:19