I try to create a very simple app using windows API.
I've done some small apps in console. This is the first time I do with Win32 apps.
I've searched and found a document from forgers which is recommended in this site. But I try to write very first line:
#include <stdafx.h>
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MessageBoxW(NULL, "Good bye Cruel World", "Note", MB_OK);
return 0;
}
But it doesn't work (erased lines from default project created by VS 2008 and write these lines).
There are two versions of most windows API calls, one which takes single byte string and one which takes 2 byte unicode strings. The single byte one has an A on the end of the name and the 2 byte one has a W. There are macros defined in windows.h so that if you leave the letter out it picks one or the other depending on compiler macros.
In your code you write -
MessageBoxW (NULL, "Good bye Cruel World", "Note", MB_OK );
You are calling the wide character version of the API with single byte strings which won't work. Either change to MessageBoxA or change your strings to wide strings -
MessageBoxW (NULL, L"Good bye Cruel World", L"Note", MB_OK );
Remove the first line. What remains is a valid Windows program, which should compile under any IDE. Your problem is the stdafx.h header which is an artefact of VS, and you may have other problems if you try to "reuse" an existing VS project. If you want to nkow how compilation really works, it's a good idea to build some simple apps not using an IDE, but a command line compiler like MinGW.
And in future, post what error messages you are getting using copy and paste.
You need at least a message loop to run, in order for the application to process messages:
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
There are many tutorials on the message loop on the web, such as here:
http://www.winprog.org/tutorial/message_loop.html
If you're just starting GUI programming, you're best off reading a book, and working through the examples. Petzold is a classic. Learning programming by collecting snippets of partially working code cribbed from random web pages is going to be time-consuming, difficult and patchy. A well-written book will lead you through the fundamentals and explain things in stages. Have fun!
Related
I'd like to create a windows application that, under normal conditions, does not have any connected terminal, but may have one in some conditions. I tried two different routes. Option A, creating a normal console application, and conditionally calling FreeConsole():
int main()
{
if (someCondition) {
HANDLE stdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleA(stdOutHandle, "hello world\n", 12, NULL, NULL);
} else {
FreeConsole();
// normal operation
}
return 0;
}
And option B, creating a WinMain based application, and conditionally calling AllocConsole().
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow)
{
if (someCondition) {
AllocConsole();
HANDLE stdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsoleA(stdOutHandle, "hello world\n", 12, NULL, NULL);
}
else {
// normal operation
}
return 0;
}
The problem with option A, is that it doesn't act exactly like a windows application, in that you can very briefly see a console window open up before normal operation continues. This isn't a huge problem, but I'd prefer the program to act, as I said, exactly like a normal windows application.
The problem with option B is that, if the program is invoked from an existing terminal, it opens up a separate terminal window and outputs to that, instead of outputting to the terminal from which it was invoked. This is a much bigger problem.
What is the appropriate solution to conditionally behave as either a console program or a windows program, without either of the problems described above?
Edit
Based on a suggestion in the comments, I tried to use the AttachConsole function.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow)
{
if (someCondition) {
AttachConsole(ATTACH_PARENT_PROCESS);
HANDLE stdOutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
Sleep(3000);
WriteConsoleA(stdOutHandle, "hello world\n", 12, NULL, NULL);
FreeConsole();
}
else {
// normal operation
}
return 0;
}
This seems to be on the right track, but there is still something missing, because my shell prompt is immediately printed out without waiting for the program to finish.
Impossible.
AttachConsole does not work 100% because cmd.exe actually checks if the process it is about to start is a GUI app or not, and alters its behavior.
The only way to make it work is to have two programs; myapp.com and myapp.exe. %PathExt% lists .com before .exe so you can make a console .exe and rename it .com at it will be executed if the user runs "myapp" (but will not work if the user types "myapp.exe"). If your program is not very big you can just ship two versions. If the program is large you can move most of the code to a .dll or make the .com a small helper that calls the .exe with a command line parameter and some pipes for stdin/stdout. Visual Studio does this (devenv.com).
If you want to check if there is already a console, check for a NULL return from GetConsoleWindow(). If it returns null, then do the AllocConsole procedure and other setup (SetWindowLong, SetConsoleMode etc)
int main() programs don't make a console by default, they happen to attach into a console if run through cmd/ps.
One way is the following:
BOOL WINAPI
AttachOrCreateConsole()
{
if(AttachConsole(ATTACH_PARENT_PROCESS))
{
return TRUE;
}
return AllocConsole();
}
This will use the parent console if available and fall back to creating one if needed.
Note that if yo do this and want to use the standard C/C++ I/O mechanisms (stdin/std::cin, stdout/std::cout) you will need to do the work needed to associate those streams to the console handle. There is plenty of material available on how to do that.
#include <iostream>
#include <windows.h>
using namespace std;
int main(){
LPWSTR test = L"c:/aizen.png";
int result = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, test, SPIF_UPDATEINIFILE);
if(result)
cout << "Wallpaper set!";
else
cout << "Error: " << GetLastError();
cin >> result;
return 0;
}
The code is simply meant to change the background wallpaper, but I keep getting Error: 2, which means "file not found". However, the file is there! I'm using microsoft visual studio 2010 and I've tried running as admin, case-sensitive, changing slashes, etc. What am I doing wrong?
Error 2 is File not found.
First, make sure that aizen.png is actually located in the root folder of drive C:\ (which on Vista and above is not likely, given that non-admin users don't typically have write access there).
If the file is indeed there, the problem is most likely that you're not properly escaping backslashes:
LPWSTR test = L"c:\\aizen.png";
The problem is you are passing a UNICODE string - LPWSTR - to an API that takes ANSI.
Nearly all Win32 APIs (all that take strings at any rate) come in two versions, one that ends in ...A for ANSI (8-bit characters), and one that ends in ...W for Wide-char, aka UNICODE (technically not 'real' unicode, but that's more than is worth getting in this reply).
If you have UNICODE #defined at compilation time, then the plain unadorned version gets #defined as the ...W version; otherwise it gets #defined as the ...A version. Take a look at winuer.h, and you'll see:
WINUSERAPI
BOOL
WINAPI
SystemParametersInfoA(
__in UINT uiAction,
__in UINT uiParam,
__inout_opt PVOID pvParam,
__in UINT fWinIni);
WINUSERAPI
BOOL
WINAPI
SystemParametersInfoW(
__in UINT uiAction,
__in UINT uiParam,
__inout_opt PVOID pvParam,
__in UINT fWinIni);
#ifdef UNICODE
#define SystemParametersInfo SystemParametersInfoW
#else
#define SystemParametersInfo SystemParametersInfoA
#endif // !UNICODE
Note that Windows has two SystemParametersInfo functions; the W one expects wide LPWSTR and the A one expect plain LPSTRs; and whether you have UNICODE defined or not selects which is the 'default' one. (You can always add the A or W manually to call either explicitly.)
What's likely happening in your original code is that because you do not have UNICODE defined, you end up using the ...A version, which expects an ANSI string, but you're passing in a UNICODE string - so it doesn't work.
The "bit of a change" you made to get it working is more than just a bit: you're now passing an ANSI string to the ...A version of the API so it works fine:
int result = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)"c:/aizen.jpg", SPIF_UPDATEINIFILE);
Alternatively, you could call the W version explicitly with a LPWSTR:
int result = SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, L"c:\\aizen.jpg", SPIF_UPDATEINIFILE);
Or, you could define UNICODE at the start of your app, use L"..." strings, and the plain version of the APIs - just add #define UNICODE at the top of your original app before #include . (UNICODE is more usually defined in a makefile or in compiler settings options instead of being defined explicitly in code, so if you're new to Win32 programming, it can come as something of a surprise feature.)
Note that LPWSTR is not deprecated; if anything, it's the opposite; typical Win32 practice since XP or so has been to use W-flavor strings across the board, so it's effectively the plain "..." strings that are considered 'deprecated' on Win32. (For example, many COM APIs use only wide strings.)
Most other functions have some protection against this; if you accidentally try to pass an ANSI string to say SetWindowTextW, you'll get a compile-time error, because the LPSTR you are passing in doesn't match the expcted LPWSTR type the function is expecting. But SystemParamtersInfo is tricky; it takes a void* for the data parameter, so will accept [almost] anything at compile time, and it's only when you call the function at runtime that you'll hit the error.
--
This, by the way, is what David Herfernan pointed out in the answer to your question the first time you posted -
Some possible causes spring to mind:
...
You have an ANSI/Unicode encoding mismatch.
It's very weird looks like if you compile with MinGW c++ compiler it actually compiles this with a bit of change:
int main(){
int result = SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)"c:/aizen.jpg", SPIF_UPDATEINIFILE);
This would work, and apparently LPWSTR is deprecated...
Visual Studio was probably having privilege issues. Run it as Admin and try again.
Can I safely assume that Windows Explorer is always started from a Windows system directory? Also, is its process always named "explorer.exe"?
And if not, how to get its full file path?
EDIT: Forgot to mention -- I need this to later find out the process ID of the Windows Explorer running in a given user session. Thus my search for its full path.
EDIT 2: Thanks everyone who contributed, and especially to sehe! After his post I found this page that explains how to set up your own shell. I made a wild test by completely replacing explorer.exe with my own process and here's the result:
Here's the full-size link if you it gets re-sized.
As you can see, I can technically replace explorer.exe with whatever process I may come up with. As you can also see in my screenshot Windows gives me a complete control over the Shell (the screenshot is my entire window.)
So the bottom line, the only way to get "explorer.exe" file path (or whatever Shell process is used) is to use those registry keys from the link I quoted above -- pretty much close to what sehe suggested, with just a few more checks to do, but it's a pretty straightforward stuff.
As for Sean Cline's suggestion, it would be a very elegant solution ONLY if we have the "stock" Windows Explorer running that comes with a tray window with that specific class name.
It is probably safe to assume that explorer.exe is always in the %windir% or %SystemRoot% as it hasn't moved for years. But, if you are trying to invoke something via Explorer, chances are you want to use the ShellExecute() function instead.
If you really do need the path, the easiest way to get it is probably with a call to SHGetKnownFolderPath() using FOLDERID_Windows as the first argument.
Edit:
Here is my stab at some code knowing that you are looking for the PID of the shell process:
DWORD trayPID;
HWND trayWnd = FindWindow("Shell_TrayWnd", NULL);
GetWindowThreadProcessId(trayWnd, &trayPID);
It looks for the hWnd of the taskbar and finds the owning PID. You will likely need to add some error handling for the case that explorer is not running and that window does not exist - unlikely, but possible.
No you can't safely assume that and none of this has to do with C++.
Also, you didn't show any code. Here goes:
The registry key for this is Software\Microsoft\Windows NT\CurrentVersion\WinLogon\Shell (see here).
#include <windows.h>
#include <malloc.h>
#include <stdio.h>
#include <string>
LONG GetStringRegKey(HKEY hKey, const std::wstring &strValueName, std::wstring &strValue, const std::wstring &strDefaultValue)
{
strValue = strDefaultValue;
WCHAR szBuffer[512];
DWORD dwBufferSize = sizeof(szBuffer);
ULONG nError;
nError = RegQueryValueExW(hKey, strValueName.c_str(), 0, NULL, (LPBYTE)szBuffer, &dwBufferSize);
if (ERROR_SUCCESS == nError)
{
strValue = szBuffer;
}
return nError;
}
int main()
{
HKEY hKey;
LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon", 0, KEY_READ, &hKey);
bool bExistsAndSuccess (lRes == ERROR_SUCCESS);
bool bDoesNotExistsSpecifically (lRes == ERROR_FILE_NOT_FOUND);
std::wstring shell;
GetStringRegKey(hKey, L"Shell", shell, L"");
}
Yes to both. Windows Explorer is always located at %WINDIR%\Explorer.exe.
I am using Windows 7 and I have to run one program in that windows but that program working in Windows XP. This is a Visual C++ program and I am using Visual Studio 2008 for this. When I am running my application, it does not throw any errors, but it does not create a directory in "c:\program files\". So can anyone help me to create directory and exe file?
This is the code I am using:
char szAppPath[MAX_PATH];
char szFileName[MAX_PATH];
DWORD dwResult;
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
dwResult = ExpandEnvironmentStrings( NULL, szAppPath, MAX_PATH); // "%ProgramFiles%"
// do same for NSim directory
strcat(szAppPath,"\\NSim");
hFind = FindFirstFile(szAppPath, &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
{
//Directory Does't Exists create New
if(!CreateDirectory(szAppPath,NULL)) //Throw Error
{
MessageBox("Unable to Create N-SIM directory","NSim Installer");
return ;
}
}
else
{
//check if is directory or not
if(!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
MessageBox("Can't Create N-SIM directory\n Another file with same name exists","NSim Installer");
return ;
}
FindClose(hFind);
}
//***************************************N-SIM Application****************************
strcpy(szFileName, szAppPath);
HRSRC hRes;
if( bRegister == FALSE)
{
strcat(szFileName,"\\NSim.exe"); //make same name of the Client & Server in program file
hRes = FindResource(NULL, MAKEINTRESOURCE(IDR_LANSIMSERVER),RT_RCDATA);
if(flagUpgrade ==0)
{
CString trial = installationDate(); //----- Detemine Expiry Date -----
setRegistry(trial);
}
}
It's a file permissions issue, plain and simple. Programs can't just go rooting around system directories in Windows 7. That's why it works "properly" in Windows XP, but not in newer versions.
I can't tell for sure, but it looks like you're trying to write an installer. If so, why are you reinventing the wheel? There are tons of great setup utilities availableāVisual Studio provides a setup project that you can customize to your needs, or look into Inno Setup, my personal favorite. A Google search will turn up plenty of other options that have already solved this problem for you, and innumerable others.
If this isn't an installer, and you're just trying to store application and/or user data in the Program Files folder, I highly recommend that you look elsewhere. You weren't supposed to shove data into the app folder under earlier versions of Windows, and Windows 7 just cuts you off at the knees if you do this. Your best bet is to follow the recommendations that existed from the beginning: Investigate the user and common Application Data folders carefully. Use the SHGetKnownFolderPath function to retrieve the full path to a known folder using its KNOWNFOLDERID. A couple of suggestions:
FOLDERID_ProgramData (a shared program data directory for all users)
FOLDERID_LocalAppData (a per-user program data directory, non-roaming)
FOLDERID_RoamingAppData (a per-user program data directory, roaming)
Alternatively, you can try running the application as an Administrator. You might want to look into creating a manifest that indicates the application requires administrator-level permissions to execute.
[edit] I edited the code in the question for readability and removed the commented out code (to see the wood for the trees). It is now obvious that nothing initialises szAppPath before calling strcat(), and calling ExpandEnvironmentStrings with NULL as the first argument is undefined (and certainly useless). Calling strcat() on an unitialised string is not likely to have the desired result. This may be an artefact of not posting the real code, or even of other peoples edits (including mine).
CreateDirectory sets the system error code on error; if you want to know what went wrong, check it! Any answer you get here will be an educated guess.
if(!CreateDirectory(szAppPath,NULL)) //Throw Error
{
DWORD errorcode = GetLastError();
LPVOID lpMsgBuf;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL );
MessageBox(NULL, (LPCTSTR)lpMsgBuf, TEXT("Error"), MB_OK);
return ;
}
If you just want to get the error code and look it up manually, then a complete directory of codes is available on MSDN, here, I would guess that ERROR_ACCESS_DENIED
(5) is most probable. A more elaborate example of error code display is given here.
windows7?
Ok, the problem is not with your program. Its with the file system permissions in Windows 7. User programs cannot create files there.
I think the problem is lack of privileges. You can debug your project to see whether the CreateDirectory function sets an error as ERROR_ACCESS_DENIED, if it does, you should make your program run with an administrator privilege. Add manifest in your project to do so.
It is intended to protect your computer against attack. Well maybe. Or Microsoft deciding to tell you what you are and not allowed to do on your own computer.
In any case you can change your UAC settings if you really have to write there in that way although that obviously exposes you to risk.
Otherwise play nice and do things the Microsoft way, using a proper installer.
How would you program a C/C++ application that could run without opening a window or console?
When you write a WinMain program, you automatically get the /SUBSYSTEM option to be windows in the compiler. (Assuming you use Visual Studio). For any other compiler a similar option might be present but the flag name might be different.
This causes the compiler to create an entry in the executable file format (PE format) that marks the executable as a windows executable.
Once this information is present in the executable, the system loader that starts the program will treat your binary as a windows executable and not a console program and therefore it does not cause console windows to automatically open when it runs.
But a windows program need not create any windows if it need not want to, much like all those programs and services that you see running in the taskbar, but do not see any corresponding windows for them. This can also happen if you create a window but opt not to show it.
All you need to do, to achieve all this is,
#include <Windows.h>
int WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int cmdShow)
{
/* do your stuff here. If you return from this function the program ends */
}
The reason you require a WinMain itself is that once you mark the subsystem as Windows, the linker assumes that your entry point function (which is called after the program loads and the C Run TIme library initializes) will be WinMain and not main. If you do not provide a WinMain in such a program you will get an un-resolved symbol error during the linking process.
In windows:
#include <windows.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// <-- Program logic here
return 0;
}
Be sure to use the /SUBSYSTEM linker switch as mentioned by Adam Mitz.
On other platforms:
int main(int argc, char**argv)
{
// <-- Program logic here
return 0;
}
If you have a need to contiguously run your program without having console or window you might find useful deamon on *NIX or services on Windows, this .NET example if you need plain win32 just google a little bit for sample.
Since your question tagged as win32 i assume that services are more relevant for you.
This also processes messages:
#include <windows.h>
#include <stdio.h>
int CALLBACK WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MSG msg;
DWORD curThreadId;
curThreadId = GetCurrentThreadId();
// Send messages to self:
PostThreadMessage(curThreadId, WM_USER, 1, 2);
PostThreadMessage(curThreadId, WM_USER+1, 3, 4);
PostThreadMessage(curThreadId, WM_USER+2, 5, 6);
PostThreadMessage(curThreadId, WM_USER+3, 7, 8);
PostThreadMessage(curThreadId, WM_QUIT, 9, 10);
while (GetMessage(&msg, NULL, 0, 0)) {
printf("message: %d; wParam: %d; lParam: %d\n", msg.message, msg.wParam, msg.lParam);
}
return (int) msg.wParam;
}
In Visual Studio Express 2010 after setting the subsystem to windows (as suggested by user17224), alternatively to changing the main to WinMain (as suggested by user17224 and Brian R. Bondy), one can set the entry function to main in properties, linker, advanced, entry point: just type main in the text box.
Use Visual Studio wizard to create the Win32 Application. But don't create the window i.e., you remove the window creation function.
Alternatively we can create Win Service application.
If you are using MSVC or Visual Studio just use the new Project Wizard and select the Console Application.