Virtual Developer Workshop: Containerized Development with Docker
In part 1 of this series, I covered what types of .DLLs you can make with MFC and how to build MFC regular and extension .DLLs. In part 2, I covered potential problems that can arise when you use .DLLs and suggested several ways to avoid them. In this article, I'll build upon the first two articles by providing more coding examples and technical details.
Exporting Resources From a .DLL
Your first step in building a .DLL which exports resources is (of course) to build a .DLL and give it some resources. You can use a regular or extension .DLL. Use the App Wizard to create your .DLL, as described in Article 1. To add a resource using the Visual C++ menu, select "Insert" and "Resource" and then select the type of resource you wish to add.
Once you've built your .DLL and given it all the resources it is to import, you must build a header file to be used by the client application. You can add a new header file using the Visual C++ menu by selecting "File | Add To Project | New | C/C++ Header File." Now open your .DLL's Resource.h file. Assuming you have compiled your .DLL, the top of this file will look something like this:
#define IDS_SOME_STRING1 1 #define IDS_SOME_STRING2 2 #define IDS_SOME_STRING3 3 #define IDD_SOME_DIALOG 11000 #define IDB_SOME_BITMAP 11001
As you can see, each of these #defines gives one of your resources an identifying integer value. Copy the complete #define for each resource you wish to export into your newly created header file.
At this point, your .DLL is almost ready to go. Only one more step is required. In articles 1 and 2, I demonstrated how to use a .DLL with implicit linking. When you are using implicit linking, the client application will attempt to connect to your .DLL automatically. If it cannot do so, the client application won't run. When you use implicit linking, it is important to remember that the client application will not attempt to connect to your .DLL unless your .DLL exports at least one class or function which is used by the client. So even if your .DLL's only purpose is to provide resources, you still must export at least one function or class which is used by the client. It doesn't matter whether your exported class or function does anything useful. It simply has to be there in order to force the client to connect to the .DLL. So all that's left to make your .DLL complete is to export a "do-nothing" function. See Article 1 for an explanation of how to export a function.
Setting Up the Client Application
Article 1 describes how to build a client application which will connect to your .DLL. Remember, your client application must actually call at least one function from your .DLL or create at least one instance of an imported class. If not, your client application will not automatically connect to your .DLL at runtime. Be sure to add a copy of the header file you created earlier to your client application project.
In some respects, an extension .DLL is easier to use when you are importing resources. This is because an extension .DLL will try to find a resource in your .DLL if it cannot find it in its own resources. Suppose your client application included the following code:
CString str; str.LoadString(IDS_SOME_STRING1); AfxMessageBox(str);
The first thing your client application will do is look for a definition of IDS_SOME_STRING1. Suppose the header file you imported defines IDS_SOME_STRING1 as 1. The client application will look in its own resources for resource #1. If it cannot find resource #1 in its own resources, it will next look in the extension .DLL. If you built a regular .DLL, the client application won't take this extra step.
Note that there is a big potential for problems here. If your client application has some other resource which is also defined as resource 1, it will attempt to load its own resource. The client only looks to your extension .DLL if it can't find the properly numbered resource within its own resources. One way to avoid this problem is to renumber your resources to avoid conflicts.
When you use a regular .DLL, the client application must be told explicitly to look to the .DLL to find the resource. Here's how you do it:
//Store the current resource handle HINSTANCE hClientResources = AfxGetResourceHandle(); //Tell the client to use the .DLL's resources AfxSetResourceHandle(::GetModuleHandle("SomeDll.dll")); //Do something with the .DLL's resources CString str; strRes.LoadString(IDS_SOME_STRING1); //Restore the client application resource handle AfxSetResourceHandle(hClientResources);
You can use this technique in your client application even if you are using an extension .DLL. With a regular .DLL it is necessary. While not absolutely necessary with an extension .DLL, it gives you the certainty that the right resources will be loaded.
Exporting a CDialog Based Class
Exporting a class derived from the CDialog class is only slightly more complicated than exporting other classes. The extra complication comes from the fact that you must also export the dialog template, which is a resource.
When you export a class, you must provide a copy of the class header file to be used by the client application. When you export a class derived from CDialog, which has a resource, you must also export the #define for the resource ID. So the easiest thing to do is simply tack the #define onto the top of the header file like this:
#ifndef IDD_HELLO_DIALOG #define IDD_SOME_DIALOG 9000 #endif
As with other exported resources, you can run into problems if the client application also has a resource with the same number. Either be careful to avoid conflicts, or use the code shown above to tell the client application explicitly where to find the correct resource.
Note that the same technique used with CDialog bases classes can also be used with any other class you wish to export which uses resources. Be sure to define the resources in the header file you provide for the client application, and take whatever steps are necessary to avoid conflicts.