MFC C++ Language Resource DLL - only override some dialogs - c++

I have successfully made a resource only DLL, and placed some dialogs, that were in the main exe. So I have Test.exe, and TestENU.dll. It works fine, but I am trying to find a way to only override some dialogs, instead of being required to have all exactly the same dialogs, and instead pull some from the base exe, as my real project has around 40 dialogs, and I only want to have to translate some, slowly over time.
(I used this link to do satellite/resource dll)
http://msdn.microsoft.com/en-us/library/8fkteez0(v=vs.90).aspx

Default resources (including dialogs) are governed by the current setting of the resource handle. You can set the resource handle at any time using AfxSetResourceHandle to point to a resource only dll, or, your executable. You'll need to be prudent in making sure that the handle is pointing to the correct resource location before instantiating a dialog, so, you'll need to save the current handle before changing it to a new resource handle, and, changing it back when you are done.

Pass a template ID to the constructor of your dialogs.
Constructor:
CTestDlg::CTestDlg(int Template, CWnd* pParent = NULL) : CDialog(Template)
{
...
Call:
int idd;
if (bTestDialogTranslationExists)
idd = IDD_TESTDIALOG_FROM_DLL;
else
idd = IDD_TESTDIALOG;
CTestDialog dlg(idd);
dlg.DoModal();
This code is for demonstration only. You might want to derive a new class from CDialog that handles localized dialog templates automatically, depending on, for example, if the Template exists in a replacement map.
// have this as a global variable or in your CWinApp class
CMap <int, int, int, int> translatedDialogsMap;
// put this maybe in you CWinApp::InitInstance before any dialog could possibly be displayed
translatedDialogsMap[IDD_TESTDIALOG] = IDD_TESTDIALOG_FROM_DLL;
translatedDialogsMap[IDD_TESTDIALOG2] = IDD_TESTDIALOG2_FROM_DLL;
translatedDialogsMap[IDD_TESTDIALOG3] = IDD_TESTDIALOG3_FROM_DLL;
...
// check in your dialog subclass constructor code, if a mapped dll dialog exists
int idd_replaced;
if (translatedDialogsMap.Lookup(Template, &idd_replaced))
Template = idd_replaced;

In 2022 I am in the process of doing the same thing - trying to localize only some resources. Like the author, I am having a very hard time, and the solution has not been found yet. Here's what I can say after reading countless articles on the subject, some making careless statements that cost hours of work just to see that they don't work.
Given : you have an EXE with "default" resources and an extension DLL that replaces only some of then (I specifically tested it on strings so far).
The resource handle (AfxGetResourceHandle()) points to the EXE
If the DLL is properly loaded, it's placed in the search list for resources
The search process is as follows:
a. The resource handle module (EXE) is searched
b. If not found, the extension DLL resources are searched
Given this order, if the EXE has the needed resource, it wins. In our task it always does, so the DLL is as good as not loaded - useless.
Some people suggest setting the DLL module as the resource handle (AfxSetResourceHandle), claming it will reverse the order. DO NOT do it. It will not reverse the order, and here's why.
Doing so will replace the EXE's handle by the DLLs, that's all. It's a one liner in the MFC source. Now the search process will go as follows:
The resource handle (AfxGetResourceHandle()) now points to the DLL
The DLL is searched
If not found, the extention DLL resources are searched again!
This is because extension DLLs are added to a search linked list when loaded, but the EXE (I should say the resource handle that defaults to the EXE) is not! It is handled separately upfront. Replacing the resource handle does nothing to the linked list.
Setting the resource handle to the DLL makes EXE-based resources unavailable, so you end up with only localized resources, and now your program will most likely not start up.
I don't see a way (yet) to add the EXE to this list. The type required is (roughly) "extension DLL", you can't just do a new CDynLinkLibrary call.
What I am thinking is separating out even the default resources into a DLL. That way both DLLs become equal and get added to the search list. The precedence will, I suppose, be governed by the LoadLirary call order, so if you first load the localized DLL and then the "main", you should be OK.
P.S. Creating resources in two languages inside the EXE, while doable, has proven to be a nightmare - it has never worked the way you'd want: first the selected UI language (see SetThreadUILanguage) then, if not found, the main in any language available. Comments on StackOverflow say "make DLLs, it's the only way" without going into details why.

Related

Is it possible to use CPropertyPage created in DLL in a main application?

I have a dialog-based MFC application that hosts CPropertySheet.
The idea is to scan some folder for DLLs, load each of them dynamically (using LoadLibrary()), acquire a pointer to a function that would work like a CPropertyPage factory, use that function to create per-DLL CPropertyPage instances and insert them to CPropertySheet.
In other words, scan a folder for plugins, acquire the per-plugin property pages and insert them to CPropertySheet of the main application so each plugin would have it's own options GUI.
Plugins are implemented in a form of regular MFC dlls (not extension dlls). I'm aware that AFX_MANAGE_STATE(AfxGetStaticModuleState()) is to be employed.
Each CPropertyPage being created must be derived from an abstract interface (say, IPluginOptionsPropPage) so it would be possible to cast the pointer that factory function returns to IPluginOptionsPropPage.
This task is supposed to be a routine one, however, I could not found any sound examples.
Yes it can be done. You can try these steps
Load the property page library using ::LoadLibrary. The return handle is the handle for the resource.
m_hResInstance = ::LoadLibrary(strFile);
Before creating the instance of the property page, set the instance of the resource handle to the library's handle.
m_hCurrent = AfxGetResourceHandle();
AfxSetResourceHandle(hResInstance);
//Create your property sheet here.
Then reset the resource handle to the current process resource.
//Reset the previous
AfxSetResourceHandle(m_hCurrent);
This will enable you to load the resource of the property page, others like header and library or function pointer can be used as intended.

Win32 multiple dialogs command handler [duplicate]

In ResourceHacker, when you open an executable (windows), you can see identifiers associated with dialogs. Does anyone have an idea from where they come? I mean, how can I do the same in my C++ program to get the ID from a HWND?
BTW, GetWindowLong(hwnd, GWL_ID) returns 0.
Thanks
The GetWindowLong(hwnd, GWL_ID) returns the identifier of a control in a dialog, but it cannot be used for the dialog itself, because dialogs simply don't have identifiers.
The identifiers associated with dialogs are actually used to refer to the resource blob itself, not to the window. They are used to create the dialog (see CreateDialog().
Once the dialog is created there is no connection to the original template or to that identifier. Actually there is no use for that ID, the dialog is simply identified by its HWND. Note that you can create several different dialog using the same dialog resource.
These identifiers are assigned (usually) sequentially by the resource editor, or manually if you create the resources by hand.
For more insight on the topic you can read about the CreateDialogIndirect() function, that creates a dialog without using a resouce.
Here you find a good answer:
http://blogs.msdn.com/b/oldnewthing/archive/2005/07/08/436815.aspx
It's like asking, "Given a plate of food, how do I recover the
original cookbook and page number for the recipe?" By doing a chemical
analysis of the food, you might be able to recover "a" recipe, but
there is nothing in the food itself that says, "I came from The Joy of
Cooking, page 253."
So the answer is that there is no way provided by Microsoft to obtain the dialog ID. They could have easily stored it anywhere to make it available, but they did not.
But there would still be a way to do it, although it is not bulletproof. You could:
1.) Get the creator file of the dialog via GetWindowModuleFileName()
2.) Load this Exe or Dll via LoadLibraryEx(..., LOAD_LIBRARY_AS_IMAGE_RESOURCE)
3.) Enumerate all RT_DIALOG resources in the Exe or Dll via EnumResourceNames() where the dialog ID is in the name: ResourceName = MAKEINTRESOURCE(IDD_DIALOG_ID)
4.) Create each enumerated dialog invisibly via LoadResource(), LockResource(), CreateDialogIndirect() but without showing the dialog with ShowWindow().
5.) Enumerate the child controls in each dialog via EnumChildWindows() and compare them to your dialog.
6.) Release all handles and destroy the dialogs.
It is not very probable that there are two identical dialogs in a Exe/Dll file. But the problem is that in WM_INITDIALOG the programmer may eliminate (destroy) or add or modify child controls. So your search algorithm would have to be fault tolerant. This would be possible by counting the congruency between each dialog from the resources and your dialog. You could count for how many child controls the ID (GetDlgCtrlID())and class name (GetClassName()) match. (e.g. Class="BUTTON" and ID = 311") While a programmer can easily change the text of a control or move it around, changing the ID is not very probable and does not make much sense and changing the class of a child control is even impossible.
As I said: It is not bullet proof, but you will find the ID of the resource that has most probably been used to create the dialog.
Be aware that not all dialogs come from a Microsoft resource.
They can be created by a GUI framework that uses its own type of templates. In this case you will never find the Dialog ID because it simply does not exist.

How to save layout settings of MFC application?

I understand there are functions that can easily write windows registry, however I found out that in new MFC project created with wizard, some information (like split bar position, visibility of controls) gets stored automatically (or at least I found no CWinApp::Write* calls in the project). Since I have also older projects that don't have this behaviour I need to figure out how to make this without help of project wizard. Would anyone please know how does this work?
The MFC control state saving magic happens in the 'New' MFC Feature Pack, specifically in the SaveState methods, for example CMFCToolBar::SaveState.
To take advantage of this you'll therefore need to upgrade your Toolbars and Menus to use the newer controls and upgrade your application to inherit from CWinAppEx. I recommend that you use a New MFC Wizard based app as a guide on how to upgrade your old MFC app.
Most of the information is saved in CPane::SaveState(), thus if you want state of some component saved, you need to use classes derived from CPane. (for more info here is the class hierarchy).
The process of saving window states is initiated through CFrameImpl::OnClosingMainFrame(). This function in turn calls CWinAppEx::SaveState() which saves some application settings and then ALL instances of CMFCToolBar (they add themselves to global list of CMFCToolBars in call to OnCreate). In a similar way all dockable panes are saved but the list belongs to your main frame. Then positioin and size of your main frame is saved.
CViews and CFrameWnds are somewhat less favored, for what I found and tried out, the only information saved was visibility.
I used that loooong time ago. If I correctly reminds it, you should save the informations you want in a overridden CWinApp::ExitInstance() before calling base class method, and you load them in CWinApp::InitInstance. Be sure to allow for default values, because at first run, there will be nothing to load, and do not forget to call (or copy) base class.

Switch between resources DLL

I am working on MFC solution that consists of many projects. One of my projects "App.exe" depends on other project output dll "SC.dll".
I added reference to the other project via Add Reference option under the App project.
I want to "based on an option" switch between languages by using resources dll.
I've another resources project that produces Arabic resources "SC_AR.dll".
I tried to use LoadLibrary based on the option ,but it failed.
Is the "Add Reference" approach is wrong?
If not, how I could switch between the Dlls?
If yes, what is the right approach to follow?
There is no need to reference a resource satellite. MFC searches satellite DLLs using the user's Windows UI language and your application's file name. To switch to another satellite, call LoadLibrary() and then call AfxSetResourceHandle().
Of course, this is assuming you are loading all resources via MFC (e.g. use CString::LoadString instead of LoadResource ). You have to use your own satellite DLL handle explicitly if you call API directly.
PS the less said about SetThreadLocale the better.
If you're using MFC then the resource dll is loaded in the boilerplate that's generated in the main file. (can't rememebr it offhand) but search for LoadResource(). You'll have to call LoadLibrary to get the hmodule to pass into this.
Be aware that if you're displaying resources from several dlls, you will have to be careful about dialogs in a different resource dll. If youre simply swapping dlls, you won't have a problem.
As MFC doesn't support Arabic, I made an English dialog and Arabic one inside the dll. And according to the Language property I send the resource ID to the Constructor of Dialog.
And for any String I use LoadString

ExitInstance not called in MFC app

Until now, I never really needed the Winapp ExitInstance() of a large MFC (Single Document Interface if it matters) app I'm working on. But now I do, mainly to cleanup memory alocations, unload some DLLs, etc. Well I soon learned by the obvious memory leaks and such that ExitInstance was not being called. Have I missed something obvious? Do I need to manually add something to the message map to make sure my ExitInstance override is called?
I guess i can do my cleanup elsewhere, but it's the best place if I can get it to run. Interestingly, I found quite a few instances of this by typing strings like "ExitInstance never called" and such into Google, and in no case were any real answers offered. The app normally closes when someone clicks the close box or the "Exit" from the File menu, and the OnClose() of the mainframe window certainly does always get called. I even tried forcing things by putting AfxGetMainWnd()->DestroyWindow(); in that mainframe OnClose() event, but still I can't get the ExitInstance() to actually run. Maybe it's just a big dummy function? Or maybe I'M just a big dummy? :-)
I had a similar problem to you...mine was caused by mixing Unicode and MBCS built code....maybe that was your underlying cause?
I had to convert an MBCS application to Unicode, but it was impossible to convert the whole project, so I had to mix Unicode compiled (the application) and MBCS compiled code (the DLLs).
Some of the MBCS DLLs were MFC extension DLLs, others were regular DLLs.
One of the MFC extension DLLs contained resources (bitmap image list, and common dialogs).
I didn't convert the DLL to UNICODE because it had a lot of dependent DLLs which would also have had to be converted, and in addition I didn't need the controls in the common dialogs to support Unicode text.
So I kept the DLL as MBCS, and used AfxSetResourceHandle prior to using any class in the MBCS DLL that used resources.....this was to make the resources get pulled from the DLL directly, instead of through the MFC resource chain, because MFC couldn't find the non-unicode resources otherwise.
I guess MFC doesn't like it when you have a mix of Unicode and non-unicode compiled code containing resources.....lookups in the resource chain fail (I guess has something to do with the conversion of resource IDs to an ID string i.e. via MAKEINTRESOURCE).
I made the main application UNICODE, and made sure the C++ headers of classes in the MBCS DLLs used CStringA in the function prototypes, or accepted wide strings and did the conversion internally.
What I found was my application wouldn't exit properly...it would stay in the MFC CWinThread::PumpMessage/AfxInternalPumpMessage() call, and ExitInstance would never be called.
To solve it, in my CMainFrame::OnDestroy() I put the following as the last 2 statements:
void CMainFrame::OnDestroy()
{
....
CFrameWnd::OnDestroy();
AfxPostQuitMessage(0);
}
I had the same problem. Turns out it was caused by some messages being pretranslated by the CWinApp object after the mainframe was destroyed. It would hit some validty check during that processing and just bail out instead of exiting through the normal exitinstance.
Overriding the apps PreTranslate message and returning immediately when there was no main frame resolved the issue.
BOOL CYourAppClass::PreTranslateMessage( MSG* pMsg )
{
// If the main window has gone away there is no need to do pre-translation.
// If the pre-translation were allowed to proceed the
// CWinAppEx::PreTranslateMessage may bail out without calling
/// the app's ExitInstance nor destructing the app object.
if ( !m_pMainWnd )
return FALSE;
return CWinAppEx::PreTranslateMessage( pMsg );
}
You say that "the OnClose() of the mainframe window certainly does always get called". I had a problem with one of my MFC apps; I had put some thread-cleanup code into my window's OnClose() event handler, and it wasn't getting called. After some pain, I put the thread-cleanup code into an OnDestroy() event-handler, and now it always gets called. So you may want to check whether or not your OnClose() event-handler always gets called.
I had exactly the same problem.
In my case, the issue was solved by overriding PostNcDestroy in CMainFrame. It seems that treating this last message in the main frame window makes it all work:
virtual void PostNcDestroy();
void CMainFrame::PostNcDestroy()
{
}
Another possibility is that there is an assertion failure happening in the middle of the shut down process. For example while destroying a window.
I found that when this happens, ExitInstance also gets skipped.
So, just before closing your application, clear the output/debug log window and see if such a message is printed.
If that's the case, solve the problem and see if ExitInstance gets called again.