extern "C" 解压缩导致奇怪的链接器错误

extern "C" Demangling Causing Strange Linker Errors

我有以下头文件

possion_surface_reconstructor.h

#ifndef POISSON_SURFACE_RECONSTRUCTOR_H
#define POISSON_SURFACE_RECONSTRUCTOR_H

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <iostream>
#include <boost/format.hpp>

#include "../core/PreProcessor.h"
#include "../core/MyMiscellany.h"
#include "../core/CmdLineParser.h"
#include "../core/PPolynomial.h"
#include "../core/FEMTree.h"
#include "../core/Ply.h"
#include "../core/PointStreamData.h"
#include "../core/Image.h"

#include "callbacks.h"

namespace MeshingAdapter
{

    #undef USE_DOUBLE                               // If enabled, double-precesion is used
    #define DATA_DEGREE 0                           // The order of the B-Spline used to splat in data for color interpolation
    #define WEIGHT_DEGREE 2                         // The order of the B-Spline used to splat in the weights for density estimation
    #define NORMAL_DEGREE 2                         // The order of the B-Spline used to splat in the normals for constructing the Laplacian constraints
    #define DEFAULT_FEM_DEGREE 1                    // The default finite-element degree
    #define DEFAULT_FEM_BOUNDARY BOUNDARY_NEUMANN   // The default finite-element boundary type
    #define DIMENSION 3     

    cmdLineParameter< char* >
        In("in"),
        Out("out"),
        TempDir("tempDir"),
        Grid("grid"),
        Tree("tree"),
        Transform("xForm");

    cmdLineReadable
        Performance("performance"),
        ShowResidual("showResidual"),
        NoComments("noComments"),
        PolygonMesh("polygonMesh"),
        NonManifold("nonManifold"),
        ASCII("ascii"),
        Density("density"),
        LinearFit("linearFit"),
        PrimalGrid("primalGrid"),
        ExactInterpolation("exact"),
        Normals("normals"),
        Colors("colors"),
        InCore("inCore"),
        Verbose("verbose");

    cmdLineParameter< int >
#ifndef FAST_COMPILE
        Degree("degree", DEFAULT_FEM_DEGREE),
#endif // !FAST_COMPILE
        Depth("depth", 8),
        KernelDepth("kernelDepth"),
        Iters("iters", 8),
        FullDepth("fullDepth", 5),
        BaseDepth("baseDepth", 0),
        BaseVCycles("baseVCycles", 1),
#ifndef FAST_COMPILE
        BType("bType", DEFAULT_FEM_BOUNDARY + 1),
#endif // !FAST_COMPILE
        MaxMemoryGB("maxMemory", 0),
#ifdef _OPENMP
        ParallelType("parallel", (int)ThreadPool::OPEN_MP),
#else // !_OPENMP
        ParallelType("parallel", (int)ThreadPool::THREAD_POOL),
#endif // _OPENMP
        ScheduleType("schedule", (int)ThreadPool::DefaultSchedule),
        ThreadChunkSize("chunkSize", (int)ThreadPool::DefaultChunkSize),
        Threads("threads", (int)std::thread::hardware_concurrency());

    cmdLineParameter< float >
        DataX("data", 32.f),
        SamplesPerNode("samplesPerNode", 1.5f),
        Scale("scale", 1.1f),
        Width("width", 0.f),
        Confidence("confidence", 0.f),
        ConfidenceBias("confidenceBias", 0.f),
        CGSolverAccuracy("cgAccuracy", 1e-3f),
        PointWeight("pointWeight");

    cmdLineReadable* params[] =
    {
    #ifndef FAST_COMPILE
        &Degree,
        &BType,
    #endif // !FAST_COMPILE
        &In,
        &Depth,
        &Out,
        &Transform,
        &Width,
        &Scale,
        &Verbose,
        &CGSolverAccuracy,
        &NoComments,
        &KernelDepth,
        &SamplesPerNode,
        &Confidence,
        &NonManifold,
        &PolygonMesh,
        &ASCII,
        &ShowResidual,
        &ConfidenceBias,
        &BaseDepth,
        &BaseVCycles,
        &PointWeight,
        &Grid,
        &Threads,
        &Tree,
        &Density,
        &FullDepth,
        &Iters,
        &DataX,
        &Colors,
        &Normals,
        &LinearFit,
        &PrimalGrid,
        &TempDir,
        &ExactInterpolation,
        &Performance,
        &MaxMemoryGB,
        &InCore,
        &ParallelType,
        &ScheduleType,
        &ThreadChunkSize,
        NULL
    };

