[C++] Template/Funktionsproblem

kickerxy123

Erfahrenes Mitglied
Hallo,

ich habe ein Problem mit Templates/Funktionen. Und zwar schreibe ich eine Thread-Klasse, die in etwa so laufen soll:
C++:
CThread(meineFunktion(para1,para2,...), someFlag);

Ziel ist also, dass eine x-beliebige Funktion, egal wie sie aufgebaut ist, in ein Thread durch meine Klasse verlagert wird.

Jetzt kriege ich das ganze kompiliert, wenn ich eine Funktion mit dem Aufbau void Func(void); habe. Allerdings nicht mit beliebig aufgebauten Funktionen. Daher hatte ich zwei Ansätze/Ideen:

1.) Variadic Templates benutzen (müsste ich mich mal einarbeiten..)
2.) Den Aufbau einer Funktion besser verstehen. Denn was eine void a(void) von anderen Funktionen trennt ist bloß eine Parameter- und Rückgabeliste und die Funktion hat nach wie vor eine Startadresse. D.h. ich müsste wissen, ob die Parameterliste immer an einer bestimmten Stelle im Speicher nach der Startadresse kommt und diese Werte manuell füllen und die Funktion als void a(void) aufrufen und am Ende den Rückgabewert aus dem Speicher holen (kann das überhaupt gehen, oder springen da alle c++ Sicherungen raus ;)?)

zu 1) das ist leider recht problematisch, da ich ein TYPANY a(TYPANY, ..) nicht in meiner Klasse weiterreichen kann (der Konstruktor müsste das weiterreichen können; dazu müsste ich z.B. die Typen speichern können etc)

Ich bin leider etwas ratlos...

Kann mir bitte wer einen Denkanstoß geben?


Danke und Gruß,
kickerxy

#edit: Die Adressen der Parameter kann ich ja ermitteln, aber die des Rückgabewertes :?:
Die wichtigste Frage wäre, ob diese manipulierte Art des Funktionsaufrufes funktionieren kann.
 
Zuletzt bearbeitet:
Hi

zu Punkt 2 && edit:
Theoretisch möglich.
Allerdings muss beim Aufruf und in der Funktion jeweils ein Stück Assembler rein.
Das Problem: Eventuell machen Geschwindigkeitsoptimierungen (und andere) vom Compiler das dann kaputt.
Das es funktioniert, kannst du dir eigentlich nur sicher sein, wenn du es nach jedem Compilerdurchlauf prüfst.

Gruß
 
Du kannst Funktionen in Visual Studio als __declspec(naked) deklarieren, dann kannst/musst du den Prolog und Epilog der Funktion selbst schreiben in Assembler bzw kannst ihn dann eben auch in einer anderen Funktion bereits schreiben und via jmp hinspringen (rücksprungadresse nicht vergessen als letztes auf den Stack zu legen). Das ist aber alles nicht sonderlich guter Programmierstil. Besser ist es die Templates aus functional und (falls vom Compiler unterstützt) std::tr1 zu verwenden, oder falls nicht die äquivalenten aus boost.

Beispiel:
C++:
void foo(int a1, int a2, int a3)
{
	std::cout << a1 << " " << a2 << " " << a3 << std::endl;
}

int main(void)
{
	std::tr1::function<void (int, int)> fun;
	fun = std::tr1::bind(foo, std::tr1::placeholders::_1, 2, std::tr1::placeholders::_2);
	fun(3, 4);
}

Ausgabe:
3 2 4

In deinem Fall:
C++:
int foo(int a1, int a2, int a3)
{
	std::cout << a1 << " " << a2 << " " << a3 << std::endl;
	return a1 + a2 + a3;
}

template<class T>
class CThread
{
	std::tr1::function<T (void)>& m_target;
public:
	CThread(std::tr1::function<T (void)> fun) : m_target(fun)
	{
	}

	T Execute()
	{
		return m_target();
	}
};

int main(void)
{
	CThread<int> t(std::tr1::bind(foo, 3, 2, 4));
	t.Execute();
}
 
