Search Unity

How to build and debug external DLLs

Discussion in 'Scripting' started by yoyo, Dec 7, 2012.

  1. yoyo

    yoyo

    Joined:
    Apr 16, 2010
    Posts:
    112
    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:
    1. create a .csproj file that uses wildcards to find all source code
    2. add this C# project to a Visual Studio solution
    3. use an MSBuild post-build target to convert Visual Studio's PDB symbols to Mono's MDB format
    4. debug with MonoDevelop from Unity, as normal
    5. 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):
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    3.  
    4.   <!-- Common Properties -->
    5.   <PropertyGroup>
    6.     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    7.     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    8.     <ProductVersion>8.0.30703</ProductVersion>
    9.     <SchemaVersion>2.0</SchemaVersion>
    10.     <ProjectGuid>{61A89EEC-36B9-49ED-8431-D6A7DBDD9EA7}</ProjectGuid>
    11.     <OutputType>Library</OutputType>
    12.     <RootNamespace>UnityDLLExample</RootNamespace>
    13.     <AssemblyName>UnityDLLExample</AssemblyName>
    14.     <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    15.     <TargetFrameworkProfile>
    16.     </TargetFrameworkProfile>
    17.     <FileAlignment>512</FileAlignment>
    18.    
    19.     <!-- Look up Unity install folder, and set the ReferencePath for locating managed assembly references. -->
    20.     <UnityInstallFolder>$([System.IO.Path]::GetDirectoryName($(registry:HKEY_CURRENT_USER\Software\Unity Technologies\Unity Editor 3.x\Location)))</UnityInstallFolder>
    21.     <ReferencePath>$(UnityInstallFolder)\Data\Managed</ReferencePath>
    22.     <MonoMdbGenerator>$(UnityInstallFolder)\Data\MonoBleedingEdge\lib\mono\4.0\pdb2mdb.exe</MonoMdbGenerator>
    23.    
    24.   </PropertyGroup>
    25.  
    26.   <!-- Debug Properties -->
    27.   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    28.     <PlatformTarget>AnyCPU</PlatformTarget>
    29.     <DebugSymbols>true</DebugSymbols>
    30.     <DebugType>full</DebugType>
    31.     <Optimize>false</Optimize>
    32.     <OutputPath>Assets\Plugins</OutputPath>
    33.     <DefineConstants>DEBUG;UNITY_3_5</DefineConstants>
    34.     <ErrorReport>prompt</ErrorReport>
    35.     <WarningLevel>4</WarningLevel>
    36.     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    37.   </PropertyGroup>
    38.  
    39.   <!-- Release Properties -->
    40.   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    41.     <PlatformTarget>AnyCPU</PlatformTarget>
    42.     <DebugType>pdbonly</DebugType>
    43.     <Optimize>true</Optimize>
    44.     <OutputPath>Assets\Plugins</OutputPath>
    45.     <DefineConstants>UNITY_3_5</DefineConstants>
    46.     <ErrorReport>prompt</ErrorReport>
    47.     <WarningLevel>4</WarningLevel>
    48.   </PropertyGroup>
    49.   <PropertyGroup>
    50.     <StartupObject />
    51.   </PropertyGroup>
    52.  
    53.   <!-- Microsoft standard stuff. -->
    54.   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
    55.  
    56.   <!-- Find all source -->
    57.   <ItemGroup>
    58.     <Compile Include="scripts\**\*.cs" />
    59.   </ItemGroup>
    60.  
    61.   <!-- References -->
    62.   <ItemGroup>
    63.     <Reference Include="System" />
    64.     <Reference Include="UnityEngine">
    65.       <Private>False</Private>
    66.     </Reference>
    67.   </ItemGroup>
    68.  
    69.   <!-- Use AfterUnityBuild from Blackbird.CSharp.targets. -->
    70.   <Target Name="AfterBuild">
    71.     <CallTarget Targets="GenerateMonoSymbols" Condition=" Exists('$(OutputPath)\$(AssemblyName).pdb') " />
    72.   </Target>
    73.  
    74.   <Target Name="GenerateMonoSymbols">
    75.     <Message Text="$(ProjectName) -> $(TargetPath).mdb" Importance="High" />
    76.     <Exec Command=""$(MonoMdbGenerator)" $(AssemblyName).dll" WorkingDirectory="$(MSBuildProjectDirectory)\$(OutputPath)" />
    77.   </Target>
    78.  
    79.   </Project>
    80.  
    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.
     
    Test1522 likes this.
  2. guavaman

    guavaman

    Joined:
    Nov 20, 2009
    Posts:
    5,624
    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!
     
    Last edited: Dec 8, 2012
  3. Demigiant

    Demigiant

    Joined:
    Jan 27, 2011
    Posts:
    3,242
    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 :)
     
  4. Demigiant

    Demigiant

    Joined:
    Jan 27, 2011
    Posts:
    3,242
    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).
     
  5. yoyo

    yoyo

    Joined:
    Apr 16, 2010
    Posts:
    112
    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.
     
  6. Demigiant

    Demigiant

    Joined:
    Jan 27, 2011
    Posts:
    3,242
    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.
     
  7. yoyo

    yoyo

    Joined:
    Apr 16, 2010
    Posts:
    112
    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.
     
  8. Demigiant

    Demigiant

    Joined:
    Jan 27, 2011
    Posts:
    3,242
    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):
    1. 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):
    1. pdb2mdb $(TargetFileName)
    which works perfectly.

    Thanks again and sorry for my dumbness :p
     
  9. chrisjjones

    chrisjjones

    Joined:
    Aug 4, 2012
    Posts:
    4
    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
     
  10. Terikon

    Terikon

    Joined:
    Oct 12, 2012
    Posts:
    6
    Great job guys.
    Works perfectly with Unity 4.
    This must improve whole lifecycle for us.
    Thanks a lot for discovery.
     
  11. BraveSirAndrew

    BraveSirAndrew

    Joined:
    Jan 29, 2013
    Posts:
    2
    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
     
  12. yoyo

    yoyo

    Joined:
    Apr 16, 2010
    Posts:
    112
    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.
     
  13. GaVaR

    GaVaR

    Joined:
    Jan 21, 2013
    Posts:
    9
    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.
     
  14. Demigiant

    Demigiant

    Joined:
    Jan 27, 2011
    Posts:
    3,242
    Pay attention, in Unity 4.1 the correct path to the pdb2mdb has changed from "mono/4.5" to "mono/4.0" :p
     
  15. Jiraiyah

    Jiraiyah

    Joined:
    Mar 4, 2013
    Posts:
    175
    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 ;)
     
  16. Demigiant

    Demigiant

    Joined:
    Jan 27, 2011
    Posts:
    3,242
    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)
     
  17. amirabiri

    amirabiri

    Joined:
    Mar 10, 2012
    Posts:
    11
    Is anyone else getting this exception?

    Code (csharp):
    1. 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.
    2.    at System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(Array array, RuntimeFieldHandle fldHandle)
    3.    at Mono.Cecil.Metadata.TableHeap..cctor()
    4.    --- End of inner exception stack trace ---
    5.    at Mono.Cecil.PE.ImageReader.ReadTableHeap()
    6.    at Mono.Cecil.PE.ImageReader.ReadImage()
    7.    at Mono.Cecil.PE.ImageReader.ReadImageFrom(Stream stream)
    8.    at Mono.Cecil.ModuleDefinition.ReadModule(Stream stream, ReaderParameters parameters)
    9.    at Mono.Cecil.ModuleDefinition.ReadModule(String fileName, ReaderParameters parameters)
    10.    at Pdb2Mdb.Driver.Main(String[] args)
    11.  
    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.
     
  18. amirabiri

    amirabiri

    Joined:
    Mar 10, 2012
    Posts:
    11
    Not sure why, but using the 2.0 pdb2mdb.exe from the mono (not monobleedingedge) solved this problem.
     
  19. Demigiant

    Demigiant

    Joined:
    Jan 27, 2011
    Posts:
    3,242
    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?
     
  20. TaiChiAnt

    TaiChiAnt

    Joined:
    May 7, 2013
    Posts:
    6
    I have the same problem as well. 2.0 pdb2mdb.exe solved this problem but not the IEnumerator Issue. :(
     
  21. apmuj

    apmuj

    Joined:
    Nov 17, 2012
    Posts:
    1
    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):
    1. System.ArgumentNullException: Argument cannot be null.
    2. Parameter name: source
    3.   at System.Linq.Check.SourceAndPredicate (System.Object source, System.Object predicate) [0x00000] in <filename unknown>:0
    4.   at System.Linq.Enumerable.Where[PdbLines] (IEnumerable`1 source, System.Func`2 predicate) [0x00000] in <filename unknown>:0
    5.   at Pdb2Mdb.Converter.GetSourceFile (Mono.CompilerServices.SymbolWriter.MonoSymbolWriter mdb, Microsoft.Cci.Pdb.PdbFunction function) [0x00000] in <filename unknown>:0
    6.   at Pdb2Mdb.Converter.ConvertFunction (Microsoft.Cci.Pdb.PdbFunction function) [0x00000] in <filename unknown>:0
    7.   at Pdb2Mdb.Converter.Convert (Mono.Cecil.AssemblyDefinition assembly, IEnumerable`1 functions, Mono.CompilerServices.SymbolWriter.MonoSymbolWriter mdb) [0x00000] in <filename unknown>:0
    8.   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):
    1. "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
     
  22. L-Tyrosine

    L-Tyrosine

    Joined:
    Apr 27, 2011
    Posts:
    305
    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"
     
  23. yoyo

    yoyo

    Joined:
    Apr 16, 2010
    Posts:
    112
    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.
     
  24. yoyo

    yoyo

    Joined:
    Apr 16, 2010
    Posts:
    112
    Ironically, Unity 4 uses the "Unity Editor 3.x" registry key, so no change is required. (I consider this a bug, but whatever.)
     
  25. fjhamming_CleVR

    fjhamming_CleVR

    Joined:
    Feb 6, 2014
    Posts:
    10
    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):
    1. <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    2.   <PropertyGroup>
    3.     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    4.     <!-- Look up Unity install folder, and set the ReferencePath for locating managed assembly references. -->
    5.     <UnityInstallFolder>$(registry:HKEY_CURRENT_USER\Software\Unity Technologies\Installer\Unity@Location x64)</UnityInstallFolder>
    6.     <ReferencePath>$(UnityInstallFolder)\Editor\Data\</ReferencePath>
    7.     <MonoFolder>$(UnityInstallFolder)\Editor\Data\MonoBleedingEdge</MonoFolder>
    8.     <MonoMdbGenerator>$(MonoFolder)\lib\mono\4.5\pdb2mdb.exe</MonoMdbGenerator>
    9.     <MonoCLI>$(MonoFolder)\bin\cli.bat</MonoCLI>
    10.   </PropertyGroup>
    11.  
    12.   <Target Name="AfterBuild">
    13.     <CallTarget Targets="GenerateMonoSymbols" Condition=" Exists('$(OutputPath)\$(AssemblyName).pdb') " />
    14.   </Target>
    15.   <Target Name="GenerateMonoSymbols">
    16.     <Message Text="Unity install folder: $(UnityInstallFolder)" Importance="high" />
    17.     <Message Text="$(ProjectName) -&gt; $(TargetPath).mdb" Importance="High" />
    18.     <Exec Command="&quot;$(MonoCLI)&quot; &quot;$(MonoMdbGenerator)&quot; $(AssemblyName).dll" WorkingDirectory="$(MSBuildProjectDirectory)\$(OutputPath)" />
    19.   </Target>
    20. </Project>
     
    Test1522 and MaliusArth like this.
  26. MaliusArth

    MaliusArth

    Joined:
    Jul 31, 2013
    Posts:
    1
    Thanks!
    I've added
    Code (CSharp):
    1. <Exec Command="$(PostBuildEvent)" Condition=" Exists('$(PostBuildEvent)') " />
    right after the "GenerateMonoSymbols" CallTarget, enabling additional project specific post build events like copy statements etc.
     
    Test1522 likes this.
  27. HarvesteR

    HarvesteR

    Joined:
    May 22, 2009
    Posts:
    531
  28. sveilleux1

    sveilleux1

    Joined:
    Sep 3, 2015
    Posts:
    4
    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.
     
    Last edited: Sep 5, 2015