Windows API C++ | Problem with GetOpenFileName / OPENFILENAME - winapi

I'm new to C++
I need to return the path to the file that the user has selected. For this i use winapi -> OPENFILENAME. When i try to return file path, i get only 1 character ("C" for disk C).
My code:
LPWSTR fileBuffer = new wchar_t[256];
OPENFILENAME ofn = { 0 };
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = fileBuffer;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = 256;
ofn.lpstrFilter = NULL;
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if (GetOpenFileName(&ofn) == TRUE)
{
printf("File name: %s\n", ofn.lpstrFile);
}
i tried to increase the buffer size, return the value of ofn.lpstrFile. The first does not change anything, the second returns a int value of disk letter

You are using wchar_t (needed by OPENFILENAME) which is not the same as char (as expected with printf()) if you are using wide APIs on Windows.
Use wprintf(), or you can use %S (capital) to tell printf() to print wide characters.

Related

Why might DeviceCapabilities() return 4294967295 for DC_BINS?

I'm fetching the selected printer tray from a WIN32 call to PrintDlgEx(). This seems to work successfully most of the time, but recently I added a new printer to my machine (a DYMO LabelWriter 450) and it caused my simple software to fail.
Upon investigation, the call to DeviceCapabilities() for DC_BINS is returning 4294967295, while all of the other printers I've tested so far return single digit bin counts.
My first inclination is to omit the bin name when the bin count is greater than a given threshold (say... 20?), but I don't love this solution.
Is there a known reason that a printer would return the max UNSIGNED INT value for this? Is it just poorly written drivers, or is there an alternate meaning? Or perhaps I totally misunderstand the intended value.
If I have to write an arbitrary cap I will, but I'd like to better understand why this situation exists. Clearly, this printer doesn't have billions of different printer trays.
Here's an MRE:
HINSTANCE hinst = GetModuleHandle(NULL);
HRESULT hResult;
PRINTDLGEX pdx = {0};
LPPRINTPAGERANGE pPageRanges = NULL;
HWND hWndOwner = GetForegroundWindow();
if(!hWndOwner){
hWndOwner = GetDesktopWindow();
}
// Allocate an array of PRINTPAGERANGE structures.
pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if(!pPageRanges){
return wprintf(L"{\"error\": \"%s\"}", GetLastError()); // "Your computer does not have enough memory to complete this operation:"
}
// Initialize the PRINTDLGEX structure.
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWndOwner;
pdx.hDevMode = NULL;
pdx.hDevNames = NULL;
pdx.hDC = NULL;
pdx.Flags = PD_RETURNDC | PD_COLLATE;
pdx.Flags2 = 0;
pdx.ExclusionFlags = 0;
pdx.nPageRanges = 0;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.hInstance = 0;
pdx.lpPrintTemplateName = NULL;
pdx.lpCallback = NULL;
pdx.nPropertyPages = 0;
pdx.lphPropertyPages = NULL;
pdx.nStartPage = START_PAGE_GENERAL;
pdx.dwResultAction = 0;
// Invoke the Print property sheet.
hResult = PrintDlgEx(&pdx);
DEVMODE * myDevMode = (DEVMODE *)GlobalLock(pdx.hDevMode);
DWORD binCount = DeviceCapabilities((CHAR*)myDevMode->dmDeviceName, nullptr, DC_BINS, nullptr, nullptr);
DWORD binNameCount = DeviceCapabilities((CHAR*)myDevMode->dmDeviceName, nullptr, DC_BINNAMES, nullptr, nullptr);
wprintf(L"\"binCount\":\"%lu\",", binCount);
wprintf(L"\"binNameCount\":\"%lu\",", binNameCount);
DeviceCapabilities() returns a signed int, not an unsigned DWORD.
The unsigned value 4294967295 is hex 0xFFFFFFFF, which is the same numeric value as a signed -1.
Per the DeviceCapabilities() documentation:
Return value
If the function succeeds, the return value depends on the setting of the fwCapability parameter. A return value of zero generally indicates that, while the function completed successfully, there was some type of failure, such as a capability that is not supported. For more details, see the descriptions for the fwCapability values.
If the function returns -1, this may mean either that the capability is not supported or there was a general function failure.
You are not accounting for the possibility of DeviceCapabilities() failing (or PrintDlgEx(), either).
Try this:
HWND hWndOwner = GetForegroundWindow();
if (!hWndOwner){
hWndOwner = GetDesktopWindow();
}
// Allocate an array of PRINTPAGERANGE structures.
LPPRINTPAGERANGE pPageRanges = (LPPRINTPAGERANGE) GlobalAlloc(GPTR, 10 * sizeof(PRINTPAGERANGE));
if (!pPageRanges){
// NOTE: GetLastError() returns DWORD, not TCHAR*! So, if you
// want to translate the error code in a human-readable string,
// use FormatMessage() instead...
return wprintf(L"{\"error\": %lu}", GetLastError());
}
// Initialize the PRINTDLGEX structure.
PRINTDLGEX pdx = {0};
pdx.lStructSize = sizeof(PRINTDLGEX);
pdx.hwndOwner = hWndOwner;
pdx.Flags = PD_RETURNDC | PD_COLLATE;
pdx.nMaxPageRanges = 10;
pdx.lpPageRanges = pPageRanges;
pdx.nMinPage = 1;
pdx.nMaxPage = 1000;
pdx.nCopies = 1;
pdx.nStartPage = START_PAGE_GENERAL;
HRESULT hResult = PrintDlgEx(&pdx);
if (hResult != S_OK)
{
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return wprintf(L"{\"error\": %d}", hResult);
}
if (pdx.dwResultAction == PD_RESULT_CANCEL)
{
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return wprintf(L"{\"error\": \"cancelled\"}");
}
DEVMODE *myDevMode = (DEVMODE*) GlobalLock(pdx.hDevMode);
int binCount = DeviceCapabilities(reinterpret_cast<TCHAR*>(myDevMode->dmDeviceName), nullptr, DC_BINS, nullptr, nullptr);
wprintf(L"\"binCount\":%d,", binCount);
int binNameCount = DeviceCapabilities(reinterpret_cast<TCHAR*>(myDevMode->dmDeviceName),
nullptr, DC_BINNAMES, nullptr, nullptr);
wprintf(L"\"binNameCount\":%d,", binNameCount);
if (binCount == -1)
{
...
}
if (binNameCount == -1)
{
...
}
...
GlobalUnlock(pdx.hDevMode);
GlobalFree(reinterpret_cast<HGLOBAL>(pPageRanges));
return ...;

