The C runtime includes a function called strstr
which is used when you want to find an exact substring within a main string. To perform a case-insensitive search, you must first force the case of both strings either to lower-case or upper-case before you call strstr
. I wanted a function which didn't require changing the case of the strings, and was also DBCS (double-byte character set) friendly.
Currently, Windows 2000 is only targeted for the Intel platform, and of course Windows 95/98 only runs on the Intel platform. That makes the use of assembly language a bit more appealing then it has in the past when NT ran on a PowerPC or an ALPHA CPU. Visual Studio doesn't include a separate assembler, and the debugger doesn't do a very good job of debugging assembly files if you add in an assembler. So, the easiest way to include assembly language into your project is to use the _asm block. In the string search functions, I let the C compiler handle the outer layer:
char* __fastcall stristrA(const char* pszMain, const char* pszSub)
{
}
Everything inside the function is handled within an _asm block. Note that MSDN has an article that says you should never use the __fastcall
calling convention for functions using _asm
blocks because the compiler could use any register for function arguments. There is another article in MSDN that states that the first two variables are always placed in the ECX and EDX registers. I'm going to believe the second article and ignore the first, but if you are concerned, you can change the calling convention and load up the ESI and EDI registers that we use from the stack. The advantage of the __fastcall
calling convention is that nothing has to be pushed or popped from the stack, making the function faster to execute.
The first thing we do is to save both a lower-case and upper-case version of the first character of the string. If the first character is non-alphabetic, then we'll end up checking it twice. However, that only costs us an additional 3 clock cycles per character, but our savings of not having to call CharLower
for every character is quite significant.
Once we have the first character set, we store the address of the CharNext
function in the EDI register. This halves the overhead for the actual call -- and we call this function for every character in the main string (we use CharNext
because it will correctly move the pointer over a DBCS character). We then walk through the main string checking every character against our upper-case and lower-case first character of the sub string. As soon as we find a first-character match, we switch to a loop that checks every character in the substring against our current position in the main string. If we have a mismatch, then we change the character in both the main string and the substring to lower case and compare again. In the sample project, we search for the word "god" in the string "What hath God wrought?". We find the match of 'G' without having to call CharLower
and are able to match the rest of the string ("od") without ever having to call CharLower
. Of course, if the main string was "What hath GOD wrought?" then we would call CharLower
for both 'O' and 'D'.
The source code provides both an ANSI and Unicode version called stristrA
and stristrW
respectively. In your header file, you can specify which version you want to use with the following:
#ifdef UNICODE
#define stristr stristrW
#else
#define stristr stristrA
#endif
char* __fastcall stristrA(const char* psz1, const char* psz2);
WCHAR* __fastcall stristrW(const WCHAR* pszMain, const WCHAR* pszSub);
In your code, you would simply specify stristr
and the correct function would be chosen depending on whether you are compiling for Unicode or not.
The sample project doesn't do anything useful, but it does call both the ANSI and Unicode version of the function so that you can step through the code in the debugger to see how it works. In the ANSI function, setting the following watch points will make it easier to see what is happening:
al
lowerch,c
(char*) esi,s
(char*) edi,s
pszTmp1,s