I would like to know the cleanest way of registering a file extension with my C++ application so that when a data file associated with my program is double clicked, the application is opened and the filename is passed as a parameter to the application.
Currently, I do this through my wix installer, but there are some instances where the application will not be installed on ths user's computer, so I also need the option of creating the registry key through the application.
Additionally, will this also mean that if the application is removed, unused entries in the registry will be left lying around?
Your basic overview of the process is found in this MSDN article. The key parts are at the bottom of the list:
Register the ProgID
A ProgID (essentially, the file type registry key) is what contains your important file type properties, such as icon, description, and context menu items including applications used when the file is double clicked. Many extensions may have the same file type. That mapping is done in the next step:
Register the file name extension for the file type
Here, you set a registry value for your extension, setting that extension's file type to the ProgID you created in the previous step.
The minimum amount of work required to get a file to open with your application is setting/creating two registry keys. In this example .reg file, I create a file type (blergcorp.blergapp.v1) and associate a file extension (.blerg) with it.
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Classes\blergcorp.blergapp.v1\shell\open\command]
#="c:\path\to\app.exe \"%1\""
[HKEY_CURRENT_USER\Software\Classes\.blerg]
#="blergcorp.blergapp.v1"
Now, you probably want to accomplish this programmatically. To be absolutely kosher, you could check for the existence of these keys, and change your program behavior accordingly, especially if you're assuming control of some common file extension. However, the goal can be accomplished by setting those two keys using the SetValue function.
I'm not positive of the exact C++ syntax, but in C# the syntax looks something like this:
Registry.SetValue(#"HKEY_CURRENT_USER\Software\Classes\blergcorp.blergapp.v1\shell\open\command", null, #"c:\path\to\app.exe \"%1\"");
Registry.SetValue(#"HKEY_CURRENT_USER\Software\Classes\.blerg", null, "blergcorp.blergapp.v1");
Of course you could manually open each sub key, manually create the ProgID and extension subkey, and then set the key value, but a nice thing about the SetValue function is that if the keys or values don't exist, they will automatically be created. Very handy.
Now, a quick word about which hive to use. Many file association examples online, including ones on MSDN, show these keys being set in HKEY_CLASSES_ROOT. I don't recommend doing this. That hive is a merged, virtual view of HKEY_LOCAL_MACHINE\Software\Classes (the system defaults) and HKEY_CURRENT_USER\Software\Classes (the per-user settings), and writes to any subkey in the hive are redirected to the same key in HKEY_LOCAL_MACHINE\Software\Classes. Now, there's no direct problem doing this, but you may run into this issue: If you write to HKCR (redirected to HKLM), and the user has specified the same keys with different values in HKCU, the HKCU values will take precedence. Therefore, your writes will succeed but you won't see any change, because HKEY_CURRENT_USER settings take precedence over HKEY_LOCAL_MACHINE settings.
Therefore, you should take this into consideration when designing your application. Now, on the flip side, you can write to only HKEY_CURRENT_USER, as my examples here show. However, that file association setting will only be loaded for the current user, and if your application has been installed for all users, your application won't launch when that other user opens the file in Windows.
That should be a decent primer for what you want to do. For further reading I suggest
Best Practices for File Association
File Types and File Association, especially
How File Associations Work
And see also my similar answer to a similar question:
Associating file extensions with a program
This is a two step process:
1. Define a program that would take care of extension: (unless you want to use existing one)
1.1 create a key in "HKCU\\Software\\Classes\\" for example
"Software\\Classes\\YourProgramName.file.ext"
1.2 create subkey "Software\\Classes\\YourProgramName.file.ext\\DefaultIcon"
1.2.1 set default value ("") to your application full path to get
icon from resources
1.3 create a subkey "Software\\Classes\\YourProgramName.file.ext\\Shell\\OperationName\\Command"
OperationName = for example Open, Print or Other
1.3.1 set default value ("") to your application full path +optional runtime params (filename)
2.Associate file extension with program.
2.1 create a key HKCU\\Software\\Classes\\.ext - here goes your extension
2.2 set default value to the program definition key
("YourProgramName.file.ext")
Below is part of the program written in c# which associate file extension. It is not c++ but i think it is simple enought to explain itself and AFAIK it is verv simmilar if not identical to the code in c++
1.
RegistryKey keyPFCTExt0 = Registry.CurrentUser.OpenSubKey("Software\\Classes\\PFCT.file.enc", true);
if (keyPFCTExt0 == null)
{
keyPFCTExt0 = Registry.CurrentUser.CreateSubKey("Software\\Classes\\PFCT.file.enc");
keyPFCTExt0.CreateSubKey("DefaultIcon");
RegistryKey keyPFCTExt0ext = Registry.CurrentUser.OpenSubKey("Software\\Classes\\PFCT.file.enc\\DefaultIcon", true);
keyPFCTExt0ext.SetValue("", Application.ExecutablePath +",0");
keyPFCTExt0ext.Close();
keyPFCTExt0.CreateSubKey("Shell\\PFCT_Decrypt\\Command");
}
keyPFCTExt0.SetValue("", "PFCT.file.enc");
keyPFCTExt0.Close();
2.
RegistryKey keyPFCTExt1 = Registry.CurrentUser.OpenSubKey("Software\\Classes\\PFCT.file.enc\\Shell\\PFCT_Decrypt\\Command", true);
if (keyPFCTExt1 == null)
keyPFCTExt1 = Registry.CurrentUser.CreateSubKey("Software\\Classes\\PFCT.file.enc\\Shell\\PFCT_Decrypt\\Command");
keyPFCTExt1.SetValue("", Application.ExecutablePath + " !d %1"); //!d %1 are optional params, here !d string and full file path
keyPFCTExt1.Close();
I don't know why people keep saying that HKEY_CURRENT_USER\Software\Classes\<.ext>'s Default value (which will redirect you into another (software-created) class.
It does work, but it will be overridden by
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\<.ext>\UserChoice
And I believe Microsoft recommends the second practice- because it's what the built-in "open with" is doing. The value of Progid" key is equal to default value of HKEY_CURRENT_USER\Software\Classes\<.ext> in this case.
I found the following while trying to manipulate associations using C#:
hkcu\software\microsoft\windows\currentVersion\explorer\fileexts.reg\userchoice -> for user specific settings. The values in the openWithProgIds
key point to the keys in the hkcr.
hkcr\xfile\shell\open\muiVerb value or hkcr\xfile\shell\open\command\default value -> affects open handler. This is the value that contains the path to a program.
hkcr\ .x -> affects context menu (new x) among other things related to the menus.
I don't know the C++ code, but given these info you must be able to manipulate the registry using the registry API.
Related
In my app the default Registry key is created called:
SetRegistryKey(_T("Trains"));
It places that "root" key value like so:
Computer\HKEY_CURRENT_USER\SOFTWARE\Trains
My app saves all the window info and other data in subkeys under Trains. I have a "reset" menu item that when the user activates, I would like to delete that "root" key and all the subkeys under it.
If I'm in registry editor, it is simply a right click and delete....and blamo, all gone.
I've read other threads here and on Google and there seems to be multiple ways to do this.... I've tried DelRegTree with little success and I'm unclear why this process is so cumbersome to do with multiple functions and recursion to find all the keys, delete them, then remove the root key.
https://learn.microsoft.com/en-us/cpp/mfc/reference/cwinapp-class?view=msvc-160#delregtree
I've tried DelRegTree(\\HKEY_CURRENT_USER\\SOFTWARE, _T("Trains")); but that doesn't work. How should I approach this seemingly easy task?
You can use the SHDeleteKey() function. You can use HKEY_CURRENT_USER for the first argument and specify the key path (from that) in the second:
#include <shlwapi.h>
LSTATUS status = SHDeleteKey(HKEY_CURRENT_USER, _T("SOFTWARE\\Trains"));
You will need to include "Shwlapi.lib" in your link.
(Note: Calling the DelRegTree member of CWinApp may also work, if you use the same arguments; but you can't add the \\SOFTWARE part to the first argument – that will have to be included in the key path/name.)
I am writing some code to create a file from a Windows 8 app in an standard way, the code looks like below:
using namespace Windows::Storage;
StorageFolder^ folder = KnownFolders::DocumentsLibrary;
String ^filename = ref new String(L"file.txt");
auto createFile = folder->CreateFileAsync(filename, CreationCollisionOption::ReplaceExisting);
concurrency::create_task(createFile).wait;
Now instead of using DocumentsLibrary, I want to write thid file to an customized file path, like:
C:\Users\<username>\AppData\Local\ExampleApp\ExampleFolder
How should I change the code to be able to do this? Thanks!
WinRT can only access a few folders. You have a few standard libraries like Pictures, Music, etc (Documents requires elevated rights) and you have the application data folders that you can find under \AppData\Local\Packages\yourpackage.
Inside of this package folder you have two main folders to store data: LocalState and RoamingState. As the names convey: the former is to store data locally while the latter will synchronize its contents whenever possible (according to the rules you define).
You can access these folders using the C++ equivalent of Windows.Storage.ApplicationData.Current.LocalFolder and Windows.Storage.ApplicationData.Current.RoamingFolder.
What you can do though is request explicit access through a FilePicker but this will prompt the user a window where he should target the directory himself.
How do we make our application start on computer startup (and of course, after the user had signed in)?
And no, I am not making a virus.
Does registry editing sound like a good idea?
My OS is Windows 8.
However, I will try to make my application available for all possible Window OS.
The correct way to do this is simply to add a shortcut to your application's executable to the user's Startup folder. You do not need to (and should not) modify the registry.
Advanced users know how to do this manually already, but it may also be an option you want to provide as part of your installer and/or a configuration dialog in your application.
To do this from C++ code, you will need to do two things:
Retrieve the location of the current user's Startup folder.
This is accomplished by calling the SHGetKnownFolderPath function and specifying the KNOWNFOLDERID of the folder you're interested in. In this case, that would be FOLDERID_Startup.
Sample code:
std::wstring GetStartupFolderPath()
{
PWSTR pszPath;
HRESULT hr = SHGetKnownFolderPath(&FOLDERID_Startup,
0, // no special options required
NULL, // no access token required
&pszPath);
if (SUCCEEDED(hr))
{
// The function succeeded, so copy the returned path to a
// C++ string, free the memory allocated by the function,
// and return the path string.
std::wstring path(pszPath);
CoTaskMemFree(static_cast<LPVOID>(pszPath));
return path;
}
else
{
// The function failed, so handle the error.
// ...
// You might want to throw an exception, or just return an
// empty string here.
throw std::runtime_error("The SHGetKnownFolderPath function failed");
}
}
Note, however, that while SHGetKnownFolderPath is the recommended function to use, it is supported only by Windows Vista and later. If you need to support older versions of the operating system, you'll need to call it dynamically on newer versions where it is available, and otherwise fall back to calling the SHGetFolderPath function. This one takes a different type of identifier, a CSIDL value. The one you want is CSIDL_STARTUP.
Create a shortcut to your application's executable.
This is accomplished using a different set of Shell API functions. I won't bother writing up sample code here because it's all quite well explained on MSDN already: Shell Links
Now you just connect the dots: when you create the shortcut to your application's executable, specify the user's Startup folder as its destination path.
The only other thing to be aware of is that there are actually multiple Startup folders. Each user has one, which is the one we retrieved above using FOLDERID_Startup. About 99% of the time, that's the one you want. Putting a shortcut to your app there will cause it to be launched automatically when that user logs on.
However, there is also a global Startup folder that is shared by all users. This one is identified by FOLDERID_CommonStartup (or CSIDL_COMMON_STARTUP) and requires administrative privileges to add items to. That makes sense, of course, because whatever you put in there is going to launch automatically when any user logs on to the computer. Only administrators can affect global behavior like this. And chances are, your app doesn't need this anyway.
Start menu
Simplest solution is to place .lnk of .bat file into the Start Menu\On startup folder. This is easiest and not too sneaky against the user.
Registry:
Another solution is creating the key in the registry keys:
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run] //All users
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce] //All users once
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run] //Currend user
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce] //Current user once
This is not that transparent - so a bit more agressive against the user.
On windows you can put a shortcut to your application in the Startup folder, or you can implement it as a service.
And that "I am not making a virus" does make you sound guilty... Maybe it is a keylogger? ;)
There are a lot of ways, but they all depend on your OS. For windows take a look at the "Task Schedualer" under "Administrative tools" in the control panel.
Maybe something like this? Note, this code snippet is not written by me.
#include <windows.h>
void Reg() {
::HKEY Handle_Key = 0;
::DWORD Dispoition = 0;
::RegOpenKeyEx( HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0,
KEY_ALL_ACCESS,
&Handle_Key );
const unsigned char Path[ MAX_PATH ] = "C:\\Windows\\YourProgramName.exe";
::RegSetValueEx( Handle_Key, "My Directory", 0, 1, Path, sizeof( unsigned char ) );
};
What do you guys think?
Here is a simplified example of a code I use in my XPCOM CPP DLL to check if a key exists in the registry.
It checks for the existance of 2 keys: HKLM\SOFTWARE\Microsoft and HKLM\SOFTWARE\Microso both exist with the same permissions, but the first one is found by this code and the second one is not... any idea why?
nsCOMPtr<nsIWindowsRegKey> regKey =
do_CreateInstance("#mozilla.org/windows-registry-key;1");
if (!regKey) {
log("can't create #mozilla.org/windows-registry-key;1");
return -1;
}
NS_NAMED_LITERAL_STRING(key2,
"SOFTWARE\\Microsoft");
if (NS_FAILED(regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
key2, nsIWindowsRegKey::ACCESS_QUERY_VALUE))) {
// FAILED
LOG("regKey:: no such key");
}
NS_NAMED_LITERAL_STRING(key1,
"SOFTWARE\\Microso");
if (NS_FAILED(regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
key1, nsIWindowsRegKey::ACCESS_QUERY_VALUE))) {
// FAILED
LOG("regKey:: no such key");
}
EDIT: To make it clear, I've created a registry key myself, called HKLM\SOFTWARE\Microso and I can access it through regedit.
nsIWindowsRegKey.Open is implemented via RegOpenKeyEx WinAPI function. This function requires existing relative path passed as argument and does not support templates. If there is no exactly same path in the registry, it falls. SOFTWARE\Microsoft is exists in the HKLM root space, HKLM\SOFTWARE\Microso is not.
The issue is most likely that you are viewing the registry with the x64 regedit and then expecting Firefox to have the same view. However, regedit is an x64 application whereas the usual Firefox builds are x86 and run inside the 32-bit subsystem (via WoW64). When an x86 application accesses the HKLM\Software key it gets redirected to HKLM\Software\Wow6432Node. You can run c:\Windows\SysWOW64\regedit.exe instead of c:\Windows\regedit.exe to see the view of the registry that an x86 application gets.
So your XPCOM component is actually trying to access HKLM\Software\Wow6432Node\Microsoft and HKLM\Software\Wow6432Node\Microso - and the former exists while the latter doesn't. If you want to access the "real" registry key instead, you have to pass the WOW64_64 flag in the third parameter of nsIWindowsRegKey.open() (it corresponds to the KEY_WOW64_64KEY flag that can be passed to the RegOpenKeyEx WinAPI function):
NS_NAMED_LITERAL_STRING(key1,
"SOFTWARE\\Microso");
if (NS_FAILED(regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, key1,
nsIWindowsRegKey::ACCESS_QUERY_VALUE | nsIWindowsRegKey::WOW64_64))) {
// FAILED
LOG("regKey:: no such key");
}
Ok, I'm really stumped with this one. I have this Compact Framework application which calls your standard .NET web service. This has been working pretty well for a while, but recently we were asked to make this software work on another Windows CE device.
This new device seems to have been manufactured in Korea, and has some weird/buggy default configuration. Regional settings on the Control Panel show that the locale is set to English(United States) and the User Interface Language and Input Language settings have this value set as well.
There are some weird issues though. On Windows Explorer, the usual '\' characters that are used to separate folders, appear as a crossed-out W. The small input panel has some characters that are also wrong, some look clearly Korean.
The problem with all of this, is that for some reason I get a PlatformNotSupportedException, when calling a Web Service method that returns a DataSet. On the debugger I can see that the the CurrentUICulture is properly set to 'en-US', but somehow this doesn't keep the program from running into this issue.
I think that the Korean language installed on the device is conflicting with the culture configuration of the Web Service, but I don't have a clue about the details, and I don't know how I could fix it. Any help would be greatly appreciated. Thanks.
This is the full stack trace:
System.PlatformNotSupportedException - at System.Globalization.CultureInfo..ctor(String name, Boolean useUserOverride)
at System.Globalization.CultureInfo..ctor(String name)
at System.Data.XSDSchema.HandleDataSet(XmlSchemaElement node, Boolean isNewDataSet)
at System.Data.XSDSchema.LoadSchema(XmlSchemaSet schemaSet, DataSet ds)
at System.Data.DataSet.ReadXSDSchema(XmlReader reader, Boolean denyResolving)
at System.Data.DataSet.ReadXml(XmlReader reader, XmlReadMode mode, Boolean denyResolving)
at System.Data.DataSet.ReadXmlSerializable(XmlReader reader)
at System.Data.DataSet.System.Xml.Serialization.IXmlSerializable.ReadXml(XmlReader reader)
at System.Xml.Serialization.XmlSerializationReader.DeserializeSerializableElement(LogicalType deserializeAs, Accessor accessor, Fixup fixup, Object fixupTarget, String identifier, Boolean emptyValue, Boolean nullValue)
at System.Xml.Serialization.XmlSerializationReader.deserializeElement(Accessor accessor, Fixup fixup, Object fixupTarget)
at System.Xml.Serialization.XmlSerializationReader.DeserializeElementMember(MemberValueCollection members, Object fixupTarget, Hashtable internalState, Boolean& firstElement)
at System.Xml.Serialization.XmlSerializationReader.deserializeMembers(MemberValueCollection members, Object fixupTarget)
at System.Xml.Serialization.XmlSerializationReader.DeserializeComplexElement(LogicalType deserializeAs, Accessor accessor, Fixup fixup, Object fixupTarget, String identifier, Boolean emptyValue, Boolean nullValue)
at System.Xml.Serialization.XmlSerializationReader.deserializeElement(Accessor accessor, Fixup fixup, Object fixupTarget)
at System.Xml.Serialization.XmlSerializationReader.DeserializeElementMember(MemberValueCollection members, Object fixupTarget, Hashtable internalState, Boolean& firstElement)
at System.Xml.Serialization.XmlSerializationReader.deserializeMembers(MemberValueCollection members, Object fixupTarget)
at System.Web.Services.Protocols.SoapMessageParser.execute()
at System.Web.Services.Protocols.SoapMessageSerializer.Deserialize(XmlReader reader, SoapHttpClientProtocol client, LogicalSoapMethod soapMethod, Boolean soap12)
at System.Web.Services.Protocols.SoapHttpClientProtocol.doInvoke(String methodName, Object[] parameters, WebClientAsyncResult asyncResult)
The culture you're trying to use is not a neutral culture and the underlying Win32 locale is not supported by the device's OS. In any case, for a web service, wouldn't you want to use the invariant culture?
EDIT: The schema for the DataSet needs to specify a culture by way of Microsoft schema extension. Try adding the namespace declaration
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
to your schema, and the attribute
msdata:Locale=""
to the xs:element that represents the root of the DataSet, to force the use of the invariant culture; a non-empty string value will be passed to the CultureInfo(string) constructor. Otherwise, if the attribute
msdata:UseCurrentLocale="true"
is present, whatever the device's current locale is will be used. If neither attribute is present, it's hardcoded to LCID 0x0409, which is US English.
You either need to change the target platform to X64 or X32 from your CSproj properties build settings or just simply turn off the said System.PlatformNotSupportedException from exception settings under debug windows option.