Error 65535 from CommDlgExtendedError after calling GetFileSaveName

So, I'm calling GetFileSaveName from some C code. The C code is called from PowerBuilder, but I really don't think that is relevant.
On just one user's computer currently (Windows 10, updates done), it returns 0, then CommDlgExtendedError return 65535 (CDERR_DIALOGFAILURE), which the docs say means "The dialog box could not be created. The common dialog box function's call to the DialogBox function failed. For example, this error occurs if the common dialog box call specifies an invalid window handle."
I know that the common dialogs aren't completely broken on her PC - I can pull up a File Open or Save dialog in Notepad, for instance.
I had a bug in my code giving the same error number in the past, where in the OPENFILENAME structure I passed in to GetOpenFileName I had set the hWndOwner variable to a handle to a window that no longer exists, but in this case it is being set to 0, so that's not the problem.
The same code is working for thousands of other users of our software! Any bright ideas what could be going on? Thanks.
OK, people have asked me to post my code initializing the OpenFileName structure. It's PowerScript (PowerBuilder's coding language) but should be pretty comprehensible).
OPENFILENAME iOFN
CONSTANT long CHARSIZE = 2
aul_flags = OFN_HIDEREADONLY + OFN_EXPLORER +
OFN_FILEMUSTEXIST+ OFN_NOCHANGEDIR + OFN_DONTADDTORECENT
RtlZeroMemory(iOFN, ll_sizeof)
// initialize structure
iOFN.lStructSize = ll_sizeof
iOFN.nFilterIndex = 1
iOFN.nMaxFile = MAX_LENGTH
iOFN.hWndOwner = il_hWnd
iOFN.Flags = aul_flags + OFN_ENABLESIZING /* needed when using hook procedure */
// allocate memory and copy title
ll_length = Len(as_title) * CHARSIZE
iOFN.lpstrTitle = LocalAlloc(LMEM_ZEROINIT, ll_length + 2)
RtlMoveMemory(iOFN.lpstrTitle, as_title, ll_length)
// allocate memory and copy filter
this.of_Parse(",", as_filter, ls_filter)
li_max = UpperBound(ls_filter) /* count of 1-based array elements */
For li_cnt = 1 To li_max
ll_length = this.of_StringToChar(Trim(ls_filter[li_cnt]), lc_filter)
Next
ll_length = UpperBound(lc_filter) * CHARSIZE
iOFN.lpstrFilter = LocalAlloc(LMEM_ZEROINIT, ll_length)
RtlMoveMemory(iOFN.lpstrFilter, lc_filter, ll_length)
// allocate memory and copy default extension (if given)
If as_extension <> "" Then
ll_length = Len(as_extension) * CHARSIZE
iOFN.lpstrDefExt = LocalAlloc(LMEM_ZEROINIT, ll_length)
RtlMoveMemory(iOFN.lpstrDefExt, as_extension, ll_length)
End If
// allocate memory and copy initialdir (if given)
If as_initdir <> "" Then
ll_length = Len(as_initdir) * CHARSIZE
iOFN.lpstrInitialDir = LocalAlloc(LMEM_ZEROINIT, ll_length)
RtlMoveMemory(iOFN.lpstrInitialDir, as_initdir, ll_length)
End If
// allocate memory for returned data
lc_pathname = Space(MAX_LENGTH)
iOFN.nMaxFile = MAX_LENGTH
iOFN.lpstrFile = LocalAlloc(LMEM_ZEROINIT, MAX_LENGTH)
If as_initialfile <> "" Then
ll_length = Len(as_initialfile) * CHARSIZE
RtlMoveMemory(iOFN.lpstrFile, as_initialfile, ll_length)
End If
// display dialog box
lb_return = GetOpenFileName(iOFN)
I recommend you to check if there is any problem with the OPENFILENAME parameter configuration. There are many parameters in OPENFILENAME that need to be configured before GetSaveFileName can be used. Maybe there is something wrong.
TCHAR szFilename[MAX_PATH] = TEXT("");
BOOL bResult = FALSE;
DWORD dwError = NOERROR;
OPENFILENAME ofn = { 0 };
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.lpstrFilter = TEXT("All Files\0*.*\0\0");
ofn.lpstrFile = szFilename;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER |
OFN_ENABLEHOOK |
OFN_HIDEREADONLY |
OFN_NOCHANGEDIR |
OFN_PATHMUSTEXIST;
bResult = GetSaveFileName(&ofn);
if (bResult == FALSE)
{
dwError = CommDlgExtendedError();
return dwError;
}

