Was ist der beste Weg, um einen Funktionszeiger von einem Typ in einen anderen umzuwandeln?

Lesezeit: 7 Minuten

Benutzer-Avatar
Adrian Mol

Ich habe Stack Overflow nach einer Antwort durchsucht, aber ich bekomme nichts Spezifisches zu diesem Problem: nur allgemeine Fälle zur Verwendung verschiedener Arten von Cast-Operatoren. Der typische Fall ist also das Abrufen einer Funktionsadresse mit Windows GetProcAddress() API-Aufruf, der einen Funktionszeiger vom Typ zurückgibt FARPROCmit: typedef INT_PTR (__stdcall *FARPROC)();.

Das Problem ist, dass die tatsächlich gesuchte Funktion selten (wenn überhaupt) diese tatsächliche Signatur hat, wie im MRCE-Code unten gezeigt. In diesem Code habe ich verschiedene Versuche gezeigt, den zurückgegebenen Wert in einen Funktionszeiger des entsprechenden Typs zu konvertieren, wobei alle außer der vierten Methode auskommentiert sind:

#include <Windows.h>
#include <iostream>

typedef DPI_AWARENESS_CONTEXT(__stdcall* TYPE_SetDPI)(DPI_AWARENESS_CONTEXT); // Function pointer typedef
static DPI_AWARENESS_CONTEXT __stdcall STUB_SetDpi(DPI_AWARENESS_CONTEXT) { return nullptr; } // Dummy 'stub' function
static DPI_AWARENESS_CONTEXT(__stdcall* REAL_SetDpi)(DPI_AWARENESS_CONTEXT) = STUB_SetDpi; // Func ptr to be assigned

using std::cout;    using std::endl;

int main()
{
    HINSTANCE hDll = LoadLibrary("User32.dll");
    if (!hDll) {
        cout << "User32.dll failed to load!\n" << endl;
        return 1;
    }
    cout << "User32.dll loaded succesfully..." << endl;

    // (1) Simple assignment doesn't work ...
//  REAL_SetDpi = GetProcAddress(hDll, "SetThreadDpiAwarenessContext");
    // (2) Using 'C'-style cast does work, but it is flagged as 'evil' ...
//  REAL_SetDpi = (TYPE_SetDPI)GetProcAddress(hDll, "SetThreadDpiAwarenessContext");
    // (3) Using reinterpret_cast: seems OK with clang-cl but MSVC doesn't like it ...
//  REAL_SetDpi = reinterpret_cast<TYPE_SetDPI>(GetProcAddress(hDll, 
    // (4) Using a temporary plain "void *": OK with MSVC but clang-cl complains ...
    void* tempPtr = GetProcAddress(hDll, "SetThreadDpiAwarenessContext");
    REAL_SetDpi = reinterpret_cast<TYPE_SetDPI>(tempPtr);
    // (5) Using a union (cheating? - but neither clang-cl nor MSVC give any warning!) ...
//  union {
//      intptr_t(__stdcall* gotProc)(void);
//      TYPE_SetDPI usrProc; // This has the 'signature' for the actual function.
//  } TwoProcs;
//  TwoProcs.gotProc = GetProcAddress(hDll, "SetThreadDpiAwarenessContext");
//  REAL_SetDpi = TwoProcs.usrProc;

    if (REAL_SetDpi == nullptr) cout << "SetThreadDpiAwarenessContext function not found!" << endl;
    else                        cout << "SetThreadDpiAwarenessContext function loaded OK!" << endl;

    FreeLibrary(hDll);
    return 0;
}

Die verschiedenen Fehler-/Warnmeldungen, die von der clang-cl und einheimisch MSVC Compiler, für jede der 5 Optionen sind wie folgt:

// (1) Simple assignment doesn't work ...
REAL_SetDpi = GetProcAddress(hDll, "SetThreadDpiAwarenessContext");

clang-cl -> error :  assigning to 'DPI_AWARENESS_CONTEXT (*)(DPI_AWARENESS_CONTEXT) __attribute__((stdcall))'
  (aka 'DPI_AWARENESS_CONTEXT__ *(*)(DPI_AWARENESS_CONTEXT__ *)') from incompatible type 'FARPROC' 
  (aka 'long long (*)()'): different number of parameters (1 vs 0)
Visual-C -> error C2440:  '=': cannot convert from 'FARPROC' to 
  'DPI_AWARENESS_CONTEXT (__cdecl *)(DPI_AWARENESS_CONTEXT)'
  message :  This conversion requires a reinterpret_cast, a C-style cast or function-style cast

