[C#/.NET] Assemblies automatisch references werkt niet

Pagina: 1
Acties:

Onderwerpen


  • MikevanEngelen
  • Registratie: Mei 2001
  • Laatst online: 14-09 15:22
Beste GoT-ers,

Ik ben ik nu al twee dagen mee aan het prutsen en ben de visie een beetje kwijt.
Wellicht kunnen jullie me op weg helpen...

We hebben in onze solution twee projecten staan, eentje heet "Operations" en de ander genaam "Processing Engine".
De "Processing Engine" genereert (/compiled & execute) voor ons source code en gebruikt daarbij de call's vanuit de classes die staan gedefinieerd in het project "Operations".

Sinds kort hebben we van alle .cs files die in het "Operations" project stonden .dll files gegenereerd zodat dit meer flexibiliteit geeft bij het bijwerken van functionaliteit (de applicatie hoeft niet offline). In tegenstelling als je een grote assembly hebt (operations.dll) die je niet kan overschrijven tijdens het draaien van de "Processing Engine".

Goed nu het probleem; sinds de DLL opsplitsing proberen we de "Processing Engine" de operaties weer te laten vinden, maar dit wil maar niet lukken.

We gebruiken hier het volgende stukje source code voor:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
        StringCollection Assemblies = new StringCollection();

        public Compiler()
        {
            cparams = new CompilerParameters();
            cparams.CompilerOptions = "/target:library /optimize";
            cparams.GenerateExecutable = false;
            cparams.GenerateInMemory = true;
            cparams.IncludeDebugInformation = true;

            Assemblies = cparams.ReferencedAssemblies;
            
            // Standard assemblies we always need to reference.
            Assemblies.Add("System.dll");

            // Base assemblies which the operations will use.
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Oracle.DataAccess.dll");
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Abstract.dll");
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Base.dll");

            // Adding the operations as assemblies.
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Operation1.dll");
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Operation2.dll");
            
            // Add the Processing Engine itself to the assemblies.
            Assemblies.Add(Assembly.GetEntryAssembly().Location);
        }
        
        // Input for this function is the generated source code, coming from earlier steps
        public Assembly CreateAssembly(string code)
        {
            Debug.WriteLine("CreateAssembly() called.");
            Dictionary<string, string> compilerOptions = new Dictionary<string, string>();
            compilerOptions.Add("CompilerVersion", "v2.0");
            CodeDomProvider compiler = new CSharpCodeProvider(compilerOptions);
            CompilerResults cresults = compiler.CompileAssemblyFromSource(cparams, new[] { code });

            _resultedAssembly = cresults.CompiledAssembly;
            return cresults.CompiledAssembly;
        }
        
        // This function will create a .cs file on a temporary location and execute this by the C# Compiler.
        public bool ExecuteCode(string code)
        {
            CreateAssembly(code);
            Assembly assembly = _resultedAssembly;
            Module[] mods = assembly.GetModules(false);
            Type[] types = mods[0].GetTypes();

            foreach (Type type in types)
            {
                BindingFlags flgs = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
                MethodInfo mi = type.GetMethod("Main", flgs);
                if (mi != null)
                {
                    // Execute the the generated code.
                    mi.Invoke(null, null);

                    return true;
                }
            }
            return true;
        }


Op het moment dat nu de mi.Invoke word uitgevoerd (dus het tijdelijk gegenereerde .cs filetje), krijgen we foutmelding als onderstaand:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BindingFailure was detected
Message: The assembly with display name 'Operation1' failed to load in the 'LoadFrom' binding context of the AppDomain with ID 1. The cause of the failure was: System.IO.FileNotFoundException: Could not load file or assembly 'LookupExchangeRateTransactionCurrency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
File name: 'LookupExchangeRateTransactionCurrency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'

=== Pre-bind state information ===
LOG: User = GLOBAL\EngeleM
LOG: DisplayName = Operation1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = file:///D:/projects/<project>/Engine/Trunk 6.0.2/SAI/Engine/bin/Debug/
LOG: Initial PrivatePath = NULL
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: D:\projects\<project>\Engine\Trunk 6.0.2\SAI\Engine\bin\Debug\Engine.vshost.exe.config
LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///D:/projects/<project>/Engine/Trunk 6.0.2/SAI/Engine/bin/Debug/Operation1.DLL.


Het lijkt wel alsof hij de DLL niet kan vinden, of op een andere lokatie zoekt.
De DLL's staan niet in de GAC en dat wil ik eigelijk ook zo houden omdat ze verder niet door andere applicaties worden gebruikt.

Hebben jullie nog suggesties die ik nog kan bekijken, want ik ben inmiddels wel het spoor bijster.

- Mike

  • whoami
  • Registratie: December 2000
  • Laatst online: 01:56
MikevanEngelen schreef op donderdag 26 februari 2009 @ 19:56:

We hebben in onze solution twee projecten staan, eentje heet "Operations" en de ander genaam "Processing Engine".
De "Processing Engine" genereert (/compiled & execute) voor ons source code en gebruikt daarbij de call's vanuit de classes die staan gedefinieerd in het project "Operations".

Sinds kort hebben we van alle .cs files die in het "Operations" project stonden .dll files gegenereerd zodat dit meer flexibiliteit geeft bij het bijwerken van functionaliteit (de applicatie hoeft niet offline). In tegenstelling als je een grote assembly hebt (operations.dll) die je niet kan overschrijven tijdens het draaien van de "Processing Engine".
Als ik het goed begrijp, pas je dus (in productie) je applicatie aan door source-files 'ergens' te dumpen, en je processing engine compileert dit dan ?
Ik vraag me af of dit wel zo'n goed idee is ...
Je kon dit ook opgelost hebben door je operations dll in een ander appdomain in te laden, en een filewatcher plaatsen op de directory waar die operations.dll staat. Van zodra je de operations.dll overschrijft, kan je dan het appdomain unloaden, de operations.dll kopieren naar een werk-directory, en 'm dan vanop die locatie in een nieuw appdomain inladen. Een beetje zoals NUnit ook werkt.
soit.
Goed nu het probleem; sinds de DLL opsplitsing proberen we de "Processing Engine" de operaties weer te laten vinden, maar dit wil maar niet lukken.

We gebruiken hier het volgende stukje source code voor:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
        StringCollection Assemblies = new StringCollection();

        public Compiler()
        {
            cparams = new CompilerParameters();
            cparams.CompilerOptions = "/target:library /optimize";
            cparams.GenerateExecutable = false;
            cparams.GenerateInMemory = true;
            cparams.IncludeDebugInformation = true;

            Assemblies = cparams.ReferencedAssemblies;
            
            // Standard assemblies we always need to reference.
            Assemblies.Add("System.dll");

            // Base assemblies which the operations will use.
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Oracle.DataAccess.dll");
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Abstract.dll");
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Base.dll");

            // Adding the operations as assemblies.
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Operation1.dll");
            Assemblies.Add(Path.GetDirectoryName(GetType().Assembly.Location) + "\\Operations\\" + "Operation2.dll");
            
            // Add the Processing Engine itself to the assemblies.
            Assemblies.Add(Assembly.GetEntryAssembly().Location);
        }
        
        // Input for this function is the generated source code, coming from earlier steps
        public Assembly CreateAssembly(string code)
        {
            Debug.WriteLine("CreateAssembly() called.");
            Dictionary<string, string> compilerOptions = new Dictionary<string, string>();
            compilerOptions.Add("CompilerVersion", "v2.0");
            CodeDomProvider compiler = new CSharpCodeProvider(compilerOptions);
            CompilerResults cresults = compiler.CompileAssemblyFromSource(cparams, new[] { code });

            _resultedAssembly = cresults.CompiledAssembly;
            return cresults.CompiledAssembly;
        }
        
        // This function will create a .cs file on a temporary location and execute this by the C# Compiler.
        public bool ExecuteCode(string code)
        {
            CreateAssembly(code);
            Assembly assembly = _resultedAssembly;
            Module[] mods = assembly.GetModules(false);
            Type[] types = mods[0].GetTypes();

            foreach (Type type in types)
            {
                BindingFlags flgs = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
                MethodInfo mi = type.GetMethod("Main", flgs);
                if (mi != null)
                {
                    // Execute the the generated code.
                    mi.Invoke(null, null);

                    return true;
                }
            }
            return true;
        }


Op het moment dat nu de mi.Invoke word uitgevoerd (dus het tijdelijk gegenereerde .cs filetje), krijgen we foutmelding als onderstaand:
[code]
BindingFailure was detected
Message: The assembly with display name 'Operation1' failed to load in the 'LoadFrom' binding context of the AppDomain with ID 1. The cause of the failure was: System.IO.FileNotFoundException: Could not load file or assembly 'LookupExchangeRateTransactionCurrency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
File name: 'LookupExchangeRateTransactionCurrency, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
Blijkbaar worden bepaalde assemblies niet gevonden, doordat ze in een plaats staan die niet onderzocht wordt door de runtime, zou ik denken.
Je zou kunnen gebruik maken de AssemblyResolve event van het AppDomain. Deze event wordt geraised wanneer een bepaalde assembly niet kon geladen worden.
In de eventhandler van die event zou je kunnen proberen om zelf de assembly te vinden & te laden.

https://fgheysels.github.io/


  • 4of9
  • Registratie: Maart 2000
  • Laatst online: 13-12-2024
je kunt de locatie van een assembly beinvloeden via de config file via de tag <codeBase>

of het searchpath van de CLR aanpassen via de tag <probing> in de config.

Aspirant Got Pappa Lid | De toekomst is niet meer wat het geweest is...


Acties:
  • 0 Henk 'm!

  • MikevanEngelen
  • Registratie: Mei 2001
  • Laatst online: 14-09 15:22
De IONotFound exception bleek wel degelijk van toepassing in ons geval.
Het bleek dat hij de betreffende DLL's wilde laden vanaf de Application Root Folder, terwijl wij dit hadden gecodeerd als Application Root Folder/Operations.

Het werkt nu! Bedankt voor jullie input!