    class PoissonSurfaceReconstructor
    {
    public:
        PoissonSurfaceReconstructor(
            ProgressCallback& progressCallback,
            WarningCallback& warningCallback,
            ErrorCallback& errorCallback);

        bool ExecuteSurfaceReconstruction(int argc, char* argv[]);

    private:
        template< unsigned int Dim, class Real >
        struct FEMTreeProfiler;

        template< unsigned int Dim, typename Real >
        struct ConstraintDual;

        template< unsigned int Dim, typename Real >
        struct SystemDual;

        template< unsigned int Dim >
        struct SystemDual< Dim, double >;

        template< class Real, typename ... SampleData, unsigned int ... FEMSigs >
        int Execute(int argc, char* argv[], UIntPack< FEMSigs ... >);

        template< unsigned int Dim, class Real, typename ... SampleData >
        int Execute(int argc, char* argv[]);

        template< typename Real, unsigned int Dim >
        int WriteGrid(ConstPointer(Real) values, int res, const char* fileName);

        template< typename Vertex, typename Real, typename SetVertexFunction, unsigned int ... FEMSigs, typename ... SampleData >
        int ExtractMesh(
            UIntPack< FEMSigs ... >,
            std::tuple< SampleData ... >,
            FEMTree< sizeof ... (FEMSigs), Real >& tree,
            const DenseNodeData< Real, UIntPack< FEMSigs ... > >& solution,
            Real isoValue,
            const std::vector< typename FEMTree< sizeof ... (FEMSigs),
            Real >::PointSample >* samples,
            std::vector< MultiPointStreamData< Real, PointStreamNormal< Real, DIMENSION >,
            MultiPointStreamData< Real, SampleData ... > > >* sampleData,
            const typename FEMTree< sizeof ... (FEMSigs), Real >::template DensityEstimator< WEIGHT_DEGREE >* density,
            const SetVertexFunction& SetVertex,
            std::vector< std::string >& comments,
            XForm< Real, sizeof...(FEMSigs) + 1 > iXForm);

        template< class Real, unsigned int Dim >
        XForm< Real, Dim + 1 > GetBoundingBoxXForm(Point< Real, Dim > min, Point< Real, Dim > max, Real scaleFactor);

        template< class Real, unsigned int Dim >
        XForm< Real, Dim + 1 > GetBoundingBoxXForm(
            Point< Real, Dim > min,
            Point< Real, Dim > max,
            Real width,
            Real scaleFactor,
            int& depth);

        template< class Real, unsigned int Dim >
        XForm< Real, Dim + 1 > GetPointXForm(InputPointStream< Real, Dim >& stream, Real width, Real scaleFactor, int& depth);

        template< class Real, unsigned int Dim >
        XForm< Real, Dim + 1 > GetPointXForm(InputPointStream< Real, Dim >& stream, Real scaleFactor);

        template<typename... Arguments>
        std::string FormatString(const std::string& fmt, const Arguments&... args);

        double Weight(double v, double start, double end);

        const float DefaultPointWeightMultiplier = 2.f;

        ProgressCallback _progressCallback;
        WarningCallback _warningCallback;
        ErrorCallback _errorCallback;
    };
}
#endif // POISSON_SURFACE_RECONSTRUCTOR_H

支持这个class 编译好的.cpp 文件。我可以将此代码编译为 .exe 并进行一些调整,这就是我想要的。但是,我需要从 C# 调用此代码,因此我创建了一个可从 C#

调用的包装器 class

configuration.h

#ifndef MESHING_DLL_CONFIG_H
#define MESHING_DLL_CONFIG_H

#if defined(_MSC_VER)
#  define LIBRARY_EXPORT __declspec(dllexport)
#  define LIBRARY_IMPORT __declspec(dllimport)
#elif defined(__GNUC__) && __GNUC__ > 3
#  define LIBRARY_EXPORT __attribute__((visibility("default")))
#  define LIBRARY_IMPORT __attribute__((visibility("default")))
#else
#  define LIBRARY_EXPORT
#  define LIBRARY_IMPORT
#endif

#ifdef LIBRARY_API_EXPORTS
#  define LIBRARY_API LIBRARY_EXPORT
#elif LIBRARY_API_IMPORTS
#  define LIBRARY_API LIBRARY_IMPORT
#else
#  define LIBRARY_API
#endif
#endif /* MESHING_DLL_CONFIG_H */

wrappers.h

