5

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?

fret
  • 1,542
  • 21
  • 36
  • I was about to say conditionally include the file using #ifndef FOO, #define FOO, CODE, #endif. But I think the problem is that the instance of the template you are instantiating is being defined twice. – ojblass Apr 16 '09 at 02:28
  • Probably not related to your link error but the member len is declared as uint32 but you are returning and int from Length(). Also that version of Length could/should be declared const, you are not modifying anything, e.g., uint Length() const { return len; } – maccullt Apr 16 '09 at 03:18
  • nothing simple here... :( can you try it on linux? – ojblass Apr 16 '09 at 06:38

6 Answers6

4

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
  • 1,542
  • 21
  • 36
0

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
  • 1,697
  • 7
  • 26
  • 35
  • I use [debug/release] multi-thread DLL for the runtime library in both vc6 and vs200x – fret Apr 16 '09 at 04:05
0

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
  • 65,341
  • 56
  • 178
  • 228
  • 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 Apr 16 '09 at 04:03
0
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.

Agnel Kurian
  • 57,975
  • 43
  • 146
  • 217
  • 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 Apr 16 '09 at 05:28
  • Could it be because of the compiler name-mangling. i.e. The name "?Length@?$GArray@PAD@@QAEHXZ" is being generated a second time. – Agnel Kurian Apr 16 '09 at 05:35
  • This might bring up some answers: http://stackoverflow.com/questions/754865/view-compiler-mangled-names-in-c – Agnel Kurian Apr 16 '09 at 05:40
0

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
  • 8,722
  • 2
  • 37
  • 61
0

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

Bartosz Milewski
  • 11,012
  • 5
  • 36
  • 45
  • 1
    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 Apr 17 '09 at 00:13
  • 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 Apr 22 '09 at 18:09
  • 1
    Using vector is good, but it doesn't address the question of "where are my multiply defined symbols coming from?" – Clay Jul 07 '09 at 19:20