我已经在Stack Overflow上搜索了答案,但是我对此问题一无所知:只有关于使用各种类型的强制转换运算符的一般情况。
因此,典型的例子是使用Windows GetProcAddress()
API调用检索函数地址,该调用返回类型为FARPROC
的函数指针,其指针为:typedef INT_PTR (__stdcall *FARPROC)();
。
问题在于,寻求的实际功能很少(如果有的话)具有该实际签名,如下面的MRCE代码所示。在这段代码中,我展示了多种尝试将返回值转换为适当类型的函数指针的尝试,除了第四个方法之外,所有其他方法都被注释掉了:
#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
(当然)会出现此错误,但是令我感到困惑的是,为什么MSVC
在明确声明__cdecl
时将函数显示为__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
通常,我会努力在代码中完全避免使用老式的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;
我确实将其发布为答案(现已撤消),@ formerlyknownas_463035818指出这是官方未定义行为和/或在C++
({{3} }由上述评论者提供。
我当前使用哪个选项?
好吧,由于我的软件专门针对Windows,所以我使用最后一个(选项4)有两个原因:(1)clang-cl
警告是“最不可怕”; (2)我想认为MSVC
可能是编译/构建Windows应用程序的最佳“中介”。
编辑:自从首次发布此问题并“审查”了所提出的各种意见和建议以来,我现在更改了所有 这种类型的强制转换实例(即从函数指针 通过
GetProcAddress
加载到我的代码中以使用以下代码 转换“功能”,在我的全局头文件中定义: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()
加载的函数指针时,遇到了意外崩溃-不是任何标准的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_UDCfnc
和Plugin_CHCfnc
是函数指针数组,如上所述加载。
最后,我的问题又是什么?
双重:
std::bind()
之类的东西?任何帮助,建议或建议,将不胜感激。
编辑:我将本机MSVC
编译器用于我的“发布”版本(使用/Wall
),并在代码中(本地)明确禁用了一些特定的警告。有时,我会通过clang-cl
编译器来运行我的整个代码库,以寻找其他可能出现躲闪性代码的警告(实际上非常有用)。