The olden standby was to read the target runtime version from the file with GetFileVersion(). It will fail with ERROR_BAD_FORMAT when the executable file does not contain the CLR header. Works in any bitness and any target architecture of the assembly. You'll have to use it like this:
#define USE_DEPRECATED_CLR_API_WITHOUT_WARNING #include <mscoree.h> #pragma comment(lib, "mscoree.lib") #include <assert.h> bool IsExeFileDotNet(LPCWSTR filename) { WCHAR buf[16]; HRESULT hr = GetFileVersion(filename, buf, 16, NULL); assert(hr == S_OK || hr == HRESULT_FROM_WIN32(ERROR_BAD_FORMAT)); return hr == S_OK; }
Do note the use of USE_DEPRECATED_CLR_API_WITHOUT_WARNING to suppress a deprecation error, the MSCorEE api is liable to disappear in a future major release of .NET.
The non-deprecated way is to use ICLRMetaHost::GetFileVersion(), disadvantage is that it can only work when the machine has .NET 4 installed. Not exactly a major problem today. Looks like this:
#include <Windows.h> #include <metahost.h> #include <assert.h> #pragma comment(lib, "mscoree.lib") bool IsExeFileDotNet(LPCWSTR filename) { ICLRMetaHost* host; HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (void**)&host); assert(SUCCEEDED(hr)); if (hr == S_OK) { WCHAR buf[16]; DWORD written; hr = host->GetVersionFromFile(filename, buf, &written); assert(hr == S_OK || hr == HRESULT_FROM_WIN32(ERROR_BAD_FORMAT)); host->Release(); } return SUCCEEDED(hr); }
Other techniques that poke the executable file directly to look for the CLR header in the file are mentioned in this Q+A. How future-proof they may be is very hard to guess.
/clr), but your answer could be even simpler, looking for an exported function_CorExeMain. Looks like the MS spec for the header is here; msdn.microsoft.com/en-us/windows/hardware/gg463119.aspx