I am creating a Windows Shell Extension to add some options in the explorer context menu by making a COM dll but I get a strange problems with a vector.
I'm trying to append contents of a std::vector (member of the class) which contains paths into a std::wstring but at the end, only one path is added to the string. I know this is a problem with the vector itself because if I replace the vector by a local one instead of m_selectedFiles, it works.
Here is the code :
HRESULT FilesEncryptContextMenuHandler::InvokeCommand(CMINVOKECOMMANDINFO *pici) {
wchar_t filename[MAX_PATH] = {0};
GetModuleFileName((HMODULE)g_hInstance, filename, MAX_PATH);
std::wstring str = filename;
std::wstring exe = str.substr(0, str.find_last_of('\\')) + L"\\FilesEncrypt.exe";
std::basic_stringstream<wchar_t> ss;
for (std::vector<std::wstring>::iterator it = m_selectedFiles.begin(); it != m_selectedFiles.end(); ++it) {
MessageBox(NULL, it->c_str(), L"Test", MB_OK);
ss << *it << L" ";
}
std::wstring args = ss.str();
MessageBox(NULL, args.c_str(), L"Test", MB_OK);
args = args.substr(0, args.size() - 1);
ShellExecute(NULL, L"open", exe.c_str(), args.c_str(), NULL, SW_SHOWNA);
return S_OK;
}
Here, The MessageBox in the for loop is called multiple times with the paths but the second MessageBox only shows PATH1. For a reason I really don't know, the other elements in the vector are not appended.
It's because the line:
ss << *it << L" ";
places a NUL in the stream, the message box stops there. You will need to copy it->begin() to it->end()-1 to the stream.
Related
i try to create link to file in StartMenu folder, my code:
bool createStartMenuEntry(string targetPath, string name){
std::wstring stemp = s2ws(targetPath);
LPCWSTR target = stemp.c_str();
WCHAR startMenuPath[MAX_PATH];
HRESULT result = SHGetFolderPathW(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, startMenuPath);
if (SUCCEEDED(result)) {
std::wstring linkPath = std::wstring(startMenuPath) + s2ws(name);
LPCWSTR link = linkPath.c_str();
//TEST MESSAGE!!!
MessageBox(NULL, LPCSTR(target), LPCSTR(link), MB_ICONWARNING);
CoInitialize(NULL);
IShellLinkW* shellLink = NULL;
result = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_ALL, IID_IShellLinkW, (void**)&shellLink);
if (SUCCEEDED(result)) {
shellLink->SetPath(target);
//shellLink->SetDescription(L"Shortcut Description");
shellLink->SetIconLocation(target, 0);
IPersistFile* persistFile;
result = shellLink->QueryInterface(IID_IPersistFile, (void**)&persistFile);
if (SUCCEEDED(result)) {
result = persistFile->Save(link, TRUE);
persistFile->Release();
}
else {
return false;
}
shellLink->Release();
}
else {
return false;
}
}
else {
return false;
}
return true;
}
String to widestring conversion:
std::wstring s2ws(const std::string& s)
{
int len;
int slength = (int)s.length() + 1;
len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
wchar_t* buf = new wchar_t[len];
MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
std::wstring r(buf);
delete[] buf;
return r;
}
When I call my func like createStartMenuEntry("E:\\file.exe" , "File"), in test message I have only first letters of path and shortcut isn't created, I think, problem in unicode conversion.
There are multiple problems here:
MessageBox(NULL, LPCSTR(target), LPCSTR(link), MB_ICONWARNING); is all kinds of wrong. You should not be casting strings like this. If you are compiling without UNICODE defined, you must use MessageBoxW() to display a LPCWSTR string. You get a single character because "c:\\" as a Unicode string is 'c',0,':',0,'\\',0,0,0 in memory, and that is the same as a "c" string when treated as a narrow ANSI string.
You ignore the result of persistFile->Save()! You also ignore the results of SetPath() and SetIconLocation().
A normal user cannot write to CSIDL_COMMON_PROGRAMS, only administrators have write access to that folder, because it is shared by all users. If you are not planning to require UAC elevation, you must write to CSIDL_PROGRAMS instead.
You should not use std::string to store paths, only std::wstring and WCHAR*/LP[C]WSTR, because paths that contain certain Unicode characters cannot be represented in a narrow ANSI string.
I can get handle from GetForegroundWindow function. And I want to get BaseName
of handle. So i used GetModelBaseName function. But I guess this function was not work correctly.
TCHAR TitleName[MAX_PATH] = TEXT("");
HANDLE hFirst = GetForegroundWindow();
GetModuleBaseName(hFirst, NULL, TitleName, MAX_PATH);
_tprintf(TEXT("%s \n"), TitleName);
Tell me, what is the problem?
You are doing it wrong, that's why it's returning false and GetLastError will return ERROR_INVALID_HANDLE (6).
HWND WINAPI GetForegroundWindow(void);
Will return the current foreground window and will return it's window handle of type HWND.
You can do this to retrieve the filename of your application:
TCHAR szName[MAX_PATH];
GetModuleBaseName(GetCurrentProcess(), GetModuleHandle(NULL), szName, MAX_PATH);
Besides, you can also use GetModuleFileName or GetMappedFileName to retrieve the full path of your application
Edit: He wants to do something else too. To retrieve the path of another process, you will have to open that process with a process id. For instance, if 9912 is the process id of Chrome then you can execute the following code to retrieve it's path
HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, 9912);
if (process)
{
char file_path[MAX_PATH];
if (::GetModuleFileNameEx(process, nullptr, file_path, MAX_PATH))
{
std::cout << file_path << std::endl;
}
else
{
std::cout << "Error retrieving path" << std::endl;
}
::CloseHandle(process);
}
I have a program that calls SHGetKnownFolderPath with FOLDERID_RoamingAppData.
If I start the program by double clicking it, it works ok.
If the program is started by a windows service (in the current user context), the function fails with error E_ACCESSDENIED (-2147024891).
This is what my code looks like:
Tstring EasyGetFolderPath(REFKNOWNFOLDERID folderid)
{
Tstring sPath = _T("");
PWSTR pszPath = NULL;
HRESULT hr = SHGetKnownFolderPath(folderid, 0, NULL, &pszPath);
if (hr == S_OK && pszPath)
{
sPath = WStringToTCHAR(pszPath);
CoTaskMemFree(pszPath);
return sPath;
}
else
{
throw HResultException(hr, _T("SHGetKnownFolderPath failed"));
}
}
Tstring EasyGetUsrAppDataPath()
{
return EasyGetFolderPath(FOLDERID_RoamingAppData);
}
static TCHAR* WStringToTCHAR(const std::wstring &s)
{
#ifdef UNICODE
TCHAR *sT = new TCHAR[s.length() + 1];
_tcscpy_s(sT, s.length() + 1, s.c_str());
return sT;
#else
std::string str = WStringToString(s);
TCHAR *sT = new TCHAR[str.length()+1];
_tcscpy_s(sT, str.length() + 1, str.c_str());
return sT;
#endif // UNICODE
}
static std::string WStringToString(const std::wstring& s, bool method = true)
{
std::string temp;
temp.assign(s.begin(), s.end());
return temp;
}
This is the code that starts the process in the current user context:
(I've removed the error handling in order to reduce verbosity)
void StartProcessInCurrentUserContext(const Tstring &sExeName, const Tstringarr &lstParams, const Tstring &sWorkingDir)
{
...
EnableDebugPrivilege();
errCode = GetProcessByName(_T("explorer.exe"), hProcess);
if (!OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken))
{
...
}
if (hProcess)
CloseHandle(hProcess);
Tstring sCmdLine = ...;
...
// Create the child process.
bSuccess = CreateProcessAsUser(hToken, NULL,
(LPTSTR)sCmdLine.c_str(), // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
sWorkingDir.length() > 0 ? (LPCTSTR)sWorkingDir.c_str() : NULL,
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
CloseHandle(hToken);
...
}
Does anyone know what the problem might be?
The documentation for SHGetKnownFolderPath says in the discussion of the hToken parameter:
In addition to passing the user's hToken, the registry hive of that specific user must be mounted.
The documentation for CreateProcessAsUser says
CreateProcessAsUser does not load the specified user's profile into the HKEY_USERS registry key.
These two paragraphs together explain why your code is not working. Fortunately, the next sentence in the documentation for CreateProcessAsUser explains what you need to do:
Therefore, to access the information in the HKEY_CURRENT_USER registry key, you must load the user's profile information into HKEY_USERS with the LoadUserProfile function before calling CreateProcessAsUser. Be sure to call UnloadUserProfile after the new process exits.
I was already using tinyxml 1 before I implemented the function GetOpenFileName in my code, so I know the load works whenever I give it a relative path or an absolute path.
I just don't understand why it doesn't work whenever the function GetOpenFileName executes first. I actually tried a few times to test and every time I executed that function, regardless of whether I used the filepath it gave me or not, tinyxml still wouldn't find the xml.
std::string tutName = getTutorialFilename();
if(tutName != "") {
std::cout << "Before replacing: " << tutName << std::endl;
boost::replace_all(tutName, "\\", "/");
bool loadTutorial = tutorial->loadTutorialSteps(tutName);
if(loadTutorial) {
std::cout << "success!" << std::endl;
} else {
std::cout << "failed: " << tutName << "to load" << std::endl;
}
}
The Function getTutorialFilename, which uses GetOpenFilename:
std::string getTutorialFilename() {
OPENFILENAME ofn; // common dialog box structure
char szFile[260]; // buffer for file name
HWND hwnd; // owner window
HANDLE hf; // file handle
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
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 = "XML\0*.xml*\0All\0*.*\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// Display the Open dialog box.
if (GetOpenFileName(&ofn)==TRUE) {
hf = CreateFile(ofn.lpstrFile,
GENERIC_READ,
0,
(LPSECURITY_ATTRIBUTES) NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
std::string tutorialFilename(szFile);
return tutorialFilename;
}
return "";
}
I know it finds the tutorialFilename with no extra spaces as I've ran the debugger on that, but I still can't understand why tinyxml fails to load.
I figured out the issue. TinyXML was outputting the error 13 - permission denied due to CreateFile blocking access to the file. I deleted that function because I don't need it.
I am using _popen to start a process to run a command and gather the output
This is my c++ code:
bool exec(string &cmd, string &result)
{
result = "";
FILE* pipe = _popen(cmd.c_str(), "rt");
if (!pipe)
return(false);
char buffer[128];
while(!feof(pipe))
{
if(fgets(buffer, 128, pipe) != NULL)
result += buffer;
}
_pclose(pipe);
return(true);
}
Is there any way of doing this without a console window opening (as it currently does at the _popen statement)?
On Windows, CreateProcess with a STARTUPINFO structure that has dwFlags to include STARTF_USESSHOWWINDOW. Then setting STARTUPINFO.dwFlags to SW_HIDE will cause the console window to be hidden when triggered. Example code (which may be poorly formatted, and contains a mix of C++ and WinAPI):
#include <windows.h>
#include <iostream>
#include <string>
using std::cout;
using std::endl;
void printError(DWORD);
int main()
{
STARTUPINFOA si = {0};
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
BOOL result = ::CreateProcessA("c:/windows/system32/notepad.exe", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if(result == 0) {
DWORD error = ::GetLastError();
printError(error);
std::string dummy;
std::getline(std::cin, dummy);
return error;
}
LPDWORD retval = new DWORD[1];
::GetExitCodeProcess(pi.hProcess, retval);
cout << "Retval: " << retval[0] << endl;
delete[] retval;
cout << "Press enter to continue..." << endl;
std::string dummy;
std::getline(std::cin, dummy);
return 0;
}
void printError(DWORD error) {
LPTSTR lpMsgBuf = nullptr;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
cout << reinterpret_cast<char*>(lpMsgBuf) << endl;
LocalFree(lpMsgBuf);
}
As far as I know, you can't1: you are starting a console application (cmd.exe, that will run the specified command), and Windows always creates a console window when starting a console application.
although, you can hide the window after the process started, or even create it hidden if you pass the appropriate flags to CreateProcess; problem is, _popen do not pass these flags, so you have to use the Win32 APIs instead of _popen to create your pipe.
[Final Edit]
a similar SO question merges everything said above and gets you your output
C++ popen command without console
[Edited again]
erk. sorry I got excited about spawning processes. I reread your q. and apart from the extra window you're actually trying to get the processes's stdout/stderr. I'd just like to add that for that purpose, all my suggestions are sadly irrelevant. but I'll leave them here for reference.
[Edited]
For no good specific reason (except that "open" works for both windows and macs), I use ShellExecute for spawning processes rather than CreateProcess. I'll research that later..but here is my StartProcess function.
Hidden or Minimized seem to produce the same result. the cmd window does come into being but it is minimized and doesn't ever pop up on the desktop which might be your primary goal.
#if defined(PLATFORM_WIN32)
#include <Windows.h>
#include <shellapi.h>
#elif defined(PLATFORM_OSX)
#include <sys/param.h>
#endif
namespace LGSysUtils
{
// -----------------------------------------------------------------------
// pWindow : {Optional} - can be NULL
// pOperation : "edit", "explore", "find", "open", "print"
// pFile : url, local file to execute
// pParameters : {Optional} - can be NULL otherwise a string of args to pass to pFile
// pDirectory : {Optional} - set cwd for process
// type : kProcessWinNormal, kProcessWinMinimized, kProcessWinMaximized, kProcessHidden
//
bool StartProcess(void* pWindow, const char* pOperation, const char* pFile, const char* pParameters, const char* pDirectory, LGSysUtils::eProcessWin type)
{
bool rc = false;
#if defined(PLATFORM_WIN32)
int showCmd;
switch(type)
{
case kProcessWinMaximized:
showCmd = SW_SHOWMAXIMIZED;
break;
case kProcessWinMinimized:
showCmd = SW_SHOWMINIMIZED;
break;
case kProcessHidden:
showCmd = SW_HIDE;
break;
case kProcessWinNormal:
default:
showCmd = SW_NORMAL;
}
int shellRC = (int)ShellExecute(reinterpret_cast<HWND>(pWindow), pOperation,pFile,pParameters,pDirectory,showCmd);
//Returns a value greater than 32 if successful, or an error value that is less than or equal to 32 otherwise.
if( shellRC > 32 )
{
rc = true;
}
#elif defined(PLATFORM_OSX)
char cmd[1024];
sprintf(cmd, "%s %s", pOperation, pFile);
int sysrc = system( cmd );
dbPrintf("sysrc = %d", sysrc);
rc = true;
#endif
return rc;
}
}
[and previously mentioned]
If you are in control of the source code for the application that is launched, you could try adding this to the top of your main.cpp (or whatever you have named it)
// make this process windowless/aka no console window
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
You could also feed those options to the linker directly. The above is easier to play with for different build configurations imho.