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?