This question is for educational purposes only.
I am aware of how a native program is working. The compiler takes each primitive and gives it an address, then uses that address in the program. For structures, it simply stacks the address together (with some padding) - but basically, a structure doesn't really "exist".
The native program doesn't tell me which fields and variables it has. It only accesses different addresses - and if I look at the assembly, I can name each address if I want to, but the program won't give me that information. So assuming I am looking for a specific variable, I cannot do it without either examining the executing of the program, or it's assembly.
The .NET environment does tell me which variables it has and which fields it has. Using the Assembly class and Reflection namespace, I can load up a file and see which fields and classes it has.
Then, using a program which searches memory (whether its native or not) I can find the physical location of the field (by using it value, filtering out etc) - like Cheat Engine does. It will give me the actual address of the field in the memory, which is accessed by the assembly made by the JIT.
I know that the MSIL does not contain information about the desired location of a specific field. I am also almost certain that the JIT will never optimize the code by removing any class.
I know that the .NET debugger is an actual class in the program which allows Visual Studio to interact with the internal information of an application. When the debugger is missing, Visual Studio cannot read or write to fields, nor can it inspect the application in any way.
Is there any way, without the use of Cheat Engine or similar tools to find the physical location of a field in a static (or of a specific instance) class in a running .NET process? Will the address be the same after each executing (such as in native program) ? May it differ only on different platforms or machines? How does the JIT decide where to place a field?
If I was unclear, I wish to do it without access to the code of the program, i.e externally by another process (like a debugger, but for programs compiled under release).
Next code inject Injector method in Paint.net and get MainForm fields.
NInject.exe
public static int Injector(string parameter)
{
try
{
var mainForm = Application.OpenForms.OfType<Form>().FirstOrDefault(form => form.GetType().FullName.EndsWith("MainForm"));
var builder = new StringBuilder();
builder.AppendFormat("process: {0}\r\n\r\n", Application.ExecutablePath);
builder.AppendFormat("type: {0}\r\n", mainForm.GetType().FullName);
foreach (var field in mainForm.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
builder.AppendFormat("field {0}: {1}\r\n", field.Name, field.GetValue(mainForm));
}
new Form()
{
Controls =
{
new TextBox
{
Text = builder.ToString(),
Multiline = true,
Dock = DockStyle.Fill
}
}
}
.ShowDialog();
}
catch (Exception exc)
{
MessageBox.Show(exc.ToString());
}
return 0;
}
static void Main(string[] args)
{
var process = System.Diagnostics.Process.GetProcessesByName("PaintDotNet").FirstOrDefault();
var processHandle = OpenProcess(ProcessAccessFlags.All, false, process.Id);
var proxyPath = System.IO.Path.Combine(System.Windows.Forms.Application.StartupPath, "NInjector.dll");
var pathBytes = System.Text.Encoding.ASCII.GetBytes(proxyPath);
var remoteBuffer = VirtualAllocEx(processHandle, IntPtr.Zero, (uint)pathBytes.Length, AllocationType.Commit, MemoryProtection.ReadWrite);
WriteProcessMemory(process.Handle, remoteBuffer, pathBytes, (uint)pathBytes.Length, IntPtr.Zero);
var remoteThread = CreateRemoteThread(processHandle, IntPtr.Zero, 0, GetProcAddress(GetModuleHandle("kernel32"), "LoadLibraryA") , remoteBuffer, 0, IntPtr.Zero);
WaitForSingleObject(remoteThread, unchecked((uint)-1));
CloseHandle(remoteThread);
}
NInjector.dll (native)
#include "MSCorEE.h"
#pragma comment (lib, "MSCorEE")
void StartTheDotNetRuntime()
{
MessageBox(0, L"Started", L"proxy", 0);
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hr = CorBindToRuntimeEx(
NULL, L"wks", 0, CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost, (PVOID*)&pClrHost);
hr = pClrHost->Start();
DWORD dwRet = 0;
hr = pClrHost->ExecuteInDefaultAppDomain(
L"bla-bla\\NInject.exe",
L"NInject.NInject_Program", L"Injector", L"MyParameter", &dwRet);
hr = pClrHost->Stop();
pClrHost->Release();
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
StartTheDotNetRuntime();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Output:
process: C:\Program Files\Paint.NET\PaintDotNet.exe
type: PaintDotNet.Dialogs.MainForm
field appWorkspace: PaintDotNet.Controls.AppWorkspace
field defaultButton: System.Windows.Forms.Button, Text:
field floaters: PaintDotNet.Dialogs.FloatingToolForm[]
field floaterOpacityTimer: [System.Windows.Forms.Timer], Interval: 25
field deferredInitializationTimer:
field components: System.ComponentModel.Container
field killAfterInit: False
field singleInstanceManager: PaintDotNet.SystemLayer.SingleInstanceManager
field queuedInstanceMessages: System.Collections.Generic.List`1[System.String]
field processingOpen: False
field scrollPosition: {X=0,Y=0}
Related
Is there a way to call this dialog from c#?
I traced the apis, but non of the calls seems to call the dialog. Dsuiext.dll sounds very promissing, but there I foud just a LDAP browser.
This Microsoft sample provides the expected result. You pass an ADS path as parameter and it calls the property window.
PropSheetHost.exe "LDAP://CN=user,DC=MyDomain,DC=MyTldDomain"
It is important that it is case sensitive, so "ldap://.." doesn't work. The code is definitely not designed to get called multiple times before terminating, so it is probably the best way to use the exe without changes like that:
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = #"PropSheetHost.exe";
startInfo.Arguments = #"LDAP://CN=user,DC=MyDomain,DC=MyTldDomain";
Process.Start(startInfo);
I wrote a wrapper to call it directly from C# and corrected the error what I found. Since I haven't programmed C for nearly 30 years, I am grateful for any hint if the implementation is incorrect. All changes are explained and marked with //MW: .... This works in my code, but you can open only one windows at a time and need to close it before opening another window.
The entry point:
__declspec(dllexport) HRESULT __stdcall CallPropSheetHost(const char* ldapPath)
{
TCHAR szTemp[MAX_ADSPATH_CHARS];
LPWSTR pwszADsPath = NULL;
HRESULT hr = E_FAIL; // MW: move before "if" and preset
CoInitialize(NULL);
{
//MW: copy the parameter
_tcsncpy_s(szTemp, ARRAYSIZE(szTemp), ldapPath, MAX_ADSPATH_CHARS - 1);
}
DWORD dwChars = lstrlen(szTemp) + 1;
pwszADsPath = new WCHAR[dwChars];
if (pwszADsPath)
{
HINSTANCE hInstance = NULL;
HWND hwndConsole = GetConsoleWindow();
if (hwndConsole)
{
hInstance = (HINSTANCE)(LONG_PTR)GetWindowLongPtr(hwndConsole, GWLP_HINSTANCE);
}
CPropSheetHost* pHost = new CPropSheetHost(hInstance);
LocalToWideChar(pwszADsPath, dwChars, szTemp, dwChars);
// Hold a reference count for the CPropSheetHost object.
pHost->AddRef();
hr = pHost->SetObject(pwszADsPath);
if (FAILED(hr))
{
goto ExitMain;
}
//MW: My implmentation returns E_Fail when the registration fails
hr = pHost->Run();
if (FAILED(hr))
{
pHost->Release();
goto ExitMain;
}
//Release the CPropSheetHost object. Other components may still hold a
//reference to the object, so this cannot just be deleted here. Let
//the object delete itself when all references are released.
pHost->Release();
}
ExitMain:
if (pwszADsPath)
{
delete pwszADsPath;
return hr; //MW: return th HRESULT
}
CoUninitialize();
return hr; //MW: return th HRESULT
}
The original implementation doesn't unregister a class. Therefore it fails when it's used multiple times. These are my changes in PropSheetHost.cpp to fix that.
//MW: new method
void CPropSheetHost::_UnregisterWndClass()
{
UnregisterClass(m_szHiddenWindowClass, m_hInst);
}
//MW: added a HRESULT and calling of "_UnregisterWndClass"
HRESULT CPropSheetHost::Run()
{
if (!m_spADObject.p)
{
return E_FAIL; //MW: added a return value
}
// Create the hidden window.
m_hwndHidden = _CreateHiddenWindow();
if (!m_hwndHidden)
{
return E_FAIL; //MW: added a return value
}
/*
Display the proeprty sheet. This is a modal call and will not return
until the property sheet is dimissed.
*/
_CreatePropertySheet();
// Destroy the hidden window.
DestroyWindow(m_hwndHidden);
//WM: Unregister the class; this call was missing
_UnregisterWndClass();
return ERROR_SUCCESS; //MW: added a return value
}
... and the call from C#:
using System;
using System.Runtime.InteropServices;
using System.Windows;
const int MAX_ADSPATH_CHARS = 2047;
[DllImport("PropSheetHost.dll", EntryPoint = "CallPropSheetHost", CallingConvention = CallingConvention.Cdecl)]
private static extern int CallPropSheetHost(string ldapPath);
///CAUTION:
/// * This call is modal and won't return until the called window is closed
/// * You can open only one window at a time. Trying opening a second window before closing the the first one fails
public static int Win32PropSheetHost(string distinguishedName, string serverOrDomain = null)
{
if (string.IsNullOrEmpty(distinguishedName)) throw new ArgumentNullException("EXC262: the distinguished name must not be null nor empty");
//<----------
/// Caution: "LDAP" must be uppercase!
string ldapPath = string.IsNullOrEmpty(serverOrDomain)
? $"LDAP://{ distinguishedName }"
: $"LDAP://{ serverOrDomain }/{ distinguishedName }";
if (ldapPath.Length > MAX_ADSPATH_CHARS) throw new ArgumentException($"EXC263: the complete lds path must not be longer than { MAX_ADSPATH_CHARS } characters (current path: \"{ ldapPath }\")");
//<----------
try
{
return CallPropSheetHost(ldapPath);
}
catch (DllNotFoundException ex)
{
/// Could't find a dll, mos likely our propsheethost.dll
return ResultWin32.ERROR_DLL_NOT_FOUND;
}
}
For the translation of the Windows Error Codes I use this class.
How do I enumerate NVMe (M2) drives an get their temperature in c# ?
It's not accessible through WMI usual queries.
There this MSFT reference to do this in c, but it's rather obscure, code is not complete:
https://learn.microsoft.com/en-us/windows/desktop/fileio/working-with-nvme-devices#temperature-queries
This answer is as much a tutorial as answer. I am a newbie to C++ and had to learn by mistakes and a lot of research to understand the range of environment related steps necessary to get code compiled and working. Along the way Visual Studio 2022 implementation of C++ has lots of quirks that I haven’t yet figured out including Functions referenced in Header Files not being found even when the Header file was properly referenced, loaded and accessible.
I have written a desktop utility program in VB which shows graphically the amount of space used on Drives as well as R/W data rates, graphs for CPU/Thread usage, Memory info, clocks, temperatures etc plus a desktop calculator and units converter. Most of the data is extracted through inbuilt PerformanceCounters except CPU/Thread Temperature which thanks to CoreTemp can be read directly from their free App when running.
Why in VB? Because it is so easy to write GUI type apps on forms with controls and manage strings and this makes up 95% of the code, with data access being the other 5%. But VB has its limitations hence C++. (Don’t wish to fire up C experts who have a thousand reasons why not to use VB)
In VB I am able to extract Drive Temperatures for SATA drives using WMI functions which adhere to S.M.A.R.T protocol, but NVMe drives don’t follow this protocol. So have implemented code as suggested by Microsoft in C++ to access NVMe drive temperatures and built it as a DLL which can be accessed by either C or VB client programs. VB does not have a Malloc memory allocation function (memset in VS) so working with unmanaged memory is more difficult, even though research has suggested it can be done. But this C++ Function is a very small amount of code, simple, fast and scanning 5 drives for temperature takes around 3ms. By comparison using WMI to read 3 SATA drives takes typically 100ms.
This is specifically written in VB Studio 2022 environment using DLL template, with the project called ReadNVMeTemperatures.
Output is a file ReadNVMeTemperatures.dll and I am happy to make this available to anyone if you don’t want to build it yourself.
Firstly the C++ files for ReadNVMeTemperatures Project
//pch.h
#pragma once
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <Windows.h>
#include "pch.h"
#include <nvme.h>
#include <winioctl.h>
#include <string>
#include <ntddstor.h>
#include <corecrt_malloc.h>
// Main.h
#pragma once
namespace GetTemp
{
class MyGetTemps
{
public:
// Receives integer a as disk number from client and returns Temperature as GetNVMeTemp
static __declspec(dllexport) int GetNVMeTemp(int a);
};
}
//main.cpp
#include "pch.h"
#include <stdexcept>
#include "main.h"
using namespace winrt;
using namespace Windows::Foundation;
using namespace std;
namespace GetTemp
{
int MyGetTemps::GetNVMeTemp(int a)
{
// a is the physical disk number and must be added to string literal below to form a string constant TargetDrive
//if only 1 known drive eg 2 to be scanned can use constexpr auto TargetDrive = L"\\\\.\\PhysicalDrive2";
std::string stddrivestring = "\\\\.\\PhysicalDrive" + to_string(a);
std::wstring widedrivestring = std::wstring(stddrivestring.begin(),
stddrivestring.end());
const wchar_t* TargetDrive = widedrivestring.c_str();
HANDLE hDevice = INVALID_HANDLE_VALUE; // handle to the drive to be examined
hDevice = CreateFileW((LPWSTR)TargetDrive, // drive to open
0, // no access to the drive
FILE_SHARE_READ | // share mode
FILE_SHARE_WRITE,
NULL, // default security attributes
OPEN_EXISTING, // disposition
FILE_FLAG_OVERLAPPED, // file attributes
NULL); // do not copy file attributes
if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
{
return (FALSE);
}
BOOL result = 0;
PVOID buffer = NULL;
ULONG bufferLength = 0;
ULONG returnedLength = 0;
SHORT mytemp0 = 0;
SHORT mytemp1 = 0;
INT mytemp = 0;
PSTORAGE_PROPERTY_QUERY query = NULL;
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR protocolDataDescr = NULL;
// Allocate buffer for use.
bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE;
buffer = malloc(bufferLength);
if (buffer == NULL) {
// printf("DeviceNVMeQueryProtocolDataTest: allocate buffer failed");
return 0;
}
// Initialize query data structure to get Identify Controller Data.
memset(buffer, 0, bufferLength);
query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;
query->PropertyId = StorageDeviceProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;
protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeLogPage;
protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;
protocolData->ProtocolDataRequestSubValue = 0; // This will be passed as the lower 32 bit of log page offset if controller supports extended data for the Get Log Page.
protocolData->ProtocolDataRequestSubValue2 = 0; // This will be passed as the higher 32 bit of log page offset if controller supports extended data for the Get Log Page.
protocolData->ProtocolDataRequestSubValue3 = 0; // This will be passed as Log Specific Identifier in CDW11.
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);
/ Send request down.
result = DeviceIoControl(hDevice,
IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL
);
if (!result || (returnedLength == 0)) {
//printf(("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log failed. Error Code %d.\n"), GetLastError());
return 0;
}
// Validate the returned data.
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
(protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
//printf(("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - data descriptor header not valid.\n"));
return 0;
}
protocolData = &protocolDataDescr->ProtocolSpecificData;
if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
(protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))) {
//printf(("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - ProtocolData Offset/Length not valid.\n"));
return 0;
}
// SMART/Health Information Log Data
{
PNVME_HEALTH_INFO_LOG smartInfo = (PNVME_HEALTH_INFO_LOG)((PCHAR)protocolData + protocolData->ProtocolDataOffset);
// print or return as mytemp the Temperature S.M.A.R.T. data for drive a
mytemp0 = (ULONG)smartInfo->Temperature[1] << 8;// | smartInfo->Temperature[0] - 273;
mytemp1 = smartInfo->Temperature[0];
mytemp = mytemp0 + mytemp1 - 273;
}
//printf(("Temperature Drive %d = %d deg\n"), (a), (mytemp));
return mytemp;
}
}
Before Building the final DLL solution add a DEF file to the project so that Visual Basic can find the entry point name GetNVMeTemp as defined in the main.h Header File. This is necessary as C++ adds additional characters to the entrypoint name that C++ understands but VB doesn’t.
The file is named ReadNVMeTemperatures.def i.e. the same name as the Project .def.
It is a text file and should look like this.
; ReadNVMeTemperatures.def - defines the exports for ReadNVMeTemperatures.dll
LIBRARY Test
DESCRIPTION 'A C++ dll that can be called from VB'
EXPORTS GetNVMeTemp
Here is an explanation on how to load into VS. Written by someone else. Thank You!!!
INSTRUCTIONS if you need to create a DEF file for your DLL by hand:
In the folder where your project file (.vcproj) is, create a new text file.
Rename it to "outputname.def", where "outputname" is the name of your project's output. By default, this is the name of the project, see Step 4 for clarifications.
In Visual Studio, go to Project->Properties. Go to Configuration Properties->Linker->Input and enter the name of the DEF you just created into the field named Module Definition File.
Repeat steps 1 - 3 for each configuration that outputs a different named DLL. I.e. my Debug configuration makes output_d.dll, so I need an output_d.def for Debug along with an output.def for Release.
Open all of the DEF files in your current Visual Studio editor. The format for the DEF is easy:
LIBRARY outputnameEXPORTS Function1 #1 Function2
Replace "outputname" with the name of the configuration output. As mentioned in step 4, this could be output in output.def and output_d in output_d.def.
List all of the functions you are exporting. Those functions need to be correctly exported using the correct syntax in your source files. You can manually assign an ordinal by using the #number syntax. Alternatively, you can leave it assigned automatically by not having it.
That's all there is to it. Make sure to Build->Rebuild Solution afterwards. Use Dependency Walker to verify your DLL is correctly exporting your functions with the expected names.
There is more information on this topic here: Exporting from a DLL Using DEF Files . Good luck!
Now the project can be built/compiled to create the DLL.
Next an example of a C++ Client app that will retrieve a drive temperature from this DLL. I have called it ReadNVMeTemperaturesClient and set it up in a separate project directory of the that name.
It is necessary within properties of the ReadNVMeTemperaturesClient VS project to tell VS where to find (Include Directories in C++ section and Linker section) the ReadNVMeTemperatures.dll and ReadNVMeTemperatures.lib files as well as the “main.h” header file of the DLL project.
Here is a link to the details
https://www.technical-recipes.com/2012/how-to-link-dll-files-to-c-projects/
Code for ReadNVMeTemperaturesClient Client App in C++
//pch.h
#pragma once
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
//Main.cpp
#include "pch.h"
#include <iostream>
#include "main.h" //This is the main.h of the dll project
using namespace winrt;
using namespace Windows::Foundation;
using namespace std;
int main()
{
// The physical Disk number in int a eg 2
int a = 2;
int drivetemp = 0;
drivetemp = GetTemp::MyGetTemps::GetNVMeTemp(a);
printf(("result %d \n"), (drivetemp));
return 0;
}
Next a more comprehensive VB set of routines to retrieve NVMe drive temperatures for the PC.
First search for and build an array of physical drive numbers. I use the
"Win32_LogicalDiskToPartition” class which has a member called “instancename” as this is the only WMI class I have found which returns a string containing physical disk number, partition number and logical drive character all in one string. These can be easily extracted using various string functions. There are other WMI classes (eg "Win32_Diskdrive", "MSFT_PhysicalDisk, "Win32_Volume") but they require cross referencing of results to enable logical drive to be associated with physical disk number, or in the case of a RAID array, multiple physical disk numbers per logical drive.
Example code including setting up array. Some array members I have filled using other WMI or VB Function calls. Additional members can be added to the array as needed.
Public Class PHDD
Public Property PCaption() As String 'eg ST6000DM003-2CY186
Public Property PPhysical() As Integer 'physical drive number eg 1
Public Property PNVMeType() As Boolean 'true is NVMe drive, false is SATA
Public Property PDiscSize() As Integer 'Physical Disk Size in GB
Public Property PDriveChar() As String 'eg "D"
Public Property PTemperature() As Integer 'eg 32
Public Property PInstanceName() As String 'eg SCSI\Disk&Ven_SAMSUNG&Prod_SSD_830_Series\5&36Da4fed&0&020000_0 as extracted from MSStorageDriver_ATAPISmartData used to retrieve SATA Temp from SMART
End Class
Public Class Form1
Public PDiscDrives = New Dictionary(Of Integer, PHDD)()
Public PDrivesnum as integer
Public Declare Function GetNVMeTemp Lib "ReadNVMeTemperatures.dll" (a As Integer) As Integer ‘the string constant in quotes may have to reference the exact location of this file during local VS execution. Eg “C:\myVBProject\ ReadNVMeTemperatures.dll”
Sub CreatePHDDArray()
Dim n As Integer
For n = 0 To 35
Dim MyPhdd = New PHDD()
PDiscDrives.Add(n, MyPhdd) 'create 36 array size for each physical drive...ie 10 more than max 26 Logical letters in case there are RAID arrays of Logical Drives with Multiple Disks
Next
End Sub
Sub ExtractDiskNumber
Dim devst As New ManagementClass("Win32_LogicalDiskToPartition")
Dim moct As ManagementObjectCollection = devst.GetInstances()
Dim driveinfostring as string, PDisk as integer, LChar as string, n as integer, tstring as string
Pdrivesnum = 0
For Each mot As ManagementObject In moct
If True Then
driveinfostring = mot.ToString
‘add code to extract and save Physical Disk number PDisk and Logical Drive Char LChar in the PHDD class array. Use VB “Instr”, “Substring” or “Mid” Functions to isolate and extract the values
n = InStr(t, "Disk #")
If n > 0 Then
tstring = Mid(t, n, 9) ' typically Disk #4, Partition// don't know why inbuilt Substring Function returns string starting at 1 character after n so using Mid() hear instead!!!
m = InStr(tstring, ",")
If m > 0 Then
PDisc = Val(tstring.Substring(6, m - 7)) ‘Got the Physical Disk
End If
End If
'Get the Logical Drive Char
n = InStr(t, "LogicalDisk.DeviceID=\")
If n > 0 Then
LChar = Mid(t, n + 22, 1) ' typically DeviceID=\E:\
End If
PDiskDrives.PPysical = PDisk
PDiskDrives.PDriveChar = LChar
Pdrivesnum +=1
end if
Next
End Sub
End Class
Here is an “instance” string example…
"\HOMEDN\root\cimv2:Win32_LogicalDiskToPartition.Antecedent=""\\HOMEDN\root\cimv2:Win32_DiskPartition.DeviceID=""Disk #4, Partition #1"""",Dependent=""\\HOMEDN\root\cimv2:Win32_LogicalDisk.DeviceID=""C:"""""
This is Logical Drive C, and PHYSICAL4 and the number 4 is all that is needed to get the NVMe Temperature for drive C.
Below Sub can now loop through all the PC drives from 0 to Pdrivesnum-1 and get Temperature.
Shared Sub ReadNVMeTemp()
Dim n As Integer
For n = 0 To Pdrivesnum - 1
PDiskDrives(n).PTemperature = GetNVMeTemp(PDiskDrives(n).Pphysical) 'Reads the Temperature of actualphysical disk from ReadNVMeTemperatures.dll into array
If PDiskDrives(n).PTemperature > 0 Then
PDiskDrives(n).PNVMeType = True 'set this value as true meaning the drive was NVMe, because a valid temperature was returned
Else
PDiskDrives(n).PNVMeType = False 'set as false indicating an SATA or USB drive or an error retrieving Temperature – all return a 0
End If
Next
End Sub
Hope I haven't missed any of the steps which will enable anyone who like me is still learning the C++ programming environment to get the code working. Parts of the code can be written more concisely and efficiently but I have tried to make it easy to follow. I only program for fun.
A fast implementation (but not the simplest) is to use the nvme.h winioctl.h ntddstor.h API and interop with that in c#. Here is a complete implementation.
Declaration side:
#include <nvme.h>
#include <winioctl.h>
#include <ntddstor.h>
#ifndef Pinvoke
#define Pinvoke extern "C" __declspec(dllexport)
#endif
typedef void(__stdcall *MessageChangedCallback)(const wchar_t* string);
class __declspec(dllexport) NVmeQuery final
{
public:
NVmeQuery(MessageChangedCallback managedDelegate);
~NVmeQuery();
template <class ... T>
auto LogMessage(T&& ... args) -> void;
auto GetTemp(const wchar_t* nvmePath) -> unsigned long;
MessageChangedCallback LogMessageChangedCallback{};
PNVME_HEALTH_INFO_LOG SmartHealthInfo{};
PSTORAGE_PROPERTY_QUERY query{};
PSTORAGE_PROTOCOL_SPECIFIC_DATA protocolSpecificData{};
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR protocolDataDescriptor{};
};
Definition side:
A function I use to retro-interop error messages to the managed side; it is critical for the understanding of what goes wrong, how/where:
template<class ...T>
auto NVmeQuery::LogMessage(T&&... args) -> void
{
wchar_t updatedMessage[256];
swprintf_s(updatedMessage, forward<T>(args)...);
if (LogMessageChangedCallback != nullptr)
LogMessageChangedCallback(updatedMessage);
}
The core function, which was not straightforward to design. It has 4 parts:
1. getting a handle to the NVMe drive
2. preparing the query
3. do the query
4. check and transmit results
auto NVmeQuery::GetTemp(const wchar_t* nvmePath) -> unsigned long
{
auto nvmeHandle = CreateFile(nvmePath, 0, 0,
0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
{
auto lastErrorID = GetLastError();
if (lastErrorID != 0)
{
LPVOID errorBuffer{};
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, lastErrorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorBuffer, 0, nullptr);
LogMessage(L"Query: ERROR creating handle to NVMe [%s]: %d, %s", nvmePath, lastErrorID, errorBuffer);
}
}
unsigned long bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters)
+ sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE;
void *buffer = malloc(bufferLength);
ZeroMemory(buffer, bufferLength);
query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescriptor = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolSpecificData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;
query->PropertyId = StorageDeviceProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;
protocolSpecificData->ProtocolType = ProtocolTypeNvme;
protocolSpecificData->DataType = NVMeDataTypeLogPage;
protocolSpecificData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;
protocolSpecificData->ProtocolDataRequestSubValue = 0;
protocolSpecificData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolSpecificData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);
unsigned long returnedLength{};
continuing, the actual query and then the miscellaneous checkings:
auto result = DeviceIoControl(nvmeHandle, IOCTL_STORAGE_QUERY_PROPERTY,
buffer, bufferLength,
buffer, bufferLength,
&returnedLength, nullptr);
if (!result || returnedLength == 0)
{
auto lastErrorID = GetLastError();
LPVOID errorBuffer{};
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, lastErrorID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errorBuffer, 0, nullptr);
LogMessage(L"Query: drive path: %s, nvmeHandle %lu", nvmePath, nvmeHandle);
LogMessage(L"Query: ERROR DeviceIoControl 0x%x %s", lastErrorID, errorBuffer);
}
if (protocolDataDescriptor->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR) ||
protocolDataDescriptor->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))
{
LogMessage(L"Query: Data descriptor header not valid (size of descriptor: %llu)", sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR));
LogMessage(L"Query: DataDesc: version %lu, size %lu", protocolDataDescriptor->Version, protocolDataDescriptor->Size);
}
protocolSpecificData = &protocolDataDescriptor->ProtocolSpecificData;
if (protocolSpecificData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) ||
protocolSpecificData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))
LogMessage(L"Query: ProtocolData Offset/Length not valid");
SmartHealthInfo = (PNVME_HEALTH_INFO_LOG)((PCHAR)protocolSpecificData + protocolSpecificData->ProtocolDataOffset);
CloseHandle(nvmeHandle);
auto temp = ((ULONG)SmartHealthInfo->Temperature[1] << 8 | SmartHealthInfo->Temperature[0]) - 273;
return temp;
} // end of GetTemp
And for the interop:
Pinvoke auto __stdcall New(MessageChangedCallback managedDelegate) -> NVmeQuery*
{
return new NVmeQuery(managedDelegate);
}
Pinvoke auto GetTemp(NVmeQuery* p, const wchar_t* nvmePath) -> unsigned long
{
return p->GetTemp(nvmePath);
}
And c# side:
public static class NVMeQuery
{
[DllImport("NVMeQuery.dll", CallingConvention = CallingConvention.StdCall)]
internal static extern IntPtr New(InteropBase.AssetCallback callback);
[DllImport("NVMeQuery.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern ulong GetTemp(IntPtr p, IntPtr drivePath);
}
public class NVMeQueries : InteropBase
{
public NVMeQueries()
{
_ptr = NVMeQuery.New(_onAssetErrorMessageChanged);
}
public ulong GetTemp() => GetTemp(#"\\.\PhysicalDrive4");
public ulong GetTemp(string drivePath)
{
var strPtr = Marshal.StringToHGlobalAuto(drivePath);
var result = NVMeQuery.GetTemp(_ptr, strPtr);
Marshal.FreeHGlobal(strPtr);
return result;
}
}
And the generic base class I use for interop:
public class InteropBase : INotifyPropertyChanged
{
protected IntPtr _ptr;
protected readonly AssetCallback _onAssetErrorMessageChanged;
public delegate void AssetCallback(IntPtr strPtr);
public List<string> LogMessages { get; private set; } = new List<string>();
public InteropBase()
{
_onAssetErrorMessageChanged = LogUpdater;
}
private unsafe void LogUpdater(IntPtr strPtr)
{
var LastLogMessage = new string((char*)strPtr);
LogMessages.Add(LastLogMessage);
OnPropertyChanged(nameof(LogMessages));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In my case, the NVMe drive I wanted to query was the 4th physical drive. We get all of them with:
Get-WmiObject Win32_DiskDrive
which will give in my case:
Partitions : 4
DeviceID : \\.\PHYSICALDRIVE4
Model : Samsung SSD 970 EVO Plus 1TB
Size : 1000202273280
Caption : Samsung SSD 970 EVO Plus 1TB
Remarks
This implementation is extremely fast (less that 1ms when there is no LogMessage call); you can define and fill your own structure to get other pieces of information that would be relevant; in that case, the structure must be hold in a field in the native class (eg., SmartHealthInfo in this example) and the query would transmit just the pointer to this structure.
I have implemented the Microsoft Code for this which I think is shorter and simpler - although the code shown here is very elegant. I have built it into a C++ DLL file which can be called by passing an integer value for the Physical Drive number. For my 5 drives, of which 2 are NVMe and 3 SATA, it returns the valid temperatures for the NVMe Drives and zero for the SATA. All take 4 ms. I use a WMI MSStorageDriver_ATAPISmartData call for the SATA drives...quite slow in comparison.
I can call this function from either C++ or VB. The VB implementation still only takes 4ms.
If anyone is after the code or a copy of the DLL file and client implementation then respond and I will list the code here. Otherwise I will assume no further interest.
I am using Visual Studio 2017. I added two projects in a solution. One project is in C# with WPF. The other is in VC++ with ATL.
From C#, I call a function in the VC++ project, which sets low level mouse hook. A part of the code in the low level mouse proc is as follows:
MSLLHOOKSTRUCT stMouse = *(MSLLHOOKSTRUCT*)(lParam);
POINT pt = stMouse.pt;
IAccessible* pAcc;
VARIANT varChild;
HRESULT hr = AccessibleObjectFromPoint(pt, &pAcc, &varChild);
VARIANT varRole;
hr = pAcc->get_accRole(varChild, &varRole);
When I test by clicking on a check box under View tab in MS Word 2013, then I get the role as client for WM_LBUTTONDOWN and WM_LBUTTONUP messages. But I should get the role as check box.
I checked with Inspect.exe that comes with Windows 10 SDK. Inspect.exe shows role correctly as check box. Inspect.exe has two options - one to see UI Automation properties and the other to see MSAA properties. I am seeing the MSAA properties.
How can I get the correct role? What method deos Inspect.exe uses?
Microsoft Active Accessibility / MSAA (based on the IAccessible interface) is a legacy API. You should now use Windows Automation. UIA is based on COM, and uses interfaces, the most important one being IUIAutomationElement. Inspect.exe uses UIA or MSAA.
Note .NET is compatible with UIA and WPF can expose its UI Elements to UIA pretty easily, through the AutomationPeer class and UIElement.OnCreateAutomationPeer Method method. WPF provides a default implementation that can be tweaked if needed.
Here is a similar example to yours in C++, using the UIA API, instead of MSAA (we could write the same using C#):
#include "stdafx.h" // needs <uiautomation.h>
class CCoInitialize { // https://blogs.msdn.microsoft.com/oldnewthing/20040520-00/?p=39243
public:
CCoInitialize() : m_hr(CoInitialize(NULL)) { }
~CCoInitialize() { if (SUCCEEDED(m_hr)) CoUninitialize(); }
operator HRESULT() const { return m_hr; }
HRESULT m_hr;
};
// this is a poor-man COM object class
class CHandler : public IUIAutomationEventHandler
{
public:
HRESULT QueryInterface(REFIID riid, LPVOID * ppv)
{
if (!ppv) return E_INVALIDARG;
*ppv = NULL;
if (riid == IID_IUnknown || riid == IID_IUIAutomationEventHandler)
{
*ppv = this;
return NOERROR;
}
return E_NOINTERFACE;
}
ULONG AddRef() { return 1; }
ULONG Release() { return 1; }
// this will be called by UIA
HRESULT HandleAutomationEvent(IUIAutomationElement *sender, EVENTID eventId)
{
wprintf(L"Event id: %u\n", eventId);
return S_OK;
}
};
int main()
{
CCoInitialize init;
// this sample uses Visual Studio's ATL smart pointers, but it's not mandatory at all
CComPtr<IUIAutomation> uia;
if (SUCCEEDED(uia.CoCreateInstance(CLSID_CUIAutomation)))
{
// get mouse pos now
POINT pt;
GetCursorPos(&pt);
// find what type of "control" was under the mouse
CComPtr<IUIAutomationElement> element;
if (SUCCEEDED(uia->ElementFromPoint(pt, &element)))
{
CComBSTR type;
element->get_CurrentLocalizedControlType(&type);
wprintf(L"type at %u,%u: %s\n", pt.x, pt.y, type.m_str);
}
// get root
CComPtr<IUIAutomationElement> root;
if (SUCCEEDED(uia->GetRootElement(&root)))
{
// add a handler that will trigger every time you open any window on the desktop
// in the real world, you'll need to delete this pointer to avoid memory leaks of course
CHandler *pHandler = new CHandler();
if (SUCCEEDED(uia->AddAutomationEventHandler(UIA_Window_WindowOpenedEventId, root, TreeScope::TreeScope_Children, NULL, pHandler)))
{
// since this is a console app, we need to run the Windows message loop otherwise, you'll never get any UIA events
// for this sample, just press CTRL-C to stop the program. in the real world, you'll need to get out properly
// if you have a standard windows app, the loop will be already there
while (TRUE)
{
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
}
}
return 0;
}
I support an old VS 2010 C++ Video Application. This application calls Video Integrators that are written in C# 2015 (and older versions as well). We have run into a unique issue that I have pinpointed to being the transfer of a camera list from the Integrator to the Video App, where the string of data is truncated at exactly 5087 characters. (resulting in only 88 of the 134 cameras being loaded into the Video Application) I am not strong at C++ at all, so I'm not entirely certain what other options I have for data objects and definition. I totally presume the issue is on the C++ side, because I can see the complete outgoing list of cameras in the C# Integrator code. (but maybe I'm mistaken)
//Video Application code (c++ 2010)
deviceLibrary.h file
//get setting extended
typedef TCHAR* (*DI_GETSETTINGEX)(HWND hParentWnd, int nParameter,LPARAM lParam);
#define DI_GETSETTINGEX_ENTRY "GetSettingEx"
class CDeviceLibrary : public CCameraWindow
{
public:
DI_GETSETTINGEX m_pGetSettingEx;
};
deviceLibrary.cpp
CDeviceLibrary::CDeviceLibrary(int nId,LPCTSTR pszName,LPCTSTR pszView,HINSTANCE hLib,HWND hWnd,LPCTSTR pszLibrary,LPCTSTR pszPropertiesString)
{
m_pGetSettingEx = (DI_GETSETTINGEX)GetProcAddress(m_hLib,DI_GETSETTINGEX_ENTRY);
}
CString CDeviceLibrary::GetSetting(int nSetting,LPARAM lParam)
{
if(m_pGetSettingEx)
return m_pGetSettingEx(AfxGetMainWnd()->m_hWnd,nSetting,lParam);
else if(m_pGetSetting)
return m_pGetSetting(nSetting,lParam);
return _T("");
}
ConfigureServers.cpp
void CConfigurationServers::LoadCameras()
{
//RIGHT HERE! THE VALUE OF strCameras only contains 5087 characters in it, when it is supposed to contain over 7000
CString strCameras = GetSetting(ParameterGetCameraList,(LPARAM)(LPCTSTR)strParameters);
}
Video Integrator Code Base (C# 2010, .NET 4.0)
public virtual String GetLibrarySetting(int Parameter,int lParam)
{
return GetCameraList(Marshal.PtrToStringAnsi((IntPtr)lParam));
}
virtual public String GetCameraList()
{
String CameraList = "";
try
{
if(!Connected)
Connect();
if(DeviceList.Count > 0)
{
List<BaseCamera>.Enumerator Cameras = DeviceList.GetEnumerator();
while(Cameras.MoveNext())
{
BaseCamera Camera = Cameras.Current;
int FeatureFlags = (int)CameraFlags.FLAG_PTZ_SUPPORT | (Camera.PTZ?(int)CameraFlags.FLAG_PTZ:0) |
(int)CameraFlags.FLAG_AUDIO_SUPPORT | (Camera.AudioIn?(int)CameraFlags.FLAG_AUDIO:0) |
(int)CameraFlags.FLAG_MIKE_SUPPORT |
(Camera.RecordNotSupported ? (int)CameraFlags.FLAG_NO_RECORD_SUPPORT | (int)CameraFlags.FLAG_NO_RECORD : 0);
CameraList += String.Format("{0};{1};{2}\n", Camera.Title.Length > 0 ? Camera.Title : Camera.Name, Camera.CameraId, FeatureFlags);
}
}
}
catch(Exception e)
{
LogException(e.Message);
}
return CameraList;
}
I am not the original creator of either of these applications. If you ask me a "why did you do...." question, I have no idea what the answer is. If any of you know of a solution that can seamlessly replace this solution, and retrieve me the full string I am looking for, wonderful. If you see the reason why I only a subset of the original list of characters is being received into the Video App, please let me know why. Thanks!
NOTE: I have stripped out hundreds of lines of code in this post. The code base is massive.
Sample text being passed, and how it is being truncated at the C++ property:
C001-P100--1-SeeTec (Peliquin);4611967493405284623;2293760
C001-P100--SeeTec (Peliquin);4611967493405284639;2293760
C002-P100--1-SeeTec (Peliquin);4611967493405284632;2293760
C002-P100--SeeTec (Peliquin);4611967493405284646;2293760
C003-P100--1-SeeTec (Peliquin);4611967493405284660;2293760
C003-P100--SeeTec (Peli"
Arg... as it turns out, C++ cannot (at least in our app) directly call a C# application. It needs an app in the middle to do some sort of conversion between the two languages. I tracked down a tiny c++ application in our system that is referenced by the program that makes the two languages talk to each other, which had the following code in it:
wchar_t* ConvertToCharW(String^ Input)
{
static wchar_t ch[5001]=L""; //TODO: make dynamic so no String^ length limit
try
{
ch[0] = ch[5000] = '\0';
// Pin memory so GC can't move it while native function is called
pin_ptr<const wchar_t> wch = PtrToStringChars(Input);
wcscpy_s(ch,5000,wch);
}
catch(...)
{
}
return ch;
}
And there is my 5000 character limit. Since I have no idea how to make it dynamic in C++, I changed it to 20000.
How can a Windows console application written in C# determine whether it is invoked in a non-interactive environment (e.g. from a service or as a scheduled task) or from an environment capable of user-interaction (e.g. Command Prompt or PowerShell)?
[EDIT: 4/2021 - new answer...]
Due to a recent change in the Visual Studio debugger, my original answer stopped working correctly when debugging. To remedy this, I'm providing an entirely different approach. The text of the original answer is included at the bottom.
1. Just the code, please...
To determine if a .NET application is running in GUI mode:
[DllImport("kernel32.dll")] static extern IntPtr GetModuleHandleW(IntPtr _);
public static bool IsGui
{
get
{
var p = GetModuleHandleW(default);
return Marshal.ReadInt16(p, Marshal.ReadInt32(p, 0x3C) + 0x5C) == 2;
}
}
This checks the Subsystem value in the PE header. For a console application, the value will be 3 instead of 2.
2. Discussion
As noted in a related question, the most reliable indicator of GUI vs. console is the "Subsystem" field in the PE header of the executable image. The following C# enum lists the allowable (documented) values:
public enum Subsystem : ushort
{
Unknown /**/ = 0x0000,
Native /**/ = 0x0001,
WindowsGui /**/ = 0x0002,
WindowsCui /**/ = 0x0003,
OS2Cui /**/ = 0x0005,
PosixCui /**/ = 0x0007,
NativeWindows /**/ = 0x0008,
WindowsCEGui /**/ = 0x0009,
EfiApplication /**/ = 0x000A,
EfiBootServiceDriver /**/ = 0x000B,
EfiRuntimeDriver /**/ = 0x000C,
EfiRom /**/ = 0x000D,
Xbox /**/ = 0x000E,
WindowsBootApplication /**/ = 0x0010,
};
As easy as that code (in that other answer) is, our case here can be vastly simplified. Since we are only specifically interested in our own running process (which is necessarily loaded), you don't have to open any file or read from the disk to obtain the subsystem value. Our executable image is guaranteed to be already mapped into memory. And it is simple to retrieve the base address for any loaded file image by calling the GetModuleHandleW function:
[DllImport("kernel32.dll")]
static extern IntPtr GetModuleHandleW(IntPtr lpModuleName);
Although we might provide a filename to this function, again things are easier and we don't have to. Passing null, or in this case, default(IntPtr.Zero) (which is the same as IntPtr.Zero), returns the base address of the virtual memory image for the current process. This eliminates the extra steps (alluded to earlier) of having to fetch the entry assembly and its Location property, etc. Without further ado, here is the new and simplified code:
static Subsystem GetSubsystem()
{
var p = GetModuleHandleW(default); // VM base address of mapped PE image
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
return (Subsystem)Marshal.ReadInt16(p + 0x5C); // PE offset to 'Subsystem' word
}
public static bool IsGui => GetSubsystem() == Subsystem.WindowsGui;
public static bool IsConsole => GetSubsystem() == Subsystem.WindowsCui;
[official end of the new answer]
3. Bonus Discussion
For the purposes of .NET, Subsystem is perhaps the most—or only—useful piece of information in the PE Header. But depending on your tolerance for minutiae, there could be other invaluable tidbits, and it's easy to use the technique just described to retrieve additional interesting data.
Obviously, by changing the final field offset (0x5C) used earlier, you can access other fields in the COFF or PE header. The next snippet illustrates this for Subsystem (as above) plus three additional fields with their respective offsets.
NOTE: To reduce clutter, the enum declarations used in the following can be found here
var p = GetModuleHandleW(default); // PE image VM mapped base address
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
var subsys = (Subsystem)Marshal.ReadInt16(p + 0x005C); // (same as before)
var machine = (ImageFileMachine)Marshal.ReadInt16(p + 0x0004); // new
var imgType = (ImageFileCharacteristics)Marshal.ReadInt16(p + 0x0016); // new
var dllFlags = (DllCharacteristics)Marshal.ReadInt16(p + 0x005E); // new
// ... etc.
To improve things when accessing multiple fields in unmanaged memory, it's essential to define an overlaying struct. This allows for direct and natural managed access using C#. For the running example, I merged the adjacent COFF and PE headers together into the following C# struct definition, and only included the four fields we deemed interesting:
[StructLayout(LayoutKind.Explicit)]
struct COFF_PE
{
[FieldOffset(0x04)] public ImageFileMachine MachineType;
[FieldOffset(0x16)] public ImageFileCharacteristics Characteristics;
[FieldOffset(0x5C)] public Subsystem Subsystem;
[FieldOffset(0x5E)] public DllCharacteristics DllCharacteristics;
};
NOTE: A fuller version of this struct, without the omitted fields, can be found here
Any interop struct such as this has to be properly setup at runtime, and there are many options for doing so. Ideally, its generally better to impose the struct overlay "in-situ" directly on the unmanaged memory, so that no memory copying needs to occur. To avoid prolonging the discussion here even further however, I will instead show an easier method that does involve copying.
var p = GetModuleHandleW(default);
var _pe = Marshal.PtrToStructure<COFF_PE>(p + Marshal.ReadInt32(p, 0x3C));
Trace.WriteLine($#"
MachineType: {_pe.MachineType}
Characteristics: {_pe.Characteristics}
Subsystem: {_pe.Subsystem}
DllCharacteristics: {_pe.DllCharacteristics}");
4. Output of the demo code
Here is the output when a console program is running...
MachineType: Amd64
Characteristics: ExecutableImage, LargeAddressAware
Subsystem: WindowsCui (3)
DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware
...compared to GUI (WPF) application:
MachineType: Amd64
Characteristics: ExecutableImage, LargeAddressAware
Subsystem: WindowsGui (2)
DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware
[OLD: original answer from 2012...]
To determine if a .NET application is running in GUI mode:
bool is_console_app = Console.OpenStandardInput(1) != Stream.Null;
Environment.UserInteractive Property
If all you're trying to do is to determine whether the console will continue to exist after your program exits (so that you can, for example, prompt the user to hit Enter before the program exits), then all you have to do is to check if your process is the only one attached to the console. If it is, then the console will be destroyed when your process exits. If there are other processes attached to the console, then the console will continue to exist (because your program won't be the last one).
For example*:
using System;
using System.Runtime.InteropServices;
namespace CheckIfConsoleWillBeDestroyedAtTheEnd
{
internal class Program
{
private static void Main(string[] args)
{
// ...
if (ConsoleWillBeDestroyedAtTheEnd())
{
Console.WriteLine("Press any key to continue . . .");
Console.ReadKey();
}
}
private static bool ConsoleWillBeDestroyedAtTheEnd()
{
var processList = new uint[1];
var processCount = GetConsoleProcessList(processList, 1);
return processCount == 1;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint GetConsoleProcessList(uint[] processList, uint processCount);
}
}
(*) Adapted from code found here.
I haven't tested it, but Environment.UserInteractive looks promising.
A possible improvement of Glenn Slayden's solution:
bool isConsoleApplication = Console.In != StreamReader.Null;
To prompt for user input in an interactive console, but do nothing when run without a console or when input has been redirected:
if (Environment.UserInteractive && !Console.IsInputRedirected)
{
Console.ReadKey();
}