Domanda

I've done an extension for Thunderbird. It calls (via js-ctypes) a C++ DLL I've written which in turn references other DLLs which are assemblies written in C# (existing code). If all the files are in the same directory as the Thunderbird executable, it all works fine.

I've now moved my own files to a directory I've made to keep them distinct from the Thunderbird files. The directory is in the path, so my C++ DLL gets loaded when called. However when it starts looking for the referenced assemblies, it fails.

Procmon shows that it is only looking for referenced assemblies in the directory which Thunderbird is running from. Not only is there no path, it's not even looking in the system directories.

What can I do to get my DLL loading its dependencies without dumping everything into Thunderbird's own folder, which as well as being somewhat messy will get silly when I port the extension to other mail programs?

Edit: Added extracts from the JS code.

From my 'init' function, there's;

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);

And where I'm actually making the call to _getStr and search for AttacherC.dll's dependencies is made is;

var ret;
ret = this._setDLLDir("C:\\Program Files (x86)\\AuthentStreamAttacher");
var str = this._getStr();

In each case, ret is true (according to the debugger on stepping through) suggesting the call to SetDllDirectory succeeds. Behaviour is the same whether I use the "A" or "W" version, there being nothing in the JS to simply let me call "SetDllDirectory". It's as if each call is happening in its own isolated context, yet in my DLL "GetPackage" uses malloc to allocate some memory which then needs to be freed in "FreePackage". FreePackage doesn't throw an exception suggesting the fact the memory's been allocated is persisting between the two calls.

More odd behaviour; if I specify a random string as the path in SetDllDirectory ("helloworld" in this case) ret is still true. So either SetDllDirectory isn't actually getting the string correctly via ctypes, or it's not doing any sanity checking on it.

My feeling now is that each js-ctypes call is happening in its own context, in some way, and it's upsetting .net's assembly search mechanism, and the only way to get this to work is to have a seperate native DLL with a single function called from javascript. This then calls SetDllDirectory and LoadLibrary in the same context to call the next wrapper in the chain, which then calls my real C# code. Messy and seems more prone to things going wrong so I'm hoping someone comes along and proves me wrong?

È stato utile?

Soluzione

Since nobody else seems to have an answer I'll document what I ended up doing.

When native code calls a dotnet DLL the CLR starts up behind the scenes to run it. Although the native code can search for the DLL in a variety of places- including that specified by SetDllDirectory- the CLR will only look in the directory from which the initial executable has been started, and in the Global Assembly Cache. To access assemblies that your DLL has been linked to by adding references to them in Visual Studio, they have to be in one of these two locations.

Since I didn't want to do either, what's needed is to make a .net DLL that is directly dependent only on framework assemblies, with no references to any of my own. This then gets the CLR up and running my code. I can then load the assembly I want to use via Assembly::LoadFrom() and invoke the method I want to use as documented here.

Of course, loading the assembly this way will still cause any other dependent assemblies to be searched for in the executable dir or in the GAC, if they're not already loaded, and in all but the most trivial case it's too complicated to bother explicitly loading each assembly in order from the most fundamental upwards. So the AssemblyResolve event is registered first. When the CLR can't find an assembly in its two search locations, it raises this event and lets me determine the full path of the assembly and load it, again using Assembly::LoadFrom().

Of course, LoadFrom needs to know the base path- the only information that seems to be available concerns the executable's directory, but there's plenty of ways to resolve that.

Altri suggerimenti

You will need to modify the DLL search path.

Make a call to SetDllDirectory before calling ctypes.open() to load your C++ DLL. Pass to SetDllDirectory the directory containing your DLL, and its dependent modules. When the call to ctypes.open() returns, call SetDllDirectory again, passing NULL, to undo the search path modification.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top