Avatar billede mr-kill Nybegynder
20. juli 2009 - 14:29 Der er 15 kommentarer og
2 løsninger

Reflection - load og unload af dll

Jeg har brug for at mit program loader og unloader en dll runtime. Der skal kaldes 2 static metoder i dll-filen og så skal den unloades så den kan slettes.

Jeg har skrevet dette og det virker fint til at loade filen og kalde metoderne, men jeg kan ikke finde ud af at unloade den igen. Jeg har læst mig frem til at man skal loade filen ind i et andet AppDomain, men jeg kan ikke findes ud af gøre det.

Nogen der kan strikke noget sammen der loader filen og kalder de 2 metoder og unloader den igen?

--- main ---
Assembly asm;

asm = Assembly.LoadFile(dllPath);

Type t = asm.GetType("XML_Updater.Updater");

MethodInfo methodInfo = t.GetMethod("UpdateXml");
string newVersion = (string)methodInfo.Invoke(null, new object[] { getRegValue("Version") });

MethodInfo methodInfo2 = t.GetMethod("FilesInVersion");
string[] filesToBackup = (string[])methodInfo2.Invoke(null, new object[] { newVersion });

--- end main ---

--- dll ---

namespace XML_Updater
{
    public static class Updater
    {
        public static string[] FilesInVersion(string version)
        {
            //do stuff
        }
    public static string UpdateXml(string version)
        {
            //do stuff
        }
    }
}

--- end dll ---

"dllPath" indeholder den funde sti til dll
Avatar billede arne_v Ekspert
20. juli 2009 - 15:10 #1
En gammel code snippet som du kan studere:

using System;
using System.Reflection;
using System.Diagnostics;

namespace E
{
    public class MainClass
    {
        public static void LoadEvent(object sender, AssemblyLoadEventArgs args)
        {
            Console.WriteLine("Load : " + ((AppDomain)sender).FriendlyName + " <- " + args.LoadedAssembly.FullName);
        }
        public static void Dump(AppDomain d)
        {
            Console.WriteLine(d.FriendlyName + " contains:");
            foreach(Assembly a in d.GetAssemblies())
            {
                Console.WriteLine("-" + a.FullName);
            }
        }
        public static void Test(string fnm)
        {
            AppDomain d = AppDomain.CreateDomain("MyDom");
            //d.AssemblyLoad += new AssemblyLoadEventHandler(LoadEvent); // causes MyDom to load loader.exe - comment out to avoid
            object x  = d.CreateInstanceAndUnwrap(fnm, "X");
            Console.WriteLine(x);
            Console.WriteLine(x);
            Console.WriteLine(x);
            Dump(AppDomain.CurrentDomain);
            //Dump(d); // causes loader.exe to load x*.dll - comment out to avoid
            AppDomain.Unload(d);
        }
        public static void Main(string[] args)
        {
            AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(LoadEvent);
            Test("x1");
            Test("x2");
            Test("x1");
            Test("x2");
        }
    }
}
Avatar billede mr-kill Nybegynder
20. juli 2009 - 16:50 #2
Kan ikke lige gennemskue det. Hvis jeg skriver:

Test(Directory.GetCurrentDirectory() + @"\XML Updater.dll");

i main så får jeg denne fejl:

Filen eller assemblyen eller en af dens afhængigheder kunne ikke indlæses. Det angivne assemblynavn eller den angivne kodebase var ugyldig. (Undtagelse fra HRESULT: 0x80131047)

Kan du poste nogle links jeg kan læse for at forstå det, eller lave det så det passer til den opstilling jeg skrev?

Jeg er heller ikke sikkert på hvordan jeg kalder de metoder jeg gerne vil kalde på den måde du skrev
Avatar billede arne_v Ekspert
20. juli 2009 - 18:23 #3
Du skal ikke bruge Test metoden - den er specifik for min demp app.

Du skal kigeg paa indholdet af den:

AppDomain d = AppDomain.CreateDomain("MyDom");

laver domaene

d.CreateInstanceAndUnwrap(dllnam, typnam)

laver en instans af typen i DLL'en

AppDomain.Unload(d);

smider det hele ud
Avatar billede mr-kill Nybegynder
20. juli 2009 - 18:57 #4
Så jeg skriver:

d.CreateInstanceAndUnwrap(dllname, "XML_Updater.Updater");

