2015-12-29 6 views
8

Im Moment baue ich Funktoren (Callable-Typen) für die verschiedenen Aufrufkonventionen (__stdcall, __cdecl, __fastcall etc.). Mit den Wrapper werde ich in der Lage sein, so etwas zu tun:Callable Objekte mit verschiedenen Aufrufkonventionen

void __stdcall foo(int arg) 
{ 
    std::printf("arg: %i\n", arg); 
} 

int main(int, char**) 
{ 
    Function<void, int> v{foo}; 
    v(1337); 

    return EXIT_SUCCESS; 
} 

Im Moment habe ich einen Wrapper für die Aufrufkonvention __stdcall aufgebaut haben, die jede __stdcall Funktion so lange zu nennen ist in der Lage, da die korrekten Parameter angegeben werden und die richtigen Argumente übergeben werden in der Klasse sieht wie folgt aus:.

template <typename ReturnT, typename... Args> 
class Function 
{ 
    // NOTE: This version of my callable types 
    // only supports the __stdcall calling 
    // convention. I need support for __cdecl, 
    // __fastcall and also __thiscall. 
    using return_t = ReturnT; 
    using callable_t = return_t(__stdcall*)(Args...); 

private: 
    callable_t mCallable; 

public: 
    template <typename FuncT> 
    Function(FuncT const &func) : 
     mCallable(func) 
    { 
     ; 
    } 

    void operator()(Args&&... args) 
    { 
     mCallable(std::forward<Args>(args)...); 
    } 
}; 

Damit in der Hand habe ich beschlossen, die anderen Wrapper zu bauen, aber ich dachte, dass das gleiche Stück Code eingeben und die Änderung der aufruf~~POS=TRUNC innerhalb von Die using-Deklaration für callable_t ist mehr Arbeit als nötig. Also wollte ich einen Weg finden, ungefähr 4 Varianten von aufrufbaren Typen zu erstellen (für jede Aufrufkonvention), aber ich konnte keinen Weg finden, dies zu tun.

Bisher habe ich versucht, eine ENUM als nicht-Typ Template-Parameter wie folgt zu verwenden:

template <CallingConvention Call, typename ReturnT, typename... ArgsT> 
class Function 
{ 
    // ... 
}; 

Aber ich weiß nicht, wie der Typ des Anruf Objekt iterieren und stellen den gewünschten Typ (I versuchte mit std :: is_same/std :: enable_if, aber das war eine Sackgasse). Ich habe auch versucht Template-Spezialisierung mit Code wie folgt:

struct StdcallT { ; }; 
struct CdeclT { ; }; 
struct FastcallT { ; }; 

template <typename CallT> 
struct BaseT { }; 

template <> struct BaseT<StdcallT> { using CallableT = void(__stdcall*)(); }; 
template <> struct BaseT<CdeclT> { using CallableT = void(__cdecl*)(); }; 
template <> struct BaseT<FastcallT> { using CallableT = void(__fastcall*)(); }; 

template <typename CallT> 
class Function 
{ 
    using CallableT = typename BaseT<CallT>::CallableT; 
}; 

Aber ich dachte nicht an den Rest der Argumente (Rückgabetyp + Parameter), so kann dies auch nicht funktionieren.

Also hat sowieso irgendwelche Ideen, was ich tun kann? Eine Methode, ich denke an einen Schalter auf den Nicht-Typ-Parameter zu tun und die richtigen wie folgen anrufen:

template <CallingConvention Call, typename ReturnT, typename... ArgsT> 
class Function 
{ 
    void operator()(ArgsT&&... args) 
    { 
     switch(Call) 
     { 
      case CallingConvention::Cdecl: 
       // Call a __cdecl version 
       break; 
      case CallingConvention::Stdcall: 
       // Call an __stdcall version 
       break; 
      // And so on... 
     } 
    } 
}; 

Und trotzdem sieht aus wie eine funktionierende Lösung ich mich gefragt, ob es einige gute Alternativen war, dass Daran denke ich nicht.

Irgendwelche Ideen?

+0

Warum das Rad neu erfinden? 'std :: function' sollte in der Lage sein, jeden Funktionszeiger oder Funktor unabhängig von der Aufrufkonvention zu speichern, solange sie mit den von Ihnen angegebenen Argumenten aufgerufen werden kann. Funktioniert es nicht für Sie, oder hat es einige konkrete Nachteile, die Sie zu Ihrer benutzerdefinierten Implementierung geführt haben? – hvd

+0

