首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将函数指针从一种类型转换到另一种类型的最佳方法是什么?

将函数指针从一种类型转换到另一种类型的最佳方法是什么?
EN

Stack Overflow用户
提问于 2019-09-17 11:29:02
回答 1查看 2.2K关注 0票数 9

我已经搜索过Stack溢出以寻找答案,但我并没有得到关于这个问题的任何具体答案:只有关于使用各种类型的cast操作符的一般情况。因此,这方面的例子是使用Windows调用检索函数地址,该调用返回类型为:GetProcAddress()的函数指针FARPROC

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

代码语言:javascript
复制
#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个选项中的每一个提供的各种错误/警告消息如下:

代码语言:javascript
复制
// (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

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

代码语言:javascript
复制
// (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:

代码语言:javascript
复制
// (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让事情变得更有趣:

代码语言:javascript
复制
// (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中的代码有什么不同的总体效果。

代码语言:javascript
复制
// (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;

我发布这篇文章是作为一个答案(现在被收回),@formerlyknownas_463035818指出,这是C++ (上述评论员给出的链接 )中官方未定义的行为和/或不允许的行为。

我现在使用哪个选项?

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

编辑:自从第一次发布这个问题,并“审阅”了所提出的各种评论和建议之后,我现在已经将代码中这种类型的所有实例(即从通过GetProcAddress__加载的函数指针)更改为使用在全局头文件中定义的以下转换‘函数’: 模板T静态内联FprocPointer(intptr_t(__stdcall* inProc)(void)) {__pragma(压制:4191) //注意:在这个表达式之后没有分号!返回reinterpret_cast(inProc);} 如果我需要(或希望)改变它们未来的工作方式,这就允许轻松/快速地定位任何这样的转换。

这有什么关系?

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

代码语言:javascript
复制
// --------------------------------------------------------------------------------------------------------------------
// 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是函数指针数组,如上文所述。

,最后,我又问了什么问题?

两倍:

  1. 无视这些警告“安全”吗?
  2. 是否有更好的方法,使用标准库(我仍然习惯使用这个)-也许类似于std::bind()

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

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

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-08-18 18:10:00

我认为C/C++缺少泛型函数指针类型,比如void*作为泛型对象指针类型。

通常,如果不调用错误的函数指针类型,则支持从一个函数指针转换为另一个函数指针。请参阅[expr.reinterpret.cast]/6

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

将一个函数指针类型转换为另一个函数指针类型时发出的警告通常是有用的。这样的转换可能会导致调用带有错误签名的函数。这样的问题可能只影响特定的CPU体系结构,或者仅在某些OS构建时才会被注意到,因此在初始测试之后可能并不明显。标准只表示未指定,请参见[expr.reinterpret.cast]/6

除了将“指针到T1”类型的prvalue转换为“指向T2的指针”类型(其中T1和T2是函数类型)并返回其原始类型产生原始指针值外,此指针转换的结果未指定。

是否可以将void*转换为函数指针类型,以及它是否具有足够的位数都是实现特定的。不过,Windows是正确的。对于一个通用问题,我不会支持Windows特有的习惯。请参阅[expr.reinterpret.cast]/8

有条件地支持将函数指针转换为对象指针类型,反之亦然。

使用联合输入双关会引发严格混叠(什么是严格的别名规则?)问题,因此不是比编译器聪明的好方法。

因此,在您的GetProcAddress GetProcAddess 调用附近,或者在GetProcAddess包装器中,可以使用本地警告抑制。使用reinterpret_cast**.**

如果您要使用一个没有警告地将一种函数类型转换为另一种函数类型的助手函数,请确保只在GetProcAddress-like场景中使用它,当您使用一些通用签名来临时存储函数指针时,但该签名不是实际的签名--而不是以不打算使用的类型调用函数指针。

抱歉的。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57973305

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档