Assembly Resolution nicht zu 100% funktional

  • C#
  • .NET 7–8

Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von siycah.

    Assembly Resolution nicht zu 100% funktional

    Moin zusammen,

    Ich habe die Auflösung von Dependencies (Assemblies) in meinem Projekt scheinbar so gut optimiert, dass sie zwar super schnell ist, allerdings dafür auch (scheinbar) nicht immer alle Assemblies in die Applikation lädt.
    Nun stehe ich zugegebenermaßen etwas auf dem Schlauch und sehe wahrscheinlich den Wald vor lauter Bäume nicht mehr.

    Etwas zum Hintergrund:
    Die Applikation ist hochmodular aufgebaut und kann zur Laufzeit Module ein-/entladen. Die Dependencies können allerdings in mehreren Ordnern zerstreut sein (ergibt sich leider aus den Abhängigkeiten zu IKVM und div. weiteren Sachen), sodass der Default-Resolver oftmals die benötigte Binary gar nicht findet und stattdessen die Applikation stirbt.

    Im Spoiler findet ihr den aktuellen Quellcode.
    Spoiler anzeigen

    C#-Quellcode

    1. private Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args) {
    2. ArgumentNullException.ThrowIfNull(sender);
    3. var folderPath = Path.GetDirectoryName(args.RequestingAssembly?.Location);
    4. if (string.IsNullOrEmpty(folderPath)) {
    5. folderPath = ConfigManager.Instance.GetPluginInstallDir().FullName;
    6. }
    7. var asmName = new AssemblyName(args.Name);
    8. if (folderPath is null || asmName.Name is null) {
    9. AppLogger?.Error("Could not find assembly {asmName} in {location}! Requested by {requesting}", asmName.Name, folderPath, args.RequestingAssembly?.GetName().Name);
    10. return default;
    11. }
    12. var loadDirInfo = new DirectoryInfo(folderPath);
    13. var rawPath = Path.Combine(folderPath, asmName.Name);
    14. var asmPath = rawPath + ".dll";
    15. Assembly? loadedAsm = default;
    16. if (!File.Exists(asmPath)) {
    17. AppLogger?.Warning("Could not find {asmPath}! Attempting brute-force load. This might take a while!", asmPath);
    18. foreach (var file in loadDirInfo.GetFiles().Where(f => f.Extension.ToLower().Equals(".dll", StringComparison.InvariantCultureIgnoreCase))) {
    19. if (file.Name.Contains(asmName.Name)) {
    20. loadedAsm = Assembly.Load(File.ReadAllBytes(file.FullName));
    21. break;
    22. }
    23. var stringContent = new string(File.ReadAllText(file.FullName).Where(c => char.IsAscii(c) || char.IsAsciiDigit(c) || char.IsWhiteSpace(c)).ToArray());
    24. if (stringContent.Contains(asmName.Name)) {
    25. loadedAsm = Assembly.Load(File.ReadAllBytes(file.FullName));
    26. if (loadedAsm.GetName().GetPublicKeyToken().SequenceEqual(asmName.GetPublicKeyToken())) {
    27. break;
    28. }
    29. }
    30. }
    31. goto Failure; // ignoriert mich, ich habe einen Sinn
    32. } else {
    33. loadedAsm = Assembly.Load(File.ReadAllBytes(asmPath));
    34. }
    35. return loadedAsm;
    36. Failure:
    37. AppLogger?.Error($"Failed to resolve assembly {args.Name}. Hermod may not work as expected.");
    38. return default;
    39. }


    Was mich dabei gerade wundert, ist folgendes:

    Sowohl durchs Besichtigen der Logmeldungen, als auch beim Durchsteppen der Anwendung kann ich sehen, dass die erforderlichen Bibliotheken gefunden und geladen(!) werden.



    Allerdings kommt dennoch folgender Fehler:

    Quellcode

    1. [21:09:13 DBG] [EmailIndexer] Initialising elasticsearch handler...
    2. [21:09:13 INF] [EmailIndexer] Connecting to and retrieving info from Elastic cluster...
    3. [21:09:14 ERR] [EmailIndexer] Failed to connect to Elasticsearch cluster! Error: Method not found: 'Void Elastic.Clients.Elasticsearch.ElasticsearchClientSettings..ctor(Elastic.Transport.NodePool)'.


    Vielleicht seht ihr ja, wo ich das verbaselt habe.

    EDIT: Um zu bestätigen, dass ich tatsächlich (trotz doppelter und dreifacher Prüfung) die richtige DLL lade, habe ich das Ding fix dekompiliert:

    Elastic.Transport.Dll enthält die NodePool.

    Und Elastic.Clients.Elasticsearch.dll enthält auch den nicht gefundenen Constructor.
    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Meine Firma: Procyon Systems

    Selbstständiger Softwareentwickler & IT-Techniker.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „siycah“ ()

    Hast du ein kleines Beispielprojekt? Ist schwer da was zu finden, für mich zumindest.

    Ansonsten ist es eventuell ein Problem mit dem Doppelladen? Nur als Idee:
    Loading multiple assemblies with the same identity without context can
    cause type identity problems similar to those caused by loading
    assemblies with the same identity into multiple contexts. See Avoid Loading an Assembly into Multiple Contexts.

    Best Practices for Assembly Loading
    @Bluespide Interessant ist ja, dass der alte Quellcode (bis auf ASP, aus mir unerklärlichen Gründen) funktioniert.

    C#-Quellcode

    1. private Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args) {
    2. var folderPath = Path.GetDirectoryName(args.RequestingAssembly?.Location);
    3. var asmName = new AssemblyName(args.Name);
    4. if (folderPath is null || asmName.Name is null) {
    5. AppLogger?.Error("Could not find assembly in {location}", folderPath);
    6. return default;
    7. }
    8. var loadDirInfo = new DirectoryInfo(folderPath);
    9. var rawPath = Path.Combine(folderPath, asmName.Name);
    10. var asmPath = rawPath + ".dll";
    11. Assembly? loadedAsm = default;
    12. if (!File.Exists(asmPath)) {
    13. AppLogger?.Warning("Could not find {asmPath}! Attempting brute-force load. This might take a while!", asmPath);
    14. foreach (var file in loadDirInfo.GetFiles().Where(f => f.Extension.ToLower().Equals(".dll", StringComparison.InvariantCultureIgnoreCase))) {
    15. if (file.Name.Contains(asmName.Name)) {
    16. loadedAsm = Assembly.LoadFrom(file.FullName);
    17. goto Resolved;
    18. }
    19. var stringContent = new string(File.ReadAllText(file.FullName).Where(c => char.IsAscii(c) || char.IsAsciiDigit(c) || char.IsWhiteSpace(c)).ToArray());
    20. if (stringContent.Contains(asmName.Name)) {
    21. loadedAsm = Assembly.LoadFrom(file.FullName);
    22. if (loadedAsm.GetName().GetPublicKeyToken().SequenceEqual(asmName.GetPublicKeyToken())) {
    23. goto Resolved;
    24. }
    25. }
    26. }
    27. goto Failure;
    28. } else {
    29. loadedAsm = Assembly.LoadFrom(asmPath);
    30. }
    31. Resolved:
    32. return loadedAsm;
    33. Failure:
    34. AppLogger?.Error($"Failed to resolve assembly {args.Name}. Hermod may not work as expected.");
    35. return default;
    36. }


    Mehrere Kontexte kann ich ausschließen, da alles in den Current AppDomain geladen wird. Anders kriege ich nicht die Geschwindigkeit fürs IPC hin.

    EDIT:
    Spoiler anzeigen

    Erwartetes Verhalten mit dem alten Quellcode:

    C#-Quellcode

    1. ​[19:26:34 DBG] [EmailIndexer] Initialising elasticsearch handler...
    2. hermod > [19:26:34 INF] [EmailIndexer] Connecting to and retrieving info from Elastic cluster...
    3. [19:26:34 INF] [EmailIndexer] Cluster status: Yellow (timed out? no)
    4. Cluster name:
    5. Active shards in cluster: 0 primary, 0 total, 0%
    6. Shards currently being initialised: 0
    7. Shards being relocated: 0
    8. Total number of nodes: 0 (0 data nodes)
    9. Pending tasks: 0
    10. API response valid: False
    11. Last server error:
    12. Server warnings: Elastic.Transport.Products.Elasticsearch.ElasticsearchResponse+<get_ElasticsearchWarnings>d__1
    13. Total indices: n/a
    14. hermod >

    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Meine Firma: Procyon Systems

    Selbstständiger Softwareentwickler & IT-Techniker.