.net:是DLL与EXE不同的依赖项加载

本文关键字:依赖 加载 EXE DLL net | 更新日期: 2023-09-27 18:19:49

我有一个非常奇怪的问题。我做了一些非常疯狂的事情:我使用IKVM将我用sbt汇编插件组装的一个庞大的hadoop库uber jar转换为dll。我写了一个小测试程序,它可以归结为以下内容:

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 dll被添加为引用,它列出了我的HDFS的内容。

然而,当我将这些代码完全封装在DLL中,向该DLL添加相同的依赖项并从控制台应用程序中调用时,我得到:

No FileSystem for scheme: hdfs

当我通过fs.hdfs.impl键在Hadoop conf中指定正确的类名时,我会得到一个ClassNotFoundException

可执行文件中的依赖项解析方式与DLL中的不同吗?或者这可能是IKVM特有的行为?

编辑:另一个奇怪的行为:当我在控制台应用程序中构造FileSystem一次,然后在DLL中调用该方法时,它就会运行。

.net:是DLL与EXE不同的依赖项加载

我自己找到了答案(再次…)

它不一定要做.net如何处理依赖项加载,而是IKVM(以及Java)如何处理类的动态加载。

我挖掘了Hadoop的源代码,发现了以下部分:

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

线路classLoader = Thread.currentThread().getContextClassLoader();在这里特别令人感兴趣。控制台应用程序的上下文类加载器是它的上下文,不引用任何Hadoop类,因此在显式设置fs.hdfs.implorg.apache.hadoop.hdfs.DistributedFileSystem时为ClassNotFoundException

幸运的是,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的上下文类加载器。