Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
355 views
in Technique[技术] by (71.8m points)

c++ - What’s the best way to cast a function pointer from one type to another?

I’ve searched Stack Overflow for an answer, but I get nothing specific to this problem: only general cases about use of various types of cast operators. So, the case in point is when retrieving a function address with the Windows GetProcAddress() API call, which returns a function pointer of type FARPROC, with: typedef INT_PTR (__stdcall *FARPROC)();.

The trouble is, the actual function sought rarely (if ever) has this actual signature, as shown in the MRCE code, below. In this code, I have shown a variety of different attempts to convert the returned value to a function pointer of the appropriate type, with all but the fourth method commented out:

#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!
" << 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;
}

The various error/warning messages given by the clang-cl and native MSVC compiler, for each of the 5 options are as follows:

// (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

This error is (of course) expected, but the one confusing thing to me is why MSVC shows my function as __cdecl when I have explicitly declared it __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

Generally, I endeavour to completely avoid old, ‘C’-style casts in my code! Where I am forced to do cast between ‘unrelated’ objects, I use explicit reinterpret_cast operators, as these are far easier to track down in code if problems arise. So, for case 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

Here, the MSVC warning is pretty much the same as for the C-style cast. Maybe I could live with this, but case 4 makes things more interesting:

// (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]

Here, MSVC gives no warning – but I feel I’m simply ‘fooling’ the compiler! I can’t see how this can have any different overall effect than the code in case 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;

I did post this as an answer (now retracted), to which @formerlyknownas_463035818 pointed out that this is officially Undefined Behaviour and/or disallowed in C++ (the link given by the aforementioned commentator).

Which option do I currently use?

Well, as my software is specifically Windows-oriented, I use the last (option 4) for two reasons: (1) the clang-cl warning is the ‘least scary’; and (2) I like to think that MSVC is probably the best ‘mediator’ for compiling/building Windows apps.

EDIT: Since first posting this question, and having 'reviewed' the various comments and suggestions made, I have now changed all instances of this type of cast (that is, from a function pointer loaded via GetProcAddress) in my code to using the following conversion 'function', defined in my global header file:

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);
}

This allows for easy/rapid location of any such casts, should I need (or wish) to change the way they work in future.

Why does it matter?

Maybe it doesn’t! However, elsewhere in my code, I have come across an unexpected crash, when using function pointers loaded via GetProcAddress() - not any standard WinAPI calls, but functions from my own DLLs, loaded as plug-in modules. The code snippet below shows a potential case-in-point:

// --------------------------------------------------------------------------------------------------------------------
// 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;
}

Note that, Plugin_UDCfnc and Plugin_CHCfnc are arrays of function pointers, loaded as described above.

And, finally, what was my question, again?

Two-fold:

  1. Is it ‘safe’ to ignore the warnings?
  2. Is there a better way, using the Standard Library (I’m still getting used to using this) – maybe something like std::bind()?

Any help, suggestions or recommendations will be greatly appreciated.

EDIT: I use the native MSVC compiler for my “Release” builds (with /Wall), and a few specific warnings explicitly disabled (locally) in code. From time to time, I run my entire code base through the clang-cl compiler, to look for other warnings of possible dodgy code (very useful, actually).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...