Help with linker error [VS 2005]

Started by
12 comments, last by Sneftel 18 years ago
I'm at wits end trying to sort out this bloody linker error. It only occurs when I include "cFileSys.h". I have no idea why the error appears, and I can't get rid of it. So if anyone could take a look, I'd be super greatful. You need to compile it with the DirectX SDK, so you just need to fix the library paths in the project options. The file (.rar | 2.5mb) can be found here. All extra files, aka the one .bmp needed, is included. I think thats all...So Thanks!!!!!
Advertisement
cFileSys.h has function definitions within the header file. Thus, these are compiled into the object file for every source file which #includes cFileSys.h. When more than one source file does so, the functions become multiply defined.

The problem here is the fact that the definitions are in the header - they need to be moved out, eg into a cFileSys.cpp. You probably cannot do this with the template functions - I'm not sure what to do with these, but I think these need to be moved to within the class definition.
Behold the article.
Ok...but that breaks the templated functions tho...
Again, go ahead and behold the article. Specifically, the last three paragraphs before "Other Considerations".
_Sigma, here are cFileSys.h and cFileSys.cpp for your viewing pleasure:

cFileSys.h
#ifndef CFILESYS_H#define CFILESYS_H/*******************  FILE ACCESS SYSTEM  ********************Created by Chris Marsh 2006The purpose of this class is to provide an easy and effective way of writing to and from filesby providing a wrapper class for the standard I/O functions.This handles binary and textual reading and writing. Text reading always returns a string, up to a line in length. Binary will always return the original data type. If you try to write anything but a string toa file in FS_TEXT mode, you'll just end up with the character represented by that number.Strange things happen if you write to a file in binary then read in text mode, or visa versa. I've tried to handlesome of the strangness, such as the string buffer failing to create. Anyways, just don't do it. =)This is presented as is, and contains no warantees what so ever.These files can be used anyway you want, but this MUST be present.THANKS to the folks at GameDev.net who helped with portions of the code.*/#include <string>#include <iostream>#include <fstream>#include <windows.h>#include <sstream>using namespace std;/* Note about open modes *//*FS_APPEND assumes we are opening the file to read and write(to the end of the file) to.FS_CREATE assumes we are creating a file(overwrite any old file) to open and read from.FS_OPEN_ONLY assumes we are only opening the file to read from. THe file must exist or a FS_READ_ERROR is raised	- This mode exists because one would have to open the file in mode FS_APPEND, which isn't intuitive.	  If the file is opened in FS_CREATE, the file is overwritten, thus if we try to read without a prior	  write, a FS_READ_ERROR is raised, as the file is empty.	- In this mode, you cannot write to the file, until you re-open the file, and specify the write type.*/#define FS_BINARY 0x0  #define FS_TEXT   0x1#define FS_APPEND 0xA#define FS_CREATE 0xB#define FS_OPEN_ONLY 0xC#define FS_SUCCESS 0x2#define FS_WRITER_INIT_ERROR 0x3#define FS_WRITE_ERROR 0x4#define FS_READER_INIT_ERROR 0x5#define FS_READ_ERROR 0x6#define FS_UNKOWN_ERROR 0x7#define FS_DELETE_FAIL 0xF#define FS_DELETE_SUCCESS 0xE#define FS_MEMORY_ALLOC_ERROR 0xFF#define FS_FUNCTION_ERROR 0xEE//type def the unsigned int data typetypedef unsigned int UINT;//Main classclass cFileSys{public:	//Overloaded write methods, handles just the basic data types atm	template< class T > int write( T input );	//template<> int write<std::string>(std::string input);	//Overloaded read methods, handles just the bast data types atm	template<class T> int read(T &output);//	template<> int read<std::string>(std::string &output, UINT maxChars, char endChar);	//constructor. MUST take parameters. no default () constructor	cFileSys(char *fileName, //file name		int action, //Append to a file or create a new file		int mode);//Type of write to perform (Binary or textual)	//destructor. Closes down our files, and deletes our variables.	~cFileSys();	//Resets the file pointer to the start of the file	//Used after a write	void resetFilePtr();		//Creates a file by opening a new stream then closing it	void createFile(char *fName);	//deletes the currently open file, after releasing it	//int deleteFile();		//closes the open file 	bool closeFile();		//Retrieves the last error in human readable form	string getLastError();		int openFile(char* fileName, int action, int mode);	int readLine(char *output, UINT maxChars, char endChar);private:	//name of the currently open file	char *fName;	//our file stream	fstream *file;	//out stream	ostream *writer;	//in stream	istream *reader;		//what mode to open the file in	int writeMode;	//holds the last triggered error 	int lastError;	//append or create the file.	int writeAction;};//write an int to file//can only be used in binary modetemplate< class T > int cFileSys::write( T input ){	switch(writeMode)	{		case FS_BINARY:			{					//Check for an invalid writer					if(!writer)					{						lastError = FS_WRITER_INIT_ERROR;						return(FS_WRITER_INIT_ERROR);					}					//if we want to append, seek to the end of the file					if(writeAction == FS_APPEND)						writer->seekp(0 , ios::end);					//write the int					if(!writer->write((char*)(&input),sizeof(input)))					{						lastError = FS_WRITE_ERROR;						return(FS_WRITE_ERROR);					}					return(FS_SUCCESS);						break;			}		case FS_TEXT:			{				//Check for an invalid writer					if(!writer)					{						lastError = FS_WRITER_INIT_ERROR;						return(FS_WRITER_INIT_ERROR);					}					//if we want to append, seek to the end of the file					if(writeAction == FS_APPEND)						writer->seekp(0 , ios::end);					stringstream ss;					ss << input;					string temp = ss.str();					if(write(temp) != FS_SUCCESS)					{						lastError = FS_WRITE_ERROR;						return(FS_WRITE_ERROR);					}					return(FS_SUCCESS);						break;			}		default:			{					lastError = FS_UNKOWN_ERROR;					return(FS_UNKOWN_ERROR);			}	}}// To avoid specialization/general template "collision"template<> int cFileSys::write< std::string >( std::string input );template<class T> int cFileSys::read(T &output){	switch(writeMode)	{		case FS_BINARY:			{				if(!reader)				{					lastError = FS_READER_INIT_ERROR;					return(FS_READER_INIT_ERROR);				}				int temp;				// Read the int in				if(!reader->read((char*)(&temp),sizeof(int)))				{					lastError = FS_READ_ERROR;					return(FS_READ_ERROR);				}				output = temp;				return(FS_SUCCESS);				break;			}		case FS_TEXT:			{				lastError = FS_FUNCTION_ERROR;				return(FS_FUNCTION_ERROR);				break;			}		default:			{				lastError = FS_UNKOWN_ERROR;				return(FS_UNKOWN_ERROR);			}	}}// To avoid specialization/general template "collision"template<> int cFileSys::read< std::string >( std::string &output );#endif
cFileSys.cpp
#include "cFileSys.h"// Constructor//takes the file to load, write mode and type of write to performcFileSys::cFileSys(char* fileName, int action, int mode){	//set internal filename	fName = fileName;	//set the internal mode	writeMode = mode;		writeAction = action;	//if we want to create a new file, call the createfile function	if(action == FS_CREATE)		createFile(fileName);	//depending on what mode we want to open the file in, 	//changes the parameters that are passed to the fstream constructor	switch(writeMode)	{		case FS_BINARY:			{				file = new fstream(fName, ios::in | ios::out | ios::binary);				break;			}		case FS_TEXT:			{				file = new fstream(fName, ios::in | ios::out);				break;			}	}   //end switch(writeMode)	//file reader set to the file buffer.	reader = new istream(file->rdbuf());		//don't create a writer if we are only looking to open the file	if(writeMode != FS_OPEN_ONLY)		//file writer 		writer = new ostream(file->rdbuf());	else		//just to be safe, set the unused writer to NULL		writer = NULL;}//Deconstructor//Closes any open files, and deletes the file reader and writercFileSys::~cFileSys(){	if(file)	{		file->close();		delete file;		file = NULL;	}		if(writer)	{		delete writer;		writer = NULL;	}		if(reader)	{		delete reader;		reader = NULL;	}}//resets the file pointer, so we can read from the startvoid cFileSys::resetFilePtr(){	//seek to 0 bytes from the begining	reader->seekg( 0, ios_base::beg );}//read one line from the file into the passed string//either maxChars - 1 in length, or a "/n" char is hitint cFileSys::readLine(char *output, UINT maxChars, char endChar){	if(!reader)	{		lastError = FS_READER_INIT_ERROR;		return(FS_READER_INIT_ERROR);	}	if(!reader->getline(output,maxChars,endChar))		return(FS_READ_ERROR);	return(FS_SUCCESS);}//hack because opening with ios::in | ios::out doesn't seem to create the file//if it doesn't exist.void cFileSys::createFile(char *fName){	fstream *tempFile = new fstream(fName, ios::out);	if(tempFile)	{		tempFile->close();		delete tempFile;		tempFile = NULL;	}}/*int cFileSys::deleteFile(){	if(file)	{		file->close();		file = NULL;	}	if(!DeleteFile((LPCWSTR)fName))	{		lastError = FS_DELETE_FAIL;		return(FS_DELETE_FAIL);	}	return (FS_DELETE_SUCCESS);}*/bool cFileSys::closeFile(){	if(file)	{		file->close();		delete file;		file = NULL;	}	if(writer)	{		delete writer;		writer = NULL;	}		if(reader)	{		delete reader;		reader = NULL;	}	return (true);}int cFileSys::openFile(char* fileName, int action, int mode){	//set internal filename	fName = fileName;	//set the internal mode	writeMode = mode;		writeAction = action;	//if we want to create a new file, call the createfile function	if(action == FS_CREATE)		createFile(fileName);	//depending on what mode we want to open the file in, 	//changes the parameters that are passed to the fstream constructor	switch(writeMode)	{		case FS_BINARY:			{				file = new fstream(fName, ios::in | ios::out | ios::binary);				break;			}		case FS_TEXT:			{				file = new fstream(fName, ios::in | ios::out);				break;			}	}   //end switch(writeMode)	//file reader set to the file buffer.	reader = new istream(file->rdbuf());		//don't create a writer if we are only looking to open the file	if(writeMode != FS_OPEN_ONLY)		//file writer 		writer = new ostream(file->rdbuf());	else		//just to be safe, set the unused writer to NULL		writer = NULL;	return FS_SUCCESS;}string cFileSys::getLastError(){	switch(lastError)	{	case FS_WRITER_INIT_ERROR:		{			return("The writer failed to open the specified file.");			break;		}	case FS_WRITE_ERROR:		{			return("There was an error writing to the file.");			break;		}	case FS_READER_INIT_ERROR:		{			return("The reader failed to open the specified file.");			break;		}	case FS_READ_ERROR:		{			return("There was an error reading from the file.");			break;		}	case FS_UNKOWN_ERROR:		{			return("An unkown error happend. Perhpas you passed an unsupported parameter?");			break;		}	case FS_DELETE_FAIL:		{			return("There was an error deleting the specified file.");			break;		}	case FS_MEMORY_ALLOC_ERROR:		{			return("There was an error allocating the string buffer. Perhaps you are trying to read a file written in text mode in binary mode?");			break;		}	case FS_FUNCTION_ERROR:		{			return("This function cannot be used in text mode.");			break;		}	default:		{			return("There have been no errors.");			break;		}	}}//write a string to the filetemplate<> int cFileSys::write<std::string>(std::string input){	//depending on the write mode, the actual write code is different	switch(writeMode)	{		case FS_BINARY:			{				//check if the writer is invalid				//this will be set if the file doesn't exist, and we are trying to append to it.				if(!writer)				{					lastError = FS_WRITER_INIT_ERROR;					return (FS_WRITER_INIT_ERROR);				}								//if we want to append, seek to the end of the file				if(writeAction == FS_APPEND)					writer->seekp(0 , ios::end);				// Size of the string				UINT stringSize = UINT(input.size());				// Write the length of the string to the file				if(!writer->write((char*)&stringSize, sizeof(UINT)))				{					lastError = FS_WRITE_ERROR;					return (FS_WRITE_ERROR);				}				// Write the string to the file				if(!writer->write(input.c_str(), stringSize))				{					lastError = FS_WRITE_ERROR;					return(FS_WRITE_ERROR);				}				//if we get this far, everything worked, so return success				return FS_SUCCESS;							}		case FS_TEXT:			{				if(!writer)				{					lastError = FS_WRITER_INIT_ERROR;					return (FS_WRITER_INIT_ERROR);				}								// Size of the string				UINT stringSize = UINT(input.size());				//if we want to append, seek to the end of the file				if(writeAction == FS_APPEND)					writer->seekp(0 , ios::end);								// Write the string to the file				if(!writer->write(input.c_str(), stringSize))				{					lastError = FS_WRITE_ERROR;					return(FS_WRITE_ERROR);				}				return(FS_SUCCESS);							}			//An incorret parameter passed?		 default:			{				lastError = FS_UNKOWN_ERROR;				return(FS_UNKOWN_ERROR);			}	}}//read a string from file, starting at the start//unless in FS_TEXT mode, pass NULL for parms 2 and 3template<> int cFileSys::read<std::string>(std::string &output)//, UINT maxChars, char endChar){	switch(writeMode)	{	case FS_BINARY:		{			//check is the reader is invalid			if(!reader)			{				lastError = FS_READER_INIT_ERROR;				return (FS_READER_INIT_ERROR);			}						//size of the string			UINT stringSize = 0;									// Read the length of the string from the file			if(!reader->read((char*)&stringSize, sizeof(UINT)))			{				lastError = FS_READ_ERROR;				return (FS_READ_ERROR);			}			//create a buffer to dump the string to			char *buffer; 			try			{				buffer = new char[stringSize + 1];			}			catch(...)			{				lastError = FS_MEMORY_ALLOC_ERROR;				return (FS_MEMORY_ALLOC_ERROR);			}			// Read the string from the file			reader->read(buffer, stringSize);			// NULL-terminate the string			buffer[stringSize] = '\0';						//set the output passed to the function to be the buffer			output = buffer;						//deletes the string buffer			delete[] buffer;			//if we got this far, return success			return (FS_SUCCESS);		}	case FS_TEXT:		{						char endChar = '\n';						//max 100 chars			UINT maxChars = 100;				char *charTemp = new char[maxChars];							if(!readLine(charTemp,maxChars,endChar))			{				lastError = FS_READ_ERROR;				return(FS_READ_ERROR);			}			output = charTemp;			delete[] charTemp;			return(FS_SUCCESS);		}	default:		{			lastError = FS_UNKOWN_ERROR;			return(FS_UNKOWN_ERROR);		}	}}

