我为Thunderbird做了一个扩展。它调用(通过js-ctypes)我编写的C ++ DLL,后者又引用其他DLL,这些DLL是用C#编写的程序集(现有代码)。如果所有文件都与Thunderbird可执行文件位于同一目录中,那么一切正常。
我现在已经将我自己的文件移动到了一个目录中,以使它们与Thunderbird文件区别开来。该目录位于路径中,因此在调用时会加载我的C ++ DLL。但是,当它开始寻找引用的程序集时,它会失败。
Procmon表明它只是在运行Thunderbird的目录中查找引用的程序集。不仅没有路径,甚至没有查看系统目录。
我可以做些什么来让我的DLL加载其依赖项而不将所有内容都转储到Thunderbird自己的文件夹中,当我将扩展程序移植到其他邮件程序时,这会变得有些愚蠢?
编辑:添加了JS代码的摘录。
从我的'init'功能,有;
this._kernel32 = ctypes.open("kernel32.dll");
this._setDLLDir = this._kernel32.declare("SetDllDirectoryA",
ctypes.default_abi,
ctypes.bool,
ctypes.char.ptr);
var ret;
ret = this._setDLLDir("C:\\Program Files (x86)\\AuthentStreamAttacher");
this._lib = ctypes.open("AttacherC.dll");
this._getStr = this._lib.declare("GetPackage",
ctypes.default_abi,
ctypes.char.ptr);
this._freeStr = this._lib.declare(“FreePackage”, ctypes.default_abi, ctypes.void_t, ctypes.char.ptr);
ret = this._setDLLDir(null);
我实际上在调用_getStr并搜索AttacherC.dll的依赖关系的地方是;
var ret;
ret = this._setDLLDir("C:\\Program Files (x86)\\AuthentStreamAttacher");
var str = this._getStr();
在每种情况下,ret都为真(根据调试程序逐步执行),表明对SetDllDirectory的调用成功。无论我使用“A”还是“W”版本,行为都是一样的,JS中没有任何内容可以简单地让我调用“SetDllDirectory”。就好像每个调用都在它自己的孤立上下文中发生,但在我的DLL“GetPackage”中使用malloc来分配一些内存,然后需要在“FreePackage”中释放它。 FreePackage不会抛出一个异常,表明已经分配的内存在两次调用之间持续存在。
更奇怪的行为;如果我将随机字符串指定为SetDllDirectory中的路径(在本例中为“helloworld”),则ret 仍然为真。因此,SetDllDirectory实际上并没有通过ctypes正确获取字符串,或者它没有对它进行任何健全性检查。
我现在的感觉是,每个js-ctypes调用都在它自己的上下文中发生,在某种程度上,它正在扰乱.net的汇编搜索机制,并且让它工作的唯一方法是拥有一个独立的本机DLL从javascript调用的单个函数。然后,它在同一个上下文中调用SetDllDirectory和LoadLibrary来调用链中的下一个包装器,然后调用我真正的C#代码。凌乱而且似乎更容易出错,所以我希望有人出现并证明我错了?
答案 0 :(得分:1)
由于其他人似乎没有答案我会记录我最终做的事情。
当本机代码调用dotnet DLL时,CLR会在后台启动以运行它。虽然本机代码可以在各种位置搜索DLL,包括SetDllDirectory指定的DLL,但CLR只会查看初始可执行文件所在的目录,以及全局程序集缓存。要通过在Visual Studio中添加对它们的引用来访问DLL已链接到的程序集,它们必须位于这两个位置之一。
由于我不想这样做,所需要的是创建一个直接依赖于框架程序集的.net DLL,而不引用任何我自己的。然后,这将获得CLR并运行我的代码。然后我可以通过Assembly::LoadFrom()
加载我想要使用的程序集,并调用我想要使用的方法here。
当然,以这种方式加载程序集仍然会导致在可执行目录或GAC中搜索任何其他依赖程序集(如果它们尚未加载),除了最简单的情况外,它太复杂了从最基本的向上按顺序显式加载每个组件。因此,首先注册AssemblyResolve事件。当CLR在其两个搜索位置找不到程序集时,它会引发此事件并让我确定程序集的完整路径并加载它,再次使用Assembly::LoadFrom()
。
当然,LoadFrom需要知道基本路径 - 似乎可用的唯一信息涉及可执行文件的目录,但有很多方法可以解决这个问题。
答案 1 :(得分:0)
您需要修改DLL search path。
在调用ctypes.open()
加载C ++ DLL之前调用SetDllDirectory
。传递给SetDllDirectory
包含DLL及其相关模块的目录。当ctypes.open()
的呼叫返回时,再次呼叫SetDllDirectory
,传递NULL
,以撤消搜索路径修改。