当调用进程是本机 Win32 C++ 应用程序时,我在动态生成 C# 代码时遇到问题。 为了缩小问题范围,我创建了以下测试项目:
DynCodeDll
:.net5 程序集(C#),通过 Roslyn 编译器动态编译和执行源代码CLRLibCaller
:.Net/CLR dll,充当桥梁,以便本机代码可以调用DynCodeDll
NativeExeCaller
:通过 DynCodeDll
从
CLRLibCaller
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 客户端所在的目录复制文件并没有帮助。错误信息还是一样
所以现在的问题是:
Microsoft.CodeAnalysis.CSharp
)?背景:测试示例是一个更大项目的一部分。无法切换到 VS2022、较新的 .net 或调用“过去”的 CLI 包装器。
谢谢您的建议
最后我想出了一个解决办法。我执行了以下步骤:
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;
}
嗯...做了这一切终于让它对我有用了。也许它可以帮助遇到同样问题的人