如何以与 g++ 和 Visual C++ 相同的方式为类定义 UUID 并使用 __uuidof?

2022-01-23 00:00:00 uuid winapi visual-c++ g++ c++

注意:这是一个question-with-answer,以便记录其他人可能认为有用的技术,以便了解其他人"更好的解决方案.请随意添加批评或问题作为评论.也请随意添加其他答案.:)

Visual C++ 一直有一个语言扩展 __uuidof(classname) 可以检索 UUID,一个 128 位的通用唯一标识符,前提是 UUID 已通过 __declspec 与类关联,这也是 Visual C++ 语言扩展:

Visual C++ has always had a language extension __uuidof(classname) that can retrieve an UUID, a 128 bit Universally Unique Identifier, provided that the UUID’s been associated with the class via __declspec, which also is a Visual C++ language extension:

#include <guiddef.h>        // GUID, another name for UUID

class
    __declspec( uuid( "290ff5cb-3a21-4740-bfda-2697ca13deae" ) )
    Foo
{};

#include <iostream>
using namespace std;

auto main()
    -> int
{
    cout << hex << __uuidof( Foo ).Data1 << endl;   // 290ff5cb
}

MinGW g++ 4.8.2(可能还有一些更早的版本)支持 __uuidof,但不支持 MSVC 的 __declspec.因此,使用 g++ 4.8.2 编译上述内容会失败,至少在我使用的 Nuwen 发行版中是这样.首先 g++ 发出警告 “'uuid' 属性指令被忽略”,然后得到一个链接器错误 “undefined reference to _GUID const&__mingw_uuidof<Foo>()”.

MinGW g++ 4.8.2 (and possibly some earlier versions) supports __uuidof, but it does not support MSVC’s __declspec. Compiling the above with g++ 4.8.2 therefore fails, at least with the Nuwen distribution that I use. First g++ issues the warning “'uuid' attribute directive ignored”, and then one gets a linker error “undefined reference to _GUID const& __mingw_uuidof<Foo>()”.

该错误提示 UUID 与 g++ 类关联的方式,即通过为该类专门化函数模板 __mingw_uuidof.

The error hints at the way an UUID is associated with a class for g++, namely by specializing the function template __mingw_uuidof for the class.

不幸的是,UUID 规范的形式与 Visual C++ 形式完全不同.它是数字,而不是字符串.而且它不是藏在 classstruct 关键字之后,而是在类的声明之后:

Unfortunately the UUID specification is then of a form that’s radically different from the Visual C++ form. It’s numbers, not a string. And it’s not tucked in after the class or struct keyword, but follows a declaration of the class:

#include <guiddef.h>        // GUID, another name for UUID

class Foo {};

template<>
auto __mingw_uuidof<Foo>()
    -> GUID const&
{
    static const GUID the_uuid = 
    {
        0x290ff5cb, 0x3a21, 0x4740,
        { 0xbf, 0xda, 0x26, 0x97, 0xca, 0x13, 0xde, 0xae }
    };
    return the_uuid;
}

#include <iostream>
using namespace std;

auto main()
    -> int
{
    cout << hex << __uuidof( Foo ).Data1 << endl;   // 290ff5cb
}

如何将 UUID 与一个类相关联,以便它可以与两种编译器一起使用?__uuidof,没有冗余,最好使用 UUID 作为直接的数字序列(与 Visual C++ 一样)?

How can one associate an UUID with a class so that it will work with both compilers’ __uuidof, without redundancy, and preferably with the UUID as a direct sequence of digits (as with Visual C++)?

推荐答案

预先免责声明:这一切都没有经过广泛的测试或审查.我刚写的.

这一事实提出了一种可能的统一方法:

One possible unification approach is suggested by this fact:

  • Visual C++ __declspec(uuid) 不需要与类的第一个声明一起提供:它可以在第一个声明之后应用.
  • A Visual C++ __declspec( uuid ) doesn’t need to be provided with the first declaration of a class: it can be applied after the first declaration.

例如Visual C++ 代码可能如下所示:

E.g. Visual C++ code can look like this:

class Foo
{};

class  __declspec( uuid( "290ff5cb-3a21-4740-bfda-2697ca13deae" ) ) Foo;

因此,which-compiler-is-it 嗅探宏 CPPX_UUID_FOR 可以定义为 …

Thus a which-compiler-is-it sniffing macro CPPX_UUID_FOR can be defined as …

