Consider the following C# code:
try
{
using (new PdfReader(filename))
{
}
}
catch
{
}
finally
{
File.Delete(filename);
}
If filename points to a non-pdf file, PdfReader constructor throws exception (as expected), but also does not release the FileStream that it internally creates, despite being called in a using block. As a result, attempt to delete the file in finally block throws exception The process cannot access the file '<filename>' because it is being used by another process.
In fact, if constructor throws an exception, it should not result in locking any resources. So the above code should be deleting non-pdf files even when PdfReader constructor is called without the using block.
Obvious workaround is to instantiate PdfReader like this:
using (var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
using (new PdfReader(fileStream))
{
}
It does work, but current behaviour of the PdfReader constructor overloads using filename argument is not expected.
This is indeed a bug on the part of iText, but it's known issue and it's already fixed in the newest develop version. iText 7.1.16 version will contain this fix, but if you need this right now you can use SNAPSHOT version from the artifactory https://repo.itextsupport.com/webapp/#/artifacts/browse/tree/General/snapshot
Related
I have a NET 5.0 console application, from which I am trying to compile and execute external code BUT also be able to update the code, unload the previously created appdomain and re-compile everything.
This is my entire static class that handles code compilation and assembly loading
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Reflection;
using Microsoft.CodeAnalysis.Emit;
using System.Runtime.Loader;
namespace Scripting
{
public static class ScriptCompiler
{
public static Dictionary<string, AppDomain> _appDomainDict = new();
public static object CompileScript(string scriptpath)
{
var tree = SyntaxFactory.ParseSyntaxTree(File.ReadAllText(scriptpath));
//Adding basic references
List<PortableExecutableReference> refs = new List<PortableExecutableReference>();
var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Private.CoreLib.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")));
refs.Add(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")));
// A single, immutable invocation to the compiler
// to produce a library
string hash_name = scriptpath.GetHashCode();
if (_appDomainDict.ContainsKey(hash_name))
{
AppDomain.Unload(_appDomainDict[hash_name]);
_appDomainDict.Remove(hash_name);
}
AppDomain new_domain = AppDomain.CreateDomain(hash_name);
_appDomainDict[hash_name] = new_domain;
var compilation = CSharpCompilation.Create(hash_name)
.WithOptions(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
allowUnsafe:true))
.AddReferences(refs.ToArray())
.AddSyntaxTrees(tree);
MemoryStream ms = new MemoryStream();
EmitResult compilationResult = compilation.Emit(ms);
ms.Seek(0, SeekOrigin.Begin);
if (compilationResult.Success)
{
// Load the assembly
Assembly asm = new_domain.Load(ms.ToArray());
object main_ob = asm.CreateInstance("SomeClass");
ms.Close();
return main_ob;
}
else
{
foreach (Diagnostic codeIssue in compilationResult.Diagnostics)
{
string issue = $"ID: {codeIssue.Id}, Message: {codeIssue.GetMessage()}," +
$" Location: { codeIssue.Location.GetLineSpan()}," +
$" Severity: { codeIssue.Severity}";
Callbacks.Logger.Log(typeof(NbScriptCompiler), issue, LogVerbosityLevel.WARNING);
}
return null;
}
}
}
}
Its all good when I am trying load the assembly in the current domain and execute from the instantiated object. The problem with this case is that since I wanna do frequent updates to the code, even if I make sure that the assembly names are different. I'll end up loading a ton of unused assemblies to the current domain.
This is why I've been trying to create a new domain and load the assembly there. But for some reason I get a platform not supported exception. Is this not possible to do in NET 5? Are there any workarounds or am I doing something wrong here.
Ok, it turns out that AppDomain support for NET Core + is very limited and in particular there seems to be only one appdomain
On .NET Core, the AppDomain implementation is limited by design and
does not provide isolation, unloading, or security boundaries. For
.NET Core, there is exactly one AppDomain. Isolation and unloading are
provided through AssemblyLoadContext. Security boundaries should be
provided by process boundaries and appropriate remoting techniques.
Source: https://learn.microsoft.com/en-us/dotnet/api/system.appdomain?view=net-6.0
And indeed, when trying to use AssemblyLoadContext and create object instances through these contexts everything worked like a charm!
One last note is that if the created context is not marked as collectible, its not possible to unload it. But this can be very easily set during AssemblyLoadContext construction.
I am getting a classic well-known error in my Springboot app when it tries to load a static resource using getResourceAsStream():
java.io.FileNotFoundException: class path resource [blah blah blah] cannot be opened because it does not exist
While doing some basic troubleshooting, I opened the contents of the generated jar file and found that everything in the jar is contained inside a folder named BOOT-INF.
Why is everything inside BOOT-INF?
How do I get rid of this?
UPDATE
Here's the actual relevant part of the code
ClassPathResource cpr = new ClassPathResource(RESOURCE_CLASSPATH_PREFIX + url);
try (InputStream is = cpr.getInputStream()) {
return DigestUtils.sha1Hex(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
Additional Clue
The failing code is inside a ServletFilter. I think this has something to do with class loaders.
I'm trying to use WriteBufferAsync in the Microsoft example for FileIO.WriteBufferAsync but GetBufferFromString doesn't compile.
Ultimately, I want to write a byte buffer to an absolute file path.
This is a copy from the example...
try
{
if (file != null)
{
IBuffer buffer = GetBufferFromString("Swift as a shadow");
await FileIO.WriteBufferAsync(file, buffer);
// Perform additional tasks after file is written
}
}
// Handle errors with catch blocks
catch (FileNotFoundException)
{
// For example, handle file not found
}
GetBufferFromString doesn't compile.
#Raymond Chen's comments are very convincing. And he is the author of the UWP official code sample. The reason why GetBufferFromString could not be compiled is you have not declared it.
private IBuffer GetBufferFromString(String str)
{
using (InMemoryRandomAccessStream memoryStream = new InMemoryRandomAccessStream())
{
using (DataWriter dataWriter = new DataWriter(memoryStream))
{
dataWriter.WriteString(str);
return dataWriter.DetachBuffer();
}
}
}
I want to write a byte buffer to an absolute file path.
For writing a buffer to an absolute file path, you could use PathIO.WriteBufferAsync method. Please note, you need make sure your file could be accessed within uwp. for example, if your file stored in picture library, you need add Picture capability. for more detail please refer UWP file access permissions.
Below is the code I used to access the asset file for a metro app I am working on.
async void readFileFromDisk (string fileName, string fileType)
{
string fileContent;
StorageFile file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(fileName);
using (IRandomAccessStream readStream = await file.OpenAsync(FileAccessMode.Read))
{
using (DataReader dataReader = new DataReader(readStream))
{
UInt32 numBytesLoaded = await dataReader.LoadAsync((UInt32)readStream.Size);
fileContent = dataReader.ReadString(numBytesLoaded);
}
}
This code is run in the handler for Loaded event for the page. I am currently getting an exception saying "Value does not fall in range". The error occurs at the first line itself, where I try to get storagefile handle from the installation folder.
On debugging, the fileName string comes out to be Null. I guess, I should be moving the code to some event which is fired at a later stage in page lifecycle, but can't seem to figure out what is the best place to do it. Suggestions??
P.S. I need to read this file before any interaction from user, as it reads the data for the level, that user will be interacting with.
Edit:
Missed a couple things.
The below function is called from the handler for loaded event.
void Game_Loaded(object sender, RoutedEventArgs e)
{
//read all level files to the strings
readFileFromDisk("//Assets/Levels/Start" + selectedLevel + ".txt", "Start");
This handler basically calls above function for different file paths, in similar manner. The string selected level is static variable, while the fileName string is created from the same.
Edit 2:
Found the issue, but solution is still far away. The return type of readFileFromDist method is causing trouble. Changed it to Task, and this part works fine, but I get "Object reference not set to an instance" error. Tried to convert Game_Loaded event handler to async too, to use await operators, but that gives me compiler error for "wrong return type".
SO, I tried removing async completely, but I guess I can't do that. There is no way to open files without using async function. So, I essentially need a way to call the readFileFromDisk function, using await, and continue with rest of the code execution once the task is completed. Something like, "IsCompleted" event for the awaited calls for the function.
Solved! Needed to use "ms:appx///Assets/filename.txt" instead of "//Assets/filename.txt".
I'm at my wits end. I keep getting this exception with HtmlAgilityPack whenever I attempt to select nodes using XPath. The problem remains even if I start a brand new solution with just this example (so it's not a problem with my application code interfering somehow. There's nothing wrong with the webpage, or my internet connection or anything like that. I know I've had this working before. I even suspected a corrupted dll somehow and re-downloaded it, but to no avail. Any ideas??
using System;
using HtmlAgilityPack;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var web = new HtmlWeb();
var doc = web.Load("http://www.google.com");
var root = doc.DocumentNode;
var links = root.SelectNodes("//a");
// Error! ArgumentOutOfRangeException: Index was out of range.
// Must be non-negative and less than the size of the collection.
}
}
}
/*
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.ThrowHelper.ThrowArgumentOutOfRangeException()
at HtmlAgilityPack.HtmlNodeNavigator.MoveToFirstChild()
at MS.Internal.Xml.XPath.XPathDescendantIterator.MoveNext()
at MS.Internal.Xml.XPath.DescendantQuery.Advance()
at MS.Internal.Xml.XPath.XPathSelectionIterator.MoveNext()
at HtmlAgilityPack.HtmlNode.SelectNodes(String xpath)
at ConsoleApplication1.Program.Main(String[] args)
*/
edit:
Well...I'm not sure how it happened or what it did, but I found the .dll installed in a GAC_MSIL folder. Removing that immediately resolved the issue. So, nevermind!