Unity C#如何正确加载托管DLL?

4
基于这里的另一个问题 (在C#项目中使用OpenH264 DLL),我基本上正在尝试使用库https://github.com/secile/OpenH264Lib.NET 在Unity中实时编码H264视频文件,使用Unity的JINT本地库用于JavaScript解释。
然而,在Unity中参考DLL方面我遇到了一些困难。我按照GitHub网站上的说明操作,成功编译了OpenH264Lib.dll(以及示例项目,并且在windows x64上可以通过visual studio 2019运行示例项目并进行编码等操作),但是当我将已编译的OpenH264Lib.dll简单拖动到Unity项目的Assets文件夹中的Plugins文件夹中时,我无法访问命名空间OpenH264Lib,标准C#示例中使用该命名空间来访问托管的函数,控制台仅显示在unity中找不到该命名空间。
好吧,所以我决定在visual studio中构建一个C# DLL,它本身加载OpenH264Lib.dll,然后从其中调用其函数,然后在Unity中使用C# DLL,因此这是我的C#代码(之前已将编译好(.NET版本4.6.1)的OpenH264Lib.dll放入Debug文件夹中,就像在示例中一样,在Visual Studio 2019中没有收到任何引用错误):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Sayfir
{
    public class Class1
    {
        public static OpenH264Lib.Encoder ok(string path)
        {
            var code = new OpenH264Lib.Encoder(path);
            return code;
        }
    }
}

好的,完美构建,没有错误,并且似乎与示例文件夹(Assets/Plugins)中的内容相同。

然后,当我将解决方案中 x64/Debug 文件夹下生成的 DLL 文件(OpenH264Lib.dll 和 Sayfir.dll)复制到 Unity 的 Plugins 文件夹中,并尝试在 Unity 中访问它时:

public class Yetzirah {
        public static void Wellz() {
            var k = Sayfir.Class1.ok("2");
            Debug.Log(k);
        }
}

我在Unity控制台中收到了一个错误:

Assets\scripts\Yetzirah.cs(45,12): error CS0012: 类型“Encoder”在未被引用的程序集“OpenH264Lib, Version=1.0.7598.38705, Culture=neutral, PublicKeyToken=null”中定义。您必须添加对该程序集的引用。

似乎它识别出了从上面的源代码编译的Sayfir.dll文件,但不认识依赖的OpenH264Lib.dll文件,尽管两者都在同一个文件夹中(在Assets/Plugins中)。

我听说过.rsp文件,但我尝试使用名称为csc.rsp的文件,并将其添加到Assets文件夹中,内容为 -r:OpenH264Lib

仍然没有效果。

同时,我也听说过.asmdef文件,但是当我创建了这样一个文件并添加以下内容时:

{
    "references":  "OpenH264Lib" 
}

但是仍然没有进展,我对asmdef文件并不了解,我也不太清楚这个错误或者在Unity中如何使用托管C++ DLL。

我还看到了一篇文章提到在Unity中引用外部DLL文件,但它说要去

项目>编辑参考>.NET程序集

问题是,我不知道如何在Unity编辑器2020中打开那个界面,或者是否有方法可以实现相同的功能。如果有的话,我不知道该怎么做,在搜索时也无法找到相关信息

我尝试使用标准dllexport创建另一个DLL,使用以下头文件内容:

extern "C" __declspec(dllexport) int well();

接下来在 .cpp 文件中进行操作。

#include "deeall.h"
__declspec(dllexport) int well() {
    return 634;
}

然后在Unity中使用C#

DllImport("deeall.dll", EntryPoint="well")]
public static extern int well();

当我仅包含任何类型的托管库时,它可以工作,但是当我将其包含在dll中时,就会出现错误:“__declspec(dllexport)”不能应用于使用“__clrcall”调用约定的函数。

如果我想要为C#包装器使用__declspec导出,这是必需的。

作为一种方式,我试图向Encoder.cpp文件本身(来自OpenH264Lib.dll的源代码)添加以下内容:

    __declspec(dllexport) int well() {
        String ^h = "ok";
        Encoder
        ^enc = 
        gcnew Encoder(
            h
        );
        return 4;
    }

