Deploying C# Solution with Native Dll into single EXE - c#

I have A C# Visual Studio 2012 Solution that relies on a native dll that I use PInvoke to access. When I deploy the app I will have to ensure that this Dll is in the app folder.
Is there anyway I can merge this Dll into the executable?
perhaps as a resource?
I have heard of ILMerge but I am told it cant cope with native code.
Any help would be appreciated.

You can create a Setup package project with Visual Studio that deploys all your files to the correct location or use other third party packaging software (like full InstallShield or alternatives)
However, your question reminds me on the Open Hardware Monitor project where they include drivers as embedded resource and extract them when the user starts the application. It works like this: they've added WinRing0.sys and WinRing0x64.sys to the project and set their Build Action to Embedded Resource, then they have a method that extracts the driver from the resource:
private static bool ExtractDriver(string fileName) {
string resourceName = "OpenHardwareMonitor.Hardware." +
(OperatingSystem.Is64BitOperatingSystem() ? "WinRing0x64.sys" :
"WinRing0.sys");
string[] names =
Assembly.GetExecutingAssembly().GetManifestResourceNames();
byte[] buffer = null;
for (int i = 0; i < names.Length; i++) {
if (names[i].Replace('\\', '.') == resourceName) {
using (Stream stream = Assembly.GetExecutingAssembly().
GetManifestResourceStream(names[i]))
{
buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
}
}
}
if (buffer == null)
return false;
try {
using (FileStream target = new FileStream(fileName, FileMode.Create)) {
target.Write(buffer, 0, buffer.Length);
target.Flush();
}
} catch (IOException) {
// for example there is not enough space on the disk
return false;
}
// make sure the file is actually writen to the file system
for (int i = 0; i < 20; i++) {
try {
if (File.Exists(fileName) &&
new FileInfo(fileName).Length == buffer.Length)
{
return true;
}
Thread.Sleep(100);
} catch (IOException) {
Thread.Sleep(10);
}
}
// file still has not the right size, something is wrong
return false;
}
They're reading the resource into a buffer, write that buffer to disk and wait until the file has been flushed to disk.

My solution is conceptually similar to the one presented by Wouter.
It's what we use in our own app, and we can use native/mixed-mode and c# dlls all embedded in the same .exe.
It extracts the dlls into a temp dir everytime the application is run. Obviously you might not want to do this in the production version, where the dlls will be stable; you might choose a different directory there (probably somewhere in %AppData%). It will use an existing dll with the same version number, though (e.g. it's only done the first time when opening the app multiple times between booting the computer).
Since we're doing
AppDomain.CurrentDomain.AssemblyResolve += (sender, args)
this function is getting called wherever the system tries to resolve a dll. And since it's initalised in the static Program class, it all works automagically.
Program.cs:
namespace MyApp
{
internal class Program
{
static Program()
{
LoadAssemblyResource.Initialize("MyApp");
}
//....
}
}
LoadAssemblyResource.cs
namespace MyAppStartup
{
public static class LoadAssemblyResource
{
private readonly static String _version_string =
Assembly.GetExecutingAssembly().GetName().Version.ToString();
private readonly static String _dll_path = Path.GetTempPath()
+ "\\MyApp\\" + _version_string;
static public String last_error_msg = null;
public static bool WriteBytesToFile(string filename, byte[] bytes)
{
try
{
var fs = new FileStream(filename, FileMode.Create, FileAccess.Write);
fs.Write(bytes, 0, bytes.Length);
fs.Close();
return true;
}
catch (Exception e)
{
Console.WriteLine("Writing file failed. Exception: {0}", e.ToString());
}
return false;
}
public static Assembly LoadUnsafe(String assembly_name, Byte[] assembly)
{
if (!Directory.Exists(_dll_path))
{
Directory.CreateDirectory(_dll_path);
Console.WriteLine("Created tmp path '" + _dll_path + "'.");
}
String fullpath = _dll_path + "\\" + assembly_name;
if (!File.Exists(fullpath))
{
Console.WriteLine("Assembly location: " + fullpath + ".");
if (!WriteBytesToFile(fullpath, assembly))
return null;
}
return Assembly.UnsafeLoadFrom(fullpath);
}
public static void Initialize(String exe_name)
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
String assembly_name = new AssemblyName(args.Name).Name + ".dll";
String resource_name = exe_name + "." + assembly_name;
using (var stream =
Assembly.GetExecutingAssembly().GetManifestResourceStream(resource_name))
{
if (stream == null)
return null;
Byte[] assembly_data = new Byte[stream.Length];
stream.Read(assembly_data, 0, assembly_data.Length);
try
{
Assembly il_assembly = Assembly.Load(assembly_data);
return il_assembly;
}
catch (System.IO.FileLoadException ex)
{
// might have failed because it's an mixed-mode dll.
last_error_msg = ex.Message;
}
Assembly mixed_mode_assembly = LoadUnsafe(assembly_name, assembly_data);
return mixed_mode_assembly;
}
};
}
}
}

