I have a question that I can't handle for a week, you won't believe it, I've already checked everything that is possible, but it still doesn't want to work correctly. Now I'm writing a script system for my game engine and I need to dynamically recomile/reload .the dll file that contains my library and scripts. To do this, I do:
Initialize Mono. (I get an appdomain (MonoDomain*) already created for me)
Open an assembly file. (dll)
Get an image from this assembly.
Use mono (I load internal calls, create C# objects, call functions, etc.).
But at some point I need to recompile/reload this assembly, because I change the script code, well, it's understandable. As I understand from a lot of read documentation and various sources, I do not have to describe the domain, assemblies and images while it is active and for this you first need:
Create a new domain.
Upload the image.
Connect the image to the object assembly (thereby making the image status OK)
And only then describe the resources from the last domain.
But just so that I don't try, either an error occurs in the mono code, or now I have reached the stage that there are no errors, but here is a pointer to a new image obtained from a newly loaded assembly (with the same name) is the same and thus it turns out that mono continues to use the same image and does not have the updated script values.
This is code of my Mono init:
mono_set_dirs(".", ".");
// 0. Load first domain, init mono + appdomain + load assembly.
const char* domainName = "ForceCS";
const char* assemblyName = "ForceCS.dll";
pMonoDomain = mono_jit_init(domainName);
if (pMonoDomain) {
pMonoAssembly = mono_domain_assembly_open(pMonoDomain, assemblyName);
if (pMonoAssembly) {
pMonoImage = mono_assembly_get_image(pMonoAssembly);
if (pMonoImage) {
AddInternalFunctionsAndScriptClasses(); // mono_add_internal_call ...
}
}
}
Then i do some stuff with my dll like create objects or call methods as i said before. And then try to reload the ForceCS.dll.
// 1. Load new domain (Works fine).
char* newDomainName = "ForceCS";
MonoDomain* newDomain = mono_domain_create_appdomain(newDomainName, NULL);
if (!mono_domain_set(newDomain, false)) {
// Cannot set domain!
}
// 2. Load image. (Works fine)
const char* dllFile = "C:\\Force\\Editor\\ForceCS.dll";
MonoImageOpenStatus status;
MonoImage* newImage = mono_image_open(dllFile, &status); ** Problem here: Shows the same address as before and that means that Mono use same .dll.**
// 3. Load new assembly. (Works fine).
MonoAssembly* assembly = mono_assembly_load_from(newImage, mono_image_get_name(newImage), &status);
if (status != MonoImageOpenStatus::MONO_IMAGE_OK) {
///Cannot load assembly!
}
assembly = mono_assembly_open(dllFile, &status);
newImage = mono_assembly_get_image(assembly);
if (newImage) {
AddInternalFunctionsAndScriptClasses();
}
// 4. Inload old domain.
MonoDomain* domainToUnload = pMonoDomain == nullptr ? mono_domain_get() : pMonoDomain;
if (domainToUnload && domainToUnload != mono_get_root_domain()) {
mono_domain_unload(domainToUnload);
mono_gc_collect(mono_gc_max_generation());
mono_domain_finalize(domainToUnload, 2000);
mono_gc_collect(mono_gc_max_generation());
pMonoDomain = newDomain;
pMonoImage = newImage;
pMonoAssembly = assembly;
}
Final problem that image always stays the same, only it works if i load this assembly with different name like ForceCS1.dll, but its not what i want. Please explain me:
When i need to close/free domains, assemblies, images.
What the connection between assembly-image.
How reload my .dll assembly.
I will be grateful for every answer.
Finally, after another week of torment, I finally managed to fix it. In general, as I understood the problem was that I was unloading my only main domain, but I did not create/set to another domain for mono so that he could access it on another thread while the assembly is being reloaded.
In general, I completely rewrote the code, I got something like:
int main() {
// Init Mono Runtime. (Create Root Domain)
mono_init();
if (mono_create_domain_and_open_assembly("Scripting Domain", "MyScript.dll"))
mono_invoke("Script", "OnCreate", 0, NULL, NULL);
while (true) {
bool is_reload_request;
std::cin >> is_reload_request;
if (is_reload_request) {
mono_destroy_domain_with_assisisated_assembly(domain);
// Update new compiled assembly.
mono_update_assembly("C:\\MonoProject\\MyScript.dll", "C:\\MonoProjectC#\\Binary\\Debug-windows-x86_64\\MyScript.dll");
if (mono_create_domain_and_open_assembly("Scripting Domain", "MyScript.dll"))
mono_invoke("Script", "OnCreate", 0, NULL, NULL);
}
}
mono_jit_cleanup(root_domain);
return 0;
}
Now I want to explain in detail what I did, for those who may also have encountered such a problem and cannot understand what and how and how to fix it.
Init Mono & create root domain.
The first thing I did was initialize mono runtime as before, thereby creating a root domain so that in case of a reloading we could switch to it.
void mono_init() {
mono_set_dirs(".", ".");
root_domain = mono_jit_init_version("Root Domain", "v4.0.30319");
}
Creating Scripting domain, loading assembly.
Then I created a new domain in our case, this is the Scripting Domain, and after creating it, I set it for mono (mono_domain_set(domain, 0)) so that all subsequent actions are performed from it. And then, just like before, I opened the my assembly MyScript.dll through the domain and got pointers to the assembly and the image.
bool mono_create_domain_and_open_assembly(char* domain_name, const char* assembly_file) {
domain = mono_domain_create_appdomain(domain_name, NULL);
if (domain) {
mono_domain_set(domain, 0);
// Open assembly.
assembly = mono_domain_assembly_open(mono_domain_get(), assembly_file);
if (assembly) {
image = mono_assembly_get_image(assembly);
if (image)
return true;
}
}
return false;
}
After this stage, I checked that my assembly was working fine, just created a Script object and called the OnCreate method from it. I will not write this code so I hope those people who have already worked with mono know how to do it.
Reloading
Now the reloading itself. In fact, everything is much simpler here than it seems, just before calling mono_domain_upload, you need to call mono_domain_set to switch our Scripting Domain to Root Domain for mono when we will creating new domain (new dll).
bool mono_destory_domain_with_assisisated_assembly(MonoDomain* domain_to_destroy) {
if (domain_to_destroy != root_domain) {
mono_domain_set(root_domain, 0);
mono_domain_unload(domain_to_destroy);
return true;
}
return false;
}
And then compile our C# project and move it to our project (or where you specified the paths for mono). Or how I did it through C++:
void mono_update_assembly(char* assembly_path, char* assembly_path_from) {
if (std::filesystem::exists(assembly_path_from)) {
if (std::filesystem::exists(assembly_path))
std::filesystem::remove(assembly_path);
std::filesystem::copy(assembly_path_from, assembly_path);
}
}
And the last step after all that has been done, we just create new domain and load new assembly.
Related
I Have a c# application that include various esternal DLL. When the application start, dlls are exctracted in the .exe folder to grant the correct execution.
here the code:
var executingAssembly = Assembly.GetExecutingAssembly();
string folderName = string.Format("{0}.Resources.DLLs", executingAssembly.GetName().Name);
var list = executingAssembly
.GetManifestResourceNames()
.ToArray();
foreach (var item in list)
{
File.WriteAllBytes(item.Replace("myapp.DLLs.", ""),
ReadAllBytes(executingAssembly.GetManifestResourceStream(item)));
}
when i close the form, i want to delete those files with this code, associated to the form closing event:
private void CleanFiles(Object sender, FormClosingEventArgs e)
{
var executingAssembly = Assembly.GetExecutingAssembly();
string folderName = string.Format("{0}.Resources.DLLs", executingAssembly.GetName().Name);
string folder = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
var list = executingAssembly
.GetManifestResourceNames()
.ToArray();
foreach (var item in list)
{
File.Delete(folder + #"\" + item.Replace("myapp.DLLs.", ""));
}
}
If I open and then close the form, it works perfectly. But, if I open the form, do some operations, it throw an exception during the closing operations because access to dlls is denied.
How can I release all dlls/resources?
I assume that if you copy these DLLs, you load and use them afterwards.
There's a slight issue with dynamically loading DLLs, being that you can't just unload them. Once loaded into your application domain, they're here to stay.
The solution is thus to simply create a new application domain, load the DLL inside of it, and when you're done, unload this new application domain.
Something like:
var dynamicDomain = AppDomain.CreateDomain("YourDomainName");
var dynamicallyLoadedAssembly = dynamicDomain.Load("YourAssembly");
// do stuff with your dynamically loaded assembly
AppDomain.Unload(dynamicDomain);
For more information on the topic: MSDN "How to: Load and unload assemblies".
You can (and should) implement exception handling around the parts where you manipulate files on the system:
try
{
// Manipulate your files here.
}
catch (Exception ex)
{
// Handle exceptions here. you can also delete the dlls here if you wish.
// Remember to check in CleanFiles if the files are already deleted.
}
finally
{
// You could also get rid of the files here.
// Finally-block is executed regardless if an exception was thrown or not.
}
Usefull links: Microsoft: Exception Handling, Microsoft: Best Ractices for exceptions
I'm currently working on a project where I want to automatically build a showcase of all the styles that are defined in the styling project of another Visual Studio solution.
To do this, I select the Styling.dll from the at runtime via OpenFileDialog from the other solutions bin/Debug folder, add its resources to my applications resources and then build the views. That works pretty well, but now I've got a problem when selecting an assembly with references to several other assemblies.
This is how I load the assemblies right now:
public void OpenFile()
{
var openFileDialog = new OpenFileDialog { Filter = "DLL Dateien (*.dll)|*.dll" };
if (openFileDialog.ShowDialog() == true)
{
var path = Path.GetDirectoryName(openFileDialog.FileName);
foreach (var dll in Directory.GetFiles(path, "*.dll").Where(dll => dll != openFileDialog.FileName && !dll.ToString().ToLower().Contains("datagrid")))
{
this.LoadResources(dll, false);
}
this.LoadResources(openFileDialog.FileName);
}
}
I put in the foreach to first load a Utils.dll that always comes with the Styling.dll, but that obviously doesnt work if there are multiple other assemblies referencing each other.
Then in LoadResources :
public void LoadResources(string assemblypath)
{
var assembly = Assembly.LoadFile(assemblypath);
var stream = assembly.GetManifestResourceStream(assembly.GetName().Name + ".g.resources");
if (stream != null)
{
var resourceReader = new ResourceReader(stream);
foreach (DictionaryEntry resource in resourceReader)
{
// Get all the .baml files from the .dll and create URI for corresponding .xaml files
if (new FileInfo(resource.Key.ToString()).Extension.Equals(".baml"))
{
var uri = new Uri("/" + assembly.GetName().Name + ";component/" + resource.Key.ToString().Replace(".baml", ".xaml"), UriKind.Relative);
// Add ResourceDictionary to Application Resources
var rd = Application.LoadComponent(uri) as ResourceDictionary;
if (rd != null)
{
Application.Current.Resources.MergedDictionaries.Add(rd);
}
}
}
}
}
Note: The exception always throws at the var rd = Application.LoadComponent(uri) as ResourceDictionary; point.
Now, how do I get my application to correctly load all the other assemblies in the directory? Right now, I always get an error telling me that the other assemblies could not be found, even though they are in the same directory as the referencing assembly.
After looking for a solution for a while now, I understood that the application automatically tries to find the missing assemblies, but doesn't search in the directory where they actually are.
Since i want to load the assemblies dynamically, I also cant just add additional search paths to my app.config beforehand, can I do it at runtime somehow?
I hope you understand my problem, any help is appreciated, thank you!
Edit:
I have already read through this question, but it didnt help me any further.
When I implement the AssemblyResolve Event, I still get FileNotFound / XamlParse exceptions, even when the file in the assemblypath exists.
Also, the handler always tries to find xxx.resources.dll files, which on the other hand do not exist in the directory.
I have an issue where I need to be able to have a compiled exe ( .net 3.5 c# ) that I will make copies of to distribute that will need to change a key for example before the exe is sent out.
I cannot compile each time a new exe is needed. This is a thin client that will be used as part of a registration process.
Is it possible to add a entry to a resource file with a blank value then when a request comes in have another application grab the blank default thin client, copy it, populate the blank value with the data needed.
If yes how? If no do you have any ideas? I have been scratching my head for a few days now and the limitation as due to the boundaries I am required to work in.
The other idea I has was to inject the value into a method, which I have no idea how I would even attempt that.
Thanks.
Convert the assembly to IL, do a textual search and replace, recompile the IL to an assembly again. Use the standard tools from the .NET SDK.
Instead of embedding the key in the assembly, put it in the app.config file (or another file delivered with the application) and prevent your application from running if the key is not present and valid. To protect it against modification by users, also add an RSA signature the config file.
This code could be used to generate XML containing your key.
public static void Main()
{
Console.WriteLine(GenerateKey());
}
public static Byte[] Transform(Byte[] bytes, ICryptoTransform xform)
{
using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
{
using (CryptoStream cstream = new CryptoStream(stream, xform, CryptoStreamMode.Write))
{
cstream.Write(bytes, 0, bytes.Length);
cstream.Close();
stream.Close();
return stream.ToArray();
}
}
}
public static string GenerateKey()
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
// This is the private key and should never be shared.
// Generate your own with RSA.Create().ToXmlString(true).
String rsaPrivateKey = "<RSAKeyValue><Modulus>uPCow37yEzlKQXgbqO9E3enSOXY1MCQB4TMbOZyk9eXmc7kuiCMhJRbrwild0LGO8KE3zci9ETBWVVSJEqUqwtZyfUjvWOLHrf5EmzribtSU2e2hlsNoB2Mu11M0SaGd3qZfYcs2gnEnljfvkDAbCyJhUlxmHeI+35w/nqSCjCk=</Modulus><Exponent>AQAB</Exponent><P>4SMSdNcOP0qAIoT2qzODgyl5yu9RubpIU3sSqky+85ZqJHXLUDjlgqAZvT71ROexJ4tMfMOgSWezHQwKWpz3sw==</P><Q>0krr7cmorhWgwCDG8jmzLMo2jafAy6tQout+1hU0bBKAQaPTGGogPB3hTnFIr84kHcRalCksI6jk4Xx/hiw+sw==</Q><DP>DtR9mb60zIx+xkdV7E8XYaNwx2JeUsqniwA3aYpmpasJ0N8FhoJI9ALRzzp/c4uDiuRNJIbKXyt6i/ZIFFH0qw==</DP><DQ>mGCxlBwLnhkN4ind/qbQriPYY8yqZuo8A9Ggln/G/IhrZyTOUWKU+Pqtx6lOghVdFjSxbapn0W8QalNMFGz7AQ==</DQ><InverseQ>WDYfqefukDvMhPHqS8EBFJFpls/pB1gKsEmTwbJu9fBxN4fZfUFPuTnCIJsrEsnyRfeNTAUFYl3hhlRYZo5GiQ==</InverseQ><D>qB8WvAmWFMW67EM8mdlReI7L7jK4bVf+YXOtJzVwfJ2PXtoUI+wTgH0Su0IRp9sR/0v/x9HZlluj0BR2O33snQCxYI8LIo5NoWhfhkVSv0QFQiDcG5Wnbizz7w2U6pcxEC2xfcoKG4yxFkAmHCIkgs/B9T86PUPSW4ZTXcwDmqU=</D></RSAKeyValue>";
rsa.FromXmlString(rsaPrivateKey);
String signedData = "<SignedData><Key>Insert your key here</Key></SignedData>";
Byte[] licenseData = System.Text.Encoding.UTF8.GetBytes(signedData);
Byte[] sigBytes = rsa.SignData(licenseData, new SHA1CryptoServiceProvider());
String sigText = System.Text.Encoding.UTF8.GetString(Transform(sigBytes, new ToBase64Transform()));
System.Text.StringBuilder sb = new StringBuilder();
using (System.Xml.XmlWriter xw = System.Xml.XmlTextWriter.Create(sb))
{
xw.WriteStartElement("License");
xw.WriteRaw(signedData);
xw.WriteElementString("Signature", sigText);
xw.WriteEndElement();
}
return sb.ToString();
}
Example output from this code:
<?xml version="1.0" encoding="utf-16"?>
<License>
<SignedData>
<Key>Insert your key here</Key>
</SignedData>
<Signature>cgpmyqaDlHFetCZbm/zo14NEcBFZWaQpyHXViuDa3d99AQ5Dw5Ya8C9WCHbTiGfRvaP4nVGyI+ezAAKj287dhHi7l5fQAggUmh9xTfDZ0slRtvYD/wISCcHfYkEhofXUFQKFNItkM9PnOTExZvo75pYPORkvKBF2UpOIIFvEIU=</Signature>
</License>
Then you can use code like this to verify it. You never have to distribute the private key:
public static Boolean CheckLicenseSignature(String licXml)
{
try
{
System.Xml.XmlDocument xd = new System.Xml.XmlDocument();
xd.LoadXml(licXml);
String licSig = xd.SelectSingleNode("/License/Signature").InnerText;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
String rsaPublicKey = "<RSAKeyValue><Modulus>uPCow37yEzlKQXgbqO9E3enSOXY1MCQB4TMbOZyk9eXmc7kuiCMhJRbrwild0LGO8KE3zci9ETBWVVSJEqUqwtZyfUjvWOLHrf5EmzribtSU2e2hlsNoB2Mu11M0SaGd3qZfYcs2gnEnljfvkDAbCyJhUlxmHeI+35w/nqSCjCk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
rsa.FromXmlString(rsaPublicKey);
Byte[] licenseData = System.Text.Encoding.UTF8.GetBytes(xd.SelectSingleNode("/License/SignedData").OuterXml);
return rsa.VerifyData(licenseData, new SHA1CryptoServiceProvider(), Transform(System.Text.Encoding.UTF8.GetBytes(licSig), new FromBase64Transform()));
}
catch (System.Xml.XmlException ex)
{
return false;
}
catch (InvalidOperationException ex)
{
return false;
}
}
From within the capability of the .NET code itself, I'm not sure if this is doable. But it is possible to dynamically generate a .NET DLL which contains some key that can be referred from the main application. That is, if you wouldn't mind a second file in the distribution.
Or if you don't mind to use Ildasm to disassemble the .exe, change the key, then use Ilasm to reassemble, then you can do something to automate that.
The accepted answer is GARBAGE!
I HAVE DONE THIS SUCCESSFULLY. MUCH EASIER
Just put your base application (.net) that needs the key somewhere with a string resource FILLED WITH "XXXXXXXXXXXXXXX" (more than you'll need)
.Net resources are usually kept at the top of the code so you will find them fast skipping the first 100,000 bytes in my case.
Then you just read it in and look for those XXXXXX's. When you find them you replace them with the real API key and replace the rest of the X's with spaces you just trim off in code. This is the answer. It works and it works well.
ApiToken at = new ApiToken(UserId, SelectedCID);
at.MakeToken();
byte[] app = System.IO.File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.GetData("DataDirectory").ToString(), "notkeyedapp.exe"));
for (int i = 100000; i < app.Length; i++)
{
if (app[i] == 0x58 && app[i + 1] == 0x58 && app[i + 2] == 0x58)
{
for (int j = 0; j < 128; j++)
{
if (at.Token.Length >= j + 1)
app[i + j] = System.Text.Encoding.ASCII.GetBytes(at.Token[j].ToString())[0];
else
app[i + j] = 0x20;
}
break;
}
}
string filename = "SoftwareProduct for - " + BaseModel.CompanyName.Replace(".", "") + ".exe";
return File(app, System.Net.Mime.MediaTypeNames.Application.Octet, filename);
I don't think You can get away without recompiling Your .exe and having key embedded into said .exe. The compilation process can be automated though via use of ildasm.exe and ilasm.exe as Daniel Earwicker suggested in his response https://stackoverflow.com/a/2742902/2358659
I'd like to expand on that if anyone else stumbles across this topic in the future.
I recently was facing similar problem due to my poor source code version control habits. In a nutshell I had an executable that was supposed to write some data to a Google Spreadsheet by referencing it's ID. Long after executable was released came another request from a different team to use the tool, but it had to write same information into a different spreadsheet in order to keep data separate for two teams. At the time I did not have the original source code, hence I was not able to change the static variable holding the original spreadsheet ID. What I did was as follows:
Using CMD.exe → call "C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\ildasm.exe" "myApplication.exe" /out="myApplication.il"
Using Notepad++ → Find and replace original ID to new ID inside myApplication.il file. This action can also be automated by writing own C# application to do this, or using PowerShell, or using vb/j-script or using some other find and replace tool available off-the-shelf, like FART (using CMD.exe → call fart.exe myApplication.il "OldKey" "NewKey")
Using CMD.exe → call "C:\Windows\Microsoft.NET\Framework\v4.0.30319\ilasm.exe" "myApplication.il" /res="myApplication.res" /key="myApplicationKeyFile.snk"
As You see, all of these steps can be put into one .bat file that takes "NewKey" as an input and produces new .exe with NewKey embedded.
I hope that helps.
What comes to my mind, but not tried yet: Create a default String in your program, for example as
static public string regGuid = "yourguidhere";
Then, search the compiled EXE with any decent hex editor. If you find the string, replace it with another test. If you still can execute the program, you could try to automate this process and voila! Here you are.
var speechEngine = new SpVoiceClass();
SetVoice(speechEngine, job.Voice);
var fileMode = SpeechStreamFileMode.SSFMCreateForWrite;
var fileStream = new SpFileStream();
try
{
fileStream.Open(filePath, fileMode, false);
speechEngine.AudioOutputStream = fileStream;
speechEngine.Speak(job.Script, SpeechVoiceSpeakFlags.SVSFPurgeBeforeSpeak | SpeechVoiceSpeakFlags.SVSFDefault); //TODO: Change to XML
//Wait for 15 minutes only
speechEngine.WaitUntilDone((uint)new TimeSpan(0, 15, 0).TotalMilliseconds);
}
finally
{
fileStream.Close();
}
This exact code works in a WinForm app, but when I run it inside a webservice I get the following
System.Runtime.InteropServices.COMException was unhandled
Message="Exception from HRESULT: 0x80045003"
Source="Interop.SpeechLib"
ErrorCode=-2147201021
Does anyone have any ideas what might be causing this error? The error code means
SPERR_UNSUPPORTED_FORMAT
For completeness here is the SetVoice method
void SetVoice(SpVoiceClass speechEngine, string voiceName)
{
var voices = speechEngine.GetVoices(null, null);
for (int index = 0; index < voices.Count; index++)
{
var currentToken = (SpObjectToken)voices.Item(index);
if (currentToken.GetDescription(0) == voiceName)
{
speechEngine.SetVoice((ISpObjectToken)currentToken);
return;
}
}
throw new Exception("Voice not found: " + voiceName);
}
I have given full access to USERS on the folder C:\Temp where the file is to be written. Any help would be appreciated!
I don't think the System.Speech works in windows service. It looks like there is a dependency to Shell, which isn't available to services. Try interop with SAPI's C++ interfaces. Some class in System.Runtime.InteropServices may help on that.
Our naming convention requires us to use a non-standard file extension. This works fine in a Winforms app, but failed on our web server. Changing the file extension back to .wav solved this error for us.
Make sure you explicitly set the format on the SPFileStream object. ISpAudio::SetState (which gets called in a lower layer from speechEngine.Speak) will return SPERR_UNSUPPORTED_FORMAT if the format isn't supported.
I just got the webservice to spawn a console app to do the processing. PITA :-)
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();
}