We all throw dozens or hundreds of scripts into our Assets folder and let Unity build and load them. Usually this works well, but there are a number of reasons for wanting to compile at least some of your code into a DLL. This post outlines a method for building code with Visual Studio and still being able to debug it with MonoDevelop. For context, I'm using Windows 7 64-bit, Visual C# 2010 Express, Unity 3.5.6 and MonoDevelop 2.8.2 (shipped with Unity 3.5.6). The information should apply to other versions and possibly even to Mac development, but I haven't tested it. The basic approach is: create a .csproj file that uses wildcards to find all source code add this C# project to a Visual Studio solution use an MSBuild post-build target to convert Visual Studio's PDB symbols to Mono's MDB format debug with MonoDevelop from Unity, as normal source code should -not- be in the Assets folder, but the generated DLL should be Here's a handmade .csproj file that demonstrates how this works: Code (csharp): <?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- Common Properties --> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProductVersion>8.0.30703</ProductVersion> <SchemaVersion>2.0</SchemaVersion> <ProjectGuid>{61A89EEC-36B9-49ED-8431-D6A7DBDD9EA7}</ProjectGuid> <OutputType>Library</OutputType> <RootNamespace>UnityDLLExample</RootNamespace> <AssemblyName>UnityDLLExample</AssemblyName> <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> <TargetFrameworkProfile> </TargetFrameworkProfile> <FileAlignment>512</FileAlignment> <!-- Look up Unity install folder, and set the ReferencePath for locating managed assembly references. --> <UnityInstallFolder>$([System.IO.Path]::GetDirectoryName($(registry:HKEY_CURRENT_USER\Software\Unity Technologies\Unity Editor 3.x\Location)))</UnityInstallFolder> <ReferencePath>$(UnityInstallFolder)\Data\Managed</ReferencePath> <MonoMdbGenerator>$(UnityInstallFolder)\Data\MonoBleedingEdge\lib\mono\4.0\pdb2mdb.exe</MonoMdbGenerator> </PropertyGroup> <!-- Debug Properties --> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <Optimize>false</Optimize> <OutputPath>Assets\Plugins</OutputPath> <DefineConstants>DEBUG;UNITY_3_5</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> <!-- Release Properties --> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PlatformTarget>AnyCPU</PlatformTarget> <DebugType>pdbonly</DebugType> <Optimize>true</Optimize> <OutputPath>Assets\Plugins</OutputPath> <DefineConstants>UNITY_3_5</DefineConstants> <ErrorReport>prompt</ErrorReport> <WarningLevel>4</WarningLevel> </PropertyGroup> <PropertyGroup> <StartupObject /> </PropertyGroup> <!-- Microsoft standard stuff. --> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <!-- Find all source --> <ItemGroup> <Compile Include="scripts\**\*.cs" /> </ItemGroup> <!-- References --> <ItemGroup> <Reference Include="System" /> <Reference Include="UnityEngine"> <Private>False</Private> </Reference> </ItemGroup> <!-- Use AfterUnityBuild from Blackbird.CSharp.targets. --> <Target Name="AfterBuild"> <CallTarget Targets="GenerateMonoSymbols" Condition=" Exists('$(OutputPath)\$(AssemblyName).pdb') " /> </Target> <Target Name="GenerateMonoSymbols"> <Message Text="$(ProjectName) -> $(TargetPath).mdb" Importance="High" /> <Exec Command=""$(MonoMdbGenerator)" $(AssemblyName).dll" WorkingDirectory="$(MSBuildProjectDirectory)\$(OutputPath)" /> </Target> </Project> To use this build file, save it as "UnityDLLExample.csproj" in a project folder with the following layout: -project/ -----UnityDLLExample.csproj (from this post) -----UnityDLLExample.sln (create with Visual Studio) -----scripts/ ---------SomeScript.cs (put your source code in here, use sub-folders if you like) -----Assets/ (Unity assets folder) ---------Plugins/ (DLL and MDB files go here) Notes: MonoDevelop doesn't support the Include="**\*.cs" wildcard syntax, so you can't build this project with MonoDevelop you *can* build with the MSBuild command line if you don't want to open Visual Studio if you have editor scripts mixed in with game scripts, you can find them with Include="**\Editor\**\*.cs" if your editor and game scripts are inter-mixed then you need to Include the editor scripts in the editor csproj and Exclude them from the game csproj Unity comes with two versions of pdb2mdb.exe -- only the one in the MonoBleedingEdge folder works for coroutines (that return IEnumerator) for hand-building .csproj files, you can get GUIDs from http://www.guidgen.com/ This thread was started after a bunch of thought and discussion on this other thread. Thanks to @guavaman for getting the ball rolling.
Amazing discovery! I can't believe this much time has passed and nobody noticed this. (Should be added to Unity docs on working with DLLs.) Now I feel silly for re-rolling it. Oh well. And thanks for taking the time to post your DLL debug solution!
Awesome! I can't believe the Unity guys never mentioned that thing about the correct pdb2mdb, and that it wasn't noticed before. Thanks a lot yoyo for this discovery, and guavaman for starting it all
Uhm, I tried using the other pdb2mdb (the one in the MonoBleedingEdge folder) but, while it compiles ok with IEnumerators, it seems it doesn't work, and Unity's log doesn't report error lines (exactly as if you didn't have an MDB at all).
I had previously tested to make sure I could step into the DLL code and see full source. This works as I described. I just checked how Debug.Log messages are handled, and what I see is that the Unity console reports the correct file and line in the shared code (so it is using the information in the MDB), but when I double-click on the error message it opens the script that calls into the DLL code, not the DLL code itself. Note that pdb2mdb.exe generates a new version of the DLL, and that both this updated DLL and the MDB need to be copied into Unity's assets folder.
Mhmm I use Visual Studio, and use a post-build routine to convert the PDB, and then copy all the files in my project's Assets folder (DLL, MDB, and eventually XML), but then the Debug.Log messages show no line at all. I wonder, did you try this with Unity 4, which I'm using? Maybe something different happens, even if it would be weird.
I've only tried with Unity 3.5.6. Do you still have Unity 3.5.x installed that you could test with? Also, have a look in your Visual Studio .csproj file and make sure you've got DebugSymbols=true and DebugType=full in the configuration you're building. I'm not sure both settings are strictly necessary, but that's what I'm using.
Ow, sorry for my previous posts: it works perfectly To call the correct pdb2mdb executable, I was referencing the full path to the correct pdb2mdb, with this line in my post-build command: Code (csharp): c:\Program Files\_Design\Unity 4\Editor\Data\MonoBleedingEdge\lib\mono\4.5\pdb2mdb.exe $(TargetFileName) Which actually did nothing, and I didn't realize (very stupidly), that I was still using an old mdb instead than a newly created one. Now I simply set the default pdb2mdb to the MonoBleedingEdge one, and simply call: Code (csharp): pdb2mdb $(TargetFileName) which works perfectly. Thanks again and sorry for my dumbness
Does anyone have this working on the Mac? We are doing cross-platform development so we have some developers working on Windows and others working in OSX. I can't seem to get the debugger to step into code that is in DLLs on OSX...it just shows the Assembly Browser which doesn't really allow for breakpoints... Of course, it works great on Windows... Any thoughts? Thanks, Chris
Great job guys. Works perfectly with Unity 4. This must improve whole lifecycle for us. Thanks a lot for discovery.
Hi Terikon Would you mind letting us know how you got this working in Unity 4? My registry doesn't contain a Location folder under Unity Editor 4.x, so the build fails. Hard coded the UnityInstallFolder path for now and everything seems to be working great, but that's just a stop gap. Cheers Andrew
I should also mention that breakpoints in the DLL work, and stepping into the DLL works, but "Go to declaration" does not -- MonoDevelop will still open up the Assembly Browser, even though it ought to be able to open the source file for you. On that note, if the Assembly Browser is not showing you disassembled source, you need to go to Tools > Options ... Preferences > Build, and then on the Assembly Folders tab, add your Unity\Editor\Data\Managed folder. Also, I have experienced cases where MonoDevelop did not step gracefully through my DLL source code -- for example F10 (step over) might advance back out of the DLL code. This seemed to be a temporary MonoDevelop glitch, and it went away after closing down and updating the solution.
Whoever is using VS among Unity might check this asset solution, so you will not ever need to add post build events to your project file by just specifying target output folder.
Pay attention, in Unity 4.1 the correct path to the pdb2mdb has changed from "mono/4.5" to "mono/4.0"
Wow, i was trying for some days now, i had copiled using monodevelop before but until now i was not able to compile using vs.net and it works on 2012 too. by the way i think the proper file on unity 4.1 is in 4.5 not in 4.0 folder
Weird! :O Can you confirm that? I can confirm that on my computer it's in 4.0 folder (for Unity 4.1.0 - it was in 4.5 folder in Unity 4.0 instead)
Is anyone else getting this exception? Code (csharp): Unhandled Exception: System.TypeInitializationException: The type initializer for 'Mono.Cecil.Metadata.TableHeap' threw an exception. ---> System.ArgumentException: Value does not fall within the expected range. at System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(Array array, RuntimeFieldHandle fldHandle) at Mono.Cecil.Metadata.TableHeap..cctor() --- End of inner exception stack trace --- at Mono.Cecil.PE.ImageReader.ReadTableHeap() at Mono.Cecil.PE.ImageReader.ReadImage() at Mono.Cecil.PE.ImageReader.ReadImageFrom(Stream stream) at Mono.Cecil.ModuleDefinition.ReadModule(Stream stream, ReaderParameters parameters) at Mono.Cecil.ModuleDefinition.ReadModule(String fileName, ReaderParameters parameters) at Pdb2Mdb.Driver.Main(String[] args) This doesn't seem to be related to the to the project, the same thing happens when I run the command from the command line.
Not sure why, but using the 2.0 pdb2mdb.exe from the mono (not monobleedingedge) solved this problem.
What Unity version are you using? Maybe they changed something in v4.1.2 - which I still didn't install. Does the 2.0 pdb2mdb.exe support IEnumerators and coroutines?
We had the same problems. Using pdb2mdb.exe from 'C:\Program Files (x86)\Unity\Editor\Data\Mono\lib\mono\2.0\pdb2mdb.exe' worked as long as we did not use IEnumerator and yield. When our *.dlls contained yield statements, pdb2mdb.exe always crashed with the following stacktrace: Code (csharp): System.ArgumentNullException: Argument cannot be null. Parameter name: source at System.Linq.Check.SourceAndPredicate (System.Object source, System.Object predicate) [0x00000] in <filename unknown>:0 at System.Linq.Enumerable.Where[PdbLines] (IEnumerable`1 source, System.Func`2 predicate) [0x00000] in <filename unknown>:0 at Pdb2Mdb.Converter.GetSourceFile (Mono.CompilerServices.SymbolWriter.MonoSymbolWriter mdb, Microsoft.Cci.Pdb.PdbFunction function) [0x00000] in <filename unknown>:0 at Pdb2Mdb.Converter.ConvertFunction (Microsoft.Cci.Pdb.PdbFunction function) [0x00000] in <filename unknown>:0 at Pdb2Mdb.Converter.Convert (Mono.Cecil.AssemblyDefinition assembly, IEnumerable`1 functions, Mono.CompilerServices.SymbolWriter.MonoSymbolWriter mdb) [0x00000] in <filename unknown>:0 at Pdb2Mdb.Driver.Convert (Mono.Cecil.AssemblyDefinition assembly, System.IO.Stream pdb, Mono.CompilerServices.SymbolWriter.MonoSymbolWriter mdb) [0x00000] in <filename unknown>:0 When switching to the other pdb2mdb.exe located at 'C:\Program Files (x86)\Unity\Editor\Data\MonoBleedingEdge\lib\mono\4.0\pdb2mdb.exe', it worked for some of our PCs but crashed on others with the error given by amirabiri. Doing some research we found that this is related to the mono version used to execute pdb2mdb.exe. We could fix this issue by using the mono.exe located at 'C:\Program Files (x86)\Unity\Editor\Data\MonoBleedingEdge\bin\mono.exe'. The command looks like (make sure the cwd is where the dll is located) Code (csharp): "C:\Program Files (x86)\Unity\Editor\Data\MonoBleedingEdge\bin\mono.exe" "C:\Program Files (x86)\Unity\Editor\Data\MonoBleedingEdge\lib\mono\4.0\pdb2mdb.exe" Your.dll
It's working ok on Unity 4.3, even with IEnumerator Path to pdb2mdb is "C:\Program Files (x86)\Unity\Editor\Data\MonoBleedingEdge\lib\mono\4.0\pdb2mdb.exe"
I'm finally on Unity 4.3, and yes, Data\MonoBleedingEdge\lib\mono\ 4.0\pdb2mdb.exe is still needed. I first tried Data\Mono\lib\mono\ 2.0\pdb2mdb.exe but it still has the IEnumerator problem we had with Unity 3.5. I also noticed that you have to run pdb2mdb.exe with the working folder set to the location of the DLL you are converting.
Ironically, Unity 4 uses the "Unity Editor 3.x" registry key, so no change is required. (I consider this a bug, but whatever.)
I Updated your script to work with Unity 5. As a bonus, I saved this file as a .targets file and just import this file in the csproject. (so I can edit the file after I work with multiple projects) Code (CSharp): <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <!-- Look up Unity install folder, and set the ReferencePath for locating managed assembly references. --> <UnityInstallFolder>$(registry:HKEY_CURRENT_USER\Software\Unity Technologies\Installer\Unity@Location x64)</UnityInstallFolder> <ReferencePath>$(UnityInstallFolder)\Editor\Data\</ReferencePath> <MonoFolder>$(UnityInstallFolder)\Editor\Data\MonoBleedingEdge</MonoFolder> <MonoMdbGenerator>$(MonoFolder)\lib\mono\4.5\pdb2mdb.exe</MonoMdbGenerator> <MonoCLI>$(MonoFolder)\bin\cli.bat</MonoCLI> </PropertyGroup> <Target Name="AfterBuild"> <CallTarget Targets="GenerateMonoSymbols" Condition=" Exists('$(OutputPath)\$(AssemblyName).pdb') " /> </Target> <Target Name="GenerateMonoSymbols"> <Message Text="Unity install folder: $(UnityInstallFolder)" Importance="high" /> <Message Text="$(ProjectName) -> $(TargetPath).mdb" Importance="High" /> <Exec Command=""$(MonoCLI)" "$(MonoMdbGenerator)" $(AssemblyName).dll" WorkingDirectory="$(MSBuildProjectDirectory)\$(OutputPath)" /> </Target> </Project>
Thanks! I've added Code (CSharp): <Exec Command="$(PostBuildEvent)" Condition=" Exists('$(PostBuildEvent)') " /> right after the "GenerateMonoSymbols" CallTarget, enabling additional project specific post build events like copy statements etc.
I've gone ahead and made a VS Project Template from that .targets file, so you can create a pre-configured project for your DLLs and just move your files over. I uploaded it to the unifycommunity wiki. http://wiki.unity3d.com/index.php/Visual_Studio_Unity_Assembly_Template Cheers
Hi, I'm using Unity 4.7.8 on OSX I can launch the debugger but can't step inside the source code of my external dll. I created a simple test.dll to get a better idea of what the problem could be. This dll is created under Visual Studio 2015 using .net 3.5. I used pdb2mdb.exe shipped with Unity 4.7.8 Windows version to generate the mdb file from test.dll call "C:\Program Files (x86)\Unity\Editor\Data\MonoBleedingEdge\bin\cli.bat" "C:\Program Files (x86)\Unity\Editor\Data\MonoBleedingEdge\lib\mono\4.0\pdb2mdb.exe" "$(TargetDir)\$(TargetName).dll" The mdb file is correctly generated. I copied the .dll.and mdb (and even the pdb to be sure) files into my Unity project Assets/Plugins folder, refreshed/sync monodevelop solution and made sure the referenced dll is the good one. Also try to reload Unity project. But I can't step into the source code of the external dll when debugging. When I press the step-in button it stays on the same line and the line highlighting turns from yellow color to green. [UPDATE] I did another test but using MonoDevelop/Xamarin. I created a simple test.dll file and moved the .dll and .mdb file into Assets/Plugins and it worked; I can step into the external dll source file when debugging. The problem seems to be with the dll generated by visual studio .net v3.5. Then I installed mono framework on Windows and compiled my csproj with mono's xbuild. The result: I cannot step into the .dll with theses. So it seems it's not related to using Microsoft .net or Mono, for compilation, but _maybe_ related to an incompatibility between dll compiled on windows versus osx. [UPDATE] It's working on windows. So my final conclusion is that you cannot create a dll and mdb from visual studio and use its debug symbols with mono develop on OSX, it's not compatible. A solution for someone who wants to work on OSX only would be to switch to Xamarin Studio IDE instead of Visual Studio. In my case I just can't do this as I'm using PlayerIO which depend on Microsoft .NET framework. I'm better off working on Windows until it's fixed.