0

I'm trying to create a thread with a non-static class member, like this:

template <class clName>
 DWORD WINAPI StartThread(PVOID ptr) {
   ((clName*)(ptr))->testf(); // this is static member name I want to be able use different names with the same function
   return 1;
 }

class Thread {
  private :
  HANDLE native_handle = 0;
  DWORD id = 0;
public :
  template <class T,class U>
  Thread(T U::*member,U* original); // I want to use different members with the same function
  bool run();
}

template<class T,class U>
Thread::Thread(T U::*member, U* original)
{
  native_handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)StartThread<U>,original, CREATE_SUSPENDED, &id);
}

bool Thread::run() {
  DWORD res = ResumeThread(native_handle);
  if (res == (DWORD)-1) return false;
  return true;
}


class testt {
public :
  void testf() {
    MessageBoxA(0, "working", "", 0);
  }
  void doIt() {
    Thread t(&testt::testf,this);
    t.run();
  }
};

int main() {
  testt tt;
  tt.doIt();
}

As you see, I can only run a particular member, so this method isn't portable and can't be used for any class or member.

I know I can use std::thread easily, but I'm working on a project that I shouldn't use any C++ runtime in, so I am creating wrappers for new/delete, threads, file I/O, etc. Otherwise, I always use std::thread and it's fantastic. In this project, I have to use Win32 API only.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
dev65
  • 1,440
  • 10
  • 25
  • Sounds like you should work to find a way to shed all those artificial constraints, so you can just write the natural working code. – Jesper Juhl May 27 '18 at 15:33
  • 1
    If you have to cast the thread proc in the CreateThread call you have declared it incorrectly. – Richard Critten May 27 '18 at 15:37
  • @jesperJuhl I think there is a method as standard thread and boost thread can do this – dev65 May 27 '18 at 15:47
  • @RichardCritten why incorrectly ? – dev65 May 27 '18 at 15:48
  • 4
    Because `StartThread` should already be a `LPTHREAD_START_ROUTINE`. There is no point to defining it as anything different. Remove the cast and see if your function meets the required prototype. If it doesn't, make it fit. Using God's Hammer to force it to fit is a bad idea. – user4581301 May 27 '18 at 15:51
  • @user4581301 used the template to use any class as I was planning to use any member in any class – dev65 May 27 '18 at 15:54
  • You need the static function as an abstraction, and the static function must fit the correct prototype. The static function can then call a member pointer that you have wrapped correctly with the appropriate parameters. Davis Herring has an idea worth following in their answer below. – user4581301 May 27 '18 at 15:58
  • I wonder... Do your assignment constraints prevent the use of a lambda expression? You may be able to use a lambda as your wrapper. – user4581301 May 27 '18 at 16:02
  • CreateThread can't run lambadas – dev65 May 27 '18 at 16:04
  • You use the lambda to store the user's parameters and pass the lambda as the user pointer to `CreateThread`. `StartThread` executes the lambda. Note: I haven't tried this. It may not work without some standard library support, but if it does, the bulk of the messy work just went away. – user4581301 May 27 '18 at 16:15
  • When the compiler tells you that your function has the wrong signature, listen to it. Your cast tells a big lie to the compiler. Don't lie to the compiler – David Heffernan May 27 '18 at 16:46
  • @RemyLebeau - in general - if we use member function declared as `__stdcall` with no explicit argument (implicit `this`) - this function will be binary compatible for `PTHREAD_START_ROUTINE` and can be correct used for thread starting point. cast look like possible only through union here – RbMm May 27 '18 at 17:14
  • @RemyLebeau - *CreateThread() expects a pointer to a standalone C style function* - this is not absolute true. `ULONG __stdcall testf()` member function - is ok for thread entry point. and can be used with `CreateThread`. like `union { LPTHREAD_START_ROUTINE pv; ULONG (__stdcall testt::* pfn)(); }; pfn = &testt::testf; CreateThread(0,0, pv, this, 0, 0);` (may be exist and another ways in modern c++) – RbMm May 27 '18 at 17:35
  • @RbMb: That's formally undefined behavior on multiple levels, and totally unnecessary. Compared to the cost of thread creation, one extra function call from a trampoline with the correct type to the "friendly" thread procedure is not a problem. – Ben Voigt Jun 09 '18 at 20:17

4 Answers4

1