#ifndef MESHING_WRAPPERS_H
#define MESHING_WRAPPERS_H

#include <exception>
#include "configuration.h"
#include "callbacks.h"
#include "poisson_surface_reconstructor.h"

typedef intptr_t ArrayHandle;

extern "C"
{
    LIBRARY_EXPORT bool ExecutePoissonSurfaceReconstruction(
        int argc, 
        char* argv[], 
        ProgressCallback progressCallback, 
        WarningCallback warningCallback, 
        ErrorCallback errorCallback);

    LIBRARY_EXPORT bool Release(ArrayHandle arrayHandle);
}
#endif // MESHING_WRAPPERS_H

wrappers.cpp

#include "wrappers.h"

using namespace MeshingAdapter;

LIBRARY_EXPORT bool ExecutePoissonSurfaceReconstruction(
    int argc,
    char* argv[],
    ProgressCallback progressCallback, 
    WarningCallback warningCallback, 
    ErrorCallback errorCallback)
{ 
    try
    {
        PoissonSurfaceReconstructor* poissonSurfaceRecon =
            new PoissonSurfaceReconstructor(
                progressCallback,
                warningCallback,
                errorCallback);
        bool success = poissonSurfaceRecon->ExecuteSurfaceReconstruction(argc, argv);
        delete poissonSurfaceRecon;
        return success;
    }
    catch (const std::exception& ex)
    {
        errorCallback(ex.what());
        return false;
    }
}

LIBRARY_EXPORT bool Release(ArrayHandle arrayHandle) 
{ 
    // #TODO can we do this smarter?
    return true;
}

但是,包含 possion_surface_reconstructor.h 会导致 114 LNK2005 个错误,主要来自 classes 我从不同的项目中引用。一个例子是

2>wrappers.obj : error LNK2005: "private: static bool ThreadPool::_Close" (?_Close@ThreadPool@@0_NA) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "public: static unsigned __int64 ThreadPool::DefaultChunkSize" (?DefaultChunkSize@ThreadPool@@2_KA) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "private: static class std::vector<class std::thread,class std::allocator<class std::thread> > ThreadPool::_Threads" (?_Threads@ThreadPool@@0V?$vector@Vthread@std@@V?$allocator@Vthread@std@@@2@@std@@A) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "private: static class std::condition_variable ThreadPool::_DoneWithWork" (?_DoneWithWork@ThreadPool@@0Vcondition_variable@std@@A) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "public: static int const (* HyperCube::MarchingSquares::edges)[5]" (?edges@MarchingSquares@HyperCube@@2QAY04$$CBHA) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "char const * * type_names" (?type_names@@3PAPEBDA) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "char const * * BoundaryNames" (?BoundaryNames@@3PAPEBDA) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "char const * * ShowGlobalResidualNames" (?ShowGlobalResidualNames@@3PAPEBDA) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "private: static unsigned int volatile ThreadPool::_RemainingTasks" (?_RemainingTasks@ThreadPool@@0IC) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "public: static char const * const StackTracer::exec" (?exec@StackTracer@@2PEBDEB) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "public: static char const * const ImageWriterParams::DefaultTileExtension" (?DefaultTileExtension@ImageWriterParams@@2PEBDEB) already defined in poisson_surface_reconstructor.obj
2>wrappers.obj : error LNK2005: "public: static enum ThreadPool::ScheduleType ThreadPool::DefaultSchedule" (?DefaultSchedule@ThreadPool@@2W4ScheduleType@1@A) already defined in poisson_surface_reconstructor.obj

还有更多。从上面的错误列表中举一个例子 ThreadPool::DefaultSchedule 这是在我引用的包含文件之一中定义的,并且是我的代码库的一部分。此 struct 定义为

struct ThreadPool
{
    enum ParallelType
    {
#ifdef _OPENMP
        OPEN_MP ,
#endif // _OPENMP
        THREAD_POOL ,
        ASYNC ,
        NONE
    };
    static const std::vector< std::string > ParallelNames;

    enum ScheduleType
    {
        STATIC ,
        DYNAMIC
    };
    static const std::vector< std::string > ScheduleNames;

    static size_t DefaultChunkSize;
    static ScheduleType DefaultSchedule;

    template< typename ... Functions >
    static void ParallelSections( const Functions & ... functions )
    {
        std::vector< std::future< void > > futures( sizeof...(Functions) );
        _ParallelSections( &futures[0] , functions ... );
        for( size_t t=0 ; t<futures.size() ; t++ ) futures[t].get();
    }

