繁体   English   中英

将 function 指针从一种类型转换为另一种类型的最佳方法是什么?

[英]What’s the best way to cast a function pointer from one type to another?

我已经在 Stack Overflow 上搜索了答案,但我没有得到任何关于这个问题的具体信息:只有关于使用各种类型转换运算符的一般情况。 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)(); .

问题是,实际的 function 很少(如果有的话)有这个实际签名,如下面的 MRCE 代码所示。 在这段代码中,我展示了将返回值转换为适当类型的 function 指针的各种不同尝试,除了第四个方法之外的所有方法都被注释掉了:

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

clang-cl和本机MSVC编译器为 5 个选项中的每一个提供的各种错误/警告消息如下:

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

这个错误(当然)是意料之中的,但让我感到困惑的是,为什么当我明确声明为__stdcall时, MSVC将我的 function 显示为__cdecl

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

一般来说,我努力在我的代码中完全避免旧的“C”风格转换,在我被迫在“不相关”对象之间进行转换的地方,我使用显式的reinterpret_cast运算符,因为如果出现问题,这些运算符更容易在代码中追踪出现。 因此,对于案例 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

在这里, MSVC警告与 C 风格的演员表几乎相同。 也许我可以忍受这个,但案例 4 让事情变得更有趣:

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

在这里, MSVC没有给出任何警告——但我觉得我只是在“愚弄”编译器。 我看不出这与案例 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;

我确实将其发布为答案(现已撤回),@以前的knownas_463035818指出这是正式的未定义行为和/或C++ (上述评论员提供的链接)中不允许这样做。

我目前使用哪个选项?

好吧,由于我的软件是专门面向 Windows 的,我使用最后一个(选项 4)有两个原因:(1) clang-cl警告是“最不可怕的”; (2) 我喜欢认为MSVC可能是编译/构建 Windows 应用程序的最佳“中介”。

编辑:自从第一次发布这个问题并“审查”了各种评论和建议以来,我现在已经在我的代码中将这种类型转换的所有实例(即从通过GetProcAddress加载的 function 指针)更改为使用以下转换“函数”,在我的全局 header 文件中定义:

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

如果我将来需要(或希望)改变它们的工作方式,这允许任何此类演员的轻松/快速定位。

为什么这有关系?

也许不是,但是,在我的代码的其他地方,当使用通过GetProcAddress()加载的 function 指针时,我遇到了意外的崩溃——不是任何标准WinAPI调用,而是来自我自己的 DLL 的函数,作为插件模块加载. 下面的代码片段显示了一个潜在的案例:

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

请注意, Plugin_UDCfncPlugin_CHCfnc是 function 指针的 arrays,如上所述加载。

最后,我的问题又是什么?

双重:

  1. 忽略警告是否“安全”?
  2. 有没有更好的方法,使用标准库(我仍然习惯使用它)——也许像std::bind()之类的?

任何帮助、建议或建议将不胜感激。

编辑:我使用本地MSVC编译器进行“发布”构建(使用/Wall ),并在代码中明确禁用(本地)一些特定警告。 有时,我会通过clang-cl编译器运行我的整个代码库,以寻找其他可能存在的可疑代码的警告(实际上非常有用)。

我认为 C/C++ 缺少通用的 function 指针类型,例如void*作为通用的 object 指针类型。

一般支持从一个 function 指针转换为另一个,前提是不要调用错误的 function 指针类型。 [expr.reinterpret.cast]/6

function 指针可以显式转换为不同类型的 function 指针。

将一个 function 指针类型转换为另一个时发出的警告通常很有用。 进行此类转换可能会导致调用带有错误签名的 function。 这样的问题可能只影响特定的 CPU 架构,或者仅在特定的操作系统版本中才会被注意到,因此在初始测试后可能并不明显。 标准只是说unspecified ,见[expr.reinterpret.cast]/6

除了将“指向 T1 的指针”类型的纯右值转换为“指向 T2 的指针”类型(其中 T1 和 T2 是 function 类型)并返回其原始类型会产生原始指针值之外,这种指针转换的结果是未指定的.

void*是否可以转换为 function 指针类型以及它是否有足够的位是特定于实现的。 不过,对于 Windows 来说确实如此。 对于一般问题,我不会赞同特定于 Windows 的习惯。 [expr.reinterpret.cast]/8

有条件地支持将 function 指针转换为 object 指针类型或反之亦然。

使用联合进行类型双关会引发严格别名问题( 什么是严格别名规则? ),因此不是智取编译器的好方法。

因此,请在GetProcAddress调用附近或GetProcAddess包装器中使用本地警告抑制。 使用reinterpret_cast

If you're going to use a helper function that casts one function type into another without a warning, be sure to use it only in GetProcAddress -like scenario, when you use some generic signature to temporary store function pointer, but that signature is not一个实际的签名——不要通过非预期类型调用 function 指针。

对不起。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM