DLL Problems

Nov 8, 2015 at 1:35 AM
I've been having DLL problems for a long time now, so I'm going to outline my problems. Please just let me know what I have to do to diagnose issue. I've streamlined my DLL setup. Here's my setup.

Image

Previously, I've had no luck in automatically loading DLLs. My plugin doesn't seem to load DLLs from the app folder. It always complains that it can't load the necessary assemblies. So, I've done this:
        private  System.Reflection.Assembly LoadAssembly(string uiDLLName)
        {
            var bytes = File.ReadAllBytes(Path.Combine(GlobalVariables.MySynthPath, uiDLLName));
            var assembly = AppDomain.CurrentDomain.Load(bytes);
            return assembly;
        }

        public MySynthPluginStub()
        {
            LoadAssembly("MySynth.AudioProcessing.dll");
            LoadAssembly("MySynth.PluginManagement.dll");
            LoadAssembly("MySynth.Modules.dll");
            LoadAssembly("MySynth.Native.dll");

           //...
        }
This seems to at least load the DLLs in to the app domain. I'm OK with this method. I don't know why it's necessary but I don't care right now. However, after a refactor, I've got a big problem where I can't load UI at all.

I did a big refactor to completely extricate the UI from the plugin. This is so that I can have a WPF UI, and eventually work on a UWP UI as well, but not change the core code at all. My main project with the plugin stub doesn't have a reference to the UI at all. The UI DLL is loaded dynamically. This is how I am trying to do it:
        private void GetEditorControl(string uiDLLName)
        {
            System.Reflection.Assembly assembly = LoadAssembly(uiDLLName);
            var editorType = assembly.GetTypes().Where(t => t.GetInterface("IEditorControl", false) != null).FirstOrDefault();
            _EditorControl = (IEditorControl)Activator.CreateInstance(editorType);
            _EditorControl.MySynthPluginStub = this;
        }
The text here will go in a config file eventually:
        public VstPluginInfo GetPluginInfo(IVstHostCommandStub dawHost)
        {
            try
            {
                _HostStubAdapter = new HostStubAdapter(dawHost);

                //TODO: Unhard code this 
                var uiDLLName = "MySynth.WPF.dll";
                GetEditorControl(uiDLLName);

                UpdatePluginInfo();
                return GetPluginInfo();
            }
            catch (Exception ex)
            {
                return null;
            }
        }
But, this is the error I get:

System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
at System.Reflection.RuntimeModule.GetTypes()
at System.Reflection.Assembly.GetTypes()
at MySynth.MySynthPluginStub.GetEditorControl(String uiDLLName)
at MySynth.MySynthPluginStub.GetPluginInfo(IVstHostCommandStub dawHost)

Loader Exception:
[0] = {"Could not load type 'MySynth.IEditorControl' from assembly 'Jacobi.Vst.Interop, Version=1.1.0.0, Culture=neutral, PublicKeyToken=fa678e13c1efc859'.":"MySynth.IEditorControl"}

I have no idea why .Net is looking for my interface in the Jacobi.Vst.Interop assembly, but I suspect it's because the interop DLL is renamed to MySynth.dll and .Net thinks that that's where it can find the type IEditorControl.

I'm very stuck. I really need to get over this hump.
asd
Nov 8, 2015 at 2:43 AM
Actually, now that I think about it, it's pretty clear what is going on.

The MySynth.WPF.dll assembly is getting compiled with a reference to MySynth.dll, but of course, at runtime, that physical DLL is actually the interop assembly. so, .Net tries to load the type from MSynth.dll but mistakenly looks in the file nanmed "MySynth.dll" which is actually the interop dll.

What can I do about this?
Nov 8, 2015 at 7:24 AM
You need to enable Fusion logs. It will tell you what the missing DLL path is resolving to. When that is established you need to find a way to put your DLL with the right name at the right place. You could also put the right DLL path in Machine.config and/or GAC util. IMO Vst.Net plugins have a few issues, DLL path resolving is one of them, judging by the amount of questions on this subject it isn't trivial. However the quick fix is easy, fusion log tells you where the missing DLL should be and if you copy it there with the right name the issue will go away. The app domain issues when your host is hosting itself as a plugin and the mouse and keyboard events in WPF are problems I haven't got the time to look into. They might not have such a quick and easy fix.
Nov 8, 2015 at 7:29 AM
I'd bet your DLL path is resolving to your host path... something like c:\program files\HostVendor\host.exe. I wish Vst.Net could do self contained single vst dll, that would bypass these issues. It is the norm for Vst coded in c++. Anyway that's not a show stopper for me because there are many workarounds. The most obvious one is registering your DLL in GAC util.
Coordinator
Nov 8, 2015 at 8:07 AM
I would try to rename so that the (renamed) plugin part is different from the internal/host part. Then you should not have to custom dynamic-load dlls. Just reference them in your project and the VST.NET assembly loader will load them for you from the plugin path (where the renamed interop is located). At least that is how it was intended.