    static void Parallel_for( size_t begin , size_t end , const std::function< void ( unsigned int , size_t ) > &iterationFunction , ScheduleType schedule=DefaultSchedule , size_t chunkSize=DefaultChunkSize )
    {
        if( begin>=end ) return;
        size_t range = end - begin;
        size_t chunks = ( range + chunkSize - 1 ) / chunkSize;
        unsigned int threads = (unsigned int)NumThreads();
        std::atomic< size_t > index;
        index.store( 0 );


        if( range<chunkSize || _ParallelType==NONE || threads==1 )
        {
            for( size_t i=begin ; i<end ; i++ ) iterationFunction( 0 , i );
            return;
        }

        auto _ChunkFunction = [ &iterationFunction , begin , end , chunkSize ]( unsigned int thread , size_t chunk )
        {
            const size_t _begin = begin + chunkSize*chunk;
            const size_t _end = std::min< size_t >( end , _begin+chunkSize );
            for( size_t i=_begin ; i<_end ; i++ ) iterationFunction( thread , i );
        };
        auto _StaticThreadFunction = [ &_ChunkFunction , chunks , threads ]( unsigned int thread )
        {
            for( size_t chunk=thread ; chunk<chunks ; chunk+=threads ) _ChunkFunction( thread , chunk );
        };
        auto _DynamicThreadFunction = [ &_ChunkFunction , chunks , &index ]( unsigned int thread )
        {
            size_t chunk;
            while( ( chunk=index.fetch_add(1) )<chunks ) _ChunkFunction( thread , chunk );
        };

        if     ( schedule==STATIC  ) _ThreadFunction = _StaticThreadFunction;
        else if( schedule==DYNAMIC ) _ThreadFunction = _DynamicThreadFunction;

        if( false ){}
#ifdef _OPENMP
        else if( _ParallelType==OPEN_MP )
        {
            if( schedule==STATIC )
#pragma omp parallel for num_threads( threads ) schedule( static , 1 )
                for( int c=0 ; c<chunks ; c++ ) _ChunkFunction( omp_get_thread_num() , c );
            else if( schedule==DYNAMIC )
#pragma omp parallel for num_threads( threads ) schedule( dynamic , 1 )
                for( int c=0 ; c<chunks ; c++ ) _ChunkFunction( omp_get_thread_num() , c );
        }
#endif // _OPENMP
        else if( _ParallelType==ASYNC )
        {
            static std::vector< std::future< void > > futures;
            futures.resize( threads-1 );
            for( unsigned int t=1 ; t<threads ; t++ ) futures[t-1] = std::async( std::launch::async , _ThreadFunction , t );
            _ThreadFunction( 0 );
            for( unsigned int t=1 ; t<threads ; t++ ) futures[t-1].get();
        }
        else if( _ParallelType==THREAD_POOL )
        {
            unsigned int targetTasks = 0;
            if( !SetAtomic( &_RemainingTasks , threads-1 , targetTasks ) )
            {
                WARN( "nested for loop, reverting to serial" );
                for( size_t i=begin ; i<end ; i++ ) iterationFunction( 0 , i );
            }
            else
            {
                _WaitingForWorkOrClose.notify_all();
                {
                    std::unique_lock< std::mutex > lock( _Mutex );
                    _DoneWithWork.wait( lock , [&]( void ){ return _RemainingTasks==0; } );
                }
            }
        }
    }

    static unsigned int NumThreads( void ){ return (unsigned int)_Threads.size()+1; }

    static void Init( ParallelType parallelType , unsigned int numThreads=std::thread::hardware_concurrency() )
    {
        _ParallelType = parallelType;
        if( _Threads.size() && !_Close )
        {
            _Close = true;
            _WaitingForWorkOrClose.notify_all();
            for( unsigned int t=0 ; t<_Threads.size() ; t++ ) _Threads[t].join();
        }
        _Close = true;
        numThreads--;
        _Threads.resize( numThreads );
        if( _ParallelType==THREAD_POOL )
        {
            _RemainingTasks = 0;
            _Close = false;
            for( unsigned int t=0 ; t<numThreads ; t++ ) _Threads[t] = std::thread( _ThreadInitFunction , t );
        }
    }
    static void Terminate( void )
    {
        if( _Threads.size() && !_Close )
        {
            _Close = true;
            _WaitingForWorkOrClose.notify_all();
            for( unsigned int t=0 ; t<_Threads.size() ; t++ ) _Threads[t].join();
            _Threads.resize( 0 );
        }
    }
private:
    ThreadPool( const ThreadPool & ){}
    ThreadPool &operator = ( const ThreadPool & ){ return *this; }