Sneftel: I don't think the article addresses all of the problems in the code (I haven't read it in detail). It doesn't talk about template (member) function specializations.


jfl.
Quote:From TFA:
There are two notable exceptions to the "no function bodies in header files", because although they look like function bodies, they aren't exactly the same.

The first exception is that of template functions. Most compilers and linkers can't handle templates being defined in different files to that which they are used in, so templates almost always need to be defined in a header so that the definition can be included in every file that needs to use it. Because of the way templates are instantiated in the code, this doesn't lead to the same errors that you would get by defining a normal function in a header. This is because templates aren't compiled at the place of definition, but are compiled as they are used by code elsewhere.

The second exception is inline functions, briefly mentioned earlier. An inline function is compiled directly into the code, rather than called in the normal way. This means that any translation unit where the code 'calls' an inline function needs to be able to see the inner workings (ie. the implementation) of that function in order to insert that function's code directly. This means that a simple function prototype is insufficient for calling the inline function, meaning that wherever you would normally just use a function prototype, you need the whole function body for an inline function. As with templates, this doesn't cause linker errors as the inline function is not actually compiled at the place of definition, but is inserted at the place of calling.
Sure, he mentions template functions, but where do you see specialization there?
Quote:Original post by Sneftel
Again, go ahead and behold the article. Specifically, the last three paragraphs before "Other Considerations".

Ah. My apologies. So I need to move all the none templated functions to a .cpp file then?
jflanglois
Thanks, that answered a lot of questions. Only thing that really blew me away was why is
// To avoid specialization/general template "collision"template<> int cFileSys::write< std::string >( std::string input );

there? and why is it defined in the .cpp instead of the .h like the other templated fns?

Cheers

This topic is closed to new replies.

Advertisement