Try to set that up and tell us what problems you encounter...
Nov 8, 2015 at 9:35 AM
Edited Nov 8, 2015 at 8:33 PM
Sorry I don't have time for a full reply here. Just something quick. As I mentioned above, this has got nothing to do with DLLs not being loaded.

The issue here is that VST.Net is tricking .Net. The assembly MySynth.WPF has a reference to MySynth.dll. So, it tries to load a type from that DLL, but of course, the DLL MySynth.dll is actually the interop DLL, so .Net gets confused and throws an error. It actually makes perfect sense. I just don't know what to do about it.

Obiwan: your advice sounds like it might be on the right path, but I don't really follow...

I'm assuming that VST.Net traverses the types my plugin assembly (which doesn't have a dll extension) looking for IVstPluginCommandStub implementations right? It then instantiates an instance of the type right? But, I think the problem is that my UI assembly needs to reference this DLL, and can't because it's been renamed.

PS: I have tried manually loading MySynth.dll in to the app domain. It just causes an error because the assembly is loaded in to the app domain twice (under two different physical names).
Nov 9, 2015 at 6:49 AM
YES!!!!!

I figured out a workaround. I moved everything down in to a lower level assembly (including my VST plugin stub).

Then, I simply inherited from the plugin stub in the main assembly named MySynth.net.vstdll (that gets renamed).

This is literally, all the code inside the DLL:

namespace MySynth
{
public class MySynthPluginWrapper : MySynthPluginStub
{
    public MySynthPluginWrapper() : base()
    {
    }
}
}

I can now load the UI assembly dynamically. I complete extrication of the UI from the processing.
Nov 9, 2015 at 6:53 AM
Just something to nitpick at here, but the class IVstPluginCommands10 references a type in System.Drawing which is UI specific. It might be better if EditorGetRect(out Rectangle rect); dealt with a non-UI specific type. It just means that I can't remove the System.UI assembly reference.
Coordinator
Nov 9, 2015 at 7:41 AM
I still think you shouldn't need to be loading any assemblies dynamically - unless you generate them on the fly or something...

I purposely chose to reuse the existing Rect. The assembly is available on any machine and the code is 'proven'. I admit the reference to Drawing might seem a little odd but should not be a problem.

Is there a concrete need to be able to remove the reference?
Nov 9, 2015 at 9:13 AM
Yes and no. I guess its a WPF class, or perhaps a Windows Forms class. It certainly won't be available in UWP - but that's not a big deal right now.

It's just about making clear distinctions between UI and processing code.
Coordinator
Nov 9, 2015 at 9:34 AM
UWP is good point - that would need to be mapped to Windows.Foundation.Rect https://msdn.microsoft.com/en-us/library/windows/apps/windows.foundation.rect.aspx
or indeed as you suggest write out own...

Making a clear distinction is hard. The VST Editor interface methods could be considered UI...
Coordinator
Nov 9, 2015 at 9:36 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Nov 10, 2015 at 1:34 AM
Edited Nov 10, 2015 at 1:35 AM
obiwanjacobi wrote:
I still think you shouldn't need to be loading any assemblies dynamically - unless you generate them on the fly or something...
  • I concur and reducing assemblies count is a priority for me.
I purposely chose to reuse the existing Rect. The assembly is available on any machine and the code is 'proven'. I admit the reference to Drawing might seem a little odd but should not be a problem.
  • Isn't the drawing namespace a member of the System.Windows.Forms assembly? If so then is it possible to handle UI in a Vst.Net host or plugin without a reference to WindowsForms?
Is there a concrete need to be able to remove the reference?
  • I'm tied to System.Windows.Forms assembly. I'd like to remove it but it seems that it is a mandatory bridge to host Win32 UI controls properly. If that's the case then I'd say there's no need to remove it. Otherwise I'd want it out for sure.
Nov 10, 2015 at 2:03 AM
@Kruddler
Actually that could explain your problems with keyboard input. Are you using a WindowsFormHost and a WinForms panel for hosting Vst plugin UI? If so then I believe you'd be tied to System.Drawing.dll anyway. If not then that might explain the problems with your message pump.
Nov 13, 2015 at 7:21 AM
Edited Nov 13, 2015 at 7:22 AM
Now I'm having an issue where I basically can't debug. I think it's because I'm loading the DLLs dynamically.

I still think you shouldn't need to be loading any assemblies dynamically

