5
\$\begingroup\$

At my work we're dealing with a lot of PHP files written by a third-party company and sometimes we want to "quickly look at" these files in Notepad rather than having to open up a full IDE.

The problem is that these files have \n line endings which as many of you know Notepad doesn't handle correctly. Sure, there are solutions like installing Notepad++ but everyone knows that the first rule of engineering is that you always reinvent the wheel every chance you get. I'm kidding, I just really like coding.

My solution: write a wrapper for Notepad that silently converts files containing Unix line endings into Windows/DOS style line endings and then opens the file in Notepad.

Program Source Code:

#include <Windows.h>
#include <strsafe.h>

// Defines for the line-ending conversion function
#define LESTATUS INT 
#define LE_NO_CHANGES_NEEDED (0)
#define LE_CHANGES_SUCCEEDED (1)
#define LE_CHANGES_FAILED   (-1)

LESTATUS WINAPI ConvertLineEndings(BYTE *inData, INT inLen, BYTE *outData, INT outLen, INT *bytesWritten)
{
    INT sourceIndex = 0, destIndex;

    // Fail immediately; no chance of success here.
    if (outLen < inLen)
        return LE_CHANGES_FAILED;

    // Try to determine if changes are needed
    while (sourceIndex < inLen)
    {
        // If an \r is immediately followed by an \n, no changes are needed to inData.
        if (inData[sourceIndex] == '\r')
        {
            if (sourceIndex < inLen - 1 && inData[sourceIndex + 1] == '\n')
            {
                memcpy(outData, inData, inLen);
                *bytesWritten = inLen;
                return LE_NO_CHANGES_NEEDED;
            }
            // If we encountered an \r without a following \n then changes are needed.
            break;
        }

        // If we encounter an \n without a preceding \r then changes are needed.
        if (inData[sourceIndex] == '\n')
            break;
        sourceIndex++;
    }

    // But, up to the point where we encountered the bad line ending, we can copy *that* much into outData.
    memcpy(outData, inData, sourceIndex);
    // If, however, that was the end of the array, then we still don't need to make changes.
    if (sourceIndex == inLen)
        return LE_NO_CHANGES_NEEDED;

    // Now, we begin the actual copying/rewriting of the array, so set destIndex to sourceIndex to begin counting.
    destIndex = sourceIndex;

    // Loop through the remainder of inData; if an \n or \r is encountered, rewrite it as appropriate into outData.
    // If outData's limits are reached before we're done, report that changes failed.
    while (sourceIndex < inLen)
    {
        switch (inData[sourceIndex])
        {
        case '\n':
        case '\r':
            sourceIndex++;
            if (destIndex + 2 >= outLen)
                return LE_CHANGES_FAILED;
            outData[destIndex++] = '\r';
            outData[destIndex++] = '\n';
            break;
        default:
            outData[destIndex++] = inData[sourceIndex++];
        }
    }

    *bytesWritten = destIndex;
    return LE_CHANGES_SUCCEEDED;
}