Related

My service doesn't change the wallpaper and I don't know why

I've created a service, which "locks" the desktop background, so you can't change it. In general, the service compares the current wallpaper with the one I want to be the new wallpaper. If it's not the same, it gets overwritten. This is my OnTimer()-Method, which gets executed every 2 seconds:
private void OnTimer(object sender, ElapsedEventArgs e)
{
if (!File.Exists("C:/Program Files/image.jpg"))
{
Assembly myAssembly = Assembly.GetExecutingAssembly();
Stream s = myAssembly.GetManifestResourceStream("MsService.Gandalf.jpg"); ;
byte[] b;
using (BinaryReader br = new BinaryReader(s))
{
b = br.ReadBytes((int)s.Length);
}
while (true)
{
try
{
File.WriteAllBytes("C:/Program Files/image.jpg", b);
break;
}
catch (Exception)
{
//stuff
}
}
}
//{path} is %Appdata%
if (!FileEquals($"{path}\\Microsoft\\Windows\\Themes\\TranscodedWallpaper", "C:/Program Files/image.jpg"))
{
byte[] file = File.ReadAllBytes("C:/Program Files/image.jpg");
while (true)
{
try
{
File.WriteAllBytes($"{path}\\Microsoft\\Windows\\Themes\\TranscodedWallpaper", file);
break;
}
catch (Exception)
{
//stuff
}
}
}
}
That's the FileEquals Method:
static bool FileEquals(string path1, string path2)
{
byte[] file1 = File.ReadAllBytes(path1);
byte[] file2 = File.ReadAllBytes(path2);
if (file1.Length == file2.Length)
{
for (int i = 0; i < file1.Length; i++)
{
if (file1[i] != file2[i])
{
return false;
}
}
return true;
}
return false;
}
When I start the service, It only changes the wallpaper if I change it actively, even if it already is another one that the one I want it to be, and only once. The service outputs no error when debugging, and the service doesn't crash. Also when I change the wallpaper, the breakpoints in the if(!File.Equals(...)) gets triggered. Virus scanner isn't alerting anything too. Why doesn't it work anyway?

Where is the Unity IAP package located?

