Microsoft Assembly configuration for 32-bit mixed C/C++ application - dll

We have a 32 bit mixed C/C++ application that we are trying to deploy to the world.
It naturally uses C and C++ runtime DLLs. We are using VS 2005.
The manifest constructed by VS2005 is the following:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC80.CRT" version="8.0.50727.42" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>
We ship this as a file in the same directory as the "application", named
(changed to protect the innocent) "application.exe.manifest".
On the face of it, it is sort of reasonable. But, in installing on some
systems, we get the message when "application.exe" is launched:
This application has failed to start because the application configuration is incorrect
One way to cure this is to run VCRedist_x86.exe from MSDN. (Unfortunately,
while we can run it, we don't know exactly what it is doing.
It appears to be parking DLLs in the SxS directories. But what else
does it do?)
a) The MS docs seem to indicate that the assembly must have an assemblyIdentity
tag directly underneath the assembly tag, that names the application itself.
This is clearly missing here, but the manifest seems to partially work in that
if we remove it, the application doesn't start even if the DLLs are present.
b) Remarkably the assembly doesn't mention the C runtime DLL. Do I need to just add that by hand?
c) We dont want to be dependent on whether the right version DLL is present on the target machine. Assuming that the assembly makes it clear which DLLs to use, how is that we can ensure the DLLs we need are on the target system? (In particular, we don't want run VCRedist or ask our customer to do this). Before assemblies came along, we solved this problem by simply placing the C and C++ DLLs in the same directory as the application .exe file, and Windows would look there first to pick them up. Can we still ship the C and C++ DLLs in the same directory? I can't figure out from the MS docs I can find how the SxS finds the appropriate dependent assemblies.
Any help appreciated.

a) The manifest xml validation clearly has some problems. Depending on the version of Windows this may or may not be an issue. Since so many applications don't correctly follow the schema (and because it was never properly enforced), I doubt it will ever be strict here.
b) The C runtime DLL is referenced in the Microsoft.VC80.CRT.manifest file, pulling it in to to the loader dependency graph. Providing you have a dependency on the manifest, you will also implicitly have a dependency on the DLL.
c) Per my earlier comment, the correct thing to do (aside from installing the latest redist system wide) is to put the CRT manifest and all three DLLs in your application directory. This is poorly documented under SxS: Private Assemblies and Installing Side-by-side Assemblies as Private Assemblies. The probe order is defined in Assembly Searching Sequence.
Generally a SxS binding failure will put an entry in the Application (for Vista+) or System Event Log (pre Vista) describing the error.
Activation context generation failed for "C:\TEMP\sxs\PEVerify.exe".Error in manifest or policy file "C:\TEMP\sxs\Microsoft.VC90.CRT.MANIFEST" on line 4.
Component identity found in manifest does not match the identity of the component requested.
Reference is Microsoft.VC90.CRT,processorArchitecture="x86",publicKeyToken="1fc8b3b9a18e3b",type="win32",version="9.0.21022.8".
Definition is Microsoft.VC90.CRT,processorArchitecture="x86",publicKeyToken="1fc8b3b9a1e18e3b",type="win32",version="9.0.30729.1".
Please use sxstrace.exe for detailed diagnosis.
You can use sxstrace.exe in (Vista+) to see what the loader is actually doing. Junfeng covers this in more detail in Diagnosing SideBySide failures.
To get a better understanding of what is happening at runtime (after the manifest has been parsed and dependencies located), enable "Show Loader Snaps" for your image file (just the filename and extension, do not enter a directory name, like so: "notepad.exe") using gflags.exe. Run your application under windbg (Visual Studio's debugger may also work) and look at the output. Make sure to disable loader snaps when you're done debugging since it will slow down the application even when no debugger is attached. Sample output looks like this:
2d6c:36b4 # 1246428223 - LdrpHandleOneOldFormatImportDescriptor - INFO: DLL "C:\Program Files\Microsoft SDKs\Windows\v6.1\Bin\PEVerify.exe" imports "MSVCR90.dll"
2d6c:36b4 # 1246428223 - LdrpMapDll - INFO: Mapping static redirected DLL "C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.4148_none_5090ab56bcba71c2\MSVCR90.dll"
ModLoad: 4fbd0000 4fc73000 C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.4148_none_5090ab56bcba71c2\MSVCR90.dll
2d6c:36b4 # 1246428285 - LdrpMapDll - INFO: Mapped DLL "C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.4148_none_5090ab56bcba71c2\MSVCR90.dll" at address 4FBD0000
2d6c:36b4 # 1246428285 - LdrpHandleOneOldFormatImportDescriptor - INFO: DLL "C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.4148_none_5090ab56bcba71c2\MSVCR90.dll" imports "KERNEL32.dll"

You could link the C/C++ runtime statically.
You can change the library linkage in the C/C++ compiler options in the Code Generation section. Change the entry from Multithreaded [Debug] DLL to Multithreaded [Debug].
Your DLL will then contain the required parts of the runtime, and the separate installation is not required.

I've never understood how the manifest stuff all hangs together ... but instead of placing the C runtime DLLs in the same directory as your exe try copying the whole 'Microsoft.VC90.CRT' folder from the redist folder in the Visual Studio install (C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.CRT on my machine).
I believe Microsoft encourages the use of shared libraries and running the official redist since it installs the libraries 'properly' and means they can patch security issues that may be found in them.

Related

How do I expose a .netstandard2.0 library with COM for use in VB6?

I have a dotnet core library, a framework 4.7.2 library and a vb6 application.
I want to write a common library for them all to access and so choose .netstandard2.0
I tried a the 4.7.2 framework wrapper library between .netstandard2.0 library and vb6.
However I ran into assembly binding problems
Looking at the docs I see
In .NET Core, the process for exposing your .NET objects to COM has been significantly streamlined in comparison to .NET Framework.
However no mention .netstandard2.0
I decided to try following the docs anyway even though my project is using .netstandard2.0
I got up to the instructions on Generating the COM Host in which case the output files ProjectName.dll, ProjectName.deps.json, ProjectName.runtimeconfig.json and ProjectName.comhost.dll should build.
However the ProjectName.comhost.dll and ProjectName.runtimeconfig.json do not create.
I see in this dotnet standard issue that Microsoft plans on having tooling support in "Preview 4"
I am running VS 16.4.5
[Update]
I decided to try making a .net core wrapper library and enabling it for com.
I was able to add my .netstandard to the wrapper library via a nuget package (I build the .netstandard library using azure devops)
When I build my wrapper library the .dll, .deps.json, .pdb, .runtimeconfig.dev.json and .runtimeconfig.json files are created in a bin\Debug\netcoreapp3.1 folder.
However none of the .netstandard library files appear in the bin\debug folder.
I copied the .netstandard library and the .netcore wrapper libraries to the same folder and ran
regsvr32 MyCoreComWrapper.comhost.dll
However no .tlb file is created which I need to be able to use from VB6
I note the following in the docs
Unlike in .NET Framework, there is no support in .NET Core for
generating a COM Type Library (TLB) from a .NET Core assembly. The
guidance is to either manually write an IDL file or a C/C++ header for
the native declarations of the COM interfaces.
I found some information on github but would love a step by step guide to making the .tlb
I thought about using latebinding instead but am unsure of how to use it with a com library.
[Update]
I put a sample project on GitHub including some VB6 files.
With VB6 referencing the .tlb referenced with the framework library.
When I try to run that I get
Could not load file or assembly 'Microsoft.EntityFrameworkCore, Version=3.1.2.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.
So I copied all the files from my framework test project to my vb6 folder, rebuilt and ran.
Then I got the error
Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.0.0,
Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.
I see the file Microsoft.Extensions.DependencyInjection.dll is present with File version 3.100.220.6706
Regarding the .NET standard, I may be wrong but I think this is not applicable here because the COM interop stuff are at a higher level than the one .NET standard is targeting; we can only talk about either .NET Core or .NET Framework for COM interop.
If you want to generate a type library, you have few options.
By far, the easiest method is just to use .NET Framework. The fact that you are wanting to create a type library negates the advantages of .NET Core already because several COM, especially the "Automation" features are Windows-only. Using framework will be fine at least until .NET Core 5 comes out.
That said, if you have a business reason for using .NET Core but still need COM support, including the type library, then based on this GitHub comment, you should be able to compile your own IDL. Note that requires you to install C++ build tools because the MIDL compiler is not really a standalone thing that you can get without the rest of the C++ build tools.
It is strongly suggested to have had read the documentation on how .NET Core handles COM activation.
Assuming having the C++ build tools is not a barrier for you, the steps would be the following:
1) Create a .idl file that defines all your COM interfaces in the IDL format. That requires some translation between the .NET interface and the COM interface. Here's a partial example of how you'd need to translate between your C# interface and COM interface as defined in IDL:
[
Guid("<some gooey>"),
InterfaceType(ComInterfaceType.InterfaceIsDual)
]
public interface IFoo
{
string Bar { get; }
string Baz(int Fizz);
}
Would be translated into IDL:
[
uuid(<assembly gooey>),
version(1.0)
]
library myFoo
{
[
uuid(<some gooey>),
object,
dual
]
interface IFoo : IDispatch {
[propget] HRESULT Bar([out, retval] BSTR* retVal);
HRESULT Baz([in] long Fizz, [out, retval] BSTR* retVal);
}
}
Once you've defined the .idl file and it is an accurate representation, you can then use MIDL to compile the .idl file into a .tlb file. Usually something like midl foo.idl /tlb: foo.tlb. You should make use of the MIDL language reference to help you write the .idl file. As a quick way to get started, you could copy your C# interfaces to a .NET framework project, use tlbexp, then use oleview (available via Visual Studio Developer Command Prompt) or olewoo to view the resulting IDL file to get you started.
The next step is to then create registry keys so that your CLSID can reference the type library. You will need to have your assembly's GUID handy and it must be used as the library's uuid in the .idl file as well.
Using IFoo interface example, you would need to create the registry similar to below (using .reg format for easy sharing/comprehension and assuming per-user installation, rather than per-machine):
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}]
#="IFoo"
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}\ProxyStubClsid32]
#="{00020424-0000-0000-C000-000000000046}"
[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}\TypeLib]
#="{assembly gooey}"
"Version"="1.0"
You will also need to create the registry in the CLSID, Interface, TypeLib, and Record as needed. This article provides a good overview of all registry keys but keep in mind it's assuming .NET framework, not .NET Core, so not all keys are applicable, especially under the CLSID branch.
Note that when you run the regsvr32, it will normally create the keys in the CLSID and Interface branches but you will need to add the TypeLib keys under the Interface's branch and also an entry to the TypeLib branch. You also will need to create the ProgId keys, too if you want to support CreateObject functionality.
Initially, you can start with just a .reg file that you can manually update & maintain but if you have several objects, then it becomes desirable to automate this. This can be also managed via the DllRegisterServer call so that when you execute regsvr32, it will take care of registering the keys. On the other hand, you're now polluting your codebase with registration code. Some elect to use installers to do the registry keys write instead.
I hope that helps you get started!
The issue is due to assembly binding resolution that fails when ran from VB6 (IDE or compiled .exe file).
Here are the steps to solve it:
Compile the VB project, for example, let's assume the compiled file is Project1.exe.
Copy all .NET assemblies (including x86 and x64 directories, and languages directory if localized version is important) aside the compiled VB6 file
Now run Project1.exe, you will get an error like this:
The error is clearly a mismatch between the version of your assemblies aside the Project1.exe file and the version of referenced assemblies (not references you've created yourself but reference embedded in these assemblies... ). You don't see that when you start a .NET program because resolution is a very complex process that depends on a lot of parameters (and it's not getting any better with .NET Core, Framework, Standard, nugets, etc.).
To futher check it's a mismatch error, you can also use the Fuslogvw.exe (Assembly Binding Log Viewer) tool from the SDK.
Now we know it's an assembly version mismatch issue, what you can do is create a file named Project1.exe.config aside Project1.exe and add assembly binding redirects to it.
The easiest way to configure it is to redirect all possible versions to the ones present in the directory that contains your program, so in your case (and as of today, as all these can evolve...), it would be something like this, possibly for every assembly you reference directly or indirectly:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
...
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" />
<!-- 3.1.2.0 is the version of the assembly you ship -->
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="3.1.2.0" />
</dependentAssembly>
...
</assemblyBinding>
</runtime>
</configuration>
Unfortunately, there are many satellite assemblies, and it's a bit tedious to create all redirects with correct information, so I've created a tool that creates a .config file with the redirects configured automatically for all .NET assemblies in a given directory: https://github.com/smourier/BindingRedirectGenerator.
If you want it to work for the VB6 IDE too, you'll have to use the same procedure in a VB6.exe.config file aside VB6.exe.
A reminder to myself
Use a demo UI to access the original DLL to confirm the call works. ( if you can't get it to work skip to making the App.Config for the unit test project using BindingRedirectGenerator )
Add a unit test in the com visible project to confirm the
call works.
Copy all the dlls created by both projects to the
release folder
For each com visible dll run as Administrator
c:\windows\microsoft.net\framework\v4.0.30319\regasm /verbose /codebase /tlb:MyLibrary.tlb c:\myproject\releasedlls\MyLibrary.dll
Install BindingRedirectGenerator to c:\brg say
At the command prompt change directory to c:\brg
BindingRedirectGenerator c:\myproject\releasedlls App.config
Rename App.config to MyVB6Project.exe.config and copy it to the same folder as MyVB6Project.exe
Remember to set up the files for the vb6.exe folder if you want to run it in the vb6 ide
Put the whole process in a script for future use ( I used a .bat)
Keep an eye on what nuget has put in app.config
Pay attention to the yellow warnings at build time!

Getting out of DLL Hell with Microsoft.VC90.CRT?

I've built a inproc com server dll which I can package as 1 file or many via the build utility py2exe. When I allow all the dependencies to remain external, I have no issues, but bundling as 1 file produces problems.
When the dll is utilized (either registering it or instantiating a com object from it), it immediately loads MSVCR90.DLL from the path c:\windows\winsxs\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6871_none_50944e7cbcb706e5\MSVCR90.DLL no matter what I do, I can't change that. There is no information that I can find (using Dependency Walker) to indicate what is causing that to load. It just happens magically...
Then, later on it loads that dll again via an explicit call to LoadLibraryA("MSVCR90.dll") (part of some py2exe black box?), but this time it does not look into the winsxs manifests / directory. Instead it looks to the system path and/or will respect a dll redirection. That's when the problem occurs. If I set the system path to start with c:\windows\winsxs\x86_microsoft.vc90.crt...\ it will load the exact same dll and be happy - but if ANY other file is utilized - inclusive of a copy of the EXACT same dll - but at a different path - then the whole thing blows up. It can't handle using two different files.
How can I fix this? Ideally, I've love to make the initial magic loading of the dll draw upon a private assembly, but no matter what I do with manifests or .dll.local etc it will not respect that until this second dll loading takes place.
Note that with the non-bundled dll (external dependencies) it always uses the winsxs MSVCR90.DLL.
I can "fix" my failure to use the dll by forcing the system path to load the winsxs copy, but that is pretty useless for a deployable com server!
The reason is that you DLL has a manifest that tells the module loader to search also in the SxS storage.
You have several choices
Build your DLL using static linkage. Not using any of the MFC-DLLs (see project settings)
Don't use a side by side manifest for the DLL and still use the MFC DLLs. But beware you have to ship those DLL with your DLL in the local path (see DLL search sequence docs)
Use a later build of VS. Later versions of VS don't use the SxS storage any more and there are no manifests for those DLLs any more.
For the 2. see this article in code project. There is an update for VS-2008 [here].
2
Build your DLL

Success of reading values from registry depends on if the application was built in IDE or from command line

I'm facing a really strange problem.
I have an application built in C++ Builder 2010. This application reads and writes a bit to the registry. Since it's a 32-bit application these keys end up in the wow6432Node. Every now and then it has appeared as though it has trouble reading the values from the registry. But only when build on the build server (using TeamCity) and never on the dev machines. Often a new commit and a rebuild would make the issue go away so it was hard to diagnose.
After some testing I noticed that I was able to reproduce it on the dev machine to. But only when building from the command line by calling msbuild manually. If the exact same project is built within the IDE there are no issues. But the exe produced when building from the command line, for some reason, can't read values from the registry.
There are no errors or warnings during builds. No files it can't find due to invalid paths or anything like that. Since msbuild is, as far as I can tell, used by the IDE when building to this has me scratching my head. I have tried to manually use different versions of msbuild etc, but nothing works.
So basically, on the same machine, my produced exe behaves differently depending on if I manually started the build from the command line or if the IDE started the build.
What on earth could this be?
After spending a lot of time trying to force the application to use specific registry views etc I was encouraged to look into the UAC manifest settings. I found that the application did in fact have a manifest file, named correctly and in the correct spot. It was also included in the .cbproj file and compiled by the resource compiler.
But, something got me thinking that perhaps it's not being used correctly. After some digging it seems like if runtime themes is enabled for the project that will create a "default" application manifest that will be used.
Disabling runtime themes will allow the compiler to actually use your custom application maniftest file (not exactly well documented, but I have found that to be the case with a lot of Embarcaderos things). By doing this I was able to set the required execution level for the application and things started to work just fine.
You can still manually enable the support for runtime themes in the maniftest file you create. You forms might look a bit strange in the ide since that will think that runtime themes are disabled.
To manually add runtime theme support you add the dependency to your custom application maniftest.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
...
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
publicKeyToken="6595b64144ccf1df"
language="*"
processorArchitecture="*"
/>
</dependentAssembly>
</dependency>
...
</assembly>
Save it as Foo.exe.manifest, where Foo.exe is your application name.
You then create a .rc file for your application. For instance FooManifest.rc
#define MANIFEST_RESOURCE_ID 1
#define RT_MANIFEST 24
MANIFEST_RESOURCE_ID RT_MANIFEST "Foo.exe.manifest"
Now you will be able to build your application using your own custom application manifest and still maintain support for runtime themes.

Registration Free Com and dll manifests

I'm trying to setup registration free COM, but have a slight problem in that I another COM object may be the client.
App.exe----->COM Server/Client dll(registered or not)-------->COM Server DLL (NOT Registered)
My questions is, is it possible to create a manifest for the second dll (COM Server/Client dll)? I do not have control of the executable, but if I did, this works if I create a client manifest for the executable and a server manifest for the COM server dll.
this is the manifest file for the middle dll. I tried embedding it and tried it external. Still doesn't work.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type="win32"
name="COMCliSer.dll"
version="1.0.0.0"
/>
<dependency>
<dependentAssembly>
<assemblyIdentity
name="COMSer.dll"
version="1.0.0.0"
/>
</dependentAssembly>
</dependency>
</assembly>
On further investigation, I can get this all to work as long as the middle dll is also registration free and the exe has an application manifest. As soon as I register the middle dll, and drop the application manifest (I do not have control of what exe will use my dll), the whole thing stops working.
If the exe has no manifest, then the dll's manifest is not taken into consideration. I can prove this by setting up everything to work. Then putting a mistake in the assembly manifest. This pops up the usual message :
Unable to create process: This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem.
If I then drop the application manifest, the application loads (albeit the CoCreateInstance fails because the dependencies are not taken into consideration)
Just add an assembly dependency to the server/client dll's manifest that points to the com server dll.
Remember that assembly manifests are different to 'application' manifests: An assembly manifest describes an assembly: gives it a name, and lists its dlls.
An application manifest is the RT_MANIFEST embedded resource, which describes the current modules dependencies.
So, in the final analysis, you would have:
app.exe, with an external (app.exe.manifest) or embedded RT_MANIFEST describing a dependency on an assembly called 'acme.clientserver'
acme.clientserver.manifest describing an assembly, and listing 'clisrv.dll' as a registration free com dll.
clisrv.dll, with an external (clisrv.dll.2.manifest) or embedded RT_MANIFEST, describing a dependency on an assembly called 'acme.server'
acme.server.manifest, describing an assembly, listing serv.dll as a registration free com dll.
serv.dll - which may, or may not in turn have a manifest listing yet more dependent assemblies.
It is technically possible to call the assembly by the dll's name, and merge both the assembly and dll manifest together - the win32 loader supports this, but some settings that are valid in application manifests are not valid in assembly manifests, which can cause the resulting assembly to fail to load. It also makes it very hard to digitally sign.
WRT the exe having to have a manifest: Usually the exe's manifest sets up the processes default activation context. I'm not 100% sure how windows behaves when the exe has no manifest, but I'm pretty sure that manifests in dlls will still be processed.
Which means the problem comes down to the lack of isolation support in CoCreateInstance - for some reason - by default - CoCreateInstance only looks in the default activation context for reg free com entries.
The way to override it is to manually build your own activation context, using the Activation Context API
The basic method would be to call:
CreateActCtx - to create an activation context from your dlls manifest.
ActivateActCtx - to activate the context
CoCreateInstance - will now search the current activation context for reg free com entries.
DeactivateActCtx - to restore the default activation context.
You can add /D ISOLATION_AWARE_ENABLED to wrap most windows calls that are effected by activation contexts, for some reason CoCreateInstance is not wrapped :/

how can a Win32 App plugin load its DLL in its own directory

My code is a plugin for a specific Application, written in C++ using Visual Studio 8. It uses two DLL from an external provider. Unfortunately, my plugin fails to start because the DLLs are not found (I put them in the same directory as the plugin itself).
When I manually move or copy the DLLs to the host application directory, then the plugin loads fine. This moving was deemed unacceptably cumbersome for the end user, and I am looking for a way for my plugin to load its DLLs transparently. What can I do?
Relevant details:
the host Application plugins are located in a directory mandated by the host application. That directory is not in the DLL search path and I don't control it.
The plugin is itself packaged as a subdirectory of the plugin directory, holding the plugin code itself, but also any resource associated with the plugin (eg images, configuration files…). I control what's inside that subdirectory, called a "bundle", but not where it's located.
the common plugin installation idiom for that App is for the end user to copy the plugin bundle to the plugin directory.
This plugin is a port from the Macintosh version of the plugin. On the Mac there is no issue because each binary contains its own dynamic library search path, which I set as I needed to for my plugin binary. To set that on the Mac simply involves a project setting in the Xcode IDE. This is why I would hope for something similar in Visual Studio, but I could not find anything relevant. Moreover, Visual Studio's help was anything but, and neither was Google.
A possible workaround would be for my code to explicitly tell Windows where to find the DLL, but I don't know how, and in any case, since my code is not even started, it hasn't got the opportunity to do so.
As a Mac developer, I realize that I may be asking for something very elementary. If such is the case, I apologize, but I have run out of hair to pull out.
You are not asking for something very elementary. Windows simply does not support what you want.
You have some options to work around this issue:
Create two DLLs. Your plugin implementation dll, that statically links against whatever other dlls you need. And a simple "facade" dll that is loaded by the hosting app. The facade dll gets to call SetDllDirectory then LoadLibrary to load your implementation dll with the required search path, and then, for each plugin exported function, it implements a stub function that uses GetProcAddress to just pass the call straight to your implementation dll.
If the plugin interface is complicated, but the dll interface you are using is not, then:
Give up and just use LoadLibrary (with an explicit path) and GetProcAddress to access the functionality in your satellite dll(s). Pain.
The final option is the least documented and most badly understood by windows programmers. Basically we use the windows version of a technology built to support .NET: Side by Side assemblies. Don't be frightened. A "Side by Side assembly" is very simply a regular old dll, but with a accompanying .manifest file that provides some extra information about it.
The reason we want to do this is the search order for dlls that are linked in via the SxS technology is different to the regular dll search order :- Namely - after searching c:\windows\WinSxS, windows will search the same folder as the dll that references the dll, NOT the folder of the exe.
Start by taking an inventory of all the satellite dlls your plugin dll needs to link to, and create an "assembly" from them. Which means: create a .manifest file with a bunch of file= nodes. You need to give the assembly a name. Lets call it "MyAssembly".
Create the file "MyAssembly.manifest" in your dll's folder, with contents similar to the following: (listing each of the dlls you need to include)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="MyAssembly" processorArchitecture="*" type="win32" version="1.0.0.1"/>
<file name="firstrequireddll.dll"/>
<file name="2ndrequireddll.dll"/>
</assembly>
Now, thats your assembly manifest. We are half done.
The next half is to actually get your dll to use the assembly, and to do that you need to add a manifest resource to your Dll file. That manifest ultimately needs to contain the following content :-
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="MyAssembly" version="1.0.0.1" processorArchitecture="*"/>
</dependentAssembly>
</dependency>
</assembly>
Apparently application manifests (which is a confusing name when embedded in a dll), are also allowed to use a <file> node, so it might be possible to skip creating an assembly, and just go with
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<file name="firstrequireddll.dll"/>
<file name="2ndrequireddll.dll"/>
</assembly>
as the dll's manifest. I havn't toyed with that iteration yet, so Im not sure how that alters the normal dll search path (if at all).
Without knowing your development environment, its hard to know how to advise you how to add a manifest to a dll. If you are editing a .rc file and entering the manifest by hand, know that in Dlls the resource id to use is 2, not 1, which it typically used in exe examples.
If you are using DevStudio 2005 or higher, there is a handy #pragma directive that will make everything magically have the correct id's and be in the correct places.
If the project settings are on their defaults, VS2005 and up will automatically generate, and embed a manifest as appropriate. this #pragma will add additional assembly dependencies
to the generated manifest :-
#if _MSC_VER >= 1400 // VS2005 added this directive
#pragma comment(linker, \
"\"/manifestdependency:type='Win32' "\
"name='Company.Product.Subsystem' "\
"version='6.0.0.0' "\
"processorArchitecture='*' "\
"language='*'\"")
#endif
Delay loaded DLLs are your friend in this situation. I faced the exact same problem a while back and it's actually rather simple. You specify to the linker (/DELAYLOAD flag) which modules are delay-loaded and basically they modules are not listed as explicit imports in the PE header so the loader won't complain when it cannot find the said modules and all the calls to functions from those modules are wrapped in a stub which ensures the module is loaded & the function is found.
So, let's say you wished to delay load the XmlLite library. First you'd specify /DELAYLOAD:XmlLite.dll in the linker flags. Then in your module's initilization function (preferably DllMain) you'd unpack the XmlLite DLL into a temporary folder and then call LoadLibrary on it. From there on it, each call to any function exported by XmlLite.dll would be resolved automatically.
Use GetModuleFileName() to find the path where your dll is located.
Then use SetDllDirectory() to add that path to the dll search path.
Assuming native code and that you can use explicit run-time dynamic link (rather than any form of implicit link), use GetModuleHandle and GetModuleFileName to find out where your dll is running from.
HMODULE hModule = GetModuleHandleW(L"RunningDll.dll");
WCHAR path[MAX_PATH];
GetModuleFileNameW(hModule, path, MAX_PATH);
Then replace the base name of the dll with the name of the plugin.dll you want to load.
CString plugin(path);
int pos = plugin.Find(L"RunningDll.dll");
plugin = plugin.Left(pos);
plugin += L"pluginName.dll";
Call LoadLibrary on the generated string.