INT APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, INT nShowCmd)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    DWORD dwExit, dwHigh, dwLow, dwRead = 0, dwWritten = 0, dwLen;
    WCHAR *wszArgQuote = NULL;
    BYTE *bIn = NULL, *bOut = NULL;
    HANDLE hHeap = GetProcessHeap(), hFile;
    INT nWritten = 0;
    LESTATUS leResult;

    StringCchLength(lpCmdLine, STRSAFE_MAX_CCH, &dwLen);
    if(dwLen<=0)
    {
        MessageBox(0, L"No filename specified", L"Notepad Wrapper", MB_OK | MB_ICONSTOP);   
        return 0;
    }

    hFile = CreateFile(lpCmdLine, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile == INVALID_HANDLE_VALUE)
    {
        MessageBox(0, L"File not found or access denied", L"Notepad Wrapper", MB_OK | MB_ICONSTOP); 
        return 0;
    }

    // HeapAlloc is most likely going to fail on a file > 2GB
    dwLow = GetFileSize(hFile, &dwHigh);
    if(dwHigh>0 || (dwLow&0x80000000)==0x80000000)
    {
        MessageBox(0, L"File is too large", L"Notepad Wrapper", MB_OK | MB_ICONSTOP);   
        return 0;
    }

    bIn = (BYTE *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, dwLow+1);
    // Worst case scenario: *every* character in the file is a newline - bOut must be at least double the size of bIn
    bOut = (BYTE *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, (dwLow+1)*2);
    if(bIn==NULL || bOut == NULL)
    {

        MessageBox(0, L"Out of memory", L"Notepad Wrapper", MB_OK | MB_ICONSTOP);
        return 0;
    }

    ReadFile(hFile, bIn, dwLow, &dwRead, NULL);
    leResult = ConvertLineEndings(bIn, dwLow, bOut, dwLow*2, &nWritten);
    if(leResult == LE_CHANGES_FAILED)
    {
        MessageBox(0, L"Could not convert line endings in file", L"Notepad Wrapper", MB_OK | MB_ICONSTOP);  
        return 0;
    }
    // Reset file pointer so that we write the new data to the start of the file
    SetFilePointer(hFile, 0, 0, FILE_BEGIN);
    WriteFile(hFile, bOut, nWritten, &dwWritten, NULL);
    CloseHandle(hFile);
    HeapFree(hHeap, 0, bIn);
    HeapFree(hHeap, 0, bOut);

    // Done converting file, now open it up in Notepad
    wszArgQuote = (WCHAR *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, (dwLen + 10) * sizeof(WCHAR));
    if(wszArgQuote==NULL)
    {
        MessageBox(0, L"Out of memory", L"Notepad Wrapper", MB_OK | MB_ICONSTOP);
        return 0;
    }

    // Surround filepath with quotes 
    StringCchPrintf(wszArgQuote, dwLen+10, L" \"%s\"", lpCmdLine);

    // Preparation & CreateProcess call
    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));

    si.cb = sizeof(STARTUPINFO);

    CreateProcess(L"C:\\Windows\\system32\\notepad.exe", wszArgQuote, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

    HeapFree(hHeap, 0, wszArgQuote);
    // Return notepad.exe's exit code
    WaitForSingleObject(pi.hProcess, INFINITE);
    GetExitCodeProcess(pi.hProcess, &dwExit);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return dwExit;
}

Registry Changes

REGEDIT4

[HKEY_CLASSES_ROOT\.php]
@="phpfile"

[HKEY_CLASSES_ROOT\phpfile\DefaultIcon]
@="%SystemRoot%\\system32\\imageres.dll,-102"

[HKEY_CLASSES_ROOT\phpfile\shell\open\command]
@="C:\\Utilities\\npwrap.exe %1"

Any comments/suggestions for improvement?

\$\endgroup\$
3
  • \$\begingroup\$ Have you ever considered that WordPad, also included with all Windows versions, will handle \n endings just fine? :) \$\endgroup\$
    – user33306
    Nov 17, 2016 at 6:01
  • \$\begingroup\$ Is there any redistributable version of this for others to use? I am actually looking for the exact same thing now. \$\endgroup\$ Mar 16, 2017 at 16:46
  • \$\begingroup\$ @meanbunny You could just compile this code! \$\endgroup\$ May 26, 2017 at 16:22

1 Answer 1

1
\$\begingroup\$

Architecture idea:

Code reads the entire file into memory, coverts it and then conditionally writes it out.

ReadFile(hFile, bIn, dwLow, &dwRead, NULL);
leResult = ConvertLineEndings(bIn, dwLow, bOut, dwLow*2, &nWritten);
...
SetFilePointer(hFile, 0, 0, FILE_BEGIN);
WriteFile(hFile, bOut, nWritten, &dwWritten, NULL);

Consider an alternative that does not delete the original until the new one is fully created. This avoids losing the file should something go wrong. An additional benefit is no real CPU memory limitation.

open input for reading
open temp file output for writing
While reading a line of the file
  convert
  append to output
close input
close output (temp file)
rename input to temp file 2
rename temp file to original
delete temp file 2

If a non-zero length file does not end with "\r\n", this is a condition that often causes trouble for various text based programs. Consider an option to insure a final "\r\n".


Robust code would check the result of file operations

// Missing check of return values.
ReadFile(hFile, bIn, dwLow, &dwRead, NULL);
...
SetFilePointer(hFile, 0, 0, FILE_BEGIN);
WriteFile(hFile, bOut, nWritten, &dwWritten, NULL);
CloseHandle(hFile);

Note that "\n\r"is translated into "\r\n\r\n" - Unlikely a major concern.


I found the excessive wide code unnecessarily challenging to follow. Recommend to re- auto-format to the width of the presentation of this site.

CreateProcess(L"C:\\Windows\\system32\\notepad.exe", wszArgQuote, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

vs.

CreateProcess(L"C:\\Windows\\system32\\notepad.exe", wszArgQuote, NULL, NULL, FALSE,
    CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP | CREATE_NEW_CONSOLE, 
    NULL, NULL, &si, &pi);

Minor: Consider using the size of the object, rather than the supposed type of the object. Less chance for error and easier maintenance.

// ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&si, sizeof si);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.