Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Windows-API- / lambda- / TLS-trick

24 views
Skip to first unread message

Bonita Montero

unread,
Jun 9, 2021, 2:03:22 PM6/9/21
to
With Windows there are several APIs to enumerate items of whatever
and you've to supply a pointer to a callback which is called for
each enumerated item. Mostly you can also supply a context-variable
so that you won't have to communicate with global variables (often
in newer ***Ex()-variants). If you have a function which hasn't a
context-variant I've found a nice trick with a lambda and thread
-local-storage to prevent a global function and a global variable.
Here's an example:

#include <Windows.h>
#include <iostream>
#include <vector>

using namespace std;

int main()
{
thread_local
vector<string> *pDateFormats;
thread_local
bool memErr;
auto enumDateFormatsProc = []( LPSTR lpDateFormatString ) -> BOOL
{
try
{
pDateFormats->emplace_back( lpDateFormatString );
return TRUE;
}
catch( ... )
{
memErr = true;
return FALSE;
}
};
vector<string> dateFormats;
pDateFormats = &dateFormats;
memErr = false;
EnumDateFormatsA( enumDateFormatsProc, LOCALE_USER_DEFAULT,
DATE_LONGDATE );
if( !memErr )
for( string &df : dateFormats )
cout << df << endl;
}

The lambda must be convertible to a function-pointer, so it can't
have any captures. And even it hasn't the proper calling-convention,
there's an implcit compilation of the callig-operator for the calling
convention of the enumeration-function.
Being convertible to a normal function-pointer reduces disables any
possibilities for capturing outer variables. But a lambda cann still
access any static or thread-local variables. I didn't make the vector
itself thread-local because the associated memory could stay resident
when an error occurs; so the thread-local storage is just a pointer
to the vector and a memory-error-flag.

Bonita Montero

unread,
Jun 9, 2021, 2:04:16 PM6/9/21
to
Of course there's EnumDateFormatsEx, but I just wanted to give
an example.

Alf P. Steinbach

unread,
Jun 9, 2021, 3:20:11 PM6/9/21
to
On 9 Jun 2021 20:03, Bonita Montero wrote:
> And even [if the lambda] hasn't the proper calling-convention,
> there's an implcit compilation of the callig-operator for the calling
> convention of the enumeration-function.

Well, there's no such guarantee in the standard.

Visual C++ overloads the function call operator of a lambda object and
that causes some bugs with respect to conversions. One I reported got
fixed. Others remain, AFAIK.

But for 64-bit Windows programming you should be safe: 1 calling
convention to rule them all.

- Alf

Bonita Montero

unread,
Jun 9, 2021, 10:31:31 PM6/9/21
to
> Well, there's no such guarantee in the standard.

I'm dealing with Windows-compilers, so the standard doesn't count.

> But for 64-bit Windows programming you should be safe: 1 calling
> convention to rule them all.

With 32-bit Windows the calling-operator is individually compiled
for each call if you have different calling-conventions.

Bonita Montero

unread,
Jun 9, 2021, 10:47:21 PM6/9/21
to
>> Well, there's no such guarantee in the standard.

> I'm dealing with Windows-compilers, so the standard doesn't count.

Here, test it yourself on Windows 32 Bit:

#include <iostream>

using namespace std;

int main()
{
thread_local
int volatile i = 0;
auto lambda = []( int add )
{
i += add;
};
void (__stdcall *volatile fs)( int ) = lambda;
void (__fastcall *volatile ff)( int ) = lambda;
fs( 123 );
ff( 456 );
cout << (void *)fs << endl;
cout << (void *)ff << endl;
}

The lambda-function is compiled twice here.

Bonita Montero

unread,
Jun 10, 2021, 5:38:22 AM6/10/21
to
> #include <iostream>
>
> using namespace std;
>
> int main()
> {
>     thread_local
>     int volatile i = 0;
>     auto lambda = []( int add )
>     {
>         i += add;
>     };
>     void (__stdcall  *volatile fs)( int ) = lambda;
>     void (__fastcall *volatile ff)( int ) = lambda;
>     fs( 123 );
>     ff( 456 );
>     cout << (void *)fs << endl;
>     cout << (void *)ff << endl;
> }
> The lambda-function is compiled twice here.

Unfortunately that doesn't work with clang-cl, the clang-variant
that claims to be mostly MSVC-compatible. I found this incompati-
bility documented from 2018 on the clang-devel mailinglist. I'm
asking why this hasn't been fixed yet - compiling the lamdda with
multiple calling-conventions shouldn't be a big deal.
0 new messages