Open .chm file at specific page/topic using command line arguments - c++

I am attempting to open a .chm file(A windows help file) at a specific page/topic by using a system call in C++.
I can successfully open the .chm file to the start page through the following code, but how can I open a .chm file to a specific page/topic inside the help file?
system("start c:/help/myhelp.chm");
PS: I know system is evil/discouraged but the system part is not really relevant its the command line arguments I pass with the .chm file(that will specify what page I want to open) that I am trying to determine.

Ok the arguments are like so:
system(" /Q /E:ON /C HH.EXE ms-its:myChm.chm::myPageName.htm");

There is an API in the Windows SDK called HtmlHelp in the HtmlHelp.h file. You can call like so:
HtmlHelp(GetDesktopWindow(), L"C:\\helpfile\\::/helptopic.html", HH_DISPLAY_TOPIC, NULL);
The Microsoft Docs - HtmlHelpA function provides more information about the function. HtmlHelp() will normally resolve to HtmlHelpA() or HtmlHelpW() depending on whether Unicode compiler option is set or not.
See as well Microsoft Docs - HTML Help API Overview.

Another option - use ShellExecute. The Microsoft help is not easy to use. This approach is much easier and in line with your question. Here is a quick routine to open a help file and pass an ID number. I have just set up some simple char’s so you can see what is going on:
void DisplayHelpTopic(int Topic)
{
// The .chm file usually has the same name as the application - if you don’t want to hardcode it...
char *CmndLine = GetCommandLine(); // Gets the command the program started with.
char Dir[255];
GetCurrentDirectory (255, Dir);
char str1[75] = "\0"; // Work string
strncat(str1, CmndLine, (strstr(CmndLine, ".exe") - CmndLine)); // Pull out the first parameter in the command line (should be the executable name) w/out the .exe
char AppName[50] = "\0";
strcpy(AppName, strrchr(str1, '\\')); // Get just the name of the executable, keeping the '\' in front for later when it is appended to the directory
char parms[300];
// Build the parameter string which includes the topic number and the fully qualified .chm application name
sprintf(parms,_T("-mapid %d ms-its:%s%s.chm"), Topic, Dir, AppName);
// Shell out, using My Window handle, specifying the Microsoft help utility, hh.exe, as the 'noun' and passing the parameter string we build above
// NOTE: The full command string will look like this:
// hh.exe -mapid 0 ms-its:C:\\Programs\\Application\\HelpFile.chm
HINSTANCE retval = ShellExecute(MyHndl, _T("open"), _T("hh.exe"), parms, NULL, SW_SHOW);
}
The topics are numbered within your .chm file. I set up a #define for each topic so if I had to change the .chm file I could just change the include file to match and not have to worry about searching through the code for hardcoded values.

Related

I can't get SHGetFileInfo to return an icon location

I'm on Windows 10 Pro and Visual Studio 2013, and I'm using SHGetFileInfoW to get an icon location (path + index) for a file type:
std::wstring wFile { L"a.bas" };
SHFILEINFOW fi {};
DWORD success = ::SHGetFileInfoW(wFile.c_str(),
FILE_ATTRIBUTE_NORMAL,
&fi,
sizeof(fi),
SHGFI_USEFILEATTRIBUTES | SHGFI_ICONLOCATION);
No matter whether wFile refers to an existing file or is just any filename, the call returns 1 indicating success. The iIcon member of fi is set to a number, but szDisplayString is empty. Not just the drive letter is overwritten with \0 (as seemed to happen here) but it is completely filled with \0.
Microsoft recommends using IExtractIcon::GetIconLocation as an alternative, but I need to get the icon for files which are not on a local filesystem, so I can't get an IShellInfo object which would get me this interface pointer.
Getting an icon handle works, on the other hand. Is this function just buggy or am I doing something wrong? Is there a workaround?
Icons can be dynamically generated and might not expose the path to its images. Icon handlers communicate this to the shell by setting the GIL_NOTFILENAME flag in their IExtractIcon::GetIconLocation implementation. If GIL_SIMULATEDOC is set the shell must also typically generate a icon on the fly.
If you call SHGetFileInfo with the SHGFI_SELECTED flag set then then function probably has to generate a new icon no matter which file type you are asking for.
If you are displaying a file list in a ListView/TreeView then you typically use SHGFI_SYSICONINDEX|SHGFI_SHELLICONSIZE|SHGFI_SMALLICON and use the system image list.
Use SHGFI_ICON if you need a HICON.
If SHGFI_ICONLOCATION is specified then SHGetFileInfo uses IExtractIcon:
Retrieve the name of the file that contains the icon representing the file specified by pszPath, as returned by the IExtractIcon::GetIconLocation method of the file's icon handler.

