4

我在 Windows 上遇到了 CMake 生成的 DLL 文件的一个令人困惑的问题。在我的库中,我使用 Curiously Recurring Template Pattern 给某些类一个唯一的 ID 号:

// da/Attribute.h:

#ifndef DA_ATTRIBUTE_H
#define DA_ATTRIBUTE_H

namespace da {

typedef unsigned int AttributeId;

class AttributeBase {
public:
    virtual AttributeId getTypeId() const=0;

protected:
    /** Static ID counter.  Every class that derives da::AttributeBase is
        assigned an increment of this counter as its type ID number */
    static AttributeId sNextId;

};

template <class Derived>
class Attribute : public AttributeBase {
private:
    static AttributeId msTypeId;

public:
    Attribute() {
        if (msTypeId == 0) {
            msTypeId = ++sNextId;
        }
    }

    virtual ~Attribute() {

    }

    /** For static contexts */
    static AttributeId typeId() {
        if (msTypeId == 0) {
            msTypeId = ++sNextId;
        }

        return msTypeId;
    }

    AttributeId getTypeId() const {
        return typeId();
    }

};

template <class Derived> AttributeId Attribute<Derived>::msTypeId = 0;

}

#endif

问题是,当我将 DLL 链接到可执行项目时,不同的 ID 方法似乎存在一些不一致。例如:

// Foo.h
struct Foo : public da::Attribute<Foo> {
    Foo() { }
};

...

// main.cpp
Foo *foo = new Foo;    

Foo->getTypeId() == 1 // True
Foo::typeId() == 1 // Should be true, but isn't.  Foo::typeId() == 2

通过 GDB 运行,在 Foo::getTypeID() 中中断,我发现“msTypeId”和“Foo::msTypeId”具有不同的内存地址。 我勒个去。

不过,这只发生在 Foo 在 DLL 中定义时。(显然,仅在 Windows 7 中——我的 Debian 构建中没有这个问题)如果我在 main.cpp 中创建派生类,或者我只是将库中的所有代码编译到可执行文件中,跳过DLL 步骤完全,它可以正常工作。

一切都是使用 MSYS 和 MinGW 编译的,在 Windows 7 Home Premium 上使用 GCC 4.7。

这是图书馆的 CMakeLists.txt,以防我在那里搞砸了:

cmake_minimum_required(VERSION 2.6)
project(foo)

add_definitions(-std=c++0x)
set(CMAKE_BUILD_TYPE Debug)

set(sources
    Foo.cpp
)

add_library(foo SHARED ${sources})
4

1 回答 1

2

您必须从共享库中导出类型。这是使用__declspec(dllexport)__declspec(dllimport)装饰器完成的。通读 MSDN 文档;它相当复杂。

__declspec(dllexport)由于在构建库和编译使用它的代码时需要具有标头__declspec(dllimport),因此通常定义一个符号,通常称为LIBRARYNAME_EXPORT#ifdef,具体取决于是否LIBRARYNAME_EXPORTS定义。

CMaketarget_EXPORTS在构建(共享)库时自动定义。可以通过设置DEFINE_SYMBOL目标属性来覆盖它。

Unix 选择不同的路径,默认情况下导出和导入共享库中的所有符号(静态和显式隐藏的除外)。由于需要解析更多符号,这会导致一些性能损失,但它更易于使用(无需更改即可从静态切换到共享库)并且更灵活(即您可以覆盖共享库中的符号,这你不能在 Windows 中做)。

于 2012-10-22T10:56:10.950 回答