This project has moved. For the latest updates, please go here.

Single C# DLL plugin with .Net 2.0 (and later) - Yes, it is possible

Oct 21, 2009 at 6:10 PM
Edited Oct 22, 2009 at 8:54 AM

You don't have to wait for .Net 4.0 to create a single DLL C# VST plugin. Using a technique I found on the internet (called reverse P/Invoke), I was able to write a little utility program that runs as a post-build step and does the following:

  1. Decompiles the C# DLL into IL code
  2. Parses throught the IL text file and looks for a special DllExport attribute that tags a method that needs to be exported as a C function
  3. Modifies the IL text file to export the method as a C function (reverse P/Invoke)
  4. Recompiles the IL text file back into a C# DLL.

This happens very fast after each build and isn't even noticeable (takes less than a second). The recompile even creates a new pdb file so that you can debug the DLL just like any other C# DLL. The only thing that doesn't work is Edit & Continue, and even though I usually use it a lot in other projects, it isn't a huge loss.

If you think about it for a while, you will realize why this works: All .Net languages are compiled down to IL code, including managed C++. This implies that IL itself supports reverse P/Invoke, since managed C++ does. Unfortunately this functionality is not exposed all the way through to C# (there is no DllExport attribute). But by doing all the steps above, it is possible to add the neccesary attributes to mark methods to be exported.

To find out more about this reverse P/Invoke mechanism, start here:

http://www.codeproject.com/KB/dotnet/DllExporter.aspx

http://www.csharphelp.com/archives3/archive500.html

http://www.codeproject.com/KB/dotnet/DllExport.aspx?fid=356836&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26

http://www.codeproject.com/KB/dotnet/emilio_managed_unmanaged.aspx

A couple of points:

  1. A lot of the code from the above links are buggy. I had to rewritea lot of it to make it work. I ended up writing a completely new utility that works the way I want it to work.
  2. Some of the examples above do not deal with calling convention. One of the examples do, and you need to use that since a VST plugin needs to use the cdecl calling convention, which is not the default.
  3. None of the examples use the /pdb command line switch when recompiling back into a DLL. You need to specify that if you want to debug the resulting DLL in Visual Studio.

If enough people show interest, I might post a working VS project that contains all the required code to create a functional single file C# VST plugin DLL.

EDIT: BTW, using the above technique, I created a single plugin base class that you derive from and override methods like OnGetVendorString, OnCanDo, OnPluginOpen, OnEditorOpen, OnProcess32, OnProcess64, etc. I think this is a much cleaner implementation than what the current VST.Net framework provides. Simple and it works well. I was also able to add code that makes your plugin UI resizable in most hosts by simply resizing the plugin window itself.

BitFlipper

Coordinator
Oct 21, 2009 at 7:45 PM

Sounds very interesting! If you could sent me the code of your base class then I can get my around the way this works (with custom marshaling types/structs etc).

Thanx very much.

Oct 21, 2009 at 8:50 PM
Edited Oct 22, 2009 at 6:17 AM

I posted the code here:

www.alienworks.com\files\VstDotNet\DotNetVstPlugins.zip

A couple of notes:

  1. You will need to modify the post build steps in the "Chords" and "PluginTemplate" projects to set your correct VST folder path. I made the project relatively portable since I sync it between my desktop and tablet PC, and it compiles and runs on both. Just update the VST paths though.
  2. You need to compile both projects under the "Utilities" folder. One project is the DllExport utility, the other is a SyncFolders utility that will sync two folders depending on which files are newer. This runs as a pre-build step and is required because the base class files are copied from the "PluginTemplate" project into any other project you might have. This is because you can't create typical dependencies on other projects since we want a single DLL. Using the SyncFolders utility, you can make changes to the base classes in any other project that uses the base classes and those changes will automatically be copied back to the PluginTemplate project, and then to any other plugin projects you might also have.
  3. You will need to modify the Debug properties that point to your host path as well as the project path to load when you start debugging.

If you run into any problems, please let me know.

BitFlipper

Coordinator
Oct 21, 2009 at 10:56 PM
Edited Oct 21, 2009 at 10:57 PM

I can see how this works. But a lot would ride on the quality of the DllExport.exe. 

Thanx very much for bringing this to my attention. I will further investigate this option and the consequence of having a customized post-build step.

I'm also very curious about the performance difference between Reversed P/Invoke and mixed C++ ;-)

Oct 21, 2009 at 11:14 PM
obiwanjacobi wrote:

I can see how this works. But a lot would ride on the quality of the DllExport.exe. 

Thanx very much for bringing this to my attention. I will further investigate this option and the consequence of having a customized post-build step.

I'm also very curious about the performance difference between Reversed P/Invoke and mixed C++ ;-)