#if !defined( CPPX_UUID_FOR )
#   if defined( _MSC_VER )
#       define CPPX_UUID_FOR    CPPX_MSVC_UUID_FOR
#   elif defined( __GNUC__ )
#       define CPPX_UUID_FOR    CPPX_GNUC_UUID_FOR
#   endif
#endif

并在类的第一个声明之后被调用:

and be invoked after the first declaration of the class:

#include <iostream>
using namespace std;

struct Foo {};
CPPX_UUID_FOR( Foo, "dbe41a75-d5da-402a-aff7-cd347877ec00" );

void test()
{
    using cppx::uuid::operator<<;
    cout << setw( 20 ) << "__uuidof: " << __uuidof( Foo ) << endl;
}

Visual C++ 的宏实现很简单:

The macro implementation for Visual C++ is trivial:

#define CPPX_MSVC_UUID_FOR( name, spec )    
    class __declspec( uuid( spec ) ) name

g++ 的宏实现有点复杂:

The macro implementation for g++ is a bit more involved:

#define CPPX_GNUC_UUID_FOR( name, spec )    
template<>                                  
inline                                      
auto __mingw_uuidof<name>()                 
    -> GUID const&                          
{                                           
    using cppx::operator"" _uuid;           
    static constexpr GUID the_uuid = spec ## _uuid; 
                                            
    return the_uuid;                        
}                                           
                                            
template<>                                  
inline                                      
auto __mingw_uuidof<name*>()                
    -> GUID const&                          
{ return __mingw_uuidof<name>(); }          
                                            
static_assert( true, "" )

…其中 static_assert 仅用于支持调用中的最后一个分号.

… where the static_assert only serves to support a final semicolon in the invocation.

用户定义的文字并不是绝对必要的,但我认为这样做很有趣.

The user defined literal is not strictly necessary, but I thought it was interesting to do it.

cppx::operator"" _uuid 是这样定义的,在命名空间 cppx:

cppx::operator"" _uuid is defined thusly, in namespace cppx:

namespace detail {
    CPPX_CONSTEXPR
    auto uuid_from_spec( char const* const s, size_t const size )
        -> cppx::Uuid
    {
        return (
            size == 36?   cppx::uuid::from(
                    reinterpret_cast<char const (&)[37]>( *s )
                            ) :
            cppx::fail(
                "An uuid spec must be 36 chars, like"
                " "dbe41a75-d5da-402a-aff7-cd347877ec00""
                )
            );
    }
}  // namespace detail

#if !(defined( _MSC_VER ) || defined( NO_USER_LITERALS ))
CPPX_CONSTEXPR
auto operator"" _uuid( char const* const s, size_t const size )
    -> cppx::Uuid
{ return detail::uuid_from_spec( s, size ); }
#endif

其中 cppx::uuid::from 之前在命名空间 cppx::uuid:

where cppx::uuid::from is defined earlier in namespace cppx::uuid:

inline CPPX_CONSTEXPR
auto from( char const (&spec)[37] )
    -> Uuid
{ return Initializable( ce, spec ); }

其中ce只是一个构造函数标记,枚举类型Const_expr,它选择constexpr构造函数uuid::Initializable 类:

where ce is just a constructor tag, of enumeration type Const_expr, that selects the constexpr constructor of the uuid::Initializable class:

struct Initializable: Uuid
{
    explicit CPPX_CONSTEXPR
    Initializable( Const_expr, char const (&spec)[37] )
        : Uuid( {
                // Data1
                (((((((((((((
                    static_cast<unsigned long>( nybble_from_hex( spec[0] ) )
                    << 4) | nybble_from_hex( spec[1] ))
                    << 4) | nybble_from_hex( spec[2] ))
                    << 4) | nybble_from_hex( spec[3] ))
                    << 4) | nybble_from_hex( spec[4] ))
                    << 4) | nybble_from_hex( spec[5] ))
                    << 4) | nybble_from_hex( spec[6] ))
                    << 4) | nybble_from_hex( spec[7] ),
                // Data2
                static_cast<unsigned short>(
                    (((((
                        static_cast<unsigned>( nybble_from_hex( spec[9] ) )
                        << 4) | nybble_from_hex( spec[10] ))
                        << 4) | nybble_from_hex( spec[11] ))
                        << 4) | nybble_from_hex( spec[12] )
                    ),
                // Data 3
                static_cast<unsigned short>(
                    (((((
                        static_cast<unsigned>( nybble_from_hex( spec[14] ) )
                        << 4) | nybble_from_hex( spec[15] ))
                        << 4) | nybble_from_hex( spec[16] ))
                        << 4) | nybble_from_hex( spec[17] )
                    ),
                // Data 4
                {
                    static_cast<unsigned char>( byte_from_hex( spec[19], spec[20] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[21], spec[22] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[24], spec[25] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[26], spec[27] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[28], spec[29] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[30], spec[31] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[32], spec[33] ) ),
                    static_cast<unsigned char>( byte_from_hex( spec[34], spec[35] ) )
                }
            } )
    {}