I'm currently working on an editor script that will ease my transition between freemium and paid versions of my game.I would like to manually import the .unitypackage file that gets imported when I click the import button under Services -> In-App Purchasing.
I am aware of the function AssetDatabase.ImportAsset(path) but I need the path of the package first.
Thanks in advance!
When you enable IAP and click Import, the following will happen:
1.Unity will generate a random file name with
FileUtil.GetUniqueTempPathInProject()
2.A full path will be constructed like this:
<ProjectName>Temp\FileUtil.GetUniqueTempPathInProject()
3.Unity will then add .unitypackage to the end of that random file name.
Now, you have something like:
<ProjectName>Temp\FileUtil.GetUniqueTempPathInProject()+".unitypackage";
4.IAP package will then be downloaded and stored to path from #3.
Illustration of what it looks like:
5.The file is then copied to
<ProjectName>Temp\TarGZ
It is saved with the file name generated from #2. No .unitypackage at the end like #3.
UnityEngine.Cloud.Purchasing
6.After that, Unity imports it with AssetDatabase.ImportPackage not AssetDatabase.ImportAsset(path,false) as mentioned in your question.
Now you can see where Unity IAP package is located but there are just few problems in what you are trying to do:
1.For the IAP package to be present, the import button from the Services Tab must be clicked. I am sure you want this to be automated.
2.The file name is not static therefore making the path hard to retrieve. As you can see from the image above, there are other files in this folder too. We don't know which one is the AIP Package.
3.When you re-start Unity, the temp folder will be deleted. So the downloaded IAP package will be lost.
The best way to get the Unity IAP package is to download it directly from Unity's server then save it to your preferred location. The code below will download the IAP package and store it at: <ProjectName>/UnityEngine.Cloud.Purchasing.unitypackage
It will also import it and then, enable it. For AIP to work, Analytics must be enabled too. This code will also do that.
I tried to hide the AIP url so that it won't be abused and Unity won't have change it.
If you want to remake this into something else, the two most important functions to look at are downloadAndInstallAIP() and deleteAndDisableAIP().
AIPDownloader Code:
using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using UnityEditor;
using UnityEditor.Analytics;
using UnityEditor.Purchasing;
using UnityEngine;
[ExecuteInEditMode]
public class AIPDownloader : MonoBehaviour
{
static string projectDirectory = Environment.CurrentDirectory;
static string aipFileName = "UnityEngine.Cloud.Purchasing.unitypackage";
static string etagName = "UnityEngine.Cloud.PurchasingETAG.text";
static string aipfullPath = "";
static string eTagfullPath = "";
static EditorApplication.CallbackFunction doneEvent;
[MenuItem("AIP/Enable AIP")]
public static void downloadAndInstallAIP()
{
//Make AIP fullpath
aipfullPath = null;
aipfullPath = Path.Combine(projectDirectory, aipFileName);
//Make AIP Etag fullpath
eTagfullPath = null;
eTagfullPath = Path.Combine(projectDirectory, etagName);
/*If the AIP File already exist at <ProjectName>/UnityEngine.Cloud.Purchasing.unitypackage,
* there is no need to re-download it.
Re-import the package
*/
if (File.Exists(aipfullPath))
{
Debug.Log("AIP Package already exist. There is no need to re-download it");
if (saveETag(null, true))
{
importAIP(aipfullPath);
return;
}
}
string[] uLink = {
"aHR0cHM=",
"Oi8vcHVibGljLWNkbg==",
"LmNsb3Vk",
"LnVuaXR5M2Q=",
"LmNvbQ==",
"L1VuaXR5RW5naW5l",
"LkNsb3Vk",
"LlB1cmNoYXNpbmc=",
"LnVuaXR5cGFja2FnZQ=="
};
prepare(uLink);
try
{
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateRemoteCertificate);
WebClient client = new WebClient();
client.DownloadFileCompleted += new AsyncCompletedEventHandler(OnDoneDownloading);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(OnDownloadProgressChanged);
client.DownloadFileAsync(new Uri(calc(uLink)), aipfullPath);
}
catch (Exception e)
{
Debug.LogError("Error: " + e.Message);
}
}
[MenuItem("AIP/Disable AIP")]
public static void deleteAndDisableAIP()
{
FileUtil.DeleteFileOrDirectory("Assets/Plugins/UnityPurchasing");
//Disable AIP
PurchasingSettings.enabled = false;
//Disable Analytics
AnalyticsSettings.enabled = false;
}
private static bool ValidateRemoteCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error)
{
return true;
}
public bool isAIPEnabled()
{
return PurchasingSettings.enabled;
}
private static bool saveETag(WebClient client, bool alreadyDownloadedAIP = false)
{
string contents = "";
if (alreadyDownloadedAIP)
{
//Load Etag from file
try
{
contents = File.ReadAllText(eTagfullPath);
return _saveEtag(contents, alreadyDownloadedAIP);
}
catch (Exception e)
{
Debug.LogWarning("File does not exist!: " + e.Message);
}
return false; //Failed
}
else
{
//Load Etag from downloaded WebClient
contents = client.ResponseHeaders.Get("ETag");
return _saveEtag(contents, alreadyDownloadedAIP);
}
}
static bool _saveEtag(string contents, bool alreadyDownloadedAIP = false)
{
if (contents != null)
{
try
{
//Save if not downloaded
if (!alreadyDownloadedAIP)
{
Directory.CreateDirectory(Path.GetDirectoryName(eTagfullPath));
File.WriteAllText(eTagfullPath, contents);
}
//Save to the etag to AIP directory
Directory.CreateDirectory(Path.GetDirectoryName("Assets/Plugins/UnityPurchasing/ETag"));
File.WriteAllText("Assets/Plugins/UnityPurchasing/ETag", contents);
return true;//Success
}
catch (Exception e)
{
Debug.LogWarning("Failed to write to file: " + e.Message);
return false; //Failed
}
}
else
{
return false; //Failed
}
}
public static void OnDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
Debug.Log("Downloading: " + e.ProgressPercentage);
}
public static void OnDoneDownloading(object sender, AsyncCompletedEventArgs args)
{
WebClient wc = (WebClient)sender;
if (wc == null || args.Error != null)
{
Debug.Log("Failed to Download AIP!");
return;
}
Debug.Log("In Download Thread. Done Downloading");
saveETag(wc, false);
doneEvent = null;
doneEvent = new EditorApplication.CallbackFunction(AfterDownLoading);
//Add doneEvent function to call back!
EditorApplication.update = (EditorApplication.CallbackFunction)Delegate.Combine(EditorApplication.update, doneEvent);
}
static void AfterDownLoading()
{
//Remove doneEvent function from call back!
EditorApplication.update = (EditorApplication.CallbackFunction)Delegate.Remove(EditorApplication.update, doneEvent);
Debug.Log("Back to Main Thread. Done Downloading!");
importAIP(aipfullPath);
}
//Import or Install AIP
public static void importAIP(string path)
{
AssetDatabase.ImportPackage(path, false);
Debug.Log("Done Importing AIP package");
//Enable Analytics
AnalyticsSettings.enabled = true;
//Enable AIP
PurchasingSettings.enabled = true;
}
private static void prepare(string[] uLink)
{
for (int i = 5; i < uLink.Length; i++)
{
byte[] textAsBytes = System.Convert.FromBase64String(uLink[i]);
uLink[i] = Encoding.UTF8.GetString(textAsBytes);
}
for (int i = 0; i < uLink.Length; i++)
{
byte[] textAsBytes = System.Convert.FromBase64String(uLink[i]);
uLink[i] = Encoding.UTF8.GetString(textAsBytes);
if (i == 4)
{
break;
}
}
}
private static string calc(string[] uLink)
{
return string.Join("", uLink);
}
}