Is there anyway to make SOMETHING automatically add selected file extension to filename, when OPENFILENAME struct and GetSaveFileName() are used?

I have this function:
void PickupFileAndSave(std::vector<unsigned char> file_data, int *error_code, char *file_mask = "All files (*.*)\0*.*\0\0")
{
OPENFILENAMEA ofn; // common dialog box structure
char szFile[MAX_PATH]; // buffer for file name
char initial_dir[MAX_PATH] = { 0 };
GetStartupPath(initial_dir);
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = GetActiveWindow();
ofn.lpstrFile = szFile;
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of szFile to initialize itself.
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = file_mask;
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = initial_dir;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_EXPLORER;
if (!GetSaveFileNameA(&ofn))
{
*error_code = GetLastError();
return;
}
char err_msg[1024] = { 0 };
std::string file_name = ofn.lpstrFile; //this stores path to file without extension
file_name.append(".");
file_name.append(ofn.lpstrDefExt); //this is NULL and fails to copy too
WriteAllBytes(file_name.c_str(), &file_data[0], file_data.size(), &err_msg[0]);
if (strlen(err_msg) > 0)
{
*error_code = GetLastError();
return;
}
}
I call it that way:
int write_error = 0;
PickupFileAndSave(compressed, &write_error, "RLE compressed files (*.rle)\0*.rle\0\0");
When I choose file it shows in the filter needed extension, but do not add it to lpstrFile.
Any ideas why and how to fix it?
You did not assign lpstrDefExt so the system will not add the extension in case you omit it. So you simply need to initialise the field before you show the dialog:
lpstrDefExt = "rle";
The documentation explains this:
lpstrDefExt
The default extension. GetOpenFileName and GetSaveFileName append this extension to the file name if the user fails to type an extension. This string can be any length, but only the first three characters are appended. The string should not contain a period (.). If this member is NULL and the user fails to type an extension, no extension is appended.
It's not clear from the code in the question but you want to handle the case where there are multiple filters and you wish to append the extension of the selected filter.
The system won't do that for you so you will have to. Read nFilterIndex after you have shown the dialog. That tells you which filter the user selected. Then parse the filter string to obtain the chosen extension, and append it to the filename if it has no extension.

