最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to properly forward non template, variadic arguments in CC++ under MSVC - Stack Overflow

programmeradmin0浏览0评论

I am trying to forward non-template variadic arguments between two functions. None of those functions is mine so I can't change their signatures. I used va_args for that purpose but (unluckily) my solution does not work properly under MSVC. Probably due to a stack overlapping I end up with a random value picked from the stack.

Here is the minimal example to reproduce my problem. Functions funcA and funcB have the same signature as the functions from my original problem.

#include <stdarg.h>

int funcA(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    auto value = a + va_arg(args, int);
    va_end(args);
    return value;
}

int funcB(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    auto value = funcA(a, args);
    va_end(args);
    return value;
}

int main() {
    return funcB(2, nullptr, 163);
}

Possible output GCC:

Program returned: 165

Possible output MSVC:

Program returned: 7731509

You can run this code from here

Above solution works correctly and as expected under GCC, but under MSVC this code returns rubbish number. I've tried to adjust this minimal example by explicitly defining calling convention to __cdecl but this does not help. I have also tried to compile it using different optimization flags but it didn't change anything;

Please note that I've seen many similar questions here, but none of them answered my question related to MSVC. I've checked how printf function forwards those arguments, but unluckily it does pass it to _vfprintf_s_l using va_list and that's what I can't do in my case.

    _Check_return_opt_
    _CRT_STDIO_INLINE int __CRTDECL _printf_s_l(
        _In_z_ _Printf_format_string_params_(0) char const* const _Format,
        _In_opt_                                _locale_t   const _Locale,
        ...)
    #if defined _NO_CRT_STDIO_INLINE
    ;
    #else
    {
        int _Result;
        va_list _ArgList;
        __crt_va_start(_ArgList, _Locale);
        _Result = _vfprintf_s_l(stdout, _Format, _Locale, _ArgList);
        __crt_va_end(_ArgList);
        return _Result;
    }
    #endif

EDIT The b parameter here auto value = funcA(a, args); was removed on purpose just to test it out under GCC. Even if I bring it back, it won't work under either GCC and MSVC.

My real/original problem is related to function hooking. I have to place a hook into a function exported by the .dll with this kind of variadic interface. So, when I do that, I end up with code which looks more or less like this:

// orig::exported_func
// this is the original function exported by the .dll
// I don't have access to its implementation
//
// hook::exported_func
// this is my stub hook function. The hook is placed in runtime,
// by overriding the original function asm code. After hooking,
// original function address is stored inside orig::exported_func.
int hook::exported_func(int a, void* b, ...) {
    // here is the place I need to forward this variadic arguments
    // I dont even need to read them, the thing it to pass proper stack size
    // Line below is just and example, it wont compile
    auto retVal = orig::exported_func(a, b, ...);
    .
    .
    .
    // do some not related staff
    .
    .
    .
    return retVal;
}

I am trying to forward non-template variadic arguments between two functions. None of those functions is mine so I can't change their signatures. I used va_args for that purpose but (unluckily) my solution does not work properly under MSVC. Probably due to a stack overlapping I end up with a random value picked from the stack.

Here is the minimal example to reproduce my problem. Functions funcA and funcB have the same signature as the functions from my original problem.

#include <stdarg.h>

int funcA(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    auto value = a + va_arg(args, int);
    va_end(args);
    return value;
}

int funcB(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    auto value = funcA(a, args);
    va_end(args);
    return value;
}

int main() {
    return funcB(2, nullptr, 163);
}

Possible output GCC:

Program returned: 165

Possible output MSVC:

Program returned: 7731509

You can run this code from here https://godbolt./z/Mjc8E3onW

Above solution works correctly and as expected under GCC, but under MSVC this code returns rubbish number. I've tried to adjust this minimal example by explicitly defining calling convention to __cdecl but this does not help. I have also tried to compile it using different optimization flags but it didn't change anything;

Please note that I've seen many similar questions here, but none of them answered my question related to MSVC. I've checked how printf function forwards those arguments, but unluckily it does pass it to _vfprintf_s_l using va_list and that's what I can't do in my case.

    _Check_return_opt_
    _CRT_STDIO_INLINE int __CRTDECL _printf_s_l(
        _In_z_ _Printf_format_string_params_(0) char const* const _Format,
        _In_opt_                                _locale_t   const _Locale,
        ...)
    #if defined _NO_CRT_STDIO_INLINE
    ;
    #else
    {
        int _Result;
        va_list _ArgList;
        __crt_va_start(_ArgList, _Locale);
        _Result = _vfprintf_s_l(stdout, _Format, _Locale, _ArgList);
        __crt_va_end(_ArgList);
        return _Result;
    }
    #endif

EDIT The b parameter here auto value = funcA(a, args); was removed on purpose just to test it out under GCC. Even if I bring it back, it won't work under either GCC and MSVC.

My real/original problem is related to function hooking. I have to place a hook into a function exported by the .dll with this kind of variadic interface. So, when I do that, I end up with code which looks more or less like this:

// orig::exported_func
// this is the original function exported by the .dll
// I don't have access to its implementation
//
// hook::exported_func
// this is my stub hook function. The hook is placed in runtime,
// by overriding the original function asm code. After hooking,
// original function address is stored inside orig::exported_func.
int hook::exported_func(int a, void* b, ...) {
    // here is the place I need to forward this variadic arguments
    // I dont even need to read them, the thing it to pass proper stack size
    // Line below is just and example, it wont compile
    auto retVal = orig::exported_func(a, b, ...);
    .
    .
    .
    // do some not related staff
    .
    .
    .
    return retVal;
}
Share Improve this question edited Nov 19, 2024 at 23:20 Jonathan Leffler 755k145 gold badges949 silver badges1.3k bronze badges asked Nov 19, 2024 at 22:14 Johny Siemano KolanoJohny Siemano Kolano 1477 bronze badges 1
  • Comments have been moved to chat; please do not continue the discussion here. Before posting a comment below this one, please review the purposes of comments. Comments that do not request clarification or suggest improvements usually belong as an answer, on Meta Stack Overflow, or in Stack Overflow Chat. Comments continuing discussion may be removed. – blackgreen Commented Nov 20, 2024 at 17:36
Add a comment  | 

3 Answers 3

Reset to default 2

How to properly forward non-template, variadic arguments in C/C++ under MSVC

There is no generic way in C or C++ to forward the variable arguments of one C-style variadic function to another.

In particular, va_list is a type like any other, not a magic language feature. You can pass one to a function (by value), or a pointer to one, or in C++ you can pass a reference to one, but none of those are in any way equivalent to forwarding the original variable arguments themselves, not even when you pass it to a variadic function. If doing so appears to work as you expect under some particular circumstances, then you can count yourself unlucky in that regard, as such a result is misleading at best.

You can unpack some number of variable arguments from the va_list and pass their specific values to the other function as ordinary arguments, but this is rarely satisfactory, because you need a separate case for each combination of argument count and types.

Since you say that neither of the functions involved is yours, I guess you have inherited someone else's mess. If possible, then, push the issue back on the responsible miscreant. But since you ask the question, I suppose you are in a position to fix the issue for yourself. In that case, one reasonable way to proceed would be to split funcA() into a variadic front end, and a va_list-based back end. Then funcB() would pass a va_list to the new funcA() back end instead of to the variadic front end. Like so:

int funcAva(int a, void* b, va_list args)
{
    return a + va_arg(args, int);
}

int funcA(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    auto value = funcAva(a, b, args);
    va_end(args);
    return value;
}

int funcB(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    auto value = funcAva(a, b, args);
    va_end(args);
    return value;
}

That preserves the signature of funcA() while providing a way for funcB() to elicit the needed behavior.

Addendum

The updated question suggests that rather than inheriting someone else's mess, you are trying to create one of your own. In that case, the original question presented a big red herring in the form of your ideas about modifying the original functions. There is no reason to think that would help you.

Under some circumstances, you can wrap a variadic function with a variadic macro. Perhaps that would serve your purpose. Perhaps not.

If you can't change the function signatures, but can change the contents, then you can stub out funcA to call another function that takes a va_list, then have funcB call that instead.

int funcA_impl(int a, void* b, va_list args)
{
    int value = a + va_arg(args, int);
    return value;
}

int funcA(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    int value = funcA_impl(a, b, args);
    va_end(args);
    return value;
}

int funcB(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    int value = funcA_impl(a, b, args);
    va_end(args);
    return value;
}

Your code is a big undefined behaviour - and that's why it behavies differently. If you cant change the functions, then you are out of luck.

Here you have the corrected version:

#include <stdarg.h>

int funcA(int a, void* b, va_list args)
{

    auto value = a + va_arg(args, int);
    return value;
}

int funcB(int a, void* b, ...)
{
    va_list args;
    va_start(args, b);
    auto value = funcA(a, b, args);
    va_end(args);
    return value;
}

int main() {
    return funcB(2, nullptr, 163);
}

In C++ you have more options:

  1. Templates:
#include <iostream>

// Variadic template function
template <typename... Args>
void processArgs(const Args&... args) 
{
    ((std::cout << args << std::endl), ...); // 
}

// Variadic function
template <typename... Args>
void passArgs(const Args&... args) 
{
    processArgs(args...);
}

int main() 
{
    passArgs(42, 'A', 3.14);
    return 0;
}
  1. std::initializer_list
#include <iostream>
#include <initializer_list>

void processArgs(const std::initializer_list<int>& args)
{
    for (int arg : args) {
        std::cout << "Integer: " << arg << std::endl;
    }
}

void passArgs(std::initializer_list<int> args)
{
    processArgs(args);
}

int main()
{
    passArgs({42, 7, 13}); // Pass a list of integers
    return 0;
}
#include <iostream>
#include <vector>
#include <any>

void processArgs(const std::vector<std::any>& args)
{
    for (const auto& arg : args) {
        if (arg.type() == typeid(int)) {
            std::cout << "Integer: " << std::any_cast<int>(arg) << std::endl;
        } else if (arg.type() == typeid(char)) {
            std::cout << "Character: " << std::any_cast<char>(arg) << std::endl;
        } else if (arg.type() == typeid(double)) {
            std::cout << "Double: " << std::any_cast<double>(arg) << std::endl;
        } else {
            std::cout << "Unknown type!" << std::endl;
        }
    }
}

void passArgs(const std::vector<std::any>& args)
{
    processArgs(args);
}

int main()
{
    passArgs({42, 'A', 3.14}); // Pass a heterogeneous list of arguments
    return 0;
}
发布评论

评论列表(0)

  1. 暂无评论