Dieser Fehler wird (natürlich) erwartet, aber das einzige Verwirrende für mich ist, warum MSVC zeigt meine Funktion als __cdecl wenn ich es ausdrücklich erklärt habe __stdcall?

// (2) Using 'C'-style cast does work, but it is flagged as dangerous ...
REAL_SetDpi = (TYPE_SetDPI)GetProcAddress(hDll, "SetThreadDpiAwarenessContext");

clang-cl -> warning :  use of old-style cast [-Wold-style-cast]
Visual-C -> warning C4191:  'type cast': unsafe conversion from 'FARPROC' to 'TYPE_SetDPI'
            warning C4191:   Calling this function through the result pointer may cause your program to fail

Im Allgemeinen bemühe ich mich, alte Umwandlungen im C-Stil in meinem Code vollständig zu vermeiden! Wo ich gezwungen bin, zwischen ‘nicht verwandten’ Objekten umzuwandeln, verwende ich explizit reinterpret_cast Operatoren, da diese im Code viel einfacher aufzuspüren sind, wenn Probleme auftreten. Also für Fall 3:

// (3) Using reinterpret_cast: seems OK with clang-cl but MSVC doesn't like it ...
REAL_SetDpi = reinterpret_cast<TYPE_SetDPI>(GetProcAddress(hDll, "SetThreadDpiAwarenessContext"));

clang-cl -> No error, no warning!
Visual-C -> warning C4191:  'reinterpret_cast': unsafe conversion from 'FARPROC' to 'TYPE_SetDPI'
            Calling this function through the result pointer may cause your program to fail

Hier die MSVC warning ist so ziemlich das gleiche wie für die Besetzung im C-Stil. Vielleicht könnte ich damit leben, aber Fall 4 macht die Sache interessanter:

// (4) Using a temporary plain "void *": OK with MSVC but clang-cl complains ...
void* tempPtr = GetProcAddress(hDll, "SetThreadDpiAwarenessContext");
REAL_SetDpi = reinterpret_cast<TYPE_SetDPI>(tempPtr);

clang-cl -> warning :  implicit conversion between pointer-to-function and pointer-to-object is a Microsoft extension
            [-Wmicrosoft-cast]
            warning :  cast between pointer-to-function and pointer-to-object is incompatible with C++98
            [-Wc++98-compat-pedantic]

Hier, MSVC gibt keine Warnung – aber ich habe das Gefühl, dass ich den Compiler einfach „täusche“! Ich kann nicht erkennen, wie dies einen anderen Gesamteffekt haben kann als der Code in Fall 3.

// (5) Using a union (cheating? - but neither clang-cl nor MSVC give any warning!) ...
union {
    intptr_t(__stdcall* gotProc)(void);
    TYPE_SetDPI usrProc; // This has the 'signature' for the actual function.
} TwoProcs;
TwoProcs.gotProc = GetProcAddress(hDll, "SetThreadDpiAwarenessContext");
REAL_SetDpi = TwoProcs.usrProc;

Ich habe dies als Antwort gepostet (jetzt zurückgezogen), worauf @Früherbekanntas_463035818 darauf hingewiesen hat, dass dies der Fall ist offiziell Undefiniertes Verhalten und/oder nicht zugelassen C++ (der Link des oben genannten Kommentators).

Welche Option nutze ich derzeit?

Nun, da meine Software speziell Windows-orientiert ist, verwende ich die letzte (Option 4) aus zwei Gründen: (1) die clang-cl Warnung ist am wenigsten beängstigend; und (2) das denke ich gerne MSVC ist wahrscheinlich der beste „Vermittler“ zum Kompilieren/Erstellen von Windows-Apps.

BEARBEITEN: Seit ich diese Frage zum ersten Mal gepostet und die verschiedenen Kommentare und Vorschläge “überprüft” habe, habe ich mich jetzt geändert alle Instanzen dieser Art von Umwandlung (d. h. von einem Funktionszeiger, der über geladen wurde GetProcAddress) in meinem Code, um die folgende Konvertierungsfunktion zu verwenden, die in meiner globalen Header-Datei definiert ist:

template<typename T> T static inline FprocPointer(intptr_t(__stdcall* inProc)(void)) {
    __pragma(warning(suppress:4191)) // Note: no semicolon after this expression!
    return reinterpret_cast<T>(inProc);
}

Dies ermöglicht ein einfaches/schnelles Auffinden solcher Besetzungen, falls ich ihre Arbeitsweise in Zukunft ändern muss (oder möchte).