Is there a WinAPI to get filename from a command line with optional spaces and other parameters?

My goal is get a file name from a command line call/string. For instance, if I have the following strings on the input:
C:\WINDOWS\system32\mstsc.exe /v:%WKSNAME% /f
"C:\Users\User Name\Desktop\My Program.exe" /?
The API should return the following respectively:
mstsc.exe
My Program.exe
So I tried to use splitpath function, and although it works for a very simple file path, it totally fails on my two examples above.
I understand that I can write my own parser (so please don't offer that.) I'm curious if there's a built-in Windows API that does it already?
PS. There must be one that OS uses internally to parse those.
PS2. Here's the code I've been toying with:
TCHAR buffFileName[MAX_PATH];
TCHAR buffExt[MAX_PATH];
LPCTSTR strInputPath = L"C:\\WINDOWS\\system32\\mstsc.exe /v:%WKSNAME% /f";
if(_tsplitpath_s(strInputPath, NULL, 0, NULL, 0, buffFileName, MAX_PATH, buffExt, MAX_PATH) == 0)
{
//Got something
}
I believe PathFindFileName may be what you're looking for. From the docs:
Searches a path for a file name.
The examples within the docs seem to show the exact behavior you describe.
If you'd rather parse the entire command-line, CommandLineToArgvW may be helpful. It takes a command-line and splits it into an array containing the filename and any arguments.
This function's parsing rules are fairly intricate, so be sure to look over the docs, but a simple explanation of them can be found in this answer.

C++: How to make a my program open a .exe with optional args

I'm having some trouble with a program. My goal is to have it open several .exe files with optional args passed. For example if I wanted to open up a pdf I could type the string below into a cmd window.
// If used in a cmd window it will open up my PDF reader and load MyPDF.pdf file
"c:\Test space\SumatraPDF.exe" "c:\Test space\Sub\MyPDF.pdf"
Here are two tries I used. The first opens the PDF but of course doesn't load the file. The second simply doesn't work.
// Opens the PDF in my program
system("\"C:\\Test space\\SumatraPDF.exe\"");
// Error I get inside of a cmd window is the comment below
// 'C:\Test' is not recognized as an internal or external command, operable program or batch file.
//system("\"C:\\Test space\\SumatraPDF.exe\" \"C:\\Test space\\Sub\\MyPDF.pdf\"");
I'm unsure of the reason why the second one does not work. It could be I'm misunderstanding something about system, or I'm not using delimiters right.
I feel like there is a library out there designed for this rather than creating a long string that uses so many delimiters.
Thanks for any help.
Welcome to Stack Overflow!
The system method works by passing it's argument to cmd /c. So you will need an extra set of quotes around it. See related question posted by sled.
As an alternative to system, take a look at the ShellExecute or ShellExecuteEx Win32 API function. It has more features although it is not as portable.
// ShellExecute needs COM to be initialized
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(sei);
sei.lpFile = prog; // program like c:\Windows\System32\notepad.exe
sei.lpParameters = args; // program arguments like c:\temp\foo.txt
sei.nShow = SW_NORMAL; // app should be visible and not maximized or minimized
ShellExecuteEx(&sei); // launch program
CoUninitialize();
More information here.

MFC C++ Reference INI file in application folder after deployment

Hoping for some help here, as I am tearing my hair out!I'm using Visual Studio 2010.
I have an MFC C++ application to deploy. I have a config.ini file that I'd like referenced when the exe starts. At the moment the user cannot double click on a *.myfile and it open, because the application does not "start in" the application folder (where I am installing the ini file to), and so cannot find the ini. I've tried the following
I tried finding info on setting the "start in" folder for the &Open action, but cannot find anything.
I can't find any info on setting a registry value of the ini file at installation, since this would be a relative reference depending on the user's choice, and so this doesn't apply.
It is unmanaged so the C++/CLI app.config solution doesn't apply.
Using the Application Data folder, but this gives me the wrong path - I'm using Windows 7, this is probably why, but I want my deployment to work on Windows XP ++.
Reading the app path at start up (from here) (put this in CMyApp::InitInstance().
Code:
CString strPath;
TCHAR* pstrExePath = strPath.GetBuffer (MAX_PATH);
::GetModuleFileName (0, pstrExePath, MAX_PATH);
strPath.ReleaseBuffer();
int pos = strPath.ReverseFind('\\');
strPath = strPath.Left(pos);
strPath += "\\config.ini";
This is the closest, but in debug mode there is a weird "\.\" in the path invalidating it. I could use a #ifdebug but this is messy surely?
Really appreciate any help - thanks!
EDIT:
Using this:
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, szPath)))
{
MessageBox(NULL,szPath, "MyApp", MB_OK|MB_ICONWARNING);
}
I get: "C:\ProgramData" as the szPath. Maybe this is right for debug mode? Or the development machine?
Thanks for all the input and prompts. It helped me get to this solution. Hopefully it helps some others to have the info in one place. This solution is a very simple one and probably not for full commercial deployment!
In VS2010 in the FileSystem (Setup)
put the config file in the User's
Application Data Folder \ Productname
Set InstallAllUsers to false, so that
you don't need conditionals on where
your config file is located, based on
the user's installation choice
In the InitInstance() function add
something like the following:
[listbreaker]
TCHAR szPath[MAX_PATH] = {0};
if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,NULL,SHGFP_TYPE_CURRENT,szPath)))
{
PathAppend(szPath, "\\MyApp\\");
// Can check to create the directory here
//if (!PathFileExists(szPath))
// CreateDirectory(szPath, NULL);
PathAppend(szPath, TEXT("MyApp.ini"));
// can check to create default file here if removed by user
//if (!PathFileExists(szPath))
//g_ConfigData.write_default() // be nice if it could write itself
//MessageBox(NULL,szPath, "MyApp", MB_OK|MB_ICONWARNING);
}
if (!g_ConfigData.ReadData( szPath ) )
MessageBox(NULL,"Configuration file cannot be read", "MyApp", MB_OK|MB_ICONWARNING);
Some useful links that really helped me on this are:
How to get the %AppData% folder in C?
http://msdn.microsoft.com/en-us/library/206sadcd.aspx
http://www.vbforums.com/showthread.php?t=491920
http://jedej.com/2011/02/22/constant-special-item-id-list-windows-special-folders/
http://www.codeproject.com/Messages/2623871/using-CSIDL_APPDATA-or-CSIDL_COMMON_APPDATA.aspx
I'd appreciate any further help on this, as I'm sure there are more refined and flexible solutions (e.g. handling "All Users" choice on installation).

