Git Product home page Git Product logo

Comments (12)

rollbear avatar rollbear commented on August 12, 2024

Hmmm.

For gcc and clang, this already works since they support having the attribute at the end, so you can write:

MAKE_MOCK1(func, int(int), __attribute__((cdecl)));

MSVC makes it more difficult since it wants the attribute in an odd location.

Most common use of mocks is to mock virtual functions from an interface, and since these attributes specify the calling convention, I would expect the attribute to be applied to base function declaration, and that inherited implementations would have to follow it. Is this incorrect? Or is this not the desired use case?

https://godbolt.org/z/q7634G7Yo

from trompeloeil.

puetzk avatar puetzk commented on August 12, 2024

MSVC makes it more difficult since it wants the attribute in an odd location.

Indeed, that's the motive of this issue. MSVC wants calling conventions between the return type and the identifier. You're quite correct that gcc/clang are more flexible in the positioning of __attribute__,

Most common use of mocks is to mock virtual functions from an interface, and since these attributes specify the calling convention, I would expect the attribute to be applied to base function declaration, and that inherited implementations would have to follow it. Is this incorrect? Or is this not the desired use case?

That's the desired use case, but the inherited implementations "have to follow it", and don't do so implicitly. If the override fails to match calling convention, you get a compile error C2695: 'Foo::foo': overriding virtual function differs from 'IFoo::foo' only by calling convention (https://godbolt.org/z/vsvT8PcGs).

GCC is the same here - your example only works because __attribute__((cdecl)) was the default anyway. If you change it to __attribute__((stdcall)) or thiscall or anything non-default, you get "error: conflicting type attributes specified for ..." just like MSVC. So this "mock virtual functions from an interface" requires MAKE_MOCK* to declare its implementation with the correct calling convention.

from trompeloeil.

rollbear avatar rollbear commented on August 12, 2024

Sigh. So implementers go out of their way to make their user's lives miserable, when there is an obvious and simple solution. Great!

I'll try, but I will not make any promises.

from trompeloeil.

puetzk avatar puetzk commented on August 12, 2024

I think the most natural place (given that both implementations consider it to be part of the signature) for override matching) is that all 3 compilers (MSVC/g++/clang) allow calling conventions to appear in a function type, e.g. MAKE_MOCK1(func, int __stdcall(int));