Warum spielt es eine Rolle?

Vielleicht nicht! An anderer Stelle in meinem Code bin ich jedoch auf einen unerwarteten Absturz gestoßen, wenn Funktionszeiger verwendet werden, die über geladen wurden GetProcAddress() – kein Standard WinAPI Aufrufe, sondern Funktionen aus meinen eigenen DLLs, die als Plug-in-Module geladen werden. Das folgende Code-Snippet zeigt einen möglichen Anwendungsfall:

// --------------------------------------------------------------------------------------------------------------------
// These two routines are the 'interceptors' for plug-in commands; they check active plug-ins for handlers or updaters:

static int      plin;   //! NOTA BENE:  We use this in the two functions below, as the use of a local 'plin' loop index
                        //  is prone to induce stack corruption (?), especially in MSVC 2017 (MFC 14) builds for x86.

void BasicApp::OnUpdatePICmd(uint32_t nID, void *pUI)
{
//! for (int plin = 0; plin < Plugin_Number; ++plin) { // Can cause problems - vide supra
    for (plin = 0;  plin < Plugin_Number;  ++plin) {
        BOOL mEbl = FALSE;  int mChk = -1;
        if ((Plugin_UDCfnc[plin] != nullptr) && Plugin_UDCfnc[plin](nID, &mEbl, &mChk)) {
            CommandEnable(pUI, mEbl ? true : false);
            if (mChk >= 0) CmdUISetCheck(pUI, mChk);
            return;
        }
    }
    CommandEnable(pUI, false);
    return;
}

void BasicApp::OnPluginCmd(uint32_t nID)
{
//! for (int plin = 0; plin < Plugin_Number; ++plin) { // Can cause problems - vide supra
    for (plin = 0; plin < Plugin_Number; ++plin) {
        piHandleFnc Handler = nullptr;  void *pParam = nullptr;
        if ((Plugin_CHCfnc[plin] != nullptr) && Plugin_CHCfnc[plin](nID, &Handler, &pParam) && (Handler != nullptr)) {
            Handler(pParam);
            return;
        }
    }
    return;
}

Beachten Sie, dass, Plugin_UDCfnc und Plugin_CHCfnc sind Arrays von Funktionszeigern, die wie oben beschrieben geladen werden.

Und zum Schluss noch mal meine Frage?

Zweifach:

  1. Ist es „sicher“, die Warnungen zu ignorieren?
  2. Gibt es einen besseren Weg, die Standardbibliothek zu verwenden (ich gewöhne mich noch daran, diese zu verwenden) – vielleicht so etwas wie std::bind()?

Jede Hilfe, Anregungen oder Empfehlungen werden sehr geschätzt.

EDIT: Ich verwende die native MSVC Compiler für meine „Release“-Builds (mit /Wall) und einige spezifische Warnungen, die explizit (lokal) im Code deaktiviert sind. Von Zeit zu Zeit lasse ich meine gesamte Codebasis durch die laufen clang-cl Compiler, um nach anderen Warnungen vor möglichem zwielichtigem Code zu suchen (eigentlich sehr nützlich).

  • Ich wage es nicht, dies als Antwort zu schreiben, aber die beste Besetzung ist definitiv keine Besetzung

    – 463035818_ist_keine_Nummer

    17. September 2019 um 11:36 Uhr

  • @früherbekanntas_463035818 – Aber wie kann ich die geladene Funktion jemals ohne Umwandlung oder irgendeine Art von Konvertierung verwenden?

    – Adrian Maulwurf

    17. September 2019 um 11:39 Uhr

  • Wenn überhaupt, würde ich die Parameter und den Rückgabetyp umwandeln, und ich würde versuchen, mich von Umwandlungen im C-Stil oder seinem Cousin fernzuhalten reinterpret_cast wenn möglich

    – 463035818_ist_keine_Nummer

    17. September 2019 um 11:40 Uhr

  • Ich würde Version 3 verwenden – es ist die gebräuchlichste, glaube ich – und (lokal) die C4191-Warnung von VC++ deaktivieren.

    – molbnilo

    17. September 2019 um 11:45 Uhr

  • Wie das Beispiel in der MS-Dokumentation docs.microsoft.com/en-us/windows/win32/api/libloaderapi/… Es ist das, was andere, die Ihren Code lesen, erwarten werden.

    – Richard Critten

    17. September 2019 um 11:48 Uhr


1015720cookie-checkWas ist der beste Weg, um einen Funktionszeiger von einem Typ in einen anderen umzuwandeln?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy