.net: загрузка зависимостей для DLL отличается от загрузки для EXE?

У меня очень странная проблема. Я сделал несколько очень сумасшедших вещей: я преобразовал жирный uber-jar библиотек hadoop, которые я собрал с плагином sbt-assembly, в dll, используя IKVM. Я написал небольшую тестовую программу, которая сводится к следующему:

var u = new java.net.URI("hdfs://my-namenode:8020/");
var fs = org.apache.hadoop.fs.FileSystem.get(u, new org.apache.hadoop.conf.Configuration());
foreach(var s in fs.listStatus(new org.apache.hadoop.fs.Path("/"))) {
    Console.WriteLine(s.getPath().toString());
}

Когда я запускаю это в консольном приложении с моим hadoop.dll и требуемыми библиотеками IKVM, добавленными в качестве ссылок, это перечисляет содержимое моей HDFS.

Однако, когда я помещаю именно этот код в DLL, добавляю те же зависимости в эту DLL и вызываю это из моего консольного приложения, я получаю:

No FileSystem for scheme: hdfs

Когда я указываю правильное имя класса в моей конфигурации Hadoop через fs.hdfs.impl ключ, я получаю ClassNotFoundException,

Разрешены ли зависимости в исполняемых файлах по-разному, чем в DLL, или это может быть специфическое поведение IKVM?

РЕДАКТИРОВАТЬ: еще одно странное поведение: когда я создаю FileSystem Один раз в моем консольном приложении и затем вызывает этот метод в DLL, он запускается.

1 ответ

Решение

Я нашел ответ сам (снова...)

Он не должен делать то, как.net обрабатывает загрузку зависимостей, но это то, как IKVM (и в этом отношении Java) обрабатывает динамическую загрузку классов.

Я покопался в исходном коде Hadoop и нашел следующий бит:

private ClassLoader classLoader;
{
  classLoader = Thread.currentThread().getContextClassLoader();
  if (classLoader == null) {
    classLoader = Configuration.class.getClassLoader();
  }
}

Линия classLoader = Thread.currentThread().getContextClassLoader(); здесь особый интерес Загрузчик класса контекста моего консольного приложения является его контекстом без ссылки на какой-либо из классов Hadoop, поэтому ClassNotFoundException когда явно fs.hdfs.impl в org.apache.hadoop.hdfs.DistributedFileSystem,

К счастью, Configuration у класса есть метод setClassLoader, поэтому при выполнении этого при создании конфигурации:

var conf = new org.apache.hadoop.conf.Configuration();
conf.setClassLoader(conf.getClass().getClassLoader());
conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");

оно работает! Это потому что conf.getClass().getClassLoader() возвращает загрузчик классов confконтекст - то есть hadoop.dll Переделанный Uber-Jar, который имеет класс.

Все еще необходимо явно указывать классы файловой системы с fs.XXXX.impl хотя, потому что механизм автоматического разрешения файловой системы выглядит так:

private static void loadFileSystems() {
  synchronized (FileSystem.class) {
    if (!FILE_SYSTEMS_LOADED) {
      ServiceLoader<FileSystem> serviceLoader = ServiceLoader.load(FileSystem.class);
      for (FileSystem fs : serviceLoader) {
        SERVICE_FILE_SYSTEMS.put(fs.getScheme(), fs.getClass());
      }
      FILE_SYSTEMS_LOADED = true;
    }
  }

Как вы можете видеть, файловые системы разрешаются здесь:

ServiceLoader<FileSystem> serviceLoader = ServiceLoader.load(FileSystem.class);

этот метод использует Thread.currentThread().getContextClassLoader() опять же, это означает, что мое консольное приложение не имеет классов hadoop.

Итак, tl;dr: после создания Configuration, установите его ClassLoader вручную на загрузчик класса контекста DLL.

Другие вопросы по тегам