så får jeg et object, men jeg har jo ikke en instans af den type det skal være?

Med den gamle metode bruger jeg:

asm.GetType("XML_Updater.Updater");

Til at finde typen, men det kan jeg ikke gøre nu?

Hvad kan jeg gøre med det object jeg får?

Jeg vil gerne kalde de metoder jeg skal bruge med det, men ved ikke lige hvordan.
Avatar billede arne_v Ekspert
20. juli 2009 - 19:04 #5
Saa skal du nok bruge>

Assembly asm = d.Load(asmname);
Avatar billede mr-kill Nybegynder
21. juli 2009 - 15:26 #6
Hvis jeg bruger:

Assembly asm = d.Load(asmname);

hvor skal jeg så skrive dll navn?

Jeg har prøvet:

asm = d.Load(loadRawFile(dllPath));

Det kan godt loade assembly, men den releaser ikke dll'en igen selvom jeg skriver:

AppDomain.Unload(d);

Når jeg til sidst prøver at slette den så siger den at den bliver brugt af en anden process
Avatar billede arne_v Ekspert
21. juli 2009 - 16:08 #7
Hvad laver loadRawFile ?

Docs paa Load er her:

http://msdn.microsoft.com/en-us/library/8wcywcds.aspx
Avatar billede mr-kill Nybegynder
21. juli 2009 - 16:14 #8
loadRawFile henter bare filens bytes:

static byte[] loadRawFile(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;
        }

Der står at man skal bruge "The display name of the assembly" i Load, men jeg skal jo på en eller anden måde ha fortalt hvor filen ligger før jeg bare kan skrive assembly name?
Avatar billede bvli Praktikant
21. juli 2009 - 17:42 #9
Du skal bruge Assembly.LoadFrom(string fileName).

Lav en (internal) innerclass som du bruger til at loade de øvrige assemblies.

F.eks. du laver en internal klasse AssemblyLoader med en instansmetode LoadAssemblies(string assemblyFileNames)

Og så i din kode i dit main appdomain:

AppDomain domain = AppDomain.CreateDomain("Assembly Loader Domain");
AssemblyLoader loader = (AssemblyLoader)domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(AssemblyLoader).FullName);
loader.LoadAssemblies(assemblyFileNames);
AppDomain.Unload(doain);


class AssemblyLoader {
  public void LoadAssemblies(string[] assemblyNames) {
    foreach (string assemblyName in assemblyNames) {
      if (File.Exists(assemblyName)) {
        Assembly ass = null;
        try {
          ass = Assembly.LoadFrom(assemblyName);
          //Gør hva' du nu skal..
        } catch (BadImageException bie) {
          Trace.WriteLine("hrmpf: " + bie);
          continue;
        }
      }
    }
  }
}

Eller sånoget lignende..

(ikke testet/compilet)
Avatar billede arne_v Ekspert
22. juli 2009 - 04:54 #10
Hvis assembly ikke ligger et sted hvor CLR vil lede efter den skal du nok bruge en anden Load.

Den udfra byte[] bør være fin nok.

Hvis der ikke er noget som helst som referer til den assembly burde AppDomain Unload smide den ud.

Og at den bliver brugt af en anden process skylde jo ikke den her kode. AppDomain og CLR aner intet om filen. Det er kun din loadRawFile som ved at de bytes kommer fra den fil og den lukker filen igen.

Det er noget andet som holder den fil åben !
Avatar billede mr-kill Nybegynder
22. juli 2009 - 23:56 #11
bvli >>

Jeg har prøvet at skrive dette og så ændre lidt i din AssemblyLoader klasse, men nu får jeg en SerializationException. Jeg er ikke så sejl til reflection, så er lidt på bar bund igen.

AppDomain domain = AppDomain.CreateDomain("Assembly Loader Domain");

