We have this simple class:
class MyStr
{
public:
MyStr() { }
wchar_t* pStr;
};
and this code that uses it:
MyStr ms1, ms2;
ms1.pStr = L"MyStr!";
ms2.pStr = L"MyStr2!";
wchar_t buffer[100];
swprintf(buffer, 100, L"%ls - %ls", ms1, ms2);
The assembly that pushes ms1 and ms2 onto the stack looks like:
FF B5 24 FE FF FF push DWORD PTR [ebp-0x1dc]
FF B5 20 FE FF FF push DWORD PTR [ebp-0x1e0]
It is actually pushing the the values/content of MyStr (which in this case is just pStr) onto the stack.
If we change MyStr to just add a simple destructor:
class MyStr
{
public:
MyStr() { }
~MyStr() { }
wchar_t* pStr;
};
now the address of ms1 and ms2 are getting passed, instead of their values/contents.
8D 85 24 FE FF FF lea eax,[ebp-0x1dc]
50 push eax
8D 85 20 FE FF FF lea eax,[ebp-0x1e0]
50 push eax
Visual Studio on Windows gives the same result both ways (always passes values/contents), but gcc on Linux gives these two different results.
The thing we cannot do is change the swprintf line - there are many thousands of them and we are trying to avoid changing them.
You may resort to MACRO
// Forward call to your method
#define swprintf(...) my_swprintf(__VA_ARGS__)
// Your class
class MyStr
{
public:
MyStr() { }
~MyStr() { }
const wchar_t* pStr;
};
// helper function to "fix" parameters
template <typename T>
std::enable_if_t<std::is_arithmetic<T>::value || std::is_pointer<T>::value, T>
normalize(T t) { return t; } // Default one which does nothing
// Fix for your class to return its member.
const wchar_t* normalize(const MyStr& t) { return t.pStr; }
// Your function which (re-)forward to real swprintf with fixed parameter
// Extra parent do avoid macro substitution
template <typename ... Ts>
auto my_swprintf(wchar_t* buffer, const wchar_t* format, const Ts&... args)
{
return (swprintf)(buffer, format, normalize(args)...);
}