I'm currently working on a text editor with the C++ windows.h built-in GUI libraries.
Every time I try to save the file, or open a file and display it to the window, it returns an error, or the open file dialog fails. The error is Exception: Segmentation Fault, and it always occurs on the line of GetOpenFileName(); Also, if it does work, FATAL ERROR: Couldn't display file and FATAL ERROR: Couldn't write file always happen. I am using g++ from MINGW MSYS, so maybe my compiler is bad?
Also, I've seen people use L"your text", but when I do it returns an error and doesn't compile, so I resorted to _T("your text"). This is because UNICODE was not enabled.
On a side note, my VS Code Intellisense is telling me that I have errors where there are none, and the program compiles correctly (the errors weren't in the place where I open/save the files).
P.S. I haven't really found any documentation on what I'm trying to do.
Here is my code as of now:
#include <windows.h>
#include <commctrl.h>
#include <shellapi.h>
#include <tchar.h>
#define ID_BUTTON_1 1
#define ID_BUTTON_2 2
#define ID_BUTTON_3 3
#define ID_BUTTON_4 4
#define ID_BUTTON_5 5
#define ID_BUTTON_6 6
#define ID_BUTTON_7 7
#define ID_BUTTON_8 8
#define ID_BUTTON_9 9
#define ID_FILE_NEW 1000
#define ID_FILE_OPEN 1010
#define ID_FILE_SAVE 1020
#define ID_FILE_QUIT 1030
//Global Variables
//Main window class name
static TCHAR szWindowClass[] = _T("Desktop Application");
//Main window title bar text
static TCHAR szTitle[] = _T("C++ GUI Application");
//Instance handle for Win32 API calls
HINSTANCE hInst;
HWND hWnd;
HWND hWndFileText;
int hWndWidth = 600;
int hWndHeight = 400;
//Forward declarations of functions included in this code
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(
_In_ HINSTANCE hInstance,
_In_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nCmdShow) {
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wcex)) {
MessageBox(NULL,
_T("Call to RegisterClassEx Failed!"),
_T("Error"),
MB_ICONERROR);
return 1;
}
//Store instance handle in our global variable
hInst = hInstance;
HMENU hFileMenu = CreateMenu();
HMENU hMenuBar = CreateMenu();
AppendMenu(hFileMenu, MF_STRING, ID_FILE_NEW, _T("New"));
AppendMenu(hFileMenu, MF_STRING, ID_FILE_OPEN, _T("Open"));
AppendMenu(hFileMenu, MF_STRING, ID_FILE_SAVE, _T("Save"));
AppendMenu(hFileMenu, MF_STRING, ID_FILE_QUIT, _T("Quit"));
AppendMenu(hMenuBar, MF_POPUP, (UINT_PTR)hFileMenu, _T("File"));
hWnd = CreateWindowEx(
WS_EX_OVERLAPPEDWINDOW, //Extended window style
szWindowClass, //Name of application
szTitle, //Title bar text
WS_OVERLAPPEDWINDOW, //window characteristics
CW_USEDEFAULT, //Default Position x
CW_USEDEFAULT, //Default Position y
hWndWidth, //Window size (width)
hWndHeight, //Window size (height)
NULL, //Window parent
hMenuBar, //Menu bar
hInstance, //first parameter from WinMain
NULL
);
if (!hWnd) {
MessageBox(NULL,
_T("Call to CreateWindowEx Failed!"),
_T("Error"),
MB_ICONERROR);
return 1;
}
HWND hWndFileText = CreateWindow(
_T("EDIT"),
_T("File text goes here"),
WS_VISIBLE | WS_CHILD | ES_LEFT | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE,
5, 30, hWndWidth - 5, hWndHeight - 30,
hWnd, NULL, hInst, NULL);
if (!hWndFileText) {
MessageBox(hWnd,
_T("Call to create texbox failed!"),
_T("Error"),
MB_ICONERROR);
return 1;
}
ShowWindow(hWnd, nCmdShow); //Value returned from CreateWindowEx,
//fourth WinMain parameter
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//WndProc (window process)
//
//Processes messages for the main window
//
//WM_PAINT - paints the main window
//WM_DESTROY - posts a quit message
char fileData[100];
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
HDC hdc; //handle to device context (hdc)
TCHAR greeting[] = _T("Hello, world!"); //Text for window
switch (message) {
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
//This is where stuff is put in the window
//like text, buttons, etc.
//For now, I'm putting a simple hello, world
//at the top left corner of the window
TextOut(hdc, 5, 5, greeting, _tcslen(greeting));
EndPaint(hWnd, &ps); //releases device context and ends paint request
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_FILE_QUIT:
DestroyWindow(hWnd);
break;
case ID_FILE_OPEN:
{
LPDWORD bytesRead = 0;
OPENFILENAME fileName; //Dialog box structure
HANDLE fileHandle;
char szfileName[260]; //Max length of file name
char* fileReadBuffer; //Place to put text read from file
DWORD fileSize;
ZeroMemory(&fileName, sizeof(fileName));
fileName.lStructSize = sizeof(fileName);
fileName.hwndOwner = hWnd;
fileName.lpstrFile = szfileName;
fileName.nMaxFile = sizeof(szfileName);
fileName.lpstrFile[0] = '\0';
fileName.lpstrFilter = _T("Text (.txt)\0*.TXT\0");
fileName.nFilterIndex = 1;
fileName.lpstrFileTitle = NULL;
fileName.nMaxFileTitle = 0;
fileName.lpstrInitialDir = NULL;
fileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&fileName)) {
fileHandle = CreateFile(fileName.lpstrFile,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
}
else {
MessageBox(hWnd, _T("Couldn't Open File!"), _T("ERROR"), MB_ICONERROR);
break;
}
fileSize = GetFileSize(fileHandle, NULL);
fileReadBuffer = (char*)GlobalAlloc(GPTR, (fileSize+1));
if (!ReadFile(fileHandle, (LPVOID)fileReadBuffer, fileSize, bytesRead, NULL)) {
MessageBox(hWnd, _T("Fatal Error: Couldn't initialize file read"), _T("ERROR"), MB_ICONERROR);
}
else {
MessageBox(hWnd, _T("File Read"), _T("Success"), MB_OK);
}
SetWindowText(hWndFileText, _T("Attempting to display file"));
fileReadBuffer[fileSize] = '\0';
if (!SetWindowText(hWndFileText, fileReadBuffer)) { //This never works
MessageBox(hWnd, _T("Fatal Error: Couldn't display file"), _T("ERROR"), MB_ICONERROR);
}
else {
MessageBox(hWnd, _T("File displayed"), _T("Success"), MB_OK);
}
CloseHandle(fileHandle);
break;
}
case ID_FILE_SAVE:
{
LPDWORD bytesWritten;
OPENFILENAME fileName; //Dialog box structure
HANDLE fileHandle;
char szfileName[260]; //Max length of file name
char fileWriteBuffer[100]; //Place to put text from textbox
fileName.lStructSize = sizeof(fileName);
fileName.hwndOwner = hWnd;
fileName.lpstrFile = szfileName;
fileName.nMaxFile = sizeof(szfileName);
fileName.lpstrFile[0] = '\0';
fileName.lpstrFilter = _T("Text (.txt)\0*.TXT\0");
fileName.nFilterIndex = 1;
fileName.lpstrFileTitle = NULL;
fileName.nMaxFileTitle = 0;
fileName.lpstrInitialDir = NULL;
fileName.Flags = OFN_EXPLORER;
if (GetOpenFileName(&fileName)) { //This is where error occurs
fileHandle = CreateFile(fileName.lpstrFile,
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
}
else {
MessageBox(hWnd, _T("Couldn't Open File For Saving!"), _T("ERROR"), MB_ICONERROR);
break;
}
GetWindowText(hWndFileText, fileWriteBuffer, 99);
if(!WriteFile(fileHandle, fileWriteBuffer, sizeof(fileWriteBuffer), bytesWritten, NULL)){ //This never works
MessageBox(hWnd, _T("FATAL ERROR: Couln't write to file!"), _T("ERROR"), MB_ICONERROR);
}
else {
MessageBox(hWnd, _T("File saved successfully!"), _T("Success"), MB_OK);
}
}
default:
break;
}
break;
case WM_CREATE:
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
}
I know I don't need the 2nd and 3rd includes, I just haven't removed them. The buttons 1-9 are for later, I haven't gotten to that yet, but they shouldn't be causing the issue.
I've looked at a bunch of documentation on how to use the CreateFile(), ReadFile(), and WriteFile() functions, and this is what I've gotten from that so far, but none of it seemed to work.
Also, I don't know how to initialize a save file dialog, so it's an open file dialog for both (but for some reason the window title for the open file dialog in the save function is A[*62]< or some other nonsense (garbage?)).
This is the entire source code, so if you want to try it, you should be able to compile it as-is.
Possibly not the only problem, but you're one null terminator short here:
Admittedly, the documentation is a bit unclear here:
If I recall correctly (and based on some old code I just checked), you actually need a pair of null characters (i.e., a pair of empty strings) after the null character that terminates the second string of the last pair. So the buffer should end with three null characters.
The GetOpenFileName and GetSaveFileName dialogs are pretty old and crufty. The Common Item Dialogs present a more up-to-date interface. In some ways, they're harder to use, because you need to know a little about COM programming. Once you're over that hurdle, they're a little harder to use, since there are methods for setting each of the options, so you get a bit more type safety.