A template parameter can be a pointer to member, so you can augment StartThread that way and make Thread::Thread’s pointer-to-member parameter a template parameter. You can’t supply explicit template arguments to a constructor template, so you’ll have to use a special “tag” argument to convey them:

template<class C,class T,T C::*P>
DWORD WINAPI StartThread(PVOID ptr) {
  (static_cast<C*>(ptr)->*P)();
  return 1;
}

template<class C,class T,T C::*P>
struct Tag {};

class Thread {
private :
  HANDLE native_handle = 0;
  DWORD id = 0;
public :
  template<class C,class T,T C::*P>
  Thread(Tag<C,T,P>,C*);
  bool run();
};

template<class C,class T,T C::*P>
Thread::Thread(Tag<C,T,P>,C* original)
{
  native_handle = CreateThread(0, 0, StartThread<C,T,P>,original,
                               CREATE_SUSPENDED, &id);
}

bool Thread::run() {
  DWORD res = ResumeThread(native_handle);
  if (res == (DWORD)-1) return false;
  return true;
}


class testt {
public :
  void testf() {
    MessageBoxA(0, "working", "", 0);
  }
  void doIt() {
    Thread t(Tag<testt,void(),&testt::testf>(),this);
    t.run();
  }
};

int main() {
  testt tt;
  tt.doIt();
}

Note the void() function type as the member type T; simpler syntax is available in C++17 with auto in a template parameter’s type.

Or preserve it as a normal parameter by making a struct containing a T* and a T::* and passing a pointer to it as your PVOID. The trick this way is that you’ll need to use type erasure to destroy that block correctly, or else use reinterpret_cast to store the pointers temporarily under fixed types (as already done for StartThread).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • I get this error : 'type cast': cannot convert from 'overloaded-function' to 'LPTHREAD_START_ROUTINE' – dev65 May 27 '18 at 16:05
  • @dev65 since you did not show your new code, we can't tell you what you are doing wrong with it. – Remy Lebeau May 27 '18 at 17:29
  • changed startthread to `template DWORD WINAPI StartRoutine(PVOID ptr) { ((clName*)(ptr))->*member(); return 1; }` then changed the constructor to : `native_handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&StartRoutine,original, CREATE_SUSPENDED, &id);` – dev65 May 27 '18 at 17:34
  • @dev65: If you added `member` as a template parameter for the constructor as well, shouldn’t it just be ``? – Davis Herring May 27 '18 at 18:39
  • when using this `template Thread::Thread(T U::* memb, U* original)` no function matches : `Thread t(&testt::testf,this);` – dev65 May 27 '18 at 19:26
  • @dev65: Ah, my mistake—I forgot that this was a constructor template. You can’t provide explicit template parameters there; let me edit. – Davis Herring May 27 '18 at 19:36
  • I almost got a solution , I'll post it as answer if no answers are being to be posted – dev65 May 28 '18 at 01:07
1

Without the use of the C++ runtime, like std::thread and std::function, your options are a bit limited.

Try something more like this:

template <class U>
class Thread {
private:
    HANDLE native_handle = 0;
    DWORD id = 0;
    U *object;
    void (U::*object_member)();

    static DWORD WINAPI ThreadProc(PVOID ptr);

public:
    Thread(void U::*member, U* obj);

    bool start();
};

template<class U>
DWORD WINAPI Thread<U>::ThreadProc(PVOID ptr) {
    Thread *t = static_cast<Thread*>(ptr);
    U *obj = t->object;
    void (U::*member)() = t->object_member;
    (obj->*member)();
    return 1;
}

template<class U>
Thread<U>::Thread(void U::*member, U* obj) :
    object_member(member), object(obj) {
    native_handle = CreateThread(0, 0, &ThreadProc, this, CREATE_SUSPENDED, &id);
}

template <class U>
bool Thread<U>::start() {
    return (ResumeThread(native_handle) != (DWORD)-1);
}

class testt {
public:
    void testf() {
        MessageBoxA(0, "working", "", 0);
    }

    void doIt() {
        Thread<testt> t(&testt::testf, this);
        t.start();
    }
};

int main() {
    testt tt;
    tt.doIt();
}

Otherwise, you may have to resort to something more like this:

class Thread {
private:
    HANDLE native_handle = 0;
    DWORD id = 0;
    void (*func)(void*);
    void *param;

    static DWORD WINAPI ThreadProc(PVOID ptr);

public:
    Thread(void (*f)(void*), void* p);
    bool start();
};

