1

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?

Craig Graham
  • 1,161
  • 11
  • 35
  • Does the TB extension guide have anything to say on this? Typically you would call `SetDllDirectory`. Or use load-time linking. The latter is not much fun though. – David Heffernan Jan 22 '14 at 12:42
  • Not that I've seen. I'm using js-ctypes to call my DLL from JavaScript, since I failed miserably with XPCOM. js-ctypes documentation covers searching for the initial DLL but not any further down. – Craig Graham Jan 22 '14 at 12:56
  • OK, then I think you have a route. You've already got a JS layer that loads your DLL. Presumably with a call to `LoadLibrary`. So add in a call to `SetDllDirectory` just before you call `LoadLibrary` on the C++ DLL. Once that returns, add a second call to `SetDllDirectory` to undo the change. – David Heffernan Jan 22 '14 at 12:58
  • Problem is I *have* no `LoadLibrary`. The C++ is a .net assembly that exports the functions I want in js but gets at other assemblies by adding them as references in the project- the actual searching and loading is happening somewhere behind the scenes. I'm dimly aware that I can probably use reflection to get rid of the references, but I've never gone there before and I'm hoping there's an easier- and less hacky- way. Like registering my assemblies in some way, but regasm at the moment just exits with "No types were registered" – Craig Graham Jan 22 '14 at 13:05
  • I think you have a mixed mode C++/CLI assembly. Your functions are exported with __declspec(dllexport) right? Or a .def file? I don't know js-ctypes, but there must be something there that invokes the loading of your DLL. What is that? Is it `ctypes.open()`? – David Heffernan Jan 22 '14 at 13:10
  • Yes, mixed mode with __declspec(dllexport). ctypes.open() loads libraries. I've twigged that as both of you said, I can actually call SetDLLDirectory by importing it the same way into js. Although SetDllDirectory isn't found (showing kernel32 is being opened) I can use SetDllDirectoryW and SetDllDirectoryA. Neither has any effect- procmon still shows the only place being searched is Thunderbird's own directory. One way around this may be to make a 'wrapper wrapper' DLL, this time purely native, and use SetDllDirectory and LoadLibrary in there, but there must be a better way. – Craig Graham Jan 23 '14 at 10:18
  • Well, `SetDllDirectory` is a macro that expands to either the Ansi or Unicode version. That is normal Win32 fare. Is the call to SetDllDirectory succeeding? What is the return value. Without your code, I'm finding it hard to debug. – David Heffernan Jan 23 '14 at 10:26
  • I've added to the original message to add code. The return value is true, suggesting the call is succeeding. Since this is in js, there aren't any macros- but since there's only two versions it's easy enough to try both. – Craig Graham Jan 23 '14 at 12:51
  • Don't you need to escape the `\` in Javascript. Shouldn't they be `\\`? – David Heffernan Jan 23 '14 at 13:05
  • My first thought always is escaping the \ http://www.afterhoursprogramming.com/tutorial/JavaScript/Backslash-Characters/ – drescherjm Jan 23 '14 at 13:18
  • Still doesn't work but good point on escaping the backslashes. My path's now a variable, with the backslashes escaped, and looking at it in the debugger shows what I want it to be. It'd have been a problem if anything was trying to use it, but whatever's loading the dependencies just seems to be ignoring it. @David not sure what you mean by escaping the ` character- I'm just using standard " characters to delimit strings. – Craig Graham Jan 23 '14 at 15:26
  • My message got garbled. A single backslash is written like this `\\\` – David Heffernan Jan 23 '14 at 15:28
  • Gotcha. They're escaped now, and the code in the question edited to show this, but even with utter gibberish in there as the path ("helloworld") I still get 'true' returned by SetDllPath. – Craig Graham Jan 23 '14 at 15:36
  • I'm not terribly encouraged by the comment you left to my answer. I think I'm going to give up now. Good luck. – David Heffernan Jan 23 '14 at 15:51

2 Answers2

2

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.

Community
  • 1
  • 1
Craig Graham
  • 1,161
  • 11
  • 35
0

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    To clarify in case anyone reading thinks this is the answer and is just waiting for me to get back to it and accept, it don't work for me as described in the main comment thread :) – Craig Graham Jan 23 '14 at 15:29
  • Is there a guide how the path should be written? Namely, should I use `"C:\My Folder\My Sub Folder"`? Should I escape `\` or use `\\`? Maybe `/`? Is there a guide to properly define the path for `SetDllDirectory()`? – Royi Sep 15 '20 at 09:04