Der Vollständigkeit halber habe ich auch noch kurz eine Implementation der Variante 2 von dir. Diese ist unsicher, unsauber und ein El Dorado für jeden, der Sicherheitslücken ausnützen möchte, aber sie funktioniert für Funktionen, die __cdecl als Aufrufkonvention verwenden (wegen dem add esp, sizeArgs) und keine komplexen Rückgabewerte haben (und wie Sheel andeutete ist es sicher gesünder den Optimierer auszuschalten).
C++:
template<class T>
class CThreadUnsafe
{
	std::vector<DWORD> m_args;
	void* m_target;

public:
	CThreadUnsafe(void* function) : m_target(function)
	{
	}

	template<class T>
	void PushArg(const T& arg)
	{
		size_t size = sizeof(T);
		size_t nDwords = size / sizeof(DWORD);
		DWORD* ptr = (DWORD*)&arg;
		for(size_t i = 0; i < nDwords; ++i)
			m_args.push_back(ptr[i]);
		if((size % sizeof(DWORD)) != 0)
		{
			DWORD last = 0;
			LPBYTE bytePtr = (LPBYTE)(ptr + nDwords);
			for(size_t i = 0; i < (size % sizeof(DWORD)); ++i)
			{
				last |= (bytePtr[i]) << (i * 8);
			}

			m_args.push_back(last);
		}
	}

	T Execute()
	{
		size_t nArgs = m_args.size();
		DWORD* args = &m_args[nArgs - 1];
		size_t sizeArgs = nArgs * sizeof(DWORD);
		size_t sizeDword = sizeof(DWORD);
		DWORD addrTarget = (DWORD)m_target;
		DWORD retVal = 0;
		__asm
		{
			mov ecx, nArgs
			mov edx, args
lblLoop1:
			push dword ptr [edx]
			dec ecx
			sub edx, sizeDword
			cmp ecx, 0
			ja lblLoop1
			mov eax, addrTarget
			call eax
			add esp, sizeArgs
			mov retVal, eax
		}
		m_args.clear();

		return (T)retVal;
	}
};

In der Verwendung sähe das dann so aus:
C++:
class FooBar
{
	int m_someMember;
	char m_someChar;
public:
	FooBar(const int& someParam) : m_someMember(someParam), m_someChar(someParam % 10 + 0x30)
	{
	}

	void Print()
	{
		std::cout << m_someMember << " -> " << m_someChar << std::endl;
	}
};

int __cdecl foo(int a1, int a2, int a3, FooBar bar, FooBar* pBar)
{
	std::cout << a1 << " " << a2 << " " << a3 << std::endl;
	pBar->Print();
	bar.Print();
	return a1 + a2 + a3;
}

int main(void)
{
	FooBar fob(301);
	FooBar* pfob = new FooBar(207);

	CThreadUnsafe<int> unsafe(foo);
	unsafe.PushArg(2);
	unsafe.PushArg(3);
	unsafe.PushArg(4);
	unsafe.PushArg(fob);
	unsafe.PushArg(pfob);
	std::cout << "Return: " << unsafe.Execute() << std::endl;

	delete pfob;
}

Mit Ausgabe:
2 3 4
207 -> 7
301 -> 1
Return: 9

Gruss
Muepe
 
Zuletzt bearbeitet:
Hallo ihr beiden,

ich danke euch! Besonders dir, Muepe32, für deine ausführlichen Beispiele.

Ich habe mir jetzt erstmal den aktuellsten MinGw/Gcc in mein Dev-Cpp integriert. Somit kann ich die TR1 nutzen =)

Ich habe nun endlich mein Ziel vollständig erreicht. Meine Klasse besteht jetzt aus Variadic Templates in Verbindung mit tr1::function / ::bind und sieht nun in der Hauptzeile wie folgt aus:
C++:
template <typename T, typename ...Tn>void CThread<T, Tn...>::set(T (* pFunc)(Tn...), Tn... args)
{
    pExecFunc = tr1::bind(pFunc, args...);
}

Ich danke nochmal vielmals für die passenden Anstöße =)

Viele Grüße,
kickerxy


#edit: Ich habe doch noch ein Problem festgestellt; ich kann zwar alle möglichen Parameter übergeben (Strings, Klassen, Structs, ...) jedoch gibt es einen Runtime Error, wenn ich als Returntype String haben möchte. Ich werd mich hier ggf. nochmal melden und lasse daher das Thema geöffnet. Danke.
 
Zuletzt bearbeitet:
Zurück