从本机应用程序调用时,对 Roslyn 的包引用不起作用

问题描述 投票:0回答:1

当调用进程是本机 Win32 C++ 应用程序时,我在动态生成 C# 代码时遇到问题。 为了缩小问题范围,我创建了以下测试项目:

  • DynCodeDll
    :.net5 程序集(C#),通过 Roslyn 编译器动态编译和执行源代码
  • CLRLibCaller
    :.Net/CLR dll,充当桥梁,以便本机代码可以调用
    DynCodeDll
  • NativeExeCaller
    :通过
    DynCodeDll
    CLRLibCaller
  • 调用函数的 Win32 应用程序

DynCodeDll

  • 通过包管理器,添加 Microsoft.CodeAnalysis.CSharp 作为唯一参考。

  • 使用的版本:3.11,因为根据https://github.com/dotnet/roslyn/blob/main/docs/wiki/NuGet-packages.md这是适用于VS2019和.net5的最新版本

      public class Class1
      {
          public Class1()
          {
              System.Diagnostics.Debug.WriteLine("managed ctor");
          }
    
          public void compileAndRunDynCode()
          {
              System.Diagnostics.Debug.WriteLine(">>> managed compileAndRunDynCode");
              DoRoslynDynCodeStuff();
              System.Diagnostics.Debug.WriteLine("<<< managed compileAndRunDynCode");
          }
    
          private void DoRoslynDynCodeStuff()
          {
              System.Diagnostics.Debug.WriteLine(">>> managed DoRoslynDynCodeStuff");
    
              // Create a syntax tree for the dynamic code
              SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
    
              //Here follows code compiling and run - not relevant to the problem. 
              System.Diagnostics.Debug.WriteLine("<<< managed DoRoslynDynCodeStuff");
              return;
          }
      }
    

CLRLibCaller

  • 引用

    DynCodeDll
    作为 Dll,而不是作为项目引用。
    CopyLocal
    设置为
    true

      #include <vcclr.h>
      #define INTEROPBRIDGE_API extern "C" __declspec(dllexport)
      gcroot<DynCodeDll::Class1^> flowControlMain;
    
      INTEROPBRIDGE_API void init()
      {
          System::Diagnostics::Debug::WriteLine(">>> CLR-Init");
          flowControlMain = gcnew DynCodeDll::Class1();
          System::Diagnostics::Debug::WriteLine("<<< CLR-Init");
      }
    
      INTEROPBRIDGE_API void dyncode()
      {
          System::Diagnostics::Debug::WriteLine(">>> CLR-dyncode");
          flowControlMain->compileAndRunDynCode();
          System::Diagnostics::Debug::WriteLine("<<< CLR-dyncode");
      }
    

NativeExeCaller

BOOL CNativeExeCallerDoc::OnNewDocument()
{
    HMODULE hModule = LoadLibrary(_T("S:\\spielwiese\\dotnet\\TestRoslyn\\AsDll\\DynCodeDll\\Debug\\CLRLibCaller.dll"));

    typedef void(*RunFlowControlSignature)();

    auto initFlowControlMethod = (RunFlowControlSignature)GetProcAddress(hModule, "init");
    auto runFlowControlMethod = (RunFlowControlSignature)GetProcAddress(hModule, "dyncode");

    initFlowControlMethod(); //calls init method from CLI wrapper and works
    runFlowControlMethod(); //calls compileDynCode from CLI wrapper and crashes, see below

    return TRUE;
}

备注:

  • 可以从另一个 .net 应用程序调用

    DynCodeDll
    。在这种情况下,引用的 dll 及其包依赖项(
    Microsoft.CodeAnalysis.dll
    Microsoft.CodeAnalysis.CSharp
    、...)将复制到调用应用程序的输出目录。

  • 执行

    NativeExeCaller
    时,我可以调试直到调用
    DoRoslynDynCodeStuff
    ,然后VS输出窗口中只会抛出异常:

      Exception thrown at 0x76EEFA72 in NativeExeCaller.exe: Microsoft C++ exception: EEFileLoadException at memory location 0x003AE20C.
      Exception thrown at 0x76EEFA72 in NativeExeCaller.exe: Microsoft C++ exception: [rethrow] at memory location 0x00000000.
      Exception thrown at 0x76EEFA72 in NativeExeCaller.exe: Microsoft C++ exception: [rethrow] at memory location 0x00000000.
      The thread 0x1b84 has exited with code 0 (0x0).
      Exception thrown at 0x76EEFA72 in NativeExeCaller.exe: Microsoft C++ exception: [rethrow] at memory location 0x00000000.
      Exception thrown: 'System.IO.FileNotFoundException' in DynCodeDll.dll
      An unhandled exception of type 'System.IO.FileNotFoundException' occurred in DynCodeDll.dll
      Could not load file or assembly 'Microsoft.CodeAnalysis, Version=3.11.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'. The system cannot find the specified file.
    

未输入

DoRoslynDynCodeStuff
方法。

未找到包依赖项这一事实并不奇怪,因为它们不存在于本机应用程序的输出目录中。但是,从正在运行的 .net 客户端所在的目录复制文件并没有帮助。错误信息还是一样

所以现在的问题是:

  • 如何通过 C++/CLR 包装器从本机应用程序调用 .net dll(该 DLL 又使用
    Microsoft.CodeAnalysis.CSharp
    )?

背景:测试示例是一个更大项目的一部分。无法切换到 VS2022、较新的 .net 或调用“过去”的 CLI 包装器。

谢谢您的建议

c# nuget c++-cli .net-5 roslyn
1个回答
0
投票

最后我想出了一个解决办法。我执行了以下步骤:

DynCodeDll

  • 在项目文件中添加

     <PropertyGroup>
       <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
     </PropertyGroup>
    

通过这样做,您可以防止 VS 将输出构建到 i 子目录“.net5.0”中。由于 c++/cli-Project 不是在这样的文件夹中创建的,因此请务必将两个 Dll 放入同一个文件夹中。要么都进入 .net5.0,要么都不进入 .net5.0

  • 添加一个

    AssemblyResolve
    处理程序,从我们自己的输出文件夹中加载缺少的程序集。这很重要,因为调用 Win32-App
    NativeExeCaller
    是我们的
    DynCodeDll
    上方的一个文件夹,我们需要在我们自己的文件夹中找到 NuGet-References:

    AppDomain currentDomain = AppDomain.CurrentDomain;
    currentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    
    
    private Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args)
    {
        //This handler is called only when the common language runtime tries to bind to the assembly and fails.
        Assembly executingAssembly = Assembly.GetExecutingAssembly(); //This is us
    
        string? applicationDirectory = Path.GetDirectoryName(executingAssembly.Location);
        if (applicationDirectory == null)
        {
          return null;
        }
    
        string[] fields = args.Name.Split(',');
        string assemblyName = fields[0];
        string? assemblyCulture = null;
        if (fields.Length >= 2)
        {
          assemblyCulture = fields[2][(fields[2].IndexOf('=') + 1)..];
        }
    
        string assemblyFileName = assemblyName + ".dll";
        string assemblyPath;
    
        if (assemblyName.EndsWith(".resources") && assemblyCulture != null)
        {
          // Specific resources are located in app subdirectories
          string resourceDirectory = Path.Combine(applicationDirectory, assemblyCulture);
          assemblyPath = Path.Combine(resourceDirectory, assemblyFileName);
        }
        else
        {
          assemblyPath = Path.Combine(applicationDirectory, assemblyFileName);
        }
    
        if (File.Exists(assemblyPath))
        {
          //Load the assembly from the specified path.
          loadingAssembly = Assembly.LoadFrom(assemblyPath);
        }
    
        return loadingAssembly;
    }
    