    explicit
    Initializable( char const (&spec)[37] )
        : Uuid()
    {
        for( int i = 0;  i < 8;  ++i )
        {
            Uuid::Data1 = (Uuid::Data1 << 4) | nybble_from_hex( spec[i] );
        }
        assert( spec[8] == '-' );
        for( int i = 9;  i < 13;  ++i )
        {
            Uuid::Data2 = (Uuid::Data2 << 4) | nybble_from_hex( spec[i] );
        }
        assert( spec[13] == '-' );
        for( int i = 14; i < 18;  ++i )
        {
            Uuid::Data3 = (Uuid::Data3 << 4) | nybble_from_hex( spec[i] );
        }
        assert( spec[18] == '-' );
        for( int i = 19; i < 23;  i += 2 )
        {
            Uuid::Data4[(i - 19)/2] = byte_from_hex( spec[i], spec[i + 1] );
        }
        assert( spec[23] == '-' );
        for( int i = 24; i < 36;  i += 2 )
        {
            Uuid::Data4[2 + (i - 24)/2] = byte_from_hex( spec[i], spec[i + 1] );
        }
    }
};

这两个构造函数的主要区别在于判断代码正确与否的难易程度,但最后一个(我第一个写的!)也有有用的 assert 语句.我不确定如何最好地为 constexpr 构造函数执行此类 assertions.甚至这是否可行,这也是为什么有两个构造函数而不是只有一个的原因之一.

The two constructors mainly differ in how easy it is to judge the correctness or not of the code, but the last one (I wrote that first!) also has useful assert statements. I'm not sure how to best do such assertions for the constexpr constructor. Or even whether that is doable, which is one reason why there are two constructors instead of just one.

哦,这里的 << 调用只是很好的旧左移,而不是花哨的自定义运算符符号输出或流或存储操作.:)

Oh, the << invocations here are just good old left-shifts, not fancy custom operator-notation output or stream or store operations. :)

nybble_from_hexbyte_from_hex 的定义很简单,但是 fail 函数有点微妙.尽管看起来它 not 是一个 constexpr 函数.相反,它是一个不返回函数.C++11 有一个表示法,[[noreturn]],但据我所知,Visual C++ 和 g++ 都不支持它.因此,我使用编译器特定的注释,如下所示:

The definitions of nybble_from_hex and byte_from_hex are pretty trivial, but the fail function is a bit subtle. In spite of appearances it’s not a constexpr function. Instead, it’s a non-returning function. C++11 has a notation to express that, [[noreturn]], but as far as I know neither Visual C++ nor g++ supports that yet. So instead I use compiler specific annotations, like this:

#if !defined( CPPX_NORETURN )
#   if defined( _MSC_VER )
#       define CPPX_NORETURN    __declspec( noreturn )
#   elif defined( __GNUC__ )
#       define CPPX_NORETURN    __attribute__((noreturn))
#   else
#       define CPPX_NORETURN    [[noreturn]]
#   endif
#endif

然后 fail 可以简单地编码为例如

and then fail can be coded up simply as e.g.

struct Whatever
{
    template< class Type >
    CPPX_CONSTEXPR operator Type () const { return Type(); }
};

inline
CPPX_NORETURN
auto fail( string const& s )
    -> Whatever
{ throw runtime_error( s ); }

我发现当 std::string 参数具有 std::string 参数时,将 fail 表示为 constexpr 函数并非易事(而且可能是不可能的),并且作为普通函数调用它抑制了 constexpr 属性.非返回变体适用于 g++.但是,我不确定标准对此有何规定.

I found it non-trivial (and possibly impossible) to express fail as a constexpr function when it has std::string argument, and as an ordinary function calls of it suppressed the constexpr property. The non-returning variant works OK with g++. However, I’m not sure what the standard has to say about this.

相关文章