cl.exe 64位与32位编译报错差异及修复方案咨询
以下是源代码:
#include <functional> #include <minwindef.h> #include <winerror.h> #include <libloaderapi.h> #include <winreg.h> namespace ffmock { /** * @brief Primary template */ template<typename T> struct function_traits; /** * @brief Template specialization for APIs */ template<typename RetType_t, typename... Args_t> struct function_traits<RetType_t(Args_t...)> { using Ret_t = RetType_t; using Sig_t = Ret_t(Args_t...); using Ptr_t = Ret_t(*)(Args_t...); using Api_t = std::function<Sig_t>; }; template<typename RetType_t, typename API_t, RetType_t RetValue, DWORD Error2Set = NO_ERROR> class Mock { protected: using Traits_t = function_traits<API_t>; using Ret_t = typename Traits_t::Ret_t; using Ptr_t = typename Traits_t::Ptr_t; using Api_t = typename Traits_t::Api_t; using Mock_t = Mock; Mock(HMODULE Module, const char* ApiName); template<typename... Args_t> Ret_t operator()(Args_t&... Args); }; } // namespace ffmock #pragma warning(disable:4273) // inconsistent dll linkage namespace Mocks { class FFRegCloseKey : public ::ffmock::Mock<LSTATUS, decltype(::RegCloseKey), ERROR_INVALID_HANDLE> { friend FFMOCK_IMPORT LSTATUS APIENTRY ::RegCloseKey( _In_ HKEY Key ); FFRegCloseKey(HMODULE Module) : Mock_t(Module, "RegCloseKey") { } }; } // namespace Mocks extern "C" FFMOCK_IMPORT LSTATUS APIENTRY RegCloseKey(HKEY Key) { static Mocks::FFRegCloseKey mock(LoadLibraryW(L"advapi32.dll")); return mock(Key); }
64位编译输出:
Executing task: C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Tools/MSVC/14.38.33130/bin/Hostx64/x64/cl.exe /nologo /Fodemo/bin/x64/ /std:c++17 /EHsc /D_DEBUG /D_AMD64_ /DWIN64 /D_UNICODE /DUNICODE /D_CONSOLE /DFFMOCK_IMPORT=__declspec(dllexport) /c C:\Play\ffmock\demo\tst\Test.cpp
Test.cpp * Press any key to close the terminal.
32位编译错误输出:
Executing task: C:/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Tools/MSVC/14.38.33130/bin/Hostx86/x86/cl.exe /nologo /Fodemo/bin/x64/ /std:c++17 /EHsc /D_DEBUG /D_X86_ /DWIN32 /D_UNICODE /DUNICODE /D_CONSOLE /DFFMOCK_IMPORT=__declspec(dllexport) /c C:\Play\ffmock\demo\tst\Test.cpp
Test.cpp C:\Play\ffmock\demo\tst\Test.cpp(42): error C2027: use of undefined type 'ffmock::function_traits<API_t>'
with
[
API_t=LSTATUS (HKEY)
] C:\Play\ffmock\demo\tst\Test.cpp(41): note: see declaration of 'ffmock::function_traits<API_t>'
with
[
API_t=LSTATUS (HKEY)
] C:\Play\ffmock\demo\tst\Test.cpp(42): note: the template instantiation context (the oldest one first) is
C:\Play\ffmock\demo\tst\Test.cpp(64): note: see reference to class template instantiation 'ffmock::Mock<LSTATUS,LSTATUS (HKEY),6,0>' being compiled C:\Play\ffmock\demo\tst\Test.cpp(42): error C2061: syntax error: identifier 'Ret_t' C:\Play\ffmock\demo\tst\Test.cpp(43): error C2027: use of undefined type 'ffmock::function_traits<API_t>'
with
[
API_t=LSTATUS (HKEY)
] C:\Play\ffmock\demo\tst\Test.cpp(41): note: see declaration of 'ffmock::function_traits<API_t>'
with
[
API_t=LSTATUS (HKEY)
] C:\Play\ffmock\demo\tst\Test.cpp(43): error C2061: syntax error: identifier 'Ptr_t' C:\Play\ffmock\demo\tst\Test.cpp(44): error C2027: use of undefined type 'ffmock::function_traits<API_t>'
with
[
API_t=LSTATUS (HKEY)
] C:\Play\ffmock\demo\tst\Test.cpp(41): note: see declaration of 'ffmock::function_traits<API_t>'
with
[
API_t=LSTATUS (HKEY)
] C:\Play\ffmock\demo\tst\Test.cpp(44): error C2061: syntax error: identifier 'Api_t' C:\Play\ffmock\demo\tst\Test.cpp(50): error C2143: syntax error: missing ';' before '('
C:\Play\ffmock\demo\tst\Test.cpp(50): error C2238: unexpected token(s) preceding ';' C:\Play\ffmock\demo\tst\Test.cpp(50): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int C:\Play\ffmock\demo\tst\Test.cpp(50): error C2988: unrecognizable template declaration/definition
C:\Play\ffmock\demo\tst\Test.cpp(50): error C2059: syntax error: '' C:\Play\ffmock\demo\tst\Test.cpp(92): error C3889: call to object of class type 'Mocks::FFRegCloseKey': no matching call operator found C:\Play\ffmock\demo\tst\Test.cpp(50): note: could be 'int ffmock::Mock<LSTATUS,LSTATUS (HKEY),6,0>::operator ()(Args_t &...)'The terminal process "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC\14.38.33130\bin\Hostx86\x86\cl.exe '/nologo', '/Fodemo/bin/x64/', '/std:c++17', '/EHsc', '/D_DEBUG', '/D_X86_', '/DWIN32', '/D_UNICODE', '/DUNICODE', '/D_CONSOLE', '/DFFMOCK_IMPORT=__declspec(dllexport)', '/c', 'C:\Play\ffmock\demo\tst\Test.cpp'" terminated with exit code: 2. * Press any key to close the terminal.
1. 扩展function_traits以支持32位API调用约定
32位Windows API默认使用__stdcall调用约定(APIENTRY在32位下等价于__stdcall),而原有function_traits模板只处理了不带调用约定的函数类型,导致模板特化不匹配。需要添加对应特化版本:
namespace ffmock { // ... 原有代码 ... /** * @brief 支持__stdcall调用约定的API特化 */ template<typename RetType_t, typename... Args_t> struct function_traits<RetType_t(__stdcall)(Args_t...)> { using Ret_t = RetType_t; using Sig_t = Ret_t(Args_t...); using Ptr_t = Ret_t(__stdcall*)(Args_t...); using Api_t = std::function<Sig_t>; }; // ... 原有代码 ... }
如果需要兼容更多调用约定(如__cdecl),可以继续添加对应的特化模板。
2. 调整调用运算符的参数类型
原operator()使用Args_t&...左值引用参数,当传入无法绑定左值引用的参数时会出现匹配问题。改为转发引用或值传递提升兼容性:
使用转发引用:
template<typename... Args_t> Ret_t operator()(Args_t&&... Args) { // 实现逻辑 }
或者直接值传递(适合Win32 API的基础类型参数):
template<typename... Args_t> Ret_t operator()(Args_t... Args) { // 实现逻辑 }
3. 修正编译输出路径(可选)
32位编译时输出路径写的是/Fodemo/bin/x64/,建议改为对应32位路径(如/Fodemo/bin/x86/),避免文件冲突,但这不是编译错误的直接原因。
修改后重新用32位MSVC编译即可解决报错问题。
内容的提问来源于stack exchange,提问作者Uriel