DWORD WINAPI Thread::ThreadProc(PVOID ptr) {
    Thread *t = static_cast<Thread*>(ptr);
    void (*func)(void*) = t->func;
    (*func)(t->param);
    return 1;
}

Thread::Thread(void (*f)(void*), void *p) :
    func(f), param(p) {
    native_handle = CreateThread(0, 0, &ThreadProc, this, CREATE_SUSPENDED, &id);
}

bool Thread::start() {
    return (ResumeThread(native_handle) != (DWORD)-1);
}

class testt {
private:
    static void proc(void *p) {
        static_cast<testt*>(p)->testf();
    }
public:
    void testf() {
        MessageBoxA(0, "working", "", 0);
    }
    void doIt() {
        Thread t(&testt::proc, this);
        t.start();
    }
};

int main() {
    testt tt;
    tt.doIt();
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • awesome solution for the current situation , keep in mind that I won't feel much limited as the project is quite small and I could use my old code with manual ThreadProc every time , but your code will save much time – dev65 May 27 '18 at 17:55
  • @RemyLabeau when I used the code I got that void was illegal so I used int instead , then I got this error : 'Thread::Thread(Thread &&)': cannot convert argument 1 from 'int (__cdecl testt::* )(void)' to 'int testt::* – dev65 May 27 '18 at 20:41
  • @dev65 I made some tweaks to the code. Try it again. I don't have access to a compiler until next week, or I would have checked it before I posted it – Remy Lebeau May 27 '18 at 23:18
  • 1
    To be fully standard-compliant, `ThreadProc` needs to be `extern "C"` (language linkage is part of a function's type), which furthermore requires it to be a free function, not a static member. To access private member data, `friend` can be safely used. – Ben Voigt Jun 09 '18 at 20:19
0

I ended with a winapi alternative for std::thread and works the same and is probably better

class letTest {
private :
  void pri(int u,float g, char l) {
      MessageBoxA(0, 0, 0, 0);
  }

public :
  int doIt(int o) {
      auto t = thread(&letTest::pri, this,5,4.2,'k'); // works well with any number and type of parameters
      t.join();
      return 5;
  }
};

also with usual functions

void ltest(int i) {
   MessageBoxA(0, to_string(i).c_str(), 0, 0);
}
int main() {
   auto t = thread(ltest, 4);
   t.join();
}
dev65
  • 1,440
  • 10
  • 25
-1

application-defined function to be executed by the CreateThread must have signature:

DWORD WINAPI ThreadProc(
  _In_ LPVOID lpParameter
);

if we was use class member (non-static) function - it must have signature

class testt {
    ULONG WINAPI testf();
};

need remember that every non static member function got pointer to this as first parameter. simply we not explicit declare it. as result ULONG WINAPI testf(/*testt* this*/); exactly match to ThreadProc callback function. and we can use it as thread entry point.

In this project, I have to use Win32 API only.

i not view for what need use wrappers for concrete this thread api, but code can look like:

template <class U>
class Thread 
{
    HANDLE _hThread;
    DWORD _id;

public :

    Thread() : _hThread(0) {}

    ~Thread() 
    { 
        if (_hThread) CloseHandle(_hThread);
    }

    ULONG Create(ULONG (WINAPI U::*member)(), U* This)
    {
        union {
            LPTHREAD_START_ROUTINE lpStartAddress;
            ULONG (WINAPI U::*_member)();
        };

        _member = member;

        if (_hThread = CreateThread(0, 0, lpStartAddress, This, CREATE_SUSPENDED, &_id))
        {
            return NOERROR;
        }

        return GetLastError();
    }

    ULONG run()
    {
        return ResumeThread(_hThread) == MAXULONG ? GetLastError() : NOERROR;
    }

    ULONG wait()
    {
        return WaitForSingleObject(_hThread, INFINITE);
    }
};


class testt {
    PCWSTR _txt, _caption;
public :

    testt(PCWSTR txt, PCWSTR caption) : _txt(txt), _caption(caption) { }

    ULONG WINAPI testf() {
        return MessageBox(0, _txt, _caption, 0);
    }

    void doIt() {

        Thread<testt> t;

        if (t.Create(&testt::testf, this) == NOERROR)
        {
            if (t.run() == NOERROR)
            {
                t.wait();
            }
        }
    }
};

void demo()
{

    testt o(L"text", L"caption");
    o.doIt();
}

however for compare code without template class, but direct start thread to class member function:

class testt {
    PCWSTR _txt, _caption;
public :

    testt(PCWSTR txt, PCWSTR caption) : _txt(txt), _caption(caption) { }

    ULONG WINAPI testf() {
        return MessageBox(0, _txt, _caption, 0);
    }

    void doIt() {

        union {
            LPTHREAD_START_ROUTINE lpStartAddress;
            ULONG (WINAPI testt::*pfn)();
        };

        pfn = &testt::testf;

        if (HANDLE hThread = CreateThread(0, 0, lpStartAddress, this, 0, 0))
        {
            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
        }
    }
};

void demo()
{

    testt o(L"text", L"caption");
    o.doIt();
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • @dev65 - sorry not understand, where *WINAPI was the missing* (in my or your code or ?) – RbMm May 27 '18 at 21:01
  • sorry , I was wrong and thought in another thing , the problem in Remy Lebeau code is that you can't cast the non static member to a callback function – dev65 May 27 '18 at 23:42
  • as I said : 'Thread::Thread(Thread &&)': cannot convert argument 1 from 'unsigned int (__cdecl testt::* )(void *)' to 'unsigned int (__cdecl *)(void *)' – dev65 May 27 '18 at 23:46
  • @dev65 - at first and main - thread entry point must be `__stdcall` - not `__cdecl`. at second - where you view conversion (type cast) in my code ? are my code not compile or wrong work ? – RbMm May 28 '18 at 06:12
  • @dev65 - but why in this case you comment this here :) ? here exist sense do comment only about my implementation – RbMm May 28 '18 at 10:02
  • 1
    That `union` trick doesn't work in C++. And calling a function through a pointer of a different type is immediately undefined behavior -- C++ has no concept of compatible function types. – Ben Voigt Jun 09 '18 at 20:22
  • @BenVoigt - i compile and test this code in c++ (*cl.exe*) and all of course ok work. union member simply share the same memory (or register), because this all and ok. and by sense we need exactly `ULONG WINAPI testf();` signature for class member - if take to account hidden *this* pointer - real signature will be `ULONG WINAPI testf(testt* this);` - this is exactly `PTHREAD_START_ROUTINE` – RbMm Jun 09 '18 at 21:33
  • *And calling a function through a pointer of a different type* - this is not different but **same** type in my code – RbMm Jun 09 '18 at 21:34
  • 1
    If it were the same type, you wouldn't be using the `union` trick (which is only legal in C anyway, C++ doesn't allow reading a different union member than the one last written to). And seeing it work once, on a particular compiler version, with particular optimization settings, doesn't prove it is legal. Undefined behavior is allowed to work some, all, or none of the time. – Ben Voigt Jun 09 '18 at 21:35
  • @BenVoigt - this is from compiler view different types, but from binary view - the same type - *__stdcall* function with one argument (*this*). and `union` "trick" the great work with all compilers and any level of optimization. with *cl.exe* especially because ms many time used union internals in windows in this manner – RbMm Jun 09 '18 at 21:39
  • 1
    @RbMm: Really it works only with cl.exe, other compilers do break code that violates strict aliasing. It's called "optimization instability". And I think you're right that MS permits it because they have legacy code (Windows for sure and probably also Office and other apps) that they build with this compiler and isn't strictly conformant. – Ben Voigt Jun 09 '18 at 21:42
  • @BenVoigt - i special test this kind of code with another compilers also (gcc, clang, icc) on x86/x64 (where i understand asm code). test and in more complex case, with say `union { float a; int b; }`. and i not found any code breaks. if you can build example where this fail by sense - will be very interesting see it. and about "strict aliasing" - this is can be breaked(all examples which i view) when we **modify** memory via one alias and compiler can assume that memory was not modified by another. but here i use read only union members – RbMm Jun 09 '18 at 21:48
  • i mean i use only that value of 2 pointers in union always the same. i not modify any memory by this pointers. can you build demo poc where this is fail (generate unexpected by sense code) ? – RbMm Jun 09 '18 at 21:49
  • 1
    http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ru-pun The big problem with optimization-unstable code is that often nothing goes wrong until you install a new compiler version and then all your bugs suddenly start to manifest. – Ben Voigt Jun 09 '18 at 21:52
  • 1
    And see where they say "this code produced no output" at http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Ru-naked – Ben Voigt Jun 09 '18 at 21:53
  • @BenVoigt - really this is not true - at first `double` is 8 byte when `int` - 4. need use `float` with `int` or `long long` with `double` - about "this code produced no output" - look and test code like https://godbolt.org/g/qZ3vvT – RbMm Jun 09 '18 at 22:10
  • @BenVoigt - or say this https://godbolt.org/g/KVbyv4. can you in opposite build poc where was unexcepted, wrong code when used union ? but not modify any memory location via one union member and then read this memory location via another member (alias). try build code where we use only that 2 union members (with the same size) always have the **same** value - only this i use – RbMm Jun 09 '18 at 22:26
  • The code exhibits undefined behavior. You cannot prove absence of undefined behavior by looking at the output of compilers. To understand this, you need to understand, why undefined behavior exists at all: It gives compiler writers flexibility in implementing their optimizers. Looking at compiler output can only determine, that any particular compiler hasn't taken advantage of the freedom it has, to produce optimized code that collides with your (undue) expectations. That surely will change one day, or when code is placed in a different code context. – IInspectable Aug 25 '18 at 15:41
  • @IInspectable - my code based on very simply thing - the union share memory. all union members have the same address (this already again clear stated in latest c++20 standard :)) ) and as result we can say that value of `pfn` and `lpStartAddress` is always equal – RbMm Aug 25 '18 at 15:46
  • C++20 isn't a thing yet. Regardless, if you know this to be safe in C++20, add a reference to documentation to this proposed answer to make it clear, that it is currently unsafe, but will be safe in a future revision of the C++ programming language. – IInspectable Aug 25 '18 at 15:52
  • @IInspectable - i already many time paste in discuss with you very old reference from msdn - *All members are stored in the same memory space and start at the same address. The stored value is overwritten each time a value is assigned to a different member.* in middle formal c++ docs say that layout of union is implementation details. and now, in latest draft - again that All members start at the same address – RbMm Aug 25 '18 at 15:56
  • @IInspectable - *As a consequence, all non-static data members of a union object have the same address.* - look for [n4741.pdf](https://github.com/cplusplus/draft/blob/master/papers/n4741.pdf) - 12.3 Unions - 2. and *A union object and its non-static data members are pointer-interconvertible* – RbMm Aug 25 '18 at 16:00
  • No one was challenging, that they weren't. And still, the strict aliasing rule allows compilers to not even attempt to read the value you stored through a different member, because the optimizer decided, that nothing can possibly have changed, and uses the cached value from the initialization. The optimizer is correct in doing this, because it is allowed to. Your proposed code breaks, once it meets an optimizer that does this. – IInspectable Aug 25 '18 at 16:04
  • @IInspectable - strict aliasing can on or off by compiler option. this is first. and from generic view - anyway this 2 pointers stored in memory (stack) or some non volatile register. compiler anyway need pass something to `CreateThread` as parameter. no another way. as result it anyway need be read from memory or use this non volatile register. and got value which i set by `pfn = &testt::testf;` – RbMm Aug 25 '18 at 16:08
  • @IInspectable - [*The details of that allocation are implementation-defined*](https://en.cppreference.com/w/cpp/language/union) vs *all non-static data members of a union object have the same address.* :)) – RbMm Aug 25 '18 at 16:09
  • [What is the Strict Aliasing Rule and Why do we care?](https://gist.github.com/shafik/848ae25ee209f698763cffee272a58f8) – IInspectable Aug 25 '18 at 16:29
  • @IInspectable - at first on some compilers this option can be off - `-fno-strict-aliasing`. for *CL* compiler this is always off how i know. here no this strict aliasing at all. and what i do here in poc - https://godbolt.org/z/k0ndo6 - are you try to say that optimizator drop `pfn = &testt::testf;` line without warning ? only here can be code broken. if this line is executed - all ok – RbMm Aug 25 '18 at 16:49
  • @IInspectable - interesting that all articles about Type Punning have concrete examples how code is defeated with aggressive optimization, but i not view how is defeat https://godbolt.org/z/7ykklI now. i already not say about cl not support this strict aliassing at all and another compilers support off it. but much more interesting discuss about addreff/relese - why you not reply here ? – RbMm Aug 25 '18 at 16:59
  • @IInspectable - even better this - https://godbolt.org/z/MG28HZ - where the whole essence is preserved. i can not found under any compiler and optimization where this is breaked, already not say about *cl* – RbMm Aug 25 '18 at 17:21