How to build and debug external DLLs

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

  1. yoyo

    yoyo

    Member

    Joined:
    Apr 16, 2010
    Messages:
    88
    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.
  2. guavaman

    guavaman

    Member

    Joined:
    Nov 20, 2009
    Messages:
    688
    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. Izitmee

    Izitmee

    Member

    Joined:
    Jan 27, 2011
    Messages:
    2,345
    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. Izitmee

    Izitmee

    Member

    Joined:
    Jan 27, 2011
    Messages:
    2,345
    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

    Member

    Joined:
    Apr 16, 2010
    Messages:
    88
    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. Izitmee

    Izitmee

    Member

    Joined:
    Jan 27, 2011
    Messages:
    2,345
    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

    Member

    Joined:
    Apr 16, 2010
    Messages:
    88
    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. Izitmee

    Izitmee

    Member

    Joined:
    Jan 27, 2011
    Messages:
    2,345
    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

    Member

    Joined:
    Aug 4, 2012
    Messages:
    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

    New Member

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

    BraveSirAndrew

    New Member

    Joined:
    Jan 29, 2013
    Messages:
    1
    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

    Member

    Joined:
    Apr 16, 2010
    Messages:
    88
    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

    New Member

    Joined:
    Jan 21, 2013
    Messages:
    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. Izitmee

    Izitmee

    Member

    Joined:
    Jan 27, 2011
    Messages:
    2,345
    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

    New Member

    Joined:
    Mar 4, 2013
    Messages:
    2
    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. Izitmee

    Izitmee

    Member

    Joined:
    Jan 27, 2011
    Messages:
    2,345
    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

    Member

    Joined:
    Mar 10, 2012
    Messages:
    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

    Member

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

    Izitmee

    Member

    Joined:
    Jan 27, 2011
    Messages:
    2,345
    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

    Member

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

    apmuj

    New Member

    Joined:
    Nov 17, 2012
    Messages:
    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

    Member

    Joined:
    Apr 27, 2011
    Messages:
    141
    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

    Member

    Joined:
    Apr 16, 2010
    Messages:
    88
    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

    Member

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