Linker error when using unordered_map but not map?

This is a very strange problem. I have two classes: a special console class (CConsole) and a test class (CHashTableTest), which I did to play with cards and unordered_maps to find out how they work.

In my console class, I have a public static CConsole member variable that provides a static console object for the rest of the project so that I can write to this console whenever I want. This works great for all my classes, including the testing class, but only when these test classes use a map, not an unordered_map!

The error I am getting is:

error LNK2001: unresolved external symbol "public static class CConsole CConsole: output" (? output @CConsole @@ 2V1 @A)

It comes from a class that calls methods on the test class, not the test class itself, but nothing strange happens in the calling class, it simply creates a CHashTableTest object (passed to the CConsole object) and calls Add and Get in the subject. It is placed in a separate project, but this is not a problem when I use the map, since the static member variable was made external using _declspec (ddlexport).

The solution is installed in Visual Studio 2012, the CConsole and CHashTableTest classes are located in the DLL project, which is the external link of the Unit Test project, where the call code exists.

CConsole and CHashTableTest Files:

Console.h

#ifndef _CONSOLE_H_ #define _CONSOLE_H_ #define _CRT_SECURE_NO_DEPRECATE #include <iostream> #include <fstream> #include <string> #include <ctime> #include <time.h> // Defines a set of output modes for the CConsole class. This will affect what output the console will target. // The default is COUT. enum _declspec(dllexport) EConsoleMode { // Output sent to a CConsole object will be printed using cout. COUT, // Output sent to a CConsole object will be printed to a file. OUTPUT_FILE, // Output sent to a CConsole object will be printed using OutputDebugString. OUTPUT_DEBUG_STRING, // Output sent to a CConsole object will be printed using Debug::WriteLine. DEBUG_WRITE_LINE, // Output sent to a CConsole object will be printed using Console::WriteLine. CONSOLE_WRITE_LINE, // Output sent to a CConsole object will not be printed. NO_OUTPUT, }; // An output wrapper class that allows logging and redirecting of log and debugging messages to different // output targets. class _declspec(dllexport) CConsole { public: static CConsole output; // Constructs a CConsole object with a specific console mode, default is COUT. CConsole(EConsoleMode mode = COUT, const char* filePath = "C:/output.txt"); ~CConsole(void); // Gets the mode of this CConsole object. EConsoleMode GetMode(); const char* GetFilePath(); // Sets the output file path of this CConsole object. void SetFilePath(const char* filePath); void TimeStamp(); // Logs a message with this specific CConsole object. An indirect call to an operator overload of // CConsole << Type template <typename T> void Log(const T &message); protected: // The mode of this CConsole object. EConsoleMode m_mode; // The file path of the output file for this CConsole object. const char* m_filePath; }; // Operator overload of CConsole << Type, queries the mode of the given CConsole object and // selects the appropriate output method associated with the mode. template <typename T> CConsole operator<< (CConsole console, const T &input) { switch(console.GetMode()) { case COUT: { std::cout << input << "\n"; break; } case OUTPUT_FILE: { ofstream fout; fout.open (console.GetFilePath(), ios::app); fout << input; fout.close(); break; } #if ON_WINDOWS case OUTPUT_DEBUG_STRING: { OutputDebugString(input); break; } case DEBUG_WRITE_LINE: { Debug::WriteLine(input); break; } case CONSOLE_WRITE_LINE: { Console::WriteLine(input); break; } #endif case NO_OUTPUT: { break; } default: { std::cout << input; break; } } return console; } // Logs a message by calling the operator overload of << on this CConsole object with the message // parameter. template <typename T> void CConsole::Log(const T &message) { this << message; } #endif 

Console.cpp

 #include "Console.h" CConsole CConsole::output = *new CConsole(OUTPUT_FILE, "C:/LocalProjects/---/output.txt"); // Known memory leak here, discussed in comments // Constructs a CConsole object by assigning the mode parameter to the // m_mode member variable. CConsole::CConsole(EConsoleMode mode, const char* filePath) { m_mode = mode; m_filePath = filePath; TimeStamp(); } CConsole::~CConsole(void) { //Log("\n\n"); } // Returns the current mode of this CConsole object. EConsoleMode CConsole::GetMode() { return m_mode; } const char* CConsole::GetFilePath() { return m_filePath; } void CConsole::TimeStamp() { if(m_mode == OUTPUT_FILE) { std::ofstream file; file.open (m_filePath, std::ios::app); // std::time_t currentTime = time(nullptr); file << "Console started: " << std::asctime(std::localtime(&currentTime)); file.close(); } } void CConsole::SetFilePath(const char* filePath) { m_filePath = filePath; } 

HashTableTest.h

 #ifndef _HASH_TABLE_TEST_H_ #define _HASH_TABLE_TEST_H_ #include <unordered_map> #include <map> #include <vector> #include "Debuggable.h" #include "Console.h" using namespace std; //template class __declspec(dllexport) unordered_map<int, double>; struct Hasher { public: size_t operator() (vector<int> const& key) const { return key[0]; } }; struct EqualFn { public: bool operator() (vector<int> const& t1, vector<int> const& t2) const { CConsole::output << "\t\tAdding, t1: " << t1[0] << ", t2: " << t2[0] << "\n"; // If I delete this line then no error! return (t1[0] == t2[0]); } }; class __declspec(dllexport) CHashTableTest : public CDebuggable { public: CHashTableTest(CConsole console = *new CConsole()); ~CHashTableTest(void); void Add(vector<int> key, bool value); bool Get(vector<int> key); private: CConsole m_console; unordered_map<vector<int>, bool, Hasher, EqualFn> m_table; // If I change this to a map then no error! }; #endif 

Just to be clear, if I change the "unordered_map" above to "map" and remove the Hasher function object from the template argument list, this will compile. If I do not get a linker error. I did not include HashTableTest.cpp because it contains almost nothing.

EDIT

I'm not sure if I'm using unordered_map correctly, here is the implementation for the CHashTableTest class:

HashTableTest.cpp

 #include "HashTableTest.h" CHashTableTest::CHashTableTest(CConsole console) : CDebuggable(console) { } CHashTableTest::~CHashTableTest(void) { } void CHashTableTest::Add(vector<int> key, bool value) { m_table[key] = value; } bool CHashTableTest::Get(vector<int> key) { return m_table[key]; } 
+5
source share
1 answer

When the import-related DLL is linked to the consuming EXE (or other DLL), the declspecs attributes for the incoming elements must be declspec(dllimport) on the receiver side. When you create the actual DLL, the same elements become available with declspec(dllexport) . Your code has the last, but not the first.

This is usually achieved with a single preprocessor macro, which is defined only for your DLL project in the preprocessor section of the compiler configuration. For example, in the headers that declare objects exported from your DLL:

 #ifndef MYDLL_HEADER_H #ifdef MYDLL_EXPORTS #define EXPORT __declspec(dllexport) #else #define EXPORT __declspec(dllimport) #endif class EXPORT CConsole { // stuff here. }; #endif 

Doing this will tell the compiler when creating a DLL to export class members, including class statics, since MYDLL_EXPORTS is defined in the preprocessor configuration flags for your DLL project.

When creating a consumption project, DO NOT define MYDLL_EXPORTS in the compiler parameter preprocessor configuration. This way it will use dllimport , not dllexport . This should shift with the linker to know where to look for what it needs.

Good luck.

+2
source

All Articles