Komfortabler Dateimanager mit vielen Funktionen

Visual Studio 2015 und die Universal CRT

By Sven on 12.05.2015 - 11:15 in Visual Studio 2015

Entwickler von portablen Programmen, die sich ohne Installation aufrufen lassen, sollten sich den Umstieg auf Visual Studio 2015 gut überlegen. Grund dafür ist die neue universelle Laufzeitbibliothek für C/C++ (Universal CRT). Bisher brauchte man nur die C/C++-Laufzeitbibliotheken msvcr120.dll und msvcp120.dll der Anwendung beilegen und die Anwendung konnte aus jedem Verzeichnis gestartet werden. Mit Visual Studio 2015 hat Microsoft die Laufzeitbibliothek in einen universellen und einen compilerabhängigen Teil aufgeteilt. Der universelle Teil ucrtbase.dll wird Bestandteil von Windows 10 und kann in Zukunft über Windows Update aktualisiert werden. Ältere Windows-Versionen können die Universal CRT ebenfalls über Windows Update einspielen, alternativ ist auch die Verwendung eines eigenständigen Installationspakets vcredist.exe möglich, das 14 MB groß ist und neben der Universal CRT auch alle anderen Laufzeitbibliotheken (inklusive MFC) enthält.

Problematisch an der ganzen Geschichte ist, dass der universelle Teil fest im Windows-Systemverzeichnis residieren soll und nicht mehr in das Anwendungsverzeichnis kopiert werden darf. Unter Windows 10 ist das nicht weiter tragisch. Der universelle Teil ist immer vorhanden, der compilerabhängige Teil kann mit der vcruntime140.dll weiter der Anwendung beigelegt werden. Auf den älteren Systemen muss der universelle Teil aber vor dem Starten der Anwendung installiert werden, entweder über Windows Update oder über das eigenständige Installationspaket. Auf dem eigenen Rechner ist das eine einmalige Sache und sollte nicht weiter stören. Der Zweck eines portablen Programms ist aber, dass man es schnell auf jedem Rechner starten kann und dafür in der Regel keine Administrator-Rechte benötigt. Mit dem Einzug der Universal CRT klappt das auf Windows XP/Vista/7 und 8 nur noch, wenn diese vom Administrator vorher installiert wurde. Ansonsten startet das Programm nicht. Man kann dies nur umgehen, indem man die C/C++- und MFC-Laufzeitbibliotheken statisch einbindet. Damit kann sich aber auch die Programmgröße wesentlich erhöhen, insbesondere wenn eine Anwendung aus mehreren Modulen besteht.

Wenn Microsoft für dieses Problem keine Lösung findet, dann werden Entwickler von portablen Programmen bei Visual Studio 2013 bleiben müssen. Man kann Visual Studio 2013 und 2015 zwar parallel installieren und in Visual Studio 2015 die Projekte auch mit den 2013-Tools erstellen. Dabei muss man aber auf die neuen Compilerfunktionen und -optimierungen verzichten.

Eine weitere Möglichkeit wäre die Verwendung der CRT/MFC aus Visual Studio 2013 in Visual Studio 2015. Dafür kopiert man die Verzeichnisse VC\atlmfc, VC\crt, VC\include und VC\lib aus 2013 nach 2015. Ein MFC-Beispielprojekt lässt sich so fehlerfrei kompilieren, beim Linken gibt es allerdings ein paar nicht aufgelöste Referenzen:

1>MainFrm.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol "__Init_thread_header" in Funktion ""protected: static struct AFX_MSGMAP const * __stdcall CMainFrame::GetThisMessageMap(void)" (?GetThisMessageMap@CMainFrame@@KGPBUAFX_MSGMAP@@XZ)".
1>stdafx.obj : error LNK2001: Nicht aufgelöstes externes Symbol "__Init_thread_header".
1>MainFrm.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol "__Init_thread_footer" in Funktion ""protected: static struct AFX_MSGMAP const * __stdcall CMainFrame::GetThisMessageMap(void)" (?GetThisMessageMap@CMainFrame@@KGPBUAFX_MSGMAP@@XZ)".
1>stdafx.obj : error LNK2001: Nicht aufgelöstes externes Symbol "__Init_thread_footer".
1>MainFrm.obj : error LNK2001: Nicht aufgelöstes externes Symbol "__Init_thread_epoch".
1>stdafx.obj : error LNK2001: Nicht aufgelöstes externes Symbol "__Init_thread_epoch".