It's not as though I want to load them dynamically. They are sitting in the same folder as my main DLL, but they don't get picked up unless I load them dynamically. As I've said, I've checked the fusion logs in the past, but it's looking for my DLLs in the Ableton program folder.

Actually that could explain your problems with keyboard input. Are you using a WindowsFormHost and a WinForms panel for hosting Vst plugin UI? If so then I believe you'd be tied to System.Drawing.dll anyway. If not then that might explain the problems with your message pump.

My UI is all WPF. I have not yet tried the idea that you suggested where I host Vst UIs inside a Windows Forms panel yet...
What makes you think that this might be affecting the message pump?
Nov 13, 2015 at 8:51 AM
Whoa!

Just as I said that, I deleted the manual DLL loads, and it just works now. I have no idea what I am doing differently, but fuck yeah!
Nov 13, 2015 at 8:09 PM
Edited Nov 13, 2015 at 8:13 PM
As I've said, I've checked the fusion logs in the past, but it's looking for my DLLs in the Ableton program folder.
  • Assembly loading is multi-level, first machine.config then GacUtil then start up folder.. if start up folder is wrong then a fix could be putting the path at a higher level (machine.config, gacutil..)
Just as I said that, I deleted the manual DLL loads, and it just works now. I have no idea what I am doing differently, but fuck yeah!
  • I'd venture fusion log could tell you what happened, at least it will tell you through which mecanism (machine.config, gacutil, startup path) it used to resolve the correct assembly path.
What makes you think that this might be affecting the message pump?
  • To put it simply WPF doesn't expose a traditional WIN32 message pump and controls do not exhibit the standard behavior expected from controls that derived from the window class (HWND every control is a Window concept).
This MSDN page talks about the issue precisely including KeyDown handling on a shared Win32/WPF message pump:
Sharing Message Loops Between Win32 and WPF
https://msdn.microsoft.com/library/aa348549%28v=vs.100%29.aspx

If you happen to make it work using hooks or a custom message pump I'd be interested to know what solution you found as I expect it to be non trivial and maybe even not stable.
Nov 15, 2015 at 9:06 PM
Edited Nov 15, 2015 at 9:06 PM
Apologies on this one. It's inexplicable as to why this wasn't working in the first place, but something I've done over time has fixed the issue which is a big load off my mind. It means that I can now successfully debug and DLLs are automatically loaded from the Vst folder.

To put it simply WPF doesn't expose a traditional WIN32 message pump and controls do not exhibit the standard behavior expected from controls that derived from the window class (HWND every control is a Window concept)

Moving this to the other thread about not accepting key strokes...
Nov 22, 2015 at 4:01 AM
I'm back to square one on this one. Today, I tried to load a control from external assembly, and I'm back to the issue where .Net wants to load DLLs from the Ableton folder as opposed to my synth's app pah. this is driving me CCCRRRAAAAASZZZZYYYYY!!!!

Ok. So,
-The dll is in the same path as my synth's DLL
-I added the reference to the DLL in the normal way in Visual Studio. There's no dynamic loading going on.
-If I call AppDomain.CurrentDomain.GetAssemblies(), I can see that the DLL is actually sitting in the app domain.

Why isn't .Net looking for the DLL that's already loaded in the AppDomain?

This is the fusion log from the exception.:
=== Pre-bind state information ===
LOG: DisplayName = VSTControls.Net, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
(Fully-specified)
LOG: Appbase = file:///C:/ProgramData/Ableton/Live 9 Suite/Program/
LOG: Initial PrivatePath = NULL

Calling assembly : (Unknown).

LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file:
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net.DLL.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net/VSTControls.Net.DLL.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net.EXE.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net/VSTControls.Net.EXE.
Nov 22, 2015 at 4:21 AM

I'm not sure what you are seeing in the AppDomain but it's most likely some form of metadata. Lazy loading of the assembly occurs on first reference use at runtime which is in the Xaml parser in this case.


LOG: This bind starts in default load context.
> No good, that's the path of the host process 'C:/ProgramData/Ableton/Live 9 Suite/Program/' and can't be changed, it's out of your control.

LOG: No application configuration file found.
> You can try having some form of VSTControls.Net.dll.config and put it somewhere, who knows..

LOG: Using host configuration file:
> You can try GAC util.

LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
> That's surefire any reference you put in there will work,

LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
> From here on it looks like it's on last try.

LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net.DLL.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net/VSTControls.Net.DLL.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net.EXE.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net/VSTControls.Net.EXE.

As you can see there isn't any good mechanism available to do assembly loading in a plugin context. All system components are using the GAC for reliable loading in a COM scenario. Vst in C++ tend to be standalone and do not operate in a stricly COM fashion. I speculate Vst.Net has a trick to load assembly dynamically, maybe it's asking for it's physical file image path at runtime and then using a convetion like YourPluginName.Vst.Net.dll or something in order to fill the missing explicit path info. What to do after this trickery in order to load other assemblies? Maybe you can reuse Vst.Net loader and use the same conventions.