Error writing Files as local user

When attempting to download and write a file as a local user (ie, not run as admin), the following code is throwing an UnauthorizedAccessException(Access to the path is denied.). Originally, I assumed that this was due to the application attempting to write files directly to the C drive. However, I get the same error when attempting to save files to the local user's documents drive, as gotten by this:
Environment.GetFolderPath(Environment.SpecialFolder.Personal);
This seems to be a windows 10 specific issue, as the application runs fine in previous versions of windows (even writing directly to C:// as a local user, which I had thought would be blocked).
private bool DownloadFile(Stream srcStream, string dstFile)
{
bool success = false;
byte[] buffer = new byte[16384];
int byteCount;
FileStream destStream = null;
try
{
destStream = File.Create(dstFile);
while ((byteCount = srcStream.Read(buffer, 0, 16384)) != 0)
{
destStream.Write(buffer, 0, byteCount);
}
success = true;
}
catch(Exception)
{
return success;
}
finally
{
try { destStream.Close(); }
catch (Exception) { }
}
return success;
}
I have checked, and the local user account has full access to their Documents folder, so I'm stumped as to why this isn't working.
Ok I just did a unit test with your code.
The problem is
destStream = File.Create(dstFile);
This is a folder not a file!
try this:
destStream = File.Create(dstFile + "\Test.txt");
And tadaaaaa. No more exception ;)
You can not write into a folder. only inside file.
and please use using() when needed :)
Unit test:
[TestMethod]
public void TestMethod1()
{
var path = Environment.GetFold`enter code here`erPath(Environment.SpecialFolder.Personal);
// path = "C:\Users\pix\Documents"
using (var memoryStream = new MemoryStream())
{
var result = DownloadFile(memoryStream, path);
Assert.IsFalse(result);
result = DownloadFile(memoryStream, Path.Combine("FILE.txt"));
Assert.IsTrue(result);
}
}
private bool DownloadFile(Stream srcStream, string dstFile)
{
bool success = false;
byte[] buffer = new byte[16384];
int byteCount;
FileStream destStream = null;
try
{
destStream = File.Create(dstFile);
while ((byteCount = srcStream.Read(buffer, 0, 16384)) != 0)
{
destStream.Write(buffer, 0, byteCount);
}
success = true;
}
catch (Exception ex)
{
return success;
}
finally
{
try { destStream.Close(); }
catch (Exception) { }
}
return success;
}