True you depend on DllExport being reliable. In fact I know that there are bugs in it because I saw it fail when I did various different things, but if you use it in its current form with the supplied PluginBase class, it should be very reliable (only 1 C# method needs to be exported). Also, because the DllExport code is right there, you can always fix it if a bug pops up. As far as a customized post-build step... It really is seamless. I don't even notice it unless I carefully look at the build output. The whole process is also very fast - it looks like roughly 0.5 seconds to run the DllExport post-build step.

As far as performance, I think it will be an improvement because you are cutting out a few intermediate calls (the whole managed C++ wrapper).

Overall, for me it is a much better solution than the managed C++ intermediate assembly. Things are just really much cleaner.

Nov 12, 2009 at 7:53 PM

BTW,

I found an additional nice feature of using this mechanism to create a single C# DLL:

After I compile the C# DLL, I have a post-build step that calls my DllExport utility. I have now updated this utility so that you can specify that it should create a 32-bit VST DLL, as well as a 64-bit VST DLL. For instance if my original VST DLL was called MyPlugin.dll, and I then run DllExport (all automated as a post-build step, of course), it pops out two new DLLs, MyPlugin x86.dll (32-bit) and MyPlugin x64.dll (64-bit). As far as VST hosts are concerned, one is a native 32-bit DLL and the other is a native 64-bit DLL. So 64-bit hosts can use the 64-bit version natively (instead of having to go through a wrapper like BitBridge in the case of Sonar x64). And the 64-bit DLL is a true 64-bit DLL: Pointers are 64-bit pointers. And I found that I really only had to make very minimal changes in my C# code to handle both platforms. The only thing really was that my VST structures are broken up (in my case the header and the body are different structures in cases like VstMidiEvent, etc), so in 2 cases I had to special case the pointer offsets when converting to/from managed/unmanaged. Other than that the code just works on 64-bit.

Seriously... How cool is that!!??

Coordinator
Nov 13, 2009 at 8:57 AM

Very cool!

Unfortunately, I am a complete x64 noob. I also run on 32 bit system myself (XP). But supporting 64 bit is something that should be in VST.NET, eventually ;-).

I have a question for you:
Does the use of you DllExport post-build utility mean that all managed VST structure must look like their unmanaged counterparts?

I made a very deliberate decision to leave some of the fields out of the managed structures. For one, I think their version scheme sucks with allocating extra room at the end of the structure. But also I do not need a separate length field for an array for instance. So my managed VST structures are similar but not exactly the same as the unmanaged VST structures. How do you handle the variable length structures like VstEvents and VstEvent?

Nov 13, 2009 at 5:25 PM
obiwanjacobi wrote:

Very cool!

Unfortunately, I am a complete x64 noob. I also run on 32 bit system myself (XP). But supporting 64 bit is something that should be in VST.NET, eventually ;-).

I have a question for you:
Does the use of you DllExport post-build utility mean that all managed VST structure must look like their unmanaged counterparts?

I made a very deliberate decision to leave some of the fields out of the managed structures. For one, I think their version scheme sucks with allocating extra room at the end of the structure. But also I do not need a separate length field for an array for instance. So my managed VST structures are similar but not exactly the same as the unmanaged VST structures. How do you handle the variable length structures like VstEvents and VstEvent?

The use of DllExport does not require any changes in the way the structures look or work, nor does it modify the structures in any way. After DllExport the code is still 100% managed like it was before. In fact all DllExport does is twiddle some flags that tells the .Net runtime how to load the DLL, and adds some flags to the methods that needs to be exported (which is fully supported by the .Net framework but not exposed via C#). No structures are touched. In fact if you use Reflector and look at the original vs post-DllExport DLLs, they should be identical.

As far as how I handle the structures (or classes)... In some cases I break the class up into two, like in the case of VstEvent. I have a VstEventHeader that contains only the type and byte size properties. Once I read that class using Marshal.PtrToStructure, I know what follows and then offset the IntPtr value to point to the new memory location and read the "body" of the event into either a VstMidiEvent or a VstSysexEvent class (which doesn't contain the header part). In some cases I have managed-specific classes that only copy over the properties I want when I get it from the unmanaged side, or I will simply mark the reserved or unneeded properties as private so you never see them anywhere else. Of course you can call the properties anything you want, they don't need to follow the unmanaged names.

Also, since the managed version of a class doesn't need the variable lenght array at the end, I simply leave it out of the class altogether. When reading in the class by using Marshal.PtrToStructure, it will then simply read up to the variable array and then afterwards I just read in the variable array using other means. This is one of the few places I needed to have 32-bit vs 64-bit specific code. It looks something like:

IntPtr bodyPtr = hdrPtr.Offset(IntPtr.Size == 4 ? 8 : 16);

Where IntPtr.Offset is an extension method I added to IntPtr.  IntPtr.Size will be 4 on 32-bit and 8 on 64-bit.