Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Linker Errors, CString, ATL, MFC, and YOU!

0.00/5 (No votes)
15 Nov 2020 1  
Linker errors due to CStringT template classes and ATL vs. MFC issues
The goal in this article is to make a well behaved library that will link up without any special options specified in the consuming applications, other than importing the library, and including the header file.

Sample Image - maximum width is 600 pixels

Introduction

The Scenario: Make an ATL based class library that can be used from both an ATL based console application and an MFC/ATL based GUI application. The class library cannot contain any MFC links. The GUI application must use ATL. The console application may not use any MFC, or link to any library that does.

Ok, that sounds like a pretty easy order, but if you are not used to playing with some linker settings, you'll get tripped up fast. You could always go the easy route by adding /FORCE:MULTIPLE in the linker command line options, but then every time you compile, you will get treated to tons of linker warnings. This is not the way to the shining path! Our goal is to make a well behaved library that will link up without any special options specified in the consuming applications, other than importing the library, and including the header file.

Terms

I'm going to call any application that links to a library a consumer. Consumers will #include the library header, and then link to the library file in their linker stage. In the sample, ConsoleApp, and Application are both consumers.

Know Thy Enemy (Your Linker Error Nemises)

Image 2

Fig 0: Setting the configuration for building.

If you compile the sample project with the configuration setting set to "Errors Galore" (Fig 0), you will be treated to loads of nasty looking error messages. LNK2005, LNK2019, LNK4098, LNK1169, LNK1120.

LNK2005 <symbol> already defined in <library> (<object generating the error>)
LNK2019 unresolved external symbol "<symbol signature>" referenced in function <function signature>
LNK4098 defaultlib '<library name>' conflicts with use of other libs; use /NODEFAULTLIB:library
LNK1169 one or more multiply defined symbols found
LNK1120 <X> unresolved externals

Table of possible linker messages from "Errors Galore" configuration.

Taking Out the Trash

Let's start with everyone's least favorite, the often seen LNK4098. This error hangs out with LNK2005. You can fix both by setting your linker options for the library to match the most restricted application that will use your library. In this case, the MFC GUI application demands that it be linked with the Multi-Threaded DLL. If you are compiling in a debug setting, you must link with Multi-Threaded Debug DLL (See Figure 1). Once you set this option for your library and recompile it, you will see LNK4098 and LNK2005 beat a hasty retreat. Because you are also going to use this library with an ATL console application, you must set Runtime Library to Multi-Threaded DLL for console application as well. NOTE: If you have other libraries that you are using that expose function signatures similar to the ones in your library, then you may still get LNK2005 errors. Find the offending library, and remove it from your project. If it's a default library, and you are sure you won't need it, you can add it to the Ignore Default Library list (See Figure 2).

Image 3

Fig 1: Setting the Runtime Library to match the Runtime Library used by the consuming application. If you were setting this option in Release configuration, then you would select Multi-threaded DLL (/MD).

Image 4

Fig 2: Sometimes when there's no other option, you can just ignore the library that the compiler is complaining about. This is not the way to walk the shining path.

Now, you can try to recompile the sample in the " Less Errors " configuration, and see that we have successfully banished LNK4098 and LNK2005. You will immediately notice that we still haven't gotten rid of an LNK2019 and LNK1120 error. What is really curious about this is that the ConsoleApp linked just fine to the library, and the call from ConsoleApp to the library is the same as the call from Application to the library. What's happening here is under the hood in atlstr.h, the header that contains the ATL definition for CString. The definition for CString in VC++ 7 is radically different from the VC++ 6 one in that it is a template class, where the VC++ 6 implementation is just a class. The problem is that MFC's CString default template is different than the one that ATL is using. What we need to do is to get both the library and the consumer application to use the same signature for the linker. Then, the linker can find the signature in the library at link time, and you won't get those nasty LNK2019 and LNK1120 errors.

MFC defines CString as:

typedef ATL::CStringT< TCHAR, StrTraitMFC< TCHAR > > CString;

While ATL defines CString as (With _ATL_CSTRING_NO_CRT defined.):

typedef CStringT< TCHAR, StrTraitATL< TCHAR > > CString;

This difference in the StrTrait is where all the problems with LNK2019 and LNK1120 come from.

