I'm working on a project with a DLL and an EXE in visual studio 2005. Amongst the code for the DLL is a template for a growable array class:
template <class Type>
class GArray
{
Type *p;
uint32 len;
uint32 alloc;
protected:
bool fixed;
public:
/// Constructor
GArray(int PreAlloc = 0)
{
p = 0;
len = 0;
fixed = false;
alloc = PreAlloc;
if (alloc)
{
int Bytes = sizeof(Type) * alloc;
p = (Type*) malloc(Bytes);
if (p)
{
memset(p, 0, Bytes);
}
else
{
alloc = 0;
}
}
}
/// Destructor
~GArray()
{
Length(0);
}
/// Returns the number of used entries
uint32 Length() const
{
return len;
}
/// Sets the length of available entries
bool Length(uint32 i)
{
if (i > 0)
{
if (i > len && fixed)
return false;
uint nalloc = alloc;
if (i < len)
{
// Shrinking
}
else
{
// Expanding
int b;
for (b = 4; (1 << b) < i; b++)
;
nalloc = 1 << b;
LgiAssert(nalloc >= i);
}
if (nalloc != alloc)
{
Type *np = (Type*)malloc(sizeof(Type) * nalloc);
if (!np)
{
return false;
}
if (p)
{
// copy across common elements
memcpy(np, p, min(len, i) * sizeof(Type));
free(p);
}
p = np;
alloc = nalloc;
}
if (i > len)
{
// zero new elements
memset(p + len, 0, sizeof(Type) * (i - len));
}
len = i;
}
else
{
if (p)
{
int Length = len;
for (uint i=0; i<Length; i++)
{
p[i].~Type();
}
free(p);
p = 0;
}
len = alloc = 0;
}
return true;
}
GArray<Type> &operator =(const GArray<Type> &a)
{
Length(a.Length());
if (p && a.p)
{
for (int i=0; i<len; i++)
{
p[i] = a.p[i];
}
}
return *this;
}
/// \brief Returns a reference a given entry.
///
/// If the entry is off the end of the array and "fixed" is false,
/// it will grow to make it valid.
Type &operator [](uint32 i)
{
static Type t;
if
(
i < 0
||
(fixed && i >= len)
)
{
ZeroObj(t);
return t;
}
#if 0
if (i > 15000000)
{
#if defined(_DEBUG) && defined(_MSC_VER)
LgiAssert(0);
#endif
ZeroObj(t);
return t;
}
#endif
if (i >= alloc)
{
// increase array length
uint nalloc = max(alloc, GARRAY_MIN_SIZE);
while (nalloc <= i)
{
nalloc <<= 1;
}
// alloc new array
Type *np = (Type*) malloc(sizeof(Type) * nalloc);
if (np)
{
// clear new cells
memset(np + len, 0, (nalloc - len) * sizeof(Type));
if (p)
{
// copy across old cells
memcpy(np, p, len * sizeof(Type));
// clear old array
free(p);
}
// new values
p = np;
alloc = nalloc;
}
else
{
static Type *t = 0;
return *t;
}
}
// adjust length of the the array
if (i + 1 > len)
{
len = i + 1;
}
return p[i];
}
/// Delete all the entries as if they are pointers to objects
void DeleteObjects()
{
for (uint i=0; i<len; i++)
{
DeleteObj(p[i]);
}
Length(0);
}
/// Delete all the entries as if they are pointers to arrays
void DeleteArrays()
{
for (int i=0; i<len; i++)
{
DeleteArray(p[i]);
}
Length(0);
}
/// Find the index of entry 'n'
int IndexOf(Type n)
{
for (uint i=0; i<len; i++)
{
if (p[i] == n) return i;
}
return -1;
}
/// Returns true if the item 'n' is in the array
bool HasItem(Type n)
{
return IndexOf(n) >= 0;
}
/// Deletes an entry
bool DeleteAt
(
/// The index of the entry to delete
uint Index,
/// true if the order of the array matters, otherwise false.
bool Ordered = false
)
{
if (p && Index >= 0 && Index < len)
{
// Delete the object
p[Index].~Type();
// Move the memory up
if (Index < len - 1)
{
if (Ordered)
{
memmove(p + Index, p + Index + 1, (len - Index - 1) * sizeof(Type) );
}
else
{
p[Index] = p[len-1];
}
}
// Adjust length
len--;
return true;
}
return false;
}
/// Deletes the entry 'n'
bool Delete
(
/// The value of the entry to delete
Type n,
/// true if the order of the array matters, otherwise false.
bool Ordered = false
)
{
int i = IndexOf(n);
if (p && i >= 0)
{
return DeleteAt(i, Ordered);
}
return false;
}
/// Appends an element
void Add
(
/// Item to insert
const Type &n
)
{
(*this)[len] = n;
}
/// Appends multiple elements
void Add
(
/// Items to insert
Type *s,
/// Length of array
int count
)
{
if (!s || count < 1)
return;
int i = len;
Length(len + count);
Type *d = p + i;
while (count--)
{
*d++ = *s++;
}
}
/// Inserts an element into the array
bool AddAt
(
/// Item to insert before
int Index,
/// Item to insert
Type n
)
{
// Make room
if (Length(len + 1))
{
if (Index < len - 1)
{
// Shift elements after insert point up one
memmove(p + Index + 1, p + Index, (len - Index - 1) * sizeof(Type) );
}
else if (Index >= len)
{
// Add at the end, not after the end...
Index = len - 1;
}
// Insert item
p[Index] = n;
return true;
}
return false;
}
/// Sorts the array
void Sort(int (*Compare)(Type*, Type*))
{
typedef int (*qsort_compare)(const void *, const void *);
qsort(p, len, sizeof(Type), (qsort_compare)Compare);
}
/// \returns a reference to a new object on the end of the array
Type &New()
{
return (*this)[len];
}
/// Returns the memory held by the array and sets itself to empty
Type *Release()
{
Type *Ptr = p;
p = 0;
len = alloc = 0;
return Ptr;
}
};
I've reused this code in the EXE in several places. However when I use it in one particular file I start getting duplicate symbol link errors:
2>lgi8d.lib(Lgi8d.dll) : error LNK2005: "public: int __thiscall GArray<char *>::Length(void)" (?Length@?$GArray@PAD@@QAEHXZ) already defined in FrameStore.obj
2>D:\Home\matthew\network_camera\src\vod_test\Debug\vod_test.exe : fatal error LNK1169: one or more multiply defined symbols found
I've used the same class in other files in the EXE without error. e.g. in Camera.cpp I have:
void DeleteFromArray(GArray<char> &a, int Start, int Len)
{
assert(Len >= 0);
int64 StartTs = LgiCurrentTime();
int Del = min(Len, a.Length() - Start);
if (Del > 0)
{
int Remain = a.Length() - Start - Del;
if (Remain > 0)
{
memmove(&a[Start], &a[Start+Del], Remain);
MoveBytes += Remain;
a.Length(Start+Remain);
}
else a.Length(Start);
}
int64 End = LgiCurrentTime();
DeleteTime += End - StartTs;
}
which compiles and links ok... but in FrameStore.cpp:
void Scan()
{
if (!Init)
{
Init = true;
GAutoString Path = FrameFile::GetPath();
GAutoPtr<GDirectory> Dir(FileDev->GetDir());
GArray<char*> k;
int64 Size = 0;
for (bool b = Dir->First(Path); b; b = Dir->Next())
{
if (!Dir->IsDir())
{
char *e = LgiGetExtension(Dir->GetName());
if (e && !stricmp(e, "mjv"))
{
char p[MAX_PATH];
Dir->Path(p, sizeof(p));
k.Add(NewStr(p));
Size += Dir->GetSize();
}
}
}
GAutoPtr<Prog> p(new Prog(Size));
for (int i=0; i<k.Length(); i++)
{
Files.Add(new FrameFile(k[i], p));
}
k.DeleteArrays();
}
}
Causes the link error on the line with "k.Length()" in it... if I comment out that it links! Yet I'm using other methods in the GArray class in the same code and they don't cause a problem.
Why should a template class thats defined entirely in a header be having this issue in the first place?
-
If you're using Visual Studio 6, make sure the following option is set:
Project->Settings->C/C++-> Code Generation->Use run-time library ===> Debug Multithreaded/Multithreaded
EDIT: In VS 2005 it's basically the same.
Project-> Properties-> Configuration Properties->C/C++-> Code Generation->Run time library-> Multi-threaded/Multi-threaded Debug
fret : I use [debug/release] multi-thread DLL for the runtime library in both vc6 and vs200x -
As a side note, you might want to split your declarations and definitions, so it's not quite as ugly:
template <class Type> class GArray { Type *p; uint32 len; uint32 alloc; protected: bool fixed; public: GArray(int PreAlloc = 0); /// Destructor ~GArray() {Length(0);} /// Returns the number of used entries int Length() {return len;} /// Sets the length of available entries bool Length(uint32 i); // etc... }; template <class Type> GArray<Type>::GArray(int PreAlloc = 0) { p = 0; len = 0; fixed = false; alloc = PreAlloc; if (alloc) { int Bytes = sizeof(Type) * alloc; p = (Type*) malloc(Bytes); if (p) { memset(p, 0, Bytes); } else { alloc = 0; } } } template <class Type> bool GArray<Type>::Length(uint32 i); { if (i > 0) { if (i > len && fixed) return false; uint nalloc = alloc; if (i < len) { // Shrinking } else { // Expanding int b; for (b = 4; (1 << b) < i; b++) ; nalloc = 1 << b; LgiAssert(nalloc >= i); } if (nalloc != alloc) { Type *np = (Type*)malloc(sizeof(Type) * nalloc); if (!np) { return false; } if (p) { // copy across common elements memcpy(np, p, min(len, i) * sizeof(Type)); free(p); } p = np; alloc = nalloc; } if (i > len) { // zero new elements memset(p + len, 0, sizeof(Type) * (i - len)); } len = i; } else { if (p) { int Length = len; for (uint i=0; i<Length; i++) { p[i].~Type(); } free(p); p = 0; } len = alloc = 0; } return true; }
// you get the point
Also,
operator=
should take aconst GArray<Type>&
to indicate that the right-hand side does not change.fret : I would split the decl and defn BUT that means lots of hassle with link time issues and where you instantiate different uses of the class. I've done that with some of my larger template classes and it's painful. -
2>lgi8d.lib(Lgi8d.dll) : error LNK2005: "public: int __thiscall GArray<char *>::Length(void)" (?Length@?$GArray@PAD@@QAEHXZ) already defined in FrameStore.obj 2>D:\Home\matthew\network_camera\src\vod_test\Debug\vod_test.exe : fatal error LNK1169: one or more multiply defined symbols found
lgi8d.lib
andvod_test.exe
are two separate binaries. The problem might lie in the fact that the.lib
already defines the symbol which the .exe is defining again.fret : Thats what the linker thinks, but I'm using this class (with the same type parameter) everywhere, in both EXE and DLL and it's just this one line thats a problem. For instance if I use the same method further down the file it works ok. WTH?Vulcan Eager : Could it be because of the compiler name-mangling. i.e. The name "?Length@?$GArray@PAD@@QAEHXZ" is being generated a second time.Vulcan Eager : This might bring up some answers: http://stackoverflow.com/questions/754865/view-compiler-mangled-names-in-c -
You could try adding a declspec(dllexport) to the class in the DLL and declspec(dllimport) in the EXE. E.g.
#if !defined(MYDLLEXPORT) // We are users of, and *importing* the library routines... #define MYLIB_SPEC __declspec(dllimport) #else // We are building and exporting the library routines... #define MYLIB_SPEC __declspec(dllexport) #endif // ... template<typename Type> class MYLIB_SPEC GArray // ...
Then make sure MYDLLEXPORT is defined in the project that builds the DLL and is not defined for the EXE.
AFAIK you don't normally need this for templates though.
More info on declspec's here: http://msdn.microsoft.com/en-us/library/a90k134d(VS.80).aspx.
fret : Yeah I use those for all my normal classes... -
Why don't you use std::vector instead?
fret : I've been trying to keep the download size and dependency list down to a minimum for years. And I wouldn't use most of STL. I just compiled STLport to see what the DLL sizes are... about 805kb, compared to my entire software download being 1mb. I may ditch my own stuff eventually... it's worth consiBartosz Milewski : T in STL stands for "template". If you don't instantiate a given template in your code, it costs you nothing. So I'm not sure where these 805kb come from.Clay : Using vector is good, but it doesn't address the question of "where are my multiply defined symbols coming from?" -
The problem:
There is another class defined in Lgi.dll that exports the GArray instantiation.
#ifdef LGI_DLL #define LgiClass __declspec(dllexport) #else #define LgiClass __declspec(dllimport) #endif class LgiClass GToken : public GArray<char*> { /// stuff... };
The solution:
Include that GToken header in my EXE, specifically the FrameStore.cpp file that uses the GArray implementation, then the compile will import those symbols from the DLL instead of duplicate them.
It would have been nice if the compiler could give me more of a hint at where the DLL was defining the symbol. Simply saying "there a duplicate somewhere" is not very helpful.
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.