AssemblyLoader loader = (AssemblyLoader)domain.CreateInstanceFromAndUnwrap( //<---
  • SerializationException[*]
  •     Assembly.GetExecutingAssembly().Location,
        typeof(AssemblyLoader).FullName);

    string newVersion;
    if (!loader.LoadAssembly(dllPath, out filesToBackup, out newVersion))
    {
        return "";
    }

    AppDomain.Unload(domain);


    arne_v >>

    Tænkte også at der måtte være noget andet der holder filen åben, men hvis jeg ikke kalder den metode der læser fra dll filen, så virker det. Den eneste gang metoden bruger filen er i den loadRawFile og den skulle gerne lukke filen igen.

    Så ved ikke om det kan lade sig gøre at det er noget med de assembly?
    Avatar billede bvli Praktikant
    23. juli 2009 - 00:27 #12
    Høh.. Det er lidt svært at se hvorfor den smider den, hvis ikke man kan se hvordan den nye version af AssemblyLoader ser ud :)

    Husk når man kommunikerer mellem to forskellige AppDomains så bruges remoting. Så enten skal din klasse være serialiserbar eller også skal den nedarve fra MarshalByRefObject.

    I øvrigt har Arne ret - noget andet må holde på dit assembly. Har du prøvet at sætte shadowCopy til true på det nye appdomain? Så vil runtime'n ihvertfald ikke holde på dit assembly.
    Avatar billede mr-kill Nybegynder
    23. juli 2009 - 00:57 #13
    Jeg har denne metode jeg kalder for at bruge dll'en:

    static string updateXML(out string[] filesToBackup)
    {
        filesToBackup = new string[0];

        AppDomainSetup setup = new AppDomainSetup();
        setup.ShadowCopyFiles = "true";
        AppDomain domain = AppDomain.CreateDomain("Assembly Loader Domain", null, setup);

        AssemblyLoader loader = (AssemblyLoader)domain.CreateInstanceFromAndUnwrap(
            Assembly.GetExecutingAssembly().Location,
            typeof(AssemblyLoader).FullName);

        string newVersion;
        if (!loader.LoadAssembly(dllPath, out filesToBackup, out newVersion))
        {
            return "";
        }

        AppDomain.Unload(domain);

        return newVersion;
    }

    og så denne innerclass.

    [Serializable]
    internal class AssemblyLoader
    {
        public bool LoadAssembly(string assemblyName,
            out string[] filesToBackup,
            out string newVersion)
        {

            filesToBackup = new string[0];
            newVersion = "";
           
            if (!File.Exists(assemblyName))
            {
                Console.WriteLine("FEJL 33!");
                return false;
            }
               
            Assembly ass = null;
            try
            {
                ass = Assembly.LoadFrom(assemblyName);

                Type t = ass.GetType("XML_Updater.Updater");

                MethodInfo methodInfo = t.GetMethod("UpdateXml");

                newVersion = (string)methodInfo.Invoke(null, new object[] { getRegValue("Version") });

                MethodInfo fieldMethodInfo = t.GetMethod("FilesInVersion");
                filesToBackup = (string[])fieldMethodInfo.Invoke(null, new object[] { newVersion });

            }
            catch
            {
                Console.WriteLine("FEJL 34!");
                return false;
            }

            return true;
           
        }
    }

    Jeg fik løst SerializationException ved at sætte [Serializable] over den. Nu kalder den igen dll'en som den skal, men filen er stadig låst når jeg vil slette den. Hvis jeg ikke kalder 'updateXML' fra main så er filen ikke låst til sidst, så det er helt sikkert en af disse 2 metoder der låser den? Eller har jeg overset noget?
    Avatar billede arne_v Ekspert
    26. juli 2009 - 04:35 #14
    Der må være noget andet som bliver loadet "normalt" fra den DLL.

    Prøv og rename den fra .dll til .notdll lige inden du kører - loadRawFile er ligeglad med navnet  men anden kode bør ikke kunde finde den så.
    Avatar billede mr-kill Nybegynder
    03. august 2009 - 19:54 #15
    Jeg fik det aldrig til at virke helt.. nogen gange virkede det og andre gange ikke.. har rodet med det alt for meget nu, finder på en anden løsning, lav et svar hvis i føler for point (bvli og arne_v)

    Tak fordi i forsøgte :)
    Avatar billede arne_v Ekspert
    03. august 2009 - 21:43 #16
    ok
    Avatar billede mr-kill Nybegynder
    07. august 2009 - 12:15 #17
    Giver ½ point til arne_v.

    bvli >> Hvis du vil ha dine point, så skrive så laver jeg en ny tråd.
    Avatar billede Ny bruger Nybegynder

    Din løsning...

    Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

    Loading billede Opret Preview

    Log ind eller opret profil

    Hov!

    For at kunne deltage på Computerworld Eksperten skal du være logget ind.

    Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

    Du kan også logge ind via nedenstående tjenester