@hvd Nehmen wir an, Sie haben eine std :: unordered_map Mapping-Zeichenfolgen wie "glCreateProgram" und "glCreateShader" geladen von opengl32.dll zu ihren Symboladressen. Sie möchten eine Template-Funktion function_cast erstellen, die Sie void für no args/returns und std :: tuple für args/no args verwenden, da sie binärkompatibel sind. So wie es aussieht, funktioniert std: function nicht für stdcall, daher wird eine Laufzeitprüfung benötigt, wenn eine Dummy-Aufrufkonvention erforderlich ist, auf der Sie das Aufrufkonvention-Casting umschalten. – Dmitry

+0

@Dmitry "Std: Funktion funktioniert nicht für Stdcall" - Ältere Versionen von GCC (bis GCC 5) ignoriert die Aufrufkonvention in Name Mangling, Linker Fehler verursachen, aber das wurde Mitte 2015 behoben, und es sollte nicht Probleme mit "std :: function" für nicht standardmäßige Aufrufkonventionen seit dann zumindest in diesem Compiler. Andere Compiler * sollten auf die gleiche Weise arbeiten. Wenn nicht, können Sie angeben, um welche es sich handelt? – hvd

Antwort

1

Wenn Sie weiterhin das Argument enumerated template verwenden möchten, können Sie dies mithilfe der Spezialisierung erreichen.

enum CallingConvention { __stdcall, ... }; 

template < CallingConvention Call > 
struct implement { 
    template</* Template arguments for call method */> 
    static ReturnT call(/* arguments to run method */); 
}; 

template < CallingConvention Call, typename ReturnT, typename... ArgsT > 
class Function 
{ 
    // ... 
    template <typename FuncT> 
    Function(FuncT const &func) : mCallable(func), mCall(Call) {} 
    CallingConvention const mCall; 

    return_t operator()(ArgsT&&... args) { 
     return implement<Call>::call</* Template arguments for call method */>(/* arguments to run method */); 
    }; 
}; 

template < > 
struct implement<__stdcall> { 
    template</* Template arguments for call method */> 
    static ReturnT call(/* arguments to run method */) { 
     // Special implementation... 
    } 
}; 

Das ist besser als eine switch-Anweisung.

(Sorry über die Kommentare für die Vorlage Argumente bin ich nicht sehr vertraut mit, wie das funktioniert)

Hier ist, wo ich die Idee für das, was ich did bekam.


Hoffe, das hilft!

1

Nun, wenn Sie Tags für jede Aufrufkonvention definieren, können Sie Tag-Versand regelmäßig verwenden:

#include <iostream> 
#include <type_traits> 

struct cdecl_tag { typedef void (__attribute__((cdecl)) *type)(); }; 
struct stdcall_tag { typedef void (__attribute__((stdcall)) *type)(); }; 
struct fastcall_tag { typedef void (__attribute__((fastcall)) *type)(); }; 

constexpr void get_func_calling_convention_tag() {}; 

template<typename R, typename... Args> 
constexpr cdecl_tag 
get_func_calling_convention_tag (R (__attribute__((cdecl)) *)(Args...)) 
{ return {}; } 

template<typename R, typename... Args> 
constexpr stdcall_tag 
get_func_calling_convention_tag (R (__attribute__((stdcall)) *)(Args...)) 
{ return {}; } 

template<typename R, typename... Args> 
constexpr fastcall_tag 
get_func_calling_convention_tag (R (__attribute__((fastcall)) *)(Args...)) 
{ return {}; } 

#define CALLING_CONVENTION_TAG(func) \ 
decltype(get_func_calling_convention_tag(&func)) 

int __attribute__((cdecl)) foo (char) { return 0; } 
long __attribute__((stdcall)) bar (int) { return 0; } 

int main() 
{ 
    std::cout << std::is_same<CALLING_CONVENTION_TAG(foo), 
           cdecl_tag>::value     << '\n' 
       << std::is_same<CALLING_CONVENTION_TAG(bar), 
           stdcall_tag>::value     << '\n' 
       << std::is_same<CALLING_CONVENTION_TAG(foo), 
           CALLING_CONVENTION_TAG(bar)>::value << std::endl; 

    return 0; 
} 

Sehen sie in Aktion: http://ideone.com/HSZztX
Dies kann natürlich weiterentwickelt werden; Die Tags können eine variadische Mitgliedschaftsvorlage Rebind enthalten, die einen Funktionszeigertyp mit der entsprechenden Aufrufkonvention zurückgibt.

Ich nehme an, Sie können sogar das Kopieren und Einfügen reduzieren, indem Sie Tagdefinitionen in einem Makro sauber haben.

Verwandte Themen