Hallo
Ich rufe über einen Multimedia Timer Thread einen externe Dll Funktion auf.
Die dll wird dynamisch gepuffert geladen alls byte array. Normalerweise nur einmal und dann wird zyklisch die Funktion aufgerufen.
An die Funktion wird eine Ilist übergeben in dem die Funktion Klassen ablegen kann um sie beim nächsten aufruf wieder verwenden zu können. In diesem fall eine Comport Verbindung.
Dann beende ich denn Timer und starte den Timer neu und ich bekomme eine exeption das die gespeicherte Klasse von einer anderen dll erzeugt wurde! Obwohl die dll gleich geblieben ist. Sie wurde auch nich neu geladen.. Nur der timer hat warscheinlich einen neuen thread gestartet für die neuen cyclischen aufrufe. Danke .was kann ich ändern um das problem zu umgehen
Die
Es hilft potentiellen Helfern ungemein, wenn Du ein wenig Code zur Verfügung stellst.
Die Wahrheit liegt nun mal im Code und weniger in der Beschreibung, was Du denkst, was Du tust 😉
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Hier der gekürzte Code. Die Timerklasse habe ich als Datei angehängt.
static byte[] loadFile(string filename)
{
FileStream fs = new FileStream(filename, FileMode.Open);
byte[] buffer = new byte[(int)fs.Length];
fs.Read(buffer, 0, buffer.Length);
fs.Close();
return buffer;
}
private void LoadAssembly(string EntryPointName)
{
Assembly asm = Assembly.Load(loadFile(@"C:\Users\User\Documents\RoslynPad\SPS.dll"));//LoadFrom(@"C:\Users\User\Documents\RoslynPad\SPS.dll");
if (asm == null) { throw new Exception("Can't load SPS.Dll"); }
Type t = asm.GetType("Program");
if (t == null) { throw new Exception("No Program Class exist in SPS.Dll"); }
if (EntryPointName == "") { EntryPointName = "EntryPointSPS"; }
methodInfoStatic = t.GetMethod(EntryPointName);
if (methodInfoStatic == null) { throw new Exception("No EntryPoint method '" + EntryPointName + "'exist in SPS.Dll"); }
}
private void StartTimer(string EntryPointName)
{
LoadAssembly(EntryPointName);
_SPSTimer = new StyroCut.CNC.Timer();
_SPSTimer.Started += _SPSTimer_Started;
_SPSTimer.Stopped += _SPSTimer_Stopped;
this._SPSTimer.Mode = TimerMode.Periodic;
this._SPSTimer.Period = Settings.Default.SPSCycleTime;
this._SPSTimer.Resolution = 5;
this._SPSTimer.SynchronizingObject = null;
this._SPSTimer.Tick += new EventHandler(TimerTick);
this._SPSTimer.Start();
}
private void TimerTick(object sender, System.EventArgs e)
{
SPSCycle(this._ICNC);
}
private void SPSCycle(ICNC CNC)
{
object[] staticParameters = new object[2];
staticParameters[0] = (ISPSInt)CNC.SPS;
staticParameters[1] = CNC;
methodInfoStatic.Invoke(null, staticParameters);
}
code der externen dll
public static void EntryPointSPS(ISPSInt SPS, ICNC CNC)
{
try
{
SPSModbus Modbus = (SPSModbus)SPS.DataObject.GetOrAdd("Modbus", (a) => new SPSModbus(SPS));
if (Modbus.connected == false) { Modbus.Connect(115200, 1); }
// Modbus.ReadRegister();
SPS.OutDataInt.SetValue("PINPWM3",0);
SPS.OutDataInt.SetValue("PINPWM5",0);
SPS.OutDataInt.SetValue("PINPWM6",0);
SPS.OutDataInt.SetValue("PINPWM9",0);
SPS.OutDataInt.SetValue("PINPWM10",0);
SPS.OutDataInt.SetValue("PINPWM11",0);
// Modbus.WriteRegister();
}
catch (Exception ex)
{
SPS.RaiseEventandAbort(new SPSExeption("SPS Main Error", ex));
}
}
hier die exeption> Fehlermeldung:
[A]SPSModbus kann nicht in **SPSModbus umgewandelt werden. Der Typ "A" stammt von der "SPS, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" im Kontext "LoadNeither" in einem Bytearray.. Der Typ "B" stammt von der "SPS, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" im Kontext "LoadNeither" in einem Bytearray..[/error]
Bitte benutze die richtigen Code-Tags [Hinweis] Wie poste ich richtig?
Ich habe noch rausgefunden das wenn ich das Assembly so lade
Assembly asm = Assembly.LoadFrom(@"C:\Users\user\Documents\RoslynPad\SPS.dll");
es funktioniert.
Ich würde aber gerne das Assembly so laden das ich die Datei zwischen den Timer Zyklen überschreiben kann und das geht so nicht da sie nur einmal in denn Speicher geladen wird.
Hi,
nun - eine Assembly die geladen wurde kann man meines Wissens nicht mehr entladen - die einzige Variante ist die AppDomain komplett zu entladen.
Hier kannst du nachschauen wie das geht: https://stackoverflow.com/questions/4887847/hot-unload-reload-of-a-dll-used-by-an-application
LG
Edit: Meint - für diese Assembly eine eigene AppDomain erstellen - und diese dann eben ggf. entladen. (sofern das hinsichtlich der Performance ok ist)
Und warum das mit Assembly.LoadFrom
funktioniert ist sogar dokumentiert
If an assembly with the same identity is already loaded, LoadFrom returns the loaded assembly even if a different path was specified.
Was macht Assembly.Load
anders?
Note that this method overload always creates a new Assembly object with its own mapping.
Noch eine Frage.
Ich habe dann die Möglichkeit eine Appdomain zu machen mit denn Nachteil das alle Objekte von MarshalByRefObject erben müssen.
Oder kann ich auch die Scripting Api von Roslyn verwenden? Weiß irgendeiner ob ich dort Objecte auf Referenz hin und herschieben kann?
Da sollte eigentlich alles zu dem Thema stehen was du brauchst.
"LoadFile() doesn’t bind through Fusion at all – the loader just goes ahead and loads exactly* what the caller requested. It doesn’t use either the Load or the LoadFrom context."
@Gerry:
Nein - dein AssemblyLoader soll von MarshalByRefObject erben - der eigentliche Typ muss das nicht.
Könnte dann z.B. so aussehen:
class Program
{
static void Main(string[] args)
{
for (var i = 0; i < 10; i++)
{
var domain = AppDomain.CreateDomain("my-temp-domain", new Evidence(), AppDomain.CurrentDomain.BaseDirectory,
AppDomain.CurrentDomain.RelativeSearchPath, false);
var loader = (SimpleAssemblyLoader) domain.CreateInstanceAndUnwrap(typeof(SimpleAssemblyLoader).Assembly.FullName,
typeof(SimpleAssemblyLoader).FullName);
var assembly = loader.Load(path: @"C:\dev\BitBucket\Trash\Trash\ConsoleApp3\bin\Debug\ClassLibrary2.dll");
var type = assembly.GetType(name: "ClassLibrary2.Test");
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("TestMethod");
var result = methodInfo.Invoke(instance, null);
AppDomain.Unload(domain);
}
}
}
public class SimpleAssemblyLoader : MarshalByRefObject
{
public Assembly Load(string path)
{
ValidatePath(path);
return Assembly.LoadFile(path);
}
private void ValidatePath(string path)
{
if (path == null) throw new ArgumentNullException(nameof(path));
if (!System.IO.File.Exists(path))
throw new ArgumentException($"path \"{path}\" does not exist");
}
}
namespace ClassLibrary2
{
public class Test
{
public string TestMethod()
{
return $"{GetType().FullName}";
}
}
}