The solution I'm going to use came from this MSDN article, which shows a similar tactic, but unfortunately the method on MSDN makes the library link to MFC, and we don't want that for our ATL library. What I am going to do instead is to change the function in the library to use CAtlString instead of CString in the method calls. If you look at the definition for CAtlString, you will notice that it is just a #define for CStringT, with a specific template. Because CAtlString explicitly states the signature for the parameter, this enables the linker to find the function in the library because it is looking for a function with parameters of type CStringT< TCHAR, StrTraitATL< TCHAR > >.

Also, I am including atlstr.h in the library's header file. This way, when the header file is included in a consumer application, CAtlString will be defined for the header file, and you won't get undefined symbols linker errors for CAtlString. This also ensures that the consumer application doesn't need any funky configuration changes; just include and go! To see the application link up correctly, change the configuration to "Debug" or "Release" and compile it.

On With the Code

Because there are three individual projects in the attached sample and they are mostly in default state, I am not going to cover them in any great detail. Let's hit the highlights, and see the source code for other miscellaneous comments.

#pragma once

// Include this here, so that any consumer code will automatically import 
// atlstr so that StrTraitATL will be defined for this header.
#include <atlstr.h>

class CHelloLib
{
public:
    CHelloLib(void);
    ~CHelloLib(void);

#ifndef _ERRORS_GALORE
    // By specifying CAtlString as the parameter token, the linker will 
        // know to use the right ATL version of CStringT, and thus find this 
        // function's signature. 
    void HelloWorld(CAtlString message);
#else
    // Cause LNK2019 by letting the linker try to guess which CStringT 
        // template to use to resolve this function signature. The linker 
        // will try to select the MFC version by default, and not find this 
        // function in the library. This causes the LNK2019.
    void HelloWorld(CString message);
#endif
};

Above, we see the source for the library include file. When included in a consumer application, this file will include atlstr.h, so that CAtlString is defined.

// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once#define WIN32_LEAN_AND_MEAN //Exclude rarely-used stuff from 
                                        // Windows headers
// TODO: reference additional headers your program requires here

#include <atlbase.h>
#include <atlstr.h>
// Define CAtlString as CString so that the IDE will show the intellesense 
// for CString instead of showing nothing for CAtlString. CAtlString will 
// still be used in library generation so that the right linking can be done.
#define CAtlString CString

Here is the stdafx.h for the Library. You should note at the bottom that I am defining CAtlString to a CString. This may seem counter productive, as in the HelloLib.h header, I just changed all the CString's to CAtlString's! However, without this #define, the intellisense doesn't seem to want to display the quick pick function list that I love so much. Thus, by casting this in stdafx.h for the library (and only the library), intellisense is enabled. When the library compiles, the compiles automatically selects the proper CStringT definition for ATL, as this is an ATL based project. I placed this #define in stdafx.h so that it would not be included in the library header file that will be used by a consumer. This way, the CAtlString declarations are in full effect when the consumer includes the library.

// ConsoleApp.cpp : Defines the entry point for the console application.
//

#ifdef _DEBUG
    #pragma comment(lib, "..\\Library\\Debug\\Library.lib")
#else
    #pragma comment(lib, "..\\Library\\Release\\Library.lib")
#endif 

#include "stdafx.h"
#include "..\Library\HelloLib.h"

int _tmain(int argc, _TCHAR* argv[])
{
    printf("Calling into library.\n");

    CHelloLib hw;
    hw.HelloWorld("I was called from a console application.");

    return 0;
}

Above, the code for ConsoleApp.cpp, shows you how to select between a debug edition of an included library, and a release version of an included library. I found this to be a quite handy trick, as it instructs the linker to bring in a library without having to adjust the project settings at all! The Application demo also uses the same trick, so I won't bother to put it in here.

Points of Interest

I think that it's kind of an issue with ATL/MFC as they are technologies that can be mixed together, they should also be able to work around each other. The above solution is rather hacky, even though it gets the job done simply. I would rather have ATL or MFC check to see if one or the other is included, and use the proper signature, but maybe that could be a goal for VC++ 8...

History

  • 11th April, 2003 - Initial creation / Shining path walk
  • 15th April, 2003 - Not as smart as I thought I was / Path suddenly went dim... had to change several things to keep from redefining CString in MFC applications when importing the library headers. Path is now back in view.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here