OK, I got it working and I'll share what I learned.
1) Decide a ProgID. It should be vender.app.versionnumber according to docs, but regedit shows that practically no vendor follows that rule. I did though.
2) Most of the MSFT docs on this topic talk about creating entries under HKEY_CLASSES_ROOT, but I found important info on http://msdn.microsoft.com/en-us/library/cc144148(v=vs.85).aspx:
Important considerations about file types include: The HKEY_CLASSES_ROOT subtree is a view formed by merging HKEY_CURRENT_USER\Software\Classes and HKEY_LOCAL_MACHINE\Software\Classes In general, HKEY_CLASSES_ROOT is intended to be read from but not written to. For more information, see the HKEY_CLASSES_ROOT article.
3) to have the association show up without rebooting, you must call SHChangeNotify(). (This threw me, because even when I had the right code, the results didn't show up correctly in Explorer.)
Here's the code I ended up with. If I go through with REGEDIT and delete all mention of .moselle (my extention) and MoselleIDE (my application) then run my program once by hand, I get the click-to-open behavior, file icon becomes the same as the app, etc. Note the code uses a logging function, and it also verbosely reports which type of success it has: 1) variable already correct, 2) variable changed, 3) variable didn't exist.
void RegSet( HKEY hkeyHive, const char* pszVar, const char* pszVa
lue ) {
HKEY hkey;
char szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof( szValueCurrent );
int iRC = RegGetValue( hkeyHive, pszVar, NULL, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize );
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if ( iRC != ERROR_SUCCESS && !bDidntExist )
AKS( AKSFatal, "RegGetValue( %s ): %s", pszVar, strerror( iRC ) );
if ( !bDidntExist ) {
if ( dwType != REG_SZ )
AKS( AKSFatal, "RegGetValue( %s ) found type unhandled %d", pszVar, dwType );
if ( strcmp( szValueCurrent, pszValue ) == 0 ) {
AKS( AKSTrace, "RegSet( \"%s\" \"%s\" ): already correct", pszVar, pszValue );
return;
}
}
DWORD dwDisposition;
iRC = RegCreateKeyEx( hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, NULL, &hkey, &dwDisposition );
if ( iRC != ERROR_SUCCESS )
AKS( AKSFatal, "RegCreateKeyEx( %s ): %s", pszVar, strerror( iRC ) );
iRC = RegSetValueEx( hkey, "", 0, REG_SZ, (BYTE*) pszValue, strlen( pszValue ) + 1 );
if ( iRC != ERROR_SUCCESS )
AKS( AKSFatal, "RegSetValueEx( %s ): %s", pszVar, strerror( iRC ) );
if ( bDidntExist )
AKS( AKSTrace, "RegSet( %s ): set to \"%s\"", pszVar, pszValue );
else
AKS( AKSTrace, "RegSet( %s ): changed \"%s\" to \"%s\"", pszVar, szValueCurrent, pszValue );
RegCloseKey(hkey);
}
int SetUpRegistry() {
//app doesn't have permission for this when run as normal user, but may for Admin? Anyway, not needed.
//RegSet( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\MoselleIDE.exe", "C:\\Moselle\\bin\\MoselleIDE.exe" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle", "Moselle.MoselleIDE.1" );
// Not needed.
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\Content Type", "text/plain" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\PerceivedType", "text" );
//Not needed, but may be be a way to have wordpad show up on the default list.
//RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\OpenWithProgIds\\Moselle.MoselleIDE.1", "" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\Moselle.MoselleIDE.1", "Moselle IDE" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\Moselle.MoselleIDE.1\\Shell\\Open\\Command", "C:\\Moselle\\bin\\MoselleIDE.exe %1" );
SHChangeNotify( SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL );
return 0;
}
Finally, yes, I know I should figure out an installer, but I'm a C++ expert, not a Windows configuration and terminology expert and its a lot easier for me to write the above 50 lines than it is to even start figuring out how to configure an installer. This is for an alpha release and I'll watch this topic for better ideas for future releases.