    template< typename Function >
    static void _ParallelSections( std::future< void > *futures , const Function &function ){ *futures = std::async( std::launch::async , function ); }
    template< typename Function , typename ... Functions >
    static void _ParallelSections( std::future< void > *futures , const Function &function , const Functions& ... functions )
    {
        *futures = std::async( std::launch::async , function );
        _ParallelSections( futures+1 , functions ... );
    }
    static void _ThreadInitFunction( unsigned int thread )
    {
        // Wait for the first job to come in
        std::unique_lock< std::mutex > lock( _Mutex );
        _WaitingForWorkOrClose.wait( lock );
        while( !_Close )
        {
            lock.unlock();
            // do the job
            _ThreadFunction( thread );

            // Notify and wait for the next job
            lock.lock();
            _RemainingTasks--;
            if( !_RemainingTasks ) _DoneWithWork.notify_all();
            _WaitingForWorkOrClose.wait( lock );
        }
    }

    static bool _Close;
    static volatile unsigned int _RemainingTasks;
    static std::mutex _Mutex;
    static std::condition_variable _WaitingForWorkOrClose , _DoneWithWork;
    static std::vector< std::thread > _Threads;
    static std::function< void ( unsigned int ) > _ThreadFunction;
    static ParallelType _ParallelType;
};

很明显,static ScheduleType DefaultSchedule; 是一个静态变量导致了一个问题,其他错误很可能是由同样的问题引起的(这不是我最初的代码,我需要将它包装起来以暴露给 C#) .

  1. 对于 ThreadPool 的情况,为什么我的 PossionSurfaceReconstructor class 的成员值类型 struct ThreadPool 会导致此问题?
  2. 最简单的解决方法是什么(不将每个错误实例的 struct 更改为 class

提前致谢。

#include 指令实质上是在 #include 出现的位置执行 text-level 复制和粘贴指定文件的内容。您的 header MyMiscellany.h 包含一堆静态成员变量的定义:

size_t ThreadPool::DefaultChunkSize = 128;
ThreadPool::ScheduleType ThreadPool::DefaultSchedule = ThreadPool::DYNAMIC;
bool ThreadPool::_Close;
volatile unsigned int ThreadPool::_RemainingTasks;
std::mutex ThreadPool::_Mutex;
std::condition_variable ThreadPool::_WaitingForWorkOrClose;
std::condition_variable ThreadPool::_DoneWithWork;
std::vector< std::thread > ThreadPool::_Threads;
std::function< void ( unsigned int ) > ThreadPool::_ThreadFunction;
ThreadPool::ParallelType ThreadPool::_ParallelType;

一旦这个header包含在多个.cpp文件中,这些变量的定义就会不止一个。一个程序[basic.def.odr]/4中的non-inline函数或变量只能有一个定义,这正是链接器所抱怨的:

2>wrappers.obj : error LNK2005: "private: static bool ThreadPool::_Close" (?_Close@ThreadPool@@0_NA) already defined in poisson_surface_reconstructor.obj
[…]

这个错误信息只是告诉你 ThreadPool::_Close 等,在 wrappers.obj 中出现了一个定义,在 poisson_surface_reconstructor.obj 中已经有一个定义。 poisson_surface_reconstructor.cppwrappers.cpp(间接)都包括 MyMiscellany.h,因此,编译这两个 .cpp 文件产生的两个 object 文件中的每一个都包含所有这些的定义变量。当定义包含 MyMiscellany.h.

时,它们被粘贴到每个 .cpp 文件中

要解决这个问题,您可以使有问题的变量内联(需要 C++17),在这种情况下,您还可以将定义保留在 class 中,而不必定义变量在 class

之外
class ThreadPool
{
    …
    inline static bool Close = true;
    …
};

或将定义移动到单独的 .cpp 文件(另请参阅 this question 了解更多信息)。


注意:extern "C"与拆解任何东西都没有关系。它所做的只是声明所讨论的实体应具有 C 语言链接,无论这在目标平台上意味着什么。在 Windows 上,例如 even C linkage may still involve name mangling

此外,我建议用 std::atomic.

替换您的 volatile 变量 _RemainingTasks 和自定义 SetAtomic 实现

最后,请注意包含双下划线的标识符以及以下划线开头后跟大写字母的标识符(例如 _Close)是保留的 [lex.name]/3。实际上不允许在代码中使用这样的标识符。