C++ 11 Tricks to Call a Slow Function With A Progress Dialog Box






4.37/5 (17 votes)
One-line call of a slow function with a progress dialog
Introduction
Let's say we have this function:
int x2(int yy, int zz)
{
Sleep(5000);
return yy + zz;
}
which takes some 5 seconds to complete. We want to show this dialog to the user:
and execute our function in a background thread.
However, it is bothersome to create custom window, different threads, different parameter passing, etc. for all such calls. Here is a way to call any function with any parameter list in an one-line call:
auto i1 = WC<int,int,int>::WaitCall(nullptr, L"Waiting...",x2,10,20);
// after 5 seconds, i1 = 30
ToDo
Make it work with references.
Background
We need C++ 11 tuples, variadic templates and so on. Rest assured, it's not that tough!
Using the Code
All is done in the class WC
, which has a static
member function WaitCall
for the user to call:
template <typename R, typename ...Args>
class WC
{
...
};
This class takes the return value and the arguments to be passed. To implement a function that takes an arbitrary number of arguments, we need something like this:
static R WaitCall(HWND hh, const wchar_t* txt, std::function<R(Args...)> fn, Args... a)
{
WAITCALL w;
w.txt = txt;
w.f = fn;
w.tu = ::make_tuple<Args...>(std::forward<Args>(a)...);
const char* res = "..."; // the resource we have created
DialogBoxIndirectParam(GetModuleHandle(0), (LPCDLGTEMPLATEW)res, hh, WaitCallDP, (LPARAM)&w);
return (R)w.rval;
}
We need the HWND
and text to be shown, a function of return value R
and arguments Args...
Note that we have created a WAITCALL
structure to store the information, because we need to show a dialog box so we have to use a structure to pass along all the information the user has provided:
struct WAITCALL
{
wstring txt;
std::function<R(Args...)> f;
std::tuple<Args...> tu;
R rval;
};
Now for the dialog box procedure. It has to process WM_INITDIALOG
to start the thread, then a custom message from the thread function when the call is over:
static INT_PTR __stdcall WaitCallDP(HWND hh, UINT mm, WPARAM, LPARAM ll)
{
WAITCALL* eel = (WAITCALL*)GetWindowLongPtr(hh, GWLP_USERDATA);
switch (mm)
{
case WM_USER + 1:
{
EndDialog(hh, IDOK);
return 0;
}
case WM_INITDIALOG:
{
eel = (WAITCALL*)ll;
SetWindowLongPtr(hh, GWLP_USERDATA, (LONG_PTR)eel);
SetDlgItemText(hh, 701, eel->txt.c_str());
SendDlgItemMessage(hh, 901, PBM_SETMARQUEE, true, 0);
std::thread t(WaitCallDPCall, hh, eel);
t.detach();
return 0;
}
}
return 0;
}
So in WM_INITDIALOG
, we start a new thread with the help of a new function, WaitCallDPCall
:
static void WaitCallDPCall(HWND hh, WAITCALL* w)
{
w->rval = callthefunction(w->f, w->tu);
SendMessage(hh, WM_USER + 1, 0, 0);
return;
}
Now we need to expand the std::tuple
we used to store the arguments into an argument pack, so it can be passed to an ordinary function that does not take a tuple. With the aid of some metaprogramming trick:
template <typename F, typename Tuple, bool Done, int Total, int... N>
struct call_impl
{
static R callthefunction(F f, Tuple && t)
{
return (R)call_impl<F, Tuple, Total == 1 + sizeof...(N),
Total, N..., sizeof...(N)>::callthefunction(f, std::forward<Tuple>(t));
}
};
template <typename F, typename Tuple, int Total, int... N>
struct call_impl<F, Tuple, true, Total, N...>
{
static R callthefunction(F f, Tuple && t)
{
UNREFERENCED_PARAMETER(t);
return (R)f(std::get<N>(std::forward<Tuple>(t))...);
}
};
template <typename F, typename Tuple>
static R callthefunction(F f, Tuple && t)
{
typedef typename std::decay<Tuple>::type ttype;
return (R)call_impl<F, Tuple, 0 == std::tuple_size<ttype>::value,
std::tuple_size<ttype>::value>::callthefunction(f, std::forward<Tuple>(t));
}
Note the UNREFERENCED_PARAMETER(t)
. When no arguments are to be passed, t
is empty, and we don't want a nasty compiler warning.
So the entire code is this one:
// Generic template
template <typename R, typename ...Args>
class WC
{
private:
template <typename F, typename Tuple, bool Done, int Total, int... N>
struct call_impl
{
static R callthefunction(F f, Tuple && t)
{
return (R)call_impl<F, Tuple, Total == 1 + sizeof...(N),
Total, N..., sizeof...(N)>::callthefunction(f, std::forward<Tuple>(t));
}
};
template <typename F, typename Tuple, int Total, int... N>
struct call_impl<F, Tuple, true, Total, N...>
{
static R callthefunction(F f, Tuple && t)
{
UNREFERENCED_PARAMETER(t);
return (R)f(std::get<N>(std::forward<Tuple>(t))...);
}
};
template <typename F, typename Tuple>
static R callthefunction(F f, Tuple && t)
{
typedef typename std::decay<Tuple>::type ttype;
return (R)call_impl<F, Tuple, 0 == std::tuple_size<ttype>::value,
std::tuple_size<ttype>::value>::callthefunction(f, std::forward<Tuple>(t));
}
struct WAITCALL
{
wstring txt;
std::function<R(Args...)> f;
std::tuple<Args...> tu;
R rval;
};
static void WaitCallDPCall(HWND hh, WAITCALL* w)
{
w->rval = callthefunction(w->f, w->tu);
SendMessage(hh, WM_USER + 1, 0, 0);
return;
}
static INT_PTR __stdcall WaitCallDP(HWND hh, UINT mm, WPARAM, LPARAM ll)
{
WAITCALL* eel = (WAITCALL*)GetWindowLongPtr(hh, GWLP_USERDATA);
switch (mm)
{
case WM_USER + 1:
{
EndDialog(hh, IDOK);
return 0;
}
case WM_INITDIALOG:
{
eel = (WAITCALL*)ll;
SetWindowLongPtr(hh, GWLP_USERDATA, (LONG_PTR)eel);
SetDlgItemText(hh, 701, eel->txt.c_str());
SendDlgItemMessage(hh, 901, PBM_SETMARQUEE, true, 0);
std::thread t(WaitCallDPCall, hh, eel);
t.detach();
return 0;
}
}
return 0;
}
public:
// User calls this
static R WaitCall(HWND hh, const wchar_t* txt, std::function<R(Args...)> fn, Args... a)
{
WAITCALL w;
w.txt = txt;
w.f = fn;
w.tu = ::make_tuple<Args...>(std::forward<Args>(a)...);
const char* res = "\x01\x00\xFF\xFF\x00\x00\x00\x00\x80\x00\x00\x00\
x48\x08\x00\x80\x03\x00\x00\x00\x00\x00\x35\x01\x70\x00\x00\x00\x00\x00\
x00\x00\x08\x00\x90\x01\x00\x01\x4D\x00\x53\x00\x20\x00\x53\x00\x68\x00\
x65\x00\x6C\x00\x6C\x00\x20\x00\x44\x00\x6C\x00\x67\x00\x00\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x07\x00\x00\x50\x07\x00\x07\x00\x27\x01\x62\x00\
xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
x00\x00\x01\x02\x02\x50\x1F\x00\x15\x00\xF6\x00\x30\x00\xBD\x02\x00\x00\
xFF\xFF\x82\x00\x53\x00\x74\x00\x61\x00\x74\x00\x69\x00\x63\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x80\x50\x1F\x00\x50\x00\
xF6\x00\x0E\x00\x85\x03\x00\x00\x6D\x00\x73\x00\x63\x00\x74\x00\x6C\x00\
x73\x00\x5F\x00\x70\x00\x72\x00\x6F\x00\x67\x00\x72\x00\x65\x00\x73\x00\
x73\x00\x33\x00\x32\x00\x00\x00\x00\x00\x00\x00";
DialogBoxIndirectParam(GetModuleHandle(0), (LPCDLGTEMPLATEW)res, hh, WaitCallDP, (LPARAM)&w);
return (R)w.rval;
}
};
The sad thing is that we might also have a void
function, so we can't have a "R rval
" inside the WAITCALL
structure. Therefore, we also have to provide a partial specialization when R
is void
:
// Partially specialized for R = void
template <typename ...Args>
class WC<void,Args...>
{
private:
template <typename F, typename Tuple, bool Done, int Total, int... N>
struct call_impl
{
static void callthefunction(F f, Tuple && t)
{
call_impl<F, Tuple, Total == 1 + sizeof...(N), Total, N...,
sizeof...(N)>::callthefunction(f, std::forward<Tuple>(t));
}
};
template <typename F, typename Tuple, int Total, int... N>
struct call_impl<F, Tuple, true, Total, N...>
{
static void callthefunction(F f, Tuple && t)
{
UNREFERENCED_PARAMETER(t);
f(std::get<N>(std::forward<Tuple>(t))...);
}
};
template <typename F, typename Tuple>
static void callthefunction(F f, Tuple && t)
{
typedef typename std::decay<Tuple>::type ttype;
call_impl<F, Tuple, 0 == std::tuple_size<ttype>::value,
std::tuple_size<ttype>::value>::callthefunction(f, std::forward<Tuple>(t));
}
struct WAITCALL
{
wstring txt;
std::function<void(Args...)> f;
std::tuple<Args...> tu;
};
static void WaitCallDPCall(HWND hh, WAITCALL* w)
{
callthefunction(w->f, w->tu);
SendMessage(hh, WM_USER + 1, 0, 0);
return;
}
static INT_PTR __stdcall WaitCallDP(HWND hh, UINT mm, WPARAM, LPARAM ll)
{
WAITCALL* eel = (WAITCALL*)GetWindowLongPtr(hh, GWLP_USERDATA);
switch (mm)
{
case WM_USER + 1:
{
EndDialog(hh, IDOK);
return 0;
}
case WM_INITDIALOG:
{
eel = (WAITCALL*)ll;
SetWindowLongPtr(hh, GWLP_USERDATA, (LONG_PTR)eel);
SetDlgItemText(hh, 701, eel->txt.c_str());
SendDlgItemMessage(hh, 901, PBM_SETMARQUEE, true, 0);
std::thread t(WaitCallDPCall, hh, eel);
t.detach();
return 0;
}
}
return 0;
}
public:
// User calls this
static void WaitCall(HWND hh, const wchar_t* txt, std::function<void(Args...)> fn, Args... a)
{
WAITCALL w;
w.txt = txt;
w.f = fn;
w.tu = ::make_tuple<Args...>(std::forward<Args>(a)...);
const char* res = "\x01\x00\xFF\xFF\x00\x00\x00\x00\x80\x00\x00\
x00\x48\x08\x00\x80\x03\x00\x00\x00\x00\x00\x35\x01\x70\x00\x00\x00\
x00\x00\x00\x00\x08\x00\x90\x01\x00\x01\x4D\x00\x53\x00\x20\x00\x53\
x00\x68\x00\x65\x00\x6C\x00\x6C\x00\x20\x00\x44\x00\x6C\x00\x67\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x50\x07\x00\x07\
x00\x27\x01\x62\x00\xFF\xFF\xFF\xFF\xFF\xFF\x80\x00\x00\x00\x00\x00\
x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x02\x50\x1F\x00\x15\x00\xF6\
x00\x30\x00\xBD\x02\x00\x00\xFF\xFF\x82\x00\x53\x00\x74\x00\x61\x00\
x74\x00\x69\x00\x63\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
x00\x09\x00\x80\x50\x1F\x00\x50\x00\xF6\x00\x0E\x00\x85\x03\x00\x00\
x6D\x00\x73\x00\x63\x00\x74\x00\x6C\x00\x73\x00\x5F\x00\x70\x00\x72\
x00\x6F\x00\x67\x00\x72\x00\x65\x00\x73\x00\x73\x00\x33\x00\x32\x00\
x00\x00\x00\x00\x00\x00";
DialogBoxIndirectParam(GetModuleHandle(0), (LPCDLGTEMPLATEW)res, hh, WaitCallDP, (LPARAM)&w);
}
};
Enjoy your one-line calls!
History
- 30-01-2016: First release