Extract Eventlog messages in a specific language

I need some help regarding the extraction of eventlog data under Windows 7.
What I try to achieve:
A computer has Windows 7 German (or any other language) installed. I want to extract the eventlog messages in Englisch to transport them to another computer where I want to store and analyze the eventlog.
This should be done somehow programatically (C# or C++).
I have tried different ways. Write a C# programm to extract the messages result always in getting the messages not in englisch but the configured language of the computer. I also tried it in C++ but also with the same result.
The other approach was then to extract the eventlog in a evtx-File and transport it to another computer with an englisch operating system. But the problem with that solution is that I also need non Windows eventlog messages (e.g. from the installed programs) which cannot be viewed on the other computer where the program and the message dlls are not installed.
Does anybody have an idea how to extract eventlog messages in English independent from the language of the operating system?
Thanks a lot,
Ulli
Here is the complete code for C++ to extract special eventlog messages in a specific language (Thanks to "Apokal" and MSDN). You can change the definitions for
Provider Name (this is the key in the registry)
Resource dll (this is the path to the message dll referenced in the registry)
Message language (this is the language code - Note: Seems the complete code is needed "DE" is not working "DE-de" works ...)
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <strsafe.h>
#define PROVIDER_NAME L"SceCli"
#define RESOURCE_DLL L"C:\\Windows\\System32\\scecli.dll"
#define MESSAGE_LANGUAGE 0x0409 // En-Us
#define MAX_TIMESTAMP_LEN 23 + 1 // mm/dd/yyyy hh:mm:ss.mmm
#define MAX_RECORD_BUFFER_SIZE 0x10000 // 64K
HANDLE GetMessageResources();
DWORD DumpRecordsInBuffer(PBYTE pBuffer, DWORD dwBytesRead);
DWORD GetEventTypeName(DWORD EventType);
LPWSTR GetMessageString(DWORD Id, DWORD argc, LPWSTR args);
void GetTimestamp(const DWORD Time, WCHAR DisplayString[]);
DWORD ApplyParameterStringsToMessage(CONST LPCWSTR pMessage, LPWSTR & pFinalMessage);
CONST LPWSTR pEventTypeNames[] = {L"Error", L"Warning", L"Informational", L"Audit Success", L"Audit Failure"};
HANDLE g_hResources = NULL;
void wmain(void)
{
HANDLE hEventLog = NULL;
DWORD status = ERROR_SUCCESS;
DWORD dwBytesToRead = 0;
DWORD dwBytesRead = 0;
DWORD dwMinimumBytesToRead = 0;
PBYTE pBuffer = NULL;
PBYTE pTemp = NULL;
// The source name (provider) must exist as a subkey of Application.
hEventLog = OpenEventLog(NULL, PROVIDER_NAME);
if (NULL == hEventLog)
{
wprintf(L"OpenEventLog failed with 0x%x.\n", GetLastError());
goto cleanup;
}
// Get the DLL that contains the string resources for the provider.
g_hResources = GetMessageResources();
if (NULL == g_hResources)
{
wprintf(L"GetMessageResources failed.\n");
goto cleanup;
}
// Allocate an initial block of memory used to read event records. The number
// of records read into the buffer will vary depending on the size of each event.
// The size of each event will vary based on the size of the user-defined
// data included with each event, the number and length of insertion
// strings, and other data appended to the end of the event record.
dwBytesToRead = MAX_RECORD_BUFFER_SIZE;
pBuffer = (PBYTE)malloc(dwBytesToRead);
if (NULL == pBuffer)
{
wprintf(L"Failed to allocate the initial memory for the record buffer.\n");
goto cleanup;
}
// Read blocks of records until you reach the end of the log or an
// error occurs. The records are read from newest to oldest. If the buffer
// is not big enough to hold a complete event record, reallocate the buffer.
while (ERROR_SUCCESS == status)
{
if (!ReadEventLog(hEventLog,
EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ,
0,
pBuffer,
dwBytesToRead,
&dwBytesRead,
&dwMinimumBytesToRead))
{
status = GetLastError();
if (ERROR_INSUFFICIENT_BUFFER == status)
{
status = ERROR_SUCCESS;
pTemp = (PBYTE)realloc(pBuffer, dwMinimumBytesToRead);
if (NULL == pTemp)
{
wprintf(L"Failed to reallocate the memory for the record buffer (%d bytes).\n", dwMinimumBytesToRead);
goto cleanup;
}
pBuffer = pTemp;
dwBytesToRead = dwMinimumBytesToRead;
}
else
{
if (ERROR_HANDLE_EOF != status)
{
wprintf(L"ReadEventLog failed with %lu.\n", status);
goto cleanup;
}
}
}
else
{
// Print the contents of each record in the buffer.
DumpRecordsInBuffer(pBuffer, dwBytesRead);
}
}
getchar();
cleanup:
if (hEventLog)
CloseEventLog(hEventLog);
if (pBuffer)
free(pBuffer);
}
// Get the provider DLL that contains the string resources for the
// category strings, event message strings, and parameter insert strings.
// For this example, the path to the DLL is hardcoded but typically,
// you would read the CategoryMessageFile, EventMessageFile, and
// ParameterMessageFile registry values under the source's registry key located
// under \SYSTEM\CurrentControlSet\Services\Eventlog\Application in
// the HKLM registry hive. In this example, all resources are included in
// the same resource-only DLL.
HANDLE GetMessageResources()
{
HANDLE hResources = NULL;
hResources = LoadLibraryEx(RESOURCE_DLL, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
if (NULL == hResources)
{
wprintf(L"LoadLibrary failed with %lu.\n", GetLastError());
}
return hResources;
}
// Loop through the buffer and print the contents of each record
// in the buffer.
DWORD DumpRecordsInBuffer(PBYTE pBuffer, DWORD dwBytesRead)
{
DWORD status = ERROR_SUCCESS;
PBYTE pRecord = pBuffer;
PBYTE pEndOfRecords = pBuffer + dwBytesRead;
LPWSTR pMessage = NULL;
LPWSTR pFinalMessage = NULL;
WCHAR TimeStamp[MAX_TIMESTAMP_LEN];
while (pRecord < pEndOfRecords)
{
// If the event was written by our provider, write the contents of the event.
if (0 == wcscmp(PROVIDER_NAME, (LPWSTR)(pRecord + sizeof(EVENTLOGRECORD))))
{
GetTimestamp(((PEVENTLOGRECORD)pRecord)->TimeGenerated, TimeStamp);
wprintf(L"Time stamp: %s\n", TimeStamp);
wprintf(L"record number: %lu\n", ((PEVENTLOGRECORD)pRecord)->RecordNumber);
wprintf(L"status code: %d\n", ((PEVENTLOGRECORD)pRecord)->EventID & 0xFFFF);
wprintf(L"event type: %s\n", pEventTypeNames[GetEventTypeName(((PEVENTLOGRECORD)pRecord)->EventType)]);
pMessage = GetMessageString(((PEVENTLOGRECORD)pRecord)->EventCategory, 0, NULL);
if (pMessage)
{
wprintf(L"event category: %s", pMessage);
LocalFree(pMessage);
pMessage = NULL;
}
pMessage = GetMessageString(((PEVENTLOGRECORD)pRecord)->EventID,
((PEVENTLOGRECORD)pRecord)->NumStrings, (LPWSTR)(pRecord + ((PEVENTLOGRECORD)pRecord)->StringOffset));
if (pMessage)
{
status = ApplyParameterStringsToMessage(pMessage, pFinalMessage);
wprintf(L"event message: %s", (pFinalMessage) ? pFinalMessage : pMessage);
LocalFree(pMessage);
pMessage = NULL;
if (pFinalMessage)
{
free(pFinalMessage);
pFinalMessage = NULL;
}
}
// To write the event data, you need to know the format of the data. In
// this example, we know that the event data is a null-terminated string.
if (((PEVENTLOGRECORD)pRecord)->DataLength > 0)
{
wprintf(L"event data: %s\n", (LPWSTR)(pRecord + ((PEVENTLOGRECORD)pRecord)->DataOffset));
}
wprintf(L"\n");
}
pRecord += ((PEVENTLOGRECORD)pRecord)->Length;
}
return status;
}
// Get an index value to the pEventTypeNames array based on
// the event type value.
DWORD GetEventTypeName(DWORD EventType)
{
DWORD index = 0;
switch (EventType)
{
case EVENTLOG_ERROR_TYPE:
index = 0;
break;
case EVENTLOG_WARNING_TYPE:
index = 1;
break;
case EVENTLOG_INFORMATION_TYPE:
index = 2;
break;
case EVENTLOG_AUDIT_SUCCESS:
index = 3;
break;
case EVENTLOG_AUDIT_FAILURE:
index = 4;
break;
}
return index;
}
// Formats the specified message. If the message uses inserts, build
// the argument list to pass to FormatMessage.
LPWSTR GetMessageString(DWORD MessageId, DWORD argc, LPWSTR argv)
{
LPWSTR pMessage = NULL;
DWORD dwFormatFlags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER;
DWORD_PTR* pArgs = NULL;
LPWSTR pString = argv;
// The insertion strings appended to the end of the event record
// are an array of strings; however, FormatMessage requires
// an array of addresses. Create an array of DWORD_PTRs based on
// the count of strings. Assign the address of each string
// to an element in the array (maintaining the same order).
if (argc > 0)
{
pArgs = (DWORD_PTR*)malloc(sizeof(DWORD_PTR) * argc);
if (pArgs)
{
dwFormatFlags |= FORMAT_MESSAGE_ARGUMENT_ARRAY;
for (DWORD i = 0; i < argc; i++)
{
pArgs[i] = (DWORD_PTR)pString;
pString += wcslen(pString) + 1;
}
}
else
{
dwFormatFlags |= FORMAT_MESSAGE_IGNORE_INSERTS;
wprintf(L"Failed to allocate memory for the insert string array.\n");
}
}
if (!FormatMessage(dwFormatFlags,
g_hResources,
MessageId,
MESSAGE_LANGUAGE,
(LPWSTR)&pMessage,
0,
(va_list*)pArgs))
{
wprintf(L"Format message failed with %lu\n", GetLastError());
}
if (pArgs)
free(pArgs);
return pMessage;
}
// If the message string contains parameter insertion strings (for example, %%4096),
// you must perform the parameter substitution yourself. To get the parameter message
// string, call FormatMessage with the message identifier found in the parameter insertion
// string (for example, 4096 is the message identifier if the parameter insertion string
// is %%4096). You then substitute the parameter insertion string in the message
// string with the actual parameter message string.
DWORD ApplyParameterStringsToMessage(CONST LPCWSTR pMessage, LPWSTR & pFinalMessage)
{
DWORD status = ERROR_SUCCESS;
DWORD dwParameterCount = 0; // Number of insertion strings found in pMessage
size_t cbBuffer = 0; // Size of the buffer in bytes
size_t cchBuffer = 0; // Size of the buffer in characters
size_t cchParameters = 0; // Number of characters in all the parameter strings
size_t cch = 0;
DWORD i = 0;
LPWSTR* pStartingAddresses = NULL; // Array of pointers to the beginning of each parameter string in pMessage
LPWSTR* pEndingAddresses = NULL; // Array of pointers to the end of each parameter string in pMessage
DWORD* pParameterIDs = NULL; // Array of parameter identifiers found in pMessage
LPWSTR* pParameters = NULL; // Array of the actual parameter strings
LPWSTR pTempMessage = (LPWSTR)pMessage;
LPWSTR pTempFinalMessage = NULL;
// Determine the number of parameter insertion strings in pMessage.
while (pTempMessage = wcschr(pTempMessage, L'%'))
{
dwParameterCount++;
pTempMessage++;
}
// If there are no parameter insertion strings in pMessage, return.
if (0 == dwParameterCount)
{
pFinalMessage = NULL;
goto cleanup;
}
// Allocate an array of pointers that will contain the beginning address
// of each parameter insertion string.
cbBuffer = sizeof(LPWSTR) * dwParameterCount;
pStartingAddresses = (LPWSTR*)malloc(cbBuffer);
if (NULL == pStartingAddresses)
{
wprintf(L"Failed to allocate memory for pStartingAddresses.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pStartingAddresses, cbBuffer);
// Allocate an array of pointers that will contain the ending address (one
// character past the of the identifier) of the each parameter insertion string.
pEndingAddresses = (LPWSTR*)malloc(cbBuffer);
if (NULL == pEndingAddresses)
{
wprintf(L"Failed to allocate memory for pEndingAddresses.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pEndingAddresses, cbBuffer);
// Allocate an array of pointers that will contain pointers to the actual
// parameter strings.
pParameters = (LPWSTR*)malloc(cbBuffer);
if (NULL == pParameters)
{
wprintf(L"Failed to allocate memory for pEndingAddresses.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pParameters, cbBuffer);
// Allocate an array of DWORDs that will contain the message identifier
// for each parameter.
pParameterIDs = (DWORD*)malloc(cbBuffer);
if (NULL == pParameterIDs)
{
wprintf(L"Failed to allocate memory for pParameterIDs.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pParameterIDs, cbBuffer);
// Find each parameter in pMessage and get the pointer to the
// beginning of the insertion string, the end of the insertion string,
// and the message identifier of the parameter.
pTempMessage = (LPWSTR)pMessage;
while (pTempMessage = wcschr(pTempMessage, L'%'))
{
if (isdigit(*(pTempMessage+1)))
{
pStartingAddresses[i] = pTempMessage;
pTempMessage++;
pParameterIDs[i] = (DWORD)_wtoi(pTempMessage);
while (isdigit(*++pTempMessage))
;
pEndingAddresses[i] = pTempMessage;
i++;
}
}
// For each parameter, use the message identifier to get the
// actual parameter string.
for (DWORD i = 0; i < dwParameterCount; i++)
{
pParameters[i] = GetMessageString(pParameterIDs[i], 0, NULL);
if (NULL == pParameters[i])
{
wprintf(L"GetMessageString could not find parameter string for insert %lu.\n", i);
status = ERROR_INVALID_PARAMETER;
goto cleanup;
}
cchParameters += wcslen(pParameters[i]);
}
// Allocate enough memory for pFinalMessage based on the length of pMessage
// and the length of each parameter string. The pFinalMessage buffer will contain
// the completed parameter substitution.
pTempMessage = (LPWSTR)pMessage;
cbBuffer = (wcslen(pMessage) + cchParameters + 1) * sizeof(WCHAR);
pFinalMessage = (LPWSTR)malloc(cbBuffer);
if (NULL == pFinalMessage)
{
wprintf(L"Failed to allocate memory for pFinalMessage.\n");
status = ERROR_OUTOFMEMORY;
goto cleanup;
}
RtlZeroMemory(pFinalMessage, cbBuffer);
cchBuffer = cbBuffer / sizeof(WCHAR);
pTempFinalMessage = pFinalMessage;
// Build the final message string.
for (DWORD i = 0; i < dwParameterCount; i++)
{
// Append the segment from pMessage. In the first iteration, this is "8 " and in the
// second iteration, this is " = 2 ".
wcsncpy_s(pTempFinalMessage, cchBuffer, pTempMessage, cch = (pStartingAddresses[i] - pTempMessage));
pTempMessage = pEndingAddresses[i];
cchBuffer -= cch;
// Append the parameter string. In the first iteration, this is "quarts" and in the
// second iteration, this is "gallons"
pTempFinalMessage += cch;
wcscpy_s(pTempFinalMessage, cchBuffer, pParameters[i]);
cchBuffer -= cch = wcslen(pParameters[i]);
pTempFinalMessage += cch;
}
// Append the last segment from pMessage, which is ".".
wcscpy_s(pTempFinalMessage, cchBuffer, pTempMessage);
cleanup:
if (ERROR_SUCCESS != status)
pFinalMessage = (LPWSTR)pMessage;
if (pStartingAddresses)
free(pStartingAddresses);
if (pEndingAddresses)
free(pEndingAddresses);
if (pParameterIDs)
free(pParameterIDs);
for (DWORD i = 0; i < dwParameterCount; i++)
{
if (pParameters[i])
LocalFree(pParameters[i]);
}
return status;
}
// Get a string that contains the time stamp of when the event
// was generated.
void GetTimestamp(const DWORD Time, WCHAR DisplayString[])
{
ULONGLONG ullTimeStamp = 0;
ULONGLONG SecsTo1970 = 116444736000000000;
SYSTEMTIME st;
FILETIME ft, ftLocal;
ullTimeStamp = Int32x32To64(Time, 10000000) + SecsTo1970;
ft.dwHighDateTime = (DWORD)((ullTimeStamp >> 32) & 0xFFFFFFFF);
ft.dwLowDateTime = (DWORD)(ullTimeStamp & 0xFFFFFFFF);
FileTimeToLocalFileTime(&ft, &ftLocal);
FileTimeToSystemTime(&ftLocal, &st);
StringCchPrintf(DisplayString, MAX_TIMESTAMP_LEN, L"%d/%d/%d %.2d:%.2d:%.2d",
st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond);
}
It's impossible to do in full way.
Here is why:
Each program that writes events to EventLog has an appropriate EventSource registered under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog. And an EventMessagFile value under EventSource key provides a path to a file that contain's event messages. So if, for example, some custom program provides only german event messages in that file, where do you get an english event messages from? The answer is from nowhere, because developers simply could not shipped event messages for other languages.
And for Windows, if you've got a german windows, but no english language pack (Microsoft's MUI) where does Windows have to get translations from? Nowhere.

GetOpenFileNameW results in FNERR_INVALIDFILENAME, or CDERR_INITIALIZATION if I call GetOpenFileNameA

Here's the code using GetOpenFileNameW:
import core.sys.windows.windows;
import std.stdio, std.string, std.utf;
pragma(lib, "comdlg32");
// Fill in some missing holes in core.sys.windows.windows.
extern (Windows) DWORD CommDlgExtendedError();
enum OFN_FILEMUSTEXIST = 0x001000;
void main()
{
auto buf = new wchar[1024];
OPENFILENAMEW ofn;
ofn.lStructSize = ofn.sizeof;
ofn.lpstrFile = buf.ptr;
ofn.nMaxFile = buf.length;
ofn.lpstrInitialDir = null;
ofn.Flags = OFN_FILEMUSTEXIST;
BOOL retval = GetOpenFileNameW(&ofn);
if (retval == 0) {
// Get 0x3002 for W and 0x0002 for A. ( http://msdn.microsoft.com/en-us/library/windows/desktop/ms646916(v=vs.85).aspx )
throw new Exception(format("GetOpenFileName failure: 0x%04X.", CommDlgExtendedError()));
}
writeln(buf);
}
This results in FNERR_INVALIDFILENAME, but I don't see any non-optional strings that I haven't filled in. And here's the code (only differences shown) for GetOpenFileNameA:
auto buf = new char[1024];
OPENFILENAMEA ofn;
// ...
BOOL retval = GetOpenFileNameA(&ofn);
This results in CDERR_INITIALIZATION, and the only elaboration MSDN gives me is
The common dialog box function failed during initialization.
This error often occurs when sufficient memory is not available.
This is on Windows 7 64 bit, DMD v2.059.
buf has to be zeroed completely. The problem here is that wchar.init == wchar.max (for error detection reasons), so your array is essentially 1024 instances of wchar.max. A simple buf[] = 0; should fix that.

Resources