You can detect the qualifiers in the usual type-traits sort of way (I'm not aware of a standard trait for this, but you can implement one in the usual way with specializations).

https://godbolt.org/z/eqqrrv1hG

#include <type_traits>

#ifdef __GNUC__
#define __stdcall __attribute__((stdcall))
#endif

template<typename F> struct is_stdcall;
template<typename R, typename... Args> struct is_stdcall<R(Args...)> : std::false_type {};
template<typename R, typename... Args> struct is_stdcall< R __stdcall(Args...)> : std::true_type {};

static_assert(is_stdcall<int __stdcall(int)>::value);
static_assert(!is_stdcall<int(int)>::value);

from trompeloeil.

puetzk avatar puetzk commented on August 12, 2024

Hmm, though this provokes a warning "stdcall' attribute ignored [-Wattributes]" from gcc on x64 (cdecl and stdcall are the same on x86_64, though different on x86). And that also makes an is_stdcall trait built from specializations ambiguous. Grr... easy enough to work around, but getting less tidy...

from trompeloeil.

puetzk avatar puetzk commented on August 12, 2024

We could skirt closer to Abominable Function Types and actually use sig directly as the function type (also avoiding return_of_t<TROMPELOEIL_REMOVE_PAREN(sig)>, TROMPELOEIL_PARAM_LIST

It seems to be OK if only the declaration (and not definition) mentions the calling convention, so one can do

using sig = int __stdcall (int);
struct IFoo {
    virtual sig foo = 0;
};

struct CFoo : IFoo {
    sig foo;
};

int CFoo::foo(int x) { return x; }

and you do get the __stdcall (callee-clean) ABI applied.

Which would be nice, since that would let you just use sig as the function type, and no even have to take it apart with return_of_t and TROMPELOEIL_PARAM_LIST. Even constness can be absorbed into that, though you're then dealing with Abominable Function Types (legal, but very weird).

But I don't know of any way to combine the declaration and definition when using a function type like this. And a macro like MAKE_MOCK needs to keep everything inlined since it can't easily emit a definition later.

from trompeloeil.

puetzk avatar puetzk commented on August 12, 2024

Starting to see why gmock needed you to wrap these attributes in a Calltype(...) so it could do token-pasting games to separate them from the other __VA_ARGS__...

from trompeloeil.

rollbear avatar rollbear commented on August 12, 2024

After thinking about this, I'm leaning towards not supporting it in the library per se. You can achieve what you want with the traditional extra level of indirection. Make a thin function, with whatever attributes you need, and have it call a mock. I can maybe add an example of this usage in the docs.

from trompeloeil.

puetzk avatar puetzk commented on August 12, 2024

Hmm. I assume you mean doing something like Mocking operator(). That's clearly possible, but pretty inconvenient if you have to do it for every method, because you can't overload by calling convention. You have to come renaming every method, e.g.

class A {
    int __stdcall foo(int x) { return cdecl_foo(x); }
    MAKE_MOCK1(cdecl_foo, int(int));
};

Which then means all the code setting expectations, etc has to use that cdecl_foo name rather than the real foo, and all the error messages will also show this. It's certainly possible, but rather clunky (of course, MSVC really gets the blame for making the whole thing unnecessarily awkward by not letting you just put callconv in the spec position).

from trompeloeil.

puetzk avatar puetzk commented on August 12, 2024

It seems easy enough to support the addition in TROMPELOEIL_MAKE_MOCK_ as @cesenaLA proposed, callconv is just another field like sig or spec that or might not might be empty, and gets expanded into its correct place in the declaration between return_of_t and name.

The hard part is the rest - how to sort a calling convention out of the __VA_ARGS__. Which is presumably the same reason you ended up with MAKE_CONST_MOCK0 instead being able to do MAKE_MOCK0(foo,void(), const)). But of course having more than just const/not-const quickly leads to a combinatorial explosion of different qualifiers.

But... in the specific case we hit (implementing COM interfaces defined in .idl/.tlb files), there's actually no such thing as const, so there's no need for the permutations. So I was being narrowly selfish, it would be quite satisfactory to just have the low-level support and then the 16 MAKE_STDMETHOD_MOCKn(name,...) forwards that called TROMPELOEIL_MAKE_MOCK_(name,,STDMETHODCALLTYPE,n, __VA_ARGS__,,) (picking STDMETHOD as the name for this limited subset by analogy to The macros for declaring and implementing COM interfaces / basetyps.h

Which does still add a fair amount of clutter (the 16 MAKE_STDMETHOD_MOCKn forwards, their #ifndef TROMPELOEIL_LONG_MACROS short aliases, etc) to mock.hpp - though I guess you could further add only the new argument to TROMPELOEIL_MAKE_MOCK_ and then exile the rest to mock_callconv.hpp or something (to avoid inflicting compile time overhead on folks who have no need for it). But it seems straightforward clutter...

Sketched this in main...puetzk:trompeloeil:STDMETHOD and I at least didn't break TROMPELOEIL_BUILD_TESTS=ON. Unless you immediately hate it I or @cesenaLA will at least give this a shot on the test code he was trying to adapt from googletest to Catch2/trompeloeil; I think it should cover what COM interfaces need (which was his motivating use).

from trompeloeil.

rollbear avatar rollbear commented on August 12, 2024

OK, cool. I'll have a look. I'm a bit busy the next few days. Remind me if I haven't come back to you some time next week.

from trompeloeil.

rollbear avatar rollbear commented on August 12, 2024

Sorry for the delay. From a short glance, your sketch looks OK. Can you make a PR?

from trompeloeil.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.