1>MFCApplication1Doc.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""void __cdecl operator delete(void *,unsigned int)" (??3@YAXPAXI@Z)" in Funktion ""public: virtual void * __thiscall CDocument::CDocumentAdapter::`scalar deleting destructor'(unsigned int)" (??_GCDocumentAdapter@CDocument@@UAEPAXI@Z)".
1>stdafx.obj : error LNK2001: Nicht aufgelöstes externes Symbol ""void __cdecl operator delete(void *,unsigned int)" (??3@YAXPAXI@Z)".

1>ChildFrm.obj : error LNK2001: Nicht aufgelöstes externes Symbol "___std_terminate".

Nach dem Setzen der zusätzlichen Compileroptionen (Projekteinstellungen – C/C++ – Befehlszeile)

/Zc:threadSafeInit-,sizedDealloc-,implicitNoexcept-

lässt sich das MFC-Beispielprojekt ohne Fehler mit dem Compiler von Visual Studio 2015 erstellen und aufrufen. Es verwendet die C-/C++/MFC-Laufzeitbibliotheken von Visual Studio 2013 und kann somit wie gewohnt verteilt werden.

Beim weiteren Test mit SpeedCommander wurden mir noch zwei weitere Referenzen nicht aufgelöst:

1>SpeedCommander.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""void __stdcall `eh vector constructor iterator'(void *,unsigned int,unsigned int,void (__thiscall*)(void *),void (__thiscall*)(void *))" (??_L@YGXPAXIIP6EX0@Z1@Z)" in Funktion ""public: virtual void __thiscall CSpeedCommanderApp::LoadState(void)" (?LoadState@CSpeedCommanderApp@@UAEXXZ)".
1>SpeedCommander.obj : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol ""void __stdcall `eh vector destructor iterator'(void *,unsigned int,unsigned int,void (__thiscall*)(void *))" (??_M@YGXPAXIIP6EX0@Z@Z)" in Funktion ""public: void * __thiscall ATL::CStringT<wchar_t,class StrTraitMFC_DLL<wchar_t,class ATL::ChTraitsCRT<wchar_t> > >::`vector deleting destructor'(unsigned int)" (??_E?$CStringT@_WV?$StrTraitMFC_DLL@_WV?$ChTraitsCRT@_W@ATL@@@@@ATL@@QAEPAXI@Z)".

Die Ursache dafür ist, dass sich die Parameter für die vom Compiler generierten Funktionen __ehvec_ctor und __ehvec_dtor leicht geändert haben und so nicht mehr denen der VS 2013-Laufzeitbibliothek entsprechen. Zur Abhilfe fügt man die Zeilen

#define CALEETYPE __stdcall
#define __RELIABILITY_CONTRACT
#define SECURITYCRITICAL_ATTRIBUTE
#define ASSERT_UNMANAGED_CODE_ATTRIBUTE

#if defined _M_IX86
#define CALLTYPE __thiscall
#else
#define CALLTYPE __stdcall
#endif

__RELIABILITY_CONTRACT
void CALEETYPE __ArrayUnwind(
	void*       ptr,                // Pointer to array to destruct
	size_t      size,               // Size of each element (including padding)
	int         count,              // Number of elements in the array
	void(CALLTYPE *pDtor)(void*)    // The destructor to call
	);

__RELIABILITY_CONTRACT
inline void CALEETYPE __ehvec_ctor(
	void*       ptr,                // Pointer to array to destruct
	size_t      size,               // Size of each element (including padding)
	//  int         count,              // Number of elements in the array
	size_t      count,              // Number of elements in the array
	void(CALLTYPE *pCtor)(void*),   // Constructor to call
	void(CALLTYPE *pDtor)(void*)    // Destructor to call should exception be thrown
	) {
	size_t i = 0;      // Count of elements constructed
	int success = 0;

	__try
	{
		// Construct the elements of the array
		for (; i < count; i++)
		{
			(*pCtor)(ptr);
			ptr = (char*)ptr + size;
		}
		success = 1;
	}
	__finally
	{
		if (!success)
			__ArrayUnwind(ptr, size, (int)i, pDtor);
	}
}

__RELIABILITY_CONTRACT
SECURITYCRITICAL_ATTRIBUTE
inline void CALEETYPE __ehvec_dtor(
	void*       ptr,                // Pointer to array to destruct
	size_t      size,               // Size of each element (including padding)
	//  int         count,              // Number of elements in the array
	size_t      count,              // Number of elements in the array
	void(CALLTYPE *pDtor)(void*)    // The destructor to call
	) {
	_Analysis_assume_(count > 0);

	int success = 0;

	// Advance pointer past end of array
	ptr = (char*)ptr + size*count;

	__try
	{
		// Destruct elements
        while (count-- > 0)
		{
			ptr = (char*)ptr - size;
			(*pDtor)(ptr);
		}
		success = 1;
	}
	__finally
	{
		if (!success)
			__ArrayUnwind(ptr, size, (int)count, pDtor);
	}
}

am Ende der stdafx.h ein. Sie sorgen dafür, dass der Linker die vom Compiler generierten Funktionen korrekt auflösen kann. Der Unterschied zwischen der VS 2013- und VS 2015-Version liegt hauptsächlich im Typ des dritten Parameters, der von int auf size_t geändert wurde.

In einem halben Jahr wird sich wohl zeigen, wie die Verteilung der Universal CRT auf älteren Windows-Systemen vorangeschritten ist. Von den mit VS 2015 angekündigten Leistungsverbesserungen beim Erstellen von Projekten habe ich bisher noch nicht viel gemerkt, vermutlich sind meine Projekte dafür auch zu klein. Die neue CodeLens-Funktion für C++ arbeitet leider nur mit Git-Repositories und nicht mit der TFS-eigenen Versionsverwaltung. Mal schauen, ob Microsoft hier noch nachlegt. Bis dahin werde ich wohl erst einmal beim bewährten Visual Studio 2013 bleiben.

Es gibt 2 Kommentare zu diesem Beitrag

Trackback URL | RSS-Feed für Kommentare

  1. Andreas sagt:

    Danke für den ausführlichen Bericht. Bin gespannt was bei uns mit VS2015 auftritt.

Top