views:

1521

answers:

6

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?

A: 

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

Gayan
I use [debug/release] multi-thread DLL for the runtime library in both vc6 and vs200x
fret
A: 

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 a const GArray<Type>& to indicate that the right-hand side does not change.

rlbond
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.
fret
A: 
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 and vod_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.

Vulcan Eager
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?
fret
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
Vulcan Eager
A: 

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.

jon hanson
Yeah I use those for all my normal classes...
fret
A: 

Why don't you use std::vector instead?

Bartosz Milewski
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 consi
fret
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.
Bartosz Milewski
Using vector is good, but it doesn't address the question of "where are my multiply defined symbols coming from?"
Clay
A: 

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.

fret