尝试直接调用Encoder函数,(以及对Encoder.h文件头的以下修改:

namespace OpenH264Lib {
    extern "C" __declspec(dllexport) int well();
    //etc.. rest of code in namespace.....

然后在Unity中进行操作。

[DllImport("OpenH264Lib6.dll", EntryPoint="well")]
public static extern int well();

但是当我调用well()时,Unity会暂停一段时间然后崩溃,没有任何错误信息

那么我该如何在Unity中让OpenH264Lib.dll工作,或者让其他类似的托管编译过的C++ DLL在Unity中工作呢?如果这不可能,那么我是否可以以其他方式在Unity中使用OpenH264?如果不能,那么在Unity中实时编码H264视频的其他解决方案有哪些(从byte[]s)?


请考虑编辑您的问题并添加代码片段,如果没有代码,您可能只会得到一些猜测而没有实际解决方案。 - Cleptus
@cleptus 我已经添加了相关的代码部分,还需要添加什么? - B''H Bi'ezras -- Boruch Hashem
你很可能离开了一些未管理的资源,并且这可能会阻止处理类。你可以使用 using 语句 来代替 Dispose,但它不能解决你的问题(它仅是一个异常安全的 Dispose 语句)。 - Cleptus
你已经暴露了一个问题,但只添加了一行代码,请查看mcve帮助页面。 - Cleptus
@cleptus 我无法创建一个新的示例,因为复制它们太过复杂,只是想知道为什么dispose会出错。 - B''H Bi'ezras -- Boruch Hashem
显示剩余2条评论
1个回答

1

这是从记忆中写的,没有经过测试,但我认为它应该可以工作。

  • 确保OpenH264Lib.dll已正确加载,否则手动加载它,例如在您的包装器中使用Assembly.Load/Assembly.LoadFrom
  • 确保openh264-1.7.0-win32.dll位于可执行文件旁边或指定完整路径
  • 确保不要混合32位和64位
  • 像之前一样构建Sayfir包装器程序集,但不要直接公开Encoder类本身,而是封装您想要使用的方法。这样Unity就不需要知道OpenH264Lib.dll的任何信息

例如


public class Wrapper 
{
  private Encoder _encoder;
  public Wrapper() 
  {
    // Be sure the openh264-1.7.0-win32.dll can be found! 
    // Native dlls should be located next to the actual executable if you call it without a full path!
    _encoder = new OpenH264Lib.Encoder("openh264-1.7.0-win32.dll");
  }

  public void Setup(/* ... */)
  {
    //_encoder.Setup(...)
  }
}


如果仍然没有起作用,尝试使用此方法重定向DLL查找路径,然后再尝试加载OpenH264Lib.dll。在编码器类的源代码中,我们可以看到对LoadLibrary函数的调用,该函数将使用您在此处提供的路径搜索DLL。通过再次调用该函数并将null作为参数来恢复正常的搜索顺序。
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetDllDirectory(string lpPathName);

https://www.pinvoke.net/default.aspx/kernel32.LoadLibrary


有趣,谢谢你的回答。你知道在运行时调用本地DLL函数是否可能吗?这可能会简化事情。 - B''H Bi'ezras -- Boruch Hashem
是的,就像你已经写的那样,可以使用 DllImport。请参考 https://github.com/secile/OpenH264Lib.NET/blob/master/OpenH264Lib/Encoder.h。你必须完全匹配签名或自己进行封送处理。 - sneusse
谢谢,虽然我认为DllImport只在C#代码中编译时起作用,但如果我想从他们的计算机中选择一个dll文件在运行时加载它呢? - B''H Bi'ezras -- Boruch Hashem
那么您可以使用标准的WinAPI GetProcAddress https://www.pinvoke.net/default.aspx/kernel32.getprocaddress,并使用GetDelegateForFunctionPointer进行转换https://learn.microsoft.com/de-de/dotnet/api/system.runtime.interopservices.marshal.getdelegateforfunctionpointer?view=netcore-3.1 - sneusse
好的,我尝试过了,但是问题在于:#1 这是一个跨平台的解决方案吗?#2 如何自动生成每种加载函数类型的委托? - B''H Bi'ezras -- Boruch Hashem
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接