How do I bake dependencies into C# console program?

I have a C# console program that uses the nuget plugin SocketIO4Net
When I build the exe and move it to my Windows 2008 server, it doesn't work, whereas on my local machine, it works.
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'SocketIOClient, Version=0.6.26.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
Is there any way I can bake all my dependencies into the exe?
I tried doing:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var resName = "converter.SocketIOClient.dll";
var thisAssembly = Assembly.GetExecutingAssembly();
using (var input = thisAssembly.GetManifestResourceStream(resName))
{
return input != null
? Assembly.Load(StreamToBytes(input))
: null;
}
};
But that didn't work. Perhaps I'm getting the resourceName wrong?
Here is my example which is based off of Embedding one dll inside another as an embedded resource and then calling it from my code but has some helpful screenshots.
using System;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using MyEmbbedFile;
namespace ProjectNameSpace
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
var resName = "ProjectNameSpace.MyEmbbedFile.dll";
var thisAssembly = Assembly.GetExecutingAssembly();
using (var input = thisAssembly.GetManifestResourceStream(resName))
{
return input != null
? Assembly.Load(StreamToBytes(input))
: null;
}
};
}
private void button1_Click(object sender, EventArgs e)
{
MyEmbbedFileApp app = new MyEmbbedFileApp();
app.DoStuff();
}
private static byte[] StreamToBytes(Stream input)
{
var capacity = input.CanSeek ? (int)input.Length : 0;
using (var output = new MemoryStream(capacity))
{
int readLength;
var buffer = new byte[4096];
do
{
readLength = input.Read(buffer, 0, buffer.Length);
output.Write(buffer, 0, readLength);
}
while (readLength != 0);
return output.ToArray();
}
}
}
}
There are 2 other things you will need to do:
You will still need to make sure you add your assembly as a reference so your code compiles. Just make sure it does not copy to the output directory.
The second thing you need to do is add your reference to the project as a normal file. Then set it's build action to Embedded Resource under properties.
Yes.
Use AppDomain.AssemblyResolve to 'hydrate' embedded assemblies at runtime.
This project SQLDiagCmd at Github contains an example of doing this. It is based on Jeffrey Ricther's method:
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
String resourceName = "AssemblyLoadingAndReflection." +
new AssemblyName(args.Name).Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
The 'trick' is where the embedded assembly is located and (as you have found), the string used to refer to it in the AssemblyResolve handler. [I don't have time right now but will look again later...]

c# Unable to open file for reading

I'm writing a program that uses FileSystemWatcher to monitor changes to a given directory, and when it receives OnCreated or OnChanged event, it copies those created/changed files to a specified directories. At first I had problems with the fact that OnChanged/OnCreated events can be sent twice (not acceptable in case it needed to process 500MB file) but I made a way around this and with what I'm REALLY BLOCKED with is getting the following IOException:
The process cannot access the file 'C:\Where are Photos\bookmarks (11).html' because it is being used by another process.
Thus, preventing the program from copying all the files it should.
So as I mentioned, when user uses this program he/she specifes monitored directory, when user copies/creates/changes file in that directory, program should get OnCreated/OnChanged event and then copy that file to few other directories.
Above error happens in all cases, if user copies few files that needs to overwrite other ones in folder being monitored or when copying bulk of several files or even sometimes when copying one file in a monitored directory.
Whole program is quite big so I'm sending the most important parts.
OnCreated:
private void OnCreated(object source, FileSystemEventArgs e) {
AddLogEntry(e.FullPath, "created", "");
// Update last access data if it's file so the same file doesn't
// get processed twice because of sending another event.
if (fileType(e.FullPath) == 2) {
lastPath = e.FullPath;
lastTime = DateTime.Now;
}
// serves no purpose now, it will be remove soon
string fileName = GetFileName(e.FullPath);
// copies file from source to few other directories
Copy(e.FullPath, fileName);
Console.WriteLine("OnCreated: " + e.FullPath);
}
OnChanged:
private void OnChanged(object source, FileSystemEventArgs e) {
// is it directory
if (fileType(e.FullPath) == 1)
return; // don't mind directory changes itself
// Only if enough time has passed or if it's some other file
// because two events can be generated
int timeDiff = ((TimeSpan)(DateTime.Now - lastTime)).Seconds;
if ((timeDiff < minSecsDiff) && (e.FullPath.Equals(lastPath))) {
Console.WriteLine("-- skipped -- {0}, timediff: {1}", e.FullPath, timeDiff);
return;
}
// Update last access data for above to work
lastPath = e.FullPath;
lastTime = DateTime.Now;
// Only if size is changed, the rest will handle other handlers
if (e.ChangeType == WatcherChangeTypes.Changed) {
AddLogEntry(e.FullPath, "changed", "");
string fileName = GetFileName(e.FullPath);
Copy(e.FullPath, fileName);
Console.WriteLine("OnChanged: " + e.FullPath);
}
}
fileType:
private int fileType(string path) {
if (Directory.Exists(path))
return 1; // directory
else if (File.Exists(path))
return 2; // file
else
return 0;
}
Copy:
private void Copy(string srcPath, string fileName) {
foreach (string dstDirectoy in paths) {
string eventType = "copied";
string error = "noerror";
string path = "";
string dirPortion = "";
// in case directory needs to be made
if (srcPath.Length > fsw.Path.Length) {
path = srcPath.Substring(fsw.Path.Length,
srcPath.Length - fsw.Path.Length);
int pos = path.LastIndexOf('\\');
if (pos != -1)
dirPortion = path.Substring(0, pos);
}
if (fileType(srcPath) == 1) {
try {
Directory.CreateDirectory(dstDirectoy + path);
//Directory.CreateDirectory(dstDirectoy + fileName);
eventType = "created";
} catch (IOException e) {
eventType = "error";
error = e.Message;
}
} else {
try {
if (!overwriteFile && File.Exists(dstDirectoy + path))
continue;
// create new dir anyway even if it exists just to be sure
Directory.CreateDirectory(dstDirectoy + dirPortion);
// copy file from where event occured to all specified directories
using (FileStream fsin = new FileStream(srcPath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
using (FileStream fsout = new FileStream(dstDirectoy + path, FileMode.Create, FileAccess.Write)) {
byte[] buffer = new byte[32768];
int bytesRead = -1;
while ((bytesRead = fsin.Read(buffer, 0, buffer.Length)) > 0)
fsout.Write(buffer, 0, bytesRead);
}
}
} catch (Exception e) {
if ((e is IOException) && (overwriteFile == false)) {
eventType = "skipped";
} else {
eventType = "error";
error = e.Message;
// attempt to find and kill the process locking the file.
// failed, miserably
System.Diagnostics.Process tool = new System.Diagnostics.Process();
tool.StartInfo.FileName = "handle.exe";
tool.StartInfo.Arguments = "\"" + srcPath + "\"";
tool.StartInfo.UseShellExecute = false;
tool.StartInfo.RedirectStandardOutput = true;
tool.Start();
tool.WaitForExit();
string outputTool = tool.StandardOutput.ReadToEnd();
string matchPattern = #"(?<=\s+pid:\s+)\b(\d+)\b(?=\s+)";
foreach (Match match in Regex.Matches(outputTool, matchPattern)) {
System.Diagnostics.Process.GetProcessById(int.Parse(match.Value)).Kill();
}
Console.WriteLine("ERROR: {0}: [ {1} ]", e.Message, srcPath);
}
}
}
AddLogEntry(dstDirectoy + path, eventType, error);
}
}
I checked everywhere in my program and whenever I use some file I use it in using block so even writing event to log (class for what I omitted since there is probably too much code already in post) wont lock the file, that is it shouldn't since all operations are using using statement block.
I simply have no clue who's locking the file if not my program "copy" process from user through Windows or something else.
Right now I have two possible "solutions" (I can't say they are clean solutions since they are hacks and as such not desirable). Since probably the problem is with fileType method (what else could lock the file?) I tried changing it to this, to simulate "blocking-until-ready-to-open" operation:
fileType:
private int fileType(string path) {
FileStream fs = null;
int ret = 0;
bool run = true;
if (Directory.Exists(path))
ret = 1;
else {
while (run) {
try {
fs = new FileStream(path, FileMode.Open);
ret = 2;
run = false;
} catch (IOException) {
} finally {
if (fs != null) {
fs.Close();
fs.Dispose();
}
}
}
}
return ret;
}
This is working as much as I could tell (test), but... it's hack, not to mention other deficients.
The other "solution" I could try (I didn't test it yet) is using GC.Collect() somewhere at the end of fileType() method. Maybe even worse "solution" than previous one.
Can someone pleas tell me, what on earth is locking the file, preventing it from opening and how can I fix that? What am I missing to see?
Thanks in advance.
The problem is most likely that the file is still being copied while you already try to access it. This can happen especially on large files.
You can try to check whether the file can be opened with write permissions before you actually start your processing. For details how to do that check here.
If you can influence the process creating the file there might be a better solution. First copy the file with a temporary extension, and then, after the copying is completed, rename it so that the FileSystemWatcher event will be triggered.
You can try with Volume Shadow Copies.
See www.codeproject.com/KB/dotnet/makeshadowcopy.aspx for more details.
FileSystemWatcher events trigger when the file begins the copy, not at the end, so it's common to run into this kind of errors.
Your first approach will work, however, I would recommend spinning the I/O intensive code on another thread, and using an incremental Sleep() instead of the busy waiting you do.
However, if you have access to the software that actually creates the files, the extension changing is a slightly less complicated solution. Just beware, that a xls filter on the FileSystemwatcher will match a file called myfile1.xls.temp, as I found that out the hard way :)

Categories