Reputation: 24235
I have a few resources that are target specific. Like a different dialog for target client and a different dialog for target admin. And also a few strings that are specific to each of the targets. I don't want resources and code that are specific to admin app, to be present in the client build.
Suppose I could have 3 resource files, admin, client and common and somehow tell the build engine to use admin and common res for admin build, and use client and common res for client build.
How can I achieve this?
How can I have more than 1 resource file and use a resource file for a specific target build.?
Upvotes: 4
Views: 5381
Reputation: 1614
If you only have a few resources that are configuration-specific, you might find it easier and more maintainable to share the .rc between configurations, but have them use different resource subsets. It's also supported by VS (2017, at least): Adding Condition for individual resources.
In Solution Explorer, double-click your .rc to open Resource View. From the resource tree, right-click the dialog, icon, etc. that you'd like to be configuration-dependent, then open Properties.
In the properties window, you should see Condition.
Now, you should already have preprocessor symbols defined exclusively for your client, server, admin builds. (If you don't, do that now! Property Pages -> C/C++ -> Preprocessor -> Preprocessor Definitions.)
Simply add this symbol to the Condition. This has the effect of adding an #ifdef
... #endif
around the code block in your .rc file, but in a VS-friendly way--it doesn't break the editor.
Note: I've noticed, however, that this process doesn't seem to work for String Tables. Can't tell whether it's a UI bug or simply unsupported.
Upvotes: 0
Reputation: 19642
OK, here is my stab at fully answering your question, if only that it would be fun to get a bounty (they don't happen that often in MFC-tagged questions :) ). Please indicate which areas of your question you think are not addressed, if any. This is all with VS 2008, changes to other versions should be minimal.
Start by adding resource files for each of your different build targets, e.g. "admin.rc", "common.rc", etc. In the solution browser, right-click on your project, "Add->New item->Resource File".
Right-click on the newly added resource files in the solution browser, select "properties" and under "General" set "Exclude from build" to "Yes".
In the Resource View, you can now add your required resources to the respective resource files.
Next, in Resource View, right-click on the "main" resource file () and select "Resource Includes".
In the lower box, at the end of whatever is there, add
#ifdef ADMIN #include "admin.rc" #endif
Of course the symbol to use in the preprocessor you can choose yourself, and the filename needs to match whatever you chose when you created the resource file earlier.
Then, in your "Admin" project configuration (I'm assuming you are using different configurations for each target?) and in the Solution Explorer, right-click on your project and navigate to "Resources->General". Under "Preprocessor Definitions", add "ADMIN" (or whatever you chose in the previous step).
Build your solution. You can verify what resources were included in your binary using something like http://www.wilsonc.demon.co.uk/d10resourceeditor.htm or http://melander.dk/reseditor/ .
Note that obviously you will have to account for the resources not being available in the various builds; so you will have to make sure that e.g. no dialogs are shown that will use a dialog IDD in their constructor. You can do this through the preprocessor as well; just add the same flag to the C++ preprocessor in the respective configuration.
Another issue is that you will have a resource.h for each .rc file, and they will each use their own numbering. You can make this work through careful naming and numbering, but I would assign a different range to each in an attempt to catch as many issues as possible at compile time. To do this, open your "resource1.h" or whatever you've called it and change the _APS_NEXT__VALUE definitions to higher numbers.
Alternatively, you could stick all resource defines into one resource.h and edit all .rc files to include that one resource.h. Just right-click on the rc file in the Solution Explorer, then select "open with" and click "Source code editor". You'll have to get familiar with the format of rc files anyway (at least for the basics), if you're going down the "complicated resource build" route. It's not difficult, just make sure you get a mental picture of how the linker, the resource compiler, the .rc files, your .cpp files and the resource.h file relate to each other. Furthermore, the MFC-specific preprocessor values look intimidating at first but they are self-explanatory mostly, and you can ignore them most of the time anyway.
The Petzold has a succinct but sufficient explanation of resource files and their format, you may want to dig that out of the closet and keep it at hand while you're getting the hang of things.
Upvotes: 9
Reputation: 19642
You can use #ifdef in your .rc file. However this will screw up the resource editor. The 'standard' way of dealing with this is having separate .rc files for each target, then #including each of them in one script in which you do the #ifdef, e.g.
#ifdef CLIENT_1
#include "client_1.rc"
#endif
#ifdef CLIENT_2
#include "client_2.rc"
#endif
This way the resource editor only needs to parse "complete" rc files. Still, working like this is a pain, because the editor gets confused easily. Once upon a time I also experimented with having a separate project for each "target", and then #including them with relative paths in my "resource includes" section. I don't remember why I didn't use that approach in the end. Using the preprocessor you can make this work, but it always feels a bit clumsy. Maybe recent versions of VS deal with it better.
Upvotes: 5
Reputation: 18431
With the given description, I do not see need of different .RC files. You can have a string table having strings for both (or multiple targets). At start up, you will have UINT variables that will point to either of them. For example:
UINT nUserConfirmationId;
UINT nAcessDeiniedMsgId;
if(target1)
{
nUserConfirmationId = IDD_ENG_STRING_ID_FOR_CONFIRM;
..
}
else
{
nUserConfirmationId = IDD_FRE_STRING_ID_FOR_CONFIRM;
...
}
And then use nUserConfirmationId
etc. variables. Similar way, you can have dialog resources (I don't see why you need different dialogs, only strings should be replaced).
Yes, it has maintenance issues, as and when you add resources.
Or, you can have resource-only DLLs, which will contain target specific resource.
Upvotes: 4