Opening a document programmatically in C++

I have a console program written in C++. Now I want to open a manual document(in .txt or .pdf) everytime a user of the program types 'manual' in console. How can I do this? Any links to a tutorial would be helpful.Thanks
Try to compile this code (Open.cpp) to Open.exe
Then, you can execute it with (for example) these parameters :
Open "C:\your file.doc"
Open "C:\your file.exe"
Open notepad
#include "windows.h"
int main(int argc, char *argv[])
{
ShellExecute(GetDesktopWindow(), "open", argv[1], NULL, NULL, SW_SHOWNORMAL);
}
Explanation of the program :
You should first include windows
library (windows.h) to get
ShellExecute and GetDesktopWindow function.
ShellExecute is the function to execute the file with parameter
argv[1] that is path to the file to be opened
Another option for lpOperation
arguments instead of "open" is
NULL. "explore" and "find" are
also the options but they are not
for opening a file.
SW_SHOWNORMAL is the constant to
show the program in normal mode (not
minimize or maximize)
Assuming you're on Windows, you're looking for the ShellExecute function. (Use the "open" verb)
In standard, platform independent, C and C++ you can use the system function to pass the name of an application to open your files.
For example, using Windows:
const char text_filename[] = "example.txt";
const char text_application[] = "notepad.exe";
std::string system_str;
system_str = text_application;
system_str += " ";
system_str += text_filename;
// Execute the application
system(system_str.c_str());
The text you send to the system function is platform specific.
In Managed C++ is its very easy
System::Diagnostics::Process::Start(path);
done !