summaryrefslogtreecommitdiff
path: root/zen/zstring.cpp
blob: 6d249e704f782e024d04bdba70d53d62f2736951 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// **************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0        *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved        *
// **************************************************************************

#include "zstring.h"
#include <stdexcept>

#ifdef ZEN_WIN
    #include "win.h"
#endif

using namespace zen;

/*
Perf test: compare strings 10 mio times; 64 bit build
-----------------------------------------------------
    string a = "Fjk84$%kgfj$%T\\\\Gffg\\gsdgf\\fgsx----------d-"
    string b = "fjK84$%kgfj$%T\\\\gfFg\\gsdgf\\fgSy----------dfdf"

Windows (UTF16 wchar_t)
  4 ns | wcscmp
 67 ns | CompareStringOrdinalFunc+ + bIgnoreCase
314 ns | LCMapString + wmemcmp

OS X (UTF8 char)
   6 ns | strcmp
  98 ns | strcasecmp
 120 ns | strncasecmp + std::min(sizeLhs, sizeRhs);
 856 ns | CFStringCreateWithCString       + CFStringCompare(kCFCompareCaseInsensitive)
1110 ns | CFStringCreateWithCStringNoCopy + CFStringCompare(kCFCompareCaseInsensitive)
________________________
time per call | function
*/


#ifdef ZEN_WIN
int cmpStringNoCase(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen)
{
    assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls!
    assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); //

#ifdef ZEN_WIN_VISTA_AND_LATER
    //"CompareStringOrdinal" (available since Windows Vista) is by a factor ~3 faster than old string comparison using "LCMapString"
    const int rv = ::CompareStringOrdinal(lhs,                      //__in  LPCWSTR lpString1,
                                          static_cast<int>(lhsLen), //__in  int cchCount1,
                                          rhs,                      //__in  LPCWSTR lpString2,
                                          static_cast<int>(rhsLen), //__in  int cchCount2,
                                          true);                    //__in  BOOL bIgnoreCase
    if (rv <= 0)
        throw std::runtime_error("Error comparing strings (CompareStringOrdinal). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
    else
        return rv - 2; //convert to C-style string compare result
#else
    //do NOT use "CompareString"; this function is NOT meant for file name comparisons (even with LOCALE_INVARIANT and SORT_STRINGSORT): for example "weiß" == "weiss"!!!
    //the only reliable way to compare filepaths (with XP) is to call "CharUpper" or "LCMapString":

    const auto minSize = std::min(lhsLen, rhsLen);

    if (minSize == 0) //LCMapString does not allow input sizes of 0!
        return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);

    auto copyToUpperCase = [minSize](const wchar_t* strIn, wchar_t* strOut)
    {
        //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString()
        if (::LCMapString(LOCALE_INVARIANT,          //__in   LCID Locale,
                          LCMAP_UPPERCASE,           //__in   DWORD dwMapFlags,
                          strIn,                     //__in   LPCTSTR lpSrcStr,
                          static_cast<int>(minSize), //__in   int cchSrc,
                          strOut,                    //__out  LPTSTR lpDestStr,
                          static_cast<int>(minSize)) == 0) //__in   int cchDest
            throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
    };

    auto eval = [&](wchar_t* bufL, wchar_t* bufR)
    {
        copyToUpperCase(lhs, bufL);
        copyToUpperCase(rhs, bufR);

        const int rv = ::wcsncmp(bufL, bufR, minSize);
        if (rv != 0)
            return rv;

        return static_cast<int>(lhsLen) - static_cast<int>(rhsLen);
    };

    if (minSize <= MAX_PATH) //performance optimization: stack
    {
        wchar_t bufferL[MAX_PATH] = {};
        wchar_t bufferR[MAX_PATH] = {};
        return eval(bufferL, bufferR);
    }
    else //use freestore
    {
        std::vector<wchar_t> buffer(2 * minSize);
        return eval(&buffer[0], &buffer[minSize]);
    }
#endif
}


void makeUpperInPlace(wchar_t* str, size_t strLen)
{
    //- use Windows' upper case conversion: faster than ::CharUpper()
    //- LOCALE_INVARIANT is NOT available with Windows 2000 -> ok
    //- MSDN: "The destination string can be the same as the source string only if LCMAP_UPPERCASE or LCMAP_LOWERCASE is set"
    if (strLen != 0) //LCMapString does not allow input sizes of 0!
        if (::LCMapString(LOCALE_INVARIANT, //__in   LCID Locale,
                          LCMAP_UPPERCASE,  //__in   DWORD dwMapFlags,
                          str,              //__in   LPCTSTR lpSrcStr,
                          static_cast<int>(strLen), //__in   int cchSrc,
                          str,              //__out  LPTSTR lpDestStr,
                          static_cast<int>(strLen)) == 0) //__in   int cchDest
            throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
}
#endif
bgstack15