Coordinator
Nov 22, 2015 at 6:23 AM
I find it very odd that it started working and now it doesn't ...? Again?

Can you provide a small sample of this problem you are having so I (we) can look into it?

Thanx,
Marc
Nov 22, 2015 at 6:57 AM
Actually, it turns out that I get the same error when I try to load this control from another WPF app. So, now, I don't know if it has anything to do with VST.Net. Sorry, this may have been a false alarm.
Nov 23, 2015 at 9:05 AM
Edited Nov 23, 2015 at 9:25 AM
Well, in the sample app the error was coming up, but for a different reason. In the sample app, it was compiling for all CPUs while the referenced DLL was x86. So, as soon as I changed the sample to x86, the problem went away.

But, the problem still happens in my synth. I'm totally stuck.

The basic problem is that .Net or VST.Net is simply not looking for the DLL in the folder of my plugin. I'm pulling my hair out. I'm stuck.

Isn't there some way I can direct .Net or VST.Net to look for my DLL in the directory where my plugin resides?

Well, you might think that using probing path might help... So, I put a config file in my plugin's directory called MySynth.dll.config. It reads:
<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <probing privatePath="C:\Program Files (x86)\VstPlugins\MySynth"/>
      </assemblyBinding>
   </runtime>
</configuration>
But, the fusion logs tell me they are not even going to look at my config file.

=== Pre-bind state information ===
LOG: DisplayName = VSTControls.Net, Version=0.0.0.0, Culture=neutral, PublicKeyToken=443827bb1fb58b20
(Fully-specified)
LOG: Appbase = file:///C:/ProgramData/Ableton/Live 9 Suite/Program/
LOG: Initial PrivatePath = NULL

Calling assembly : (Unknown).

LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file:
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Post-policy reference: VSTControls.Net, Version=0.0.0.0, Culture=neutral, PublicKeyToken=443827bb1fb58b20
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net.DLL.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net/VSTControls.Net.DLL.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net.EXE.
LOG: Attempting download of new URL file:///C:/ProgramData/Ableton/Live 9 Suite/Program/VSTControls.Net/VSTControls.Net.EXE.

PS: Putting the DLL in the GAC resolves the problem. But, I'm trying to avoid doing that. I'm intent on xcopy deployment. I will use this as a workaround for now.
Coordinator
Nov 23, 2015 at 9:46 AM
VST.NET has code that does exactly that - loading dependencies from the plugin folder.

I am confused about the scenario you are using here...

If you can provide a sample that exhibits the problem, I can look into it.
Nov 23, 2015 at 8:20 PM
Maybe it's a case of loading a dependency which itself has dependency in the same folder?
The first loads OK but the second one is not seen by the dynamic loader (if there is a dynamic loader in Vst.Net).

If you had the correct StartUpPath you could load assemblies dynamically.

I don't know of any full proof mechanism to get that path in a Host/Plugin context but you can something along the lines of:
String location = System.Reflection.Assembly.GetExecutingAssembly().Location;
String startUpPath = System.IO.Path.GetDirectoryName(location);
System.Reflection.Assembly.LoadFile(System.IO.Path.Combine(startUpPath, "YourAssembly.dll"));
Coordinator
Nov 24, 2015 at 5:40 AM
VST.NET does have a loader. It simply attaches the AssemblyResolve event on the current AppDomain.
https://vstnet.codeplex.com/SourceControl/latest#Source/Code/Jacobi.Vst.Core/Plugin/AssemblyLoader.cs
My understanding was that all references that could not be found went through this event - so also dependencies of dependencies etc...

The loader attempts to load the Assembly from the plugin folder - where the renamed interop dll is located.
It also looks in several other places, like the vstnetProbePath from the exe.config file. It does assume the .dll extension.

It uses the FileFinder class to search for the file and determine the location to load from.
https://vstnet.codeplex.com/SourceControl/latest#Source/Code/Jacobi.Vst.Core/Plugin/FileFinder.cs
The FileFinder has a collection of base paths and file extensions and searches for a specific file name with one of the extension is all of the paths.
Nov 25, 2015 at 8:28 PM
"Maybe it's a case of loading a dependency which itself has dependency in the same folder? "

Yury. I suspect you are right.
Coordinator
Nov 28, 2015 at 6:31 AM
For the record:

The problem lies with VST.NET when the plugin loads dependent assemblies after it has been constructed.
VST.NET does not hang on to the plugins folder and does not know where to look for the assembly.

I have made an issue for this.

The work around is:
Hope it helps,
Marc