DependencyCopier

添加另一个项目,这是一个 .net .exe 并从中引用

DynCodeDll
。它不必有任何实现。只需将输出文件夹设置为与
DynCodeDll
相同即可。通过这样做,VS 构建过程会将 CodeAnalysis-NuGet-package 复制到该文件夹中。 如果您仅从 c++/cli-Project
DynCodeDll
引用
CLRLibCaller
,这些引用不会被复制,我认为这是一个错误。 请确保将此项目的
AppendTargetFrameworkToOutputPath
设置为
false

NativeExeCaller

在调用

LoadLibrary
之前,将
DynCodeDll
的输出文件夹添加到 DllSearchpath。否则
LoadLibrary
会失败,因为它不会加载
CLRLibCaller.dll
的依赖项,主要是
ijwhost.dll
(我认为这是 .net 运行时的一部分)

  CString CFileTools::GetPath(const CString& sFileNameWithPathAndName)
  {
    CString szDrive, szDir, szFilename, szExt;
  
    _tsplitpath_s(sFileNameWithPathAndName,
        szDrive.GetBuffer(_MAX_PATH), _MAX_PATH,
        szDir.GetBuffer(_MAX_PATH), _MAX_PATH,
        szFilename.GetBuffer(_MAX_PATH), _MAX_PATH,
        szExt.GetBuffer(_MAX_PATH), _MAX_PATH);
  
    szDrive.ReleaseBuffer();
    szDir.ReleaseBuffer();
    szFilename.ReleaseBuffer();
    szExt.ReleaseBuffer();
    szDir = szDrive + szDir;
    return szDir;
  }
  
  BOOL CNativeExeCallerDoc::OnNewDocument()
  {
    CString szModuleFilePathname;
    GetModuleFileName(nullptr, szModuleFilePathname.GetBuffer(_MAX_PATH), _MAX_PATH);
    szModuleFilePathname.ReleaseBuffer();
  
    //Extract the Path without .exe
    CString sOurOwnPath = CFileTools::GetPath(szModuleFilePathname);
    CString sPathToFlowControl = sOurOwnPath + _T("\\DynCodeDll");
    BOOL bDirAdded = SetDllDirectory(static_cast<LPCTSTR>(sPathToFlowControl));
    ASSERT(bDirAdded == TRUE);
  
    HMODULE hModule = AfxLoadLibrary(_T("CLRLibCaller.dll"));
    ASSERT(hModule != NULL);
  
    typedef void(*RunFlowControlSignature)();
  
    auto initFlowControlMethod = (RunFlowControlSignature)GetProcAddress(hModule, "init");
    auto runFlowControlMethod = (RunFlowControlSignature)GetProcAddress(hModule, "dyncode");
  
    initFlowControlMethod(); //calls init method from CLI wrapper and works
    runFlowControlMethod(); //calls compileDynCode 
  
    return TRUE;
  }

嗯...做了这一切终于让它对我有用了。也许它可以帮助遇到同样问题的人

© www.soinside.com 2019 - 2024. All rights reserved.