Search Unity

How to find all method references in Visual Studio 2015 from compiled/referenced DLL in Unity?

Discussion in 'Code Editors & IDEs' started by Kerozard, Mar 8, 2017.

  1. Kerozard

    Kerozard

    Joined:
    Nov 26, 2012
    Posts:
    10
    I have a Visual Studio solution with multiple projects for my current game project. Among these are:
    • A Unity project that contains the code for the game client
    • A console application that contains the code for a game server
    • A class library containing common code, especially game logic
    Since it is not possible to directly reference another VS project in the Unity project, I have to copy the logic library post build to a Unity asset folder. Unity then automatically adds a reference to the compiled DLL to the VS project for the game client. The server application however has a normal project reference to the logic DLL.

    My issue is that I can not get VS or Resharper to recognize that my logic library project and the (compiled and referenced) DLL in the Unity project are actually the same thing. When I, for example, right-click a method in the logic library and select "Find all references" it shows those from the logic project itself and calls from the console application as well, but it obviously does not show calls in the client since there (technically) aren't any.

    Is there a way to let Resharper/Visual Studio know that any calls made to the compiled logic dll should be matched to the project instead?

    I am running Visual Studio 2015, Resharper 2016.2.2 and Unity3d 5.5.

    Thanks in advance.
     
  2. DirtyHippy

    DirtyHippy

    Joined:
    Jul 17, 2012
    Posts:
    224
    Have you figured out how to make this work? I've had a dll I setup years ago, that I never used beyond testing it with a hello world and that I could debug it. Recently the thousands of loose scripts I have developed over the years have driven me to the point where I cannot take it anymore. I moved a small number of project-agnostic support files that are highly shareable to the dll. Everything works fine. And then I tried some typical operations like go to definition, refactor, find all references, and it became pretty clear (like you noticed) that the reference was not a project reference (since clearly, how could Unity know). I came to the same conclusion you did.

    Not having these tools is pretty much a dealbreaker for dlls for me. I cannot imagine how frustrated I would become if I could not use them. At best I might move the minimal number of stable support scripts in there that I need commonly for bootstrapping test projects for testing ideas outside of my game, which has become ponderously slow to build (which I was hoping this endeavor would mitigate to some degree tbh).

    So I have messed around a bit. It appears that Unity recreates the csproj files when it loads. However, it doesn't appear to rebuild them when they themselves change (the .csproj files). I was able to manually change the assembly-csharp csproj file and replace the reference to my library with a project reference after unity loaded and unity didn't recreate the csproj (hence it didn't overwrite my changes immediately). It also didn't rewrite the change when I built my dll and the post build step copied it into Unity. All normal operations (go to definition, etc work fine at this point). This implies to me that it is perhaps possible to hook into Unity's build step and write something that would rip out the direct library reference and replace it with a project reference in the csproj once it is done (since unity would recreate the csproj files anytime you added/removed anything to the unity project). This would make it (mostly transparent). Also after making the change and building my lib, everything ran fine. So I guess there is a possibility of making this work with a little hacking? Dunno.
     
  3. Kerozard

    Kerozard

    Joined:
    Nov 26, 2012
    Posts:
    10
    I only got to the same point you did. Manually changing the csproj file does work, but Unity regenerates it too regularly to make that a viable solution.

    From https://docs.unity3d.com/Manual/VisualStudioIntegration.html :
    • Unity automatically creates and maintains a Visual Studio .sln and .csproj file. Whenever somebody adds/renames/moves/deletes a file from within Unity, Unity regenerates the .sln and .csproj files. You can add files to your solution from Visual Studio as well. Unity will then import those new files, and the next time Unity creates the project files again, it will create them with this new file included.
    It is one of the most annoying things I have to deal with right now. Not being able to refactor the code of the external project properly from within VS is aggravating at times. I have to refactor the code and then let Unity compile it so I can work my way through the errors and adapt the code manually.
     
  4. DirtyHippy

    DirtyHippy

    Joined:
    Jul 17, 2012
    Posts:
    224
    How about writing a little Visual studio extension that swaps out the reference in the csproj? That way you could just hit a hotkey to do it when refactoring, and then (unfortunately), wait for the project to reload. It doesn't seem to recreate the csproj when you just change scripts and let unity recompile. However, if you move anything around in the project, well, you gotta do it again.

    Really annoying.
     
  5. Kerozard

    Kerozard

    Joined:
    Nov 26, 2012
    Posts:
    10
    So I dug around a bit, following your idea of changing the way Unity3d generates the csproj file and it is easier than I expected to hook into the build process.

    I ended up simply removing the DLL <Reference> and adding a new <ProjectReference> in their respective <ItemGroup> tags after the files have been generated. Rather than going the XML route, I simply did some very basic regex/string replacements, because I am lazy. :p

    This code is executed every time after the project files have been regenerated.

    Thank you for pointing me in the right direction. Sometimes you just need to know where to look.

    Code (CSharp):
    1.  
    2. using System;
    3. using System.IO;
    4. using System.Text.RegularExpressions;
    5. using UnityEditor;
    6. using UnityEngine;
    7.  
    8. namespace Assets.Editor
    9. {
    10.     public class ReferenceExchange : AssetPostprocessor
    11.     {
    12.         public static void OnGeneratedCSProjectFiles()
    13.         {
    14.             var projectDirectory = Directory.GetParent(Application.dataPath).FullName; ;
    15.             var projectName = Path.GetFileName(projectDirectory);
    16.  
    17.             var csProjFile = Path.Combine(projectDirectory, string.Format("{0}.csproj", projectName));
    18.  
    19.             try
    20.             {
    21.                 // read, change and overwrite the csproj file in here
    22.                 var content = File.ReadAllText(csProjFile);
    23.                 var result = Regex.Replace(content, @"<Reference Include=\""DoD.Core\"">(.|\n)*<\/Reference>", "");
    24.  
    25.                var index = result.IndexOf("<ProjectReference", StringComparison.Ordinal);
    26.                if (index != -1)
    27.                {
    28.                    var newReference = @"<ProjectReference Include=""..\DoD.Core\DoD.Core.csproj"">" + Environment.NewLine +
    29.                        "      <Project>{INSERT_PROJECTUID}</Project> " + Environment.NewLine +
    30.                        "      <Name>DoD.Core</Name>" + Environment.NewLine +
    31.                        "    </ProjectReference>" + Environment.NewLine +
    32.                        "    ";
    33.                    result = result.Insert(index, newReference);
    34.                }
    35.  
    36.                File.WriteAllText(csProjFile, result);
    37.            }
    38.            catch (Exception e)
    39.            {
    40.                Console.WriteLine("Project file could not be changed.");
    41.                Console.Write(e.Message);
    42.            }
    43.        }
    44.    }
    45. }
    46.  
    The formatting looks a bit off due to the escaped strings. :)
     
  6. DirtyHippy

    DirtyHippy

    Joined:
    Jul 17, 2012
    Posts:
    224
    Thanks! I was hoping you would volunteer to write this. It was 3am and I didn't feel like it :).

    I modified the source to account for multiple libraries to be swapped:

    Code (CSharp):
    1.     /// <summary>
    2.     /// When Unity autogens its project files, it adds file references to external dlls that you have built and copied in.  This means you cannot use various meta related editor
    3.     /// actions like go to definition, refactor, etc because even though you have the actual project that built the DLL in the solution, it doesn't have a project reference to it.
    4.     /// This ties into the build process of unity and will change these csproj files automatically after Unity rebuilds them (which happens after source is added/removed/changed).
    5.     /// </summary>
    6.     public class ReferenceExchange:AssetPostprocessor
    7.     {
    8.         public class ReferenceExchangeMeta
    9.         {
    10.             /// <summary>
    11.             /// Project name.
    12.             /// </summary>
    13.             public string ProjectName;
    14.  
    15.             /// <summary>
    16.             /// Project guid (from the csproj file).
    17.             /// </summary>
    18.             public string TargetProjectGuid;
    19.  
    20.             /// <summary>
    21.             /// Relative path to the project which will be used as the project reference.
    22.             /// </summary>
    23.             public string TargetProjectPath;
    24.  
    25.             public ReferenceExchangeMeta (string projectName, string targetProjectGuid, string targetProjectPath)
    26.             {
    27.                 ProjectName = projectName;
    28.                 TargetProjectGuid = targetProjectGuid;
    29.                 TargetProjectPath = targetProjectPath;
    30.             }
    31.         }
    32.  
    33.         /// <summary>
    34.         /// List of references that will be swapped out.
    35.         /// </summary>
    36.         public static ReferenceExchangeMeta [] References = new ReferenceExchangeMeta []
    37.         {
    38.             new ReferenceExchangeMeta ("MyProjectName", "MyProjectGuidWithNoBraces", @"MyProjectRelativePath") // e.g. ("MyProject", "35ec7784-734f-4b3c-a4dc-a39198549624", @"..\MyProjectDir\MyProject.csproj")
    39.         };
    40.  
    41.         public static void OnGeneratedCSProjectFiles()
    42.         {
    43.             var projectDirectory = Directory.GetParent(Application.dataPath).FullName;
    44.             var projectName = Path.GetFileName(projectDirectory);
    45.             var csProjFile = Path.Combine(projectDirectory, string.Format("{0}.csproj", projectName));
    46.  
    47.             try
    48.             {
    49.                 // read, change and overwrite the csproj file in here
    50.                 var content = File.ReadAllText(csProjFile);
    51.  
    52.                 foreach (var referenceEntry in References)
    53.                 {
    54.                     var regex = string.Format (@"<Reference Include=\""{0}\"">(.|\n)*<\/Reference>", referenceEntry.ProjectName);
    55.  
    56.                    // Remove the original file reference
    57.                    content = Regex.Replace (content, regex, "");
    58.  
    59.                    // Find the project reference tag.
    60.                    var index = content.IndexOf("<ProjectReference", StringComparison.Ordinal);
    61.                    if(index != -1)
    62.                    {
    63.  
    64.                        var newReference = @"<ProjectReference Include=""" + referenceEntry.TargetProjectPath + @""">" + Environment.NewLine +
    65.                        "      <Project>{" + referenceEntry.TargetProjectGuid + "}</Project> " + Environment.NewLine +
    66.                        "      <Name>" + referenceEntry.ProjectName + "</Name>" + Environment.NewLine +
    67.                        "    </ProjectReference>" + Environment.NewLine +
    68.                        "    ";
    69.  
    70.                        content = content.Insert(index,newReference);
    71.                    }
    72.                }
    73.  
    74.                File.WriteAllText(csProjFile, content);
    75.            }
    76.            catch(Exception e)
    77.            {
    78.                UnityEngine.Debug.Log ("Project file could not be changed.");
    79.                UnityEngine.Debug.Log (e.Message);
    80.            }
    81.        }
    82.    }
     
  7. DirtyHippy

    DirtyHippy

    Joined:
    Jul 17, 2012
    Posts:
    224
    Thanks again! Much much better now.
     
  8. Kerozard

    Kerozard

    Joined:
    Nov 26, 2012
    Posts:
    10
    A beautiful multi-purpose solution. Thank you for taking the time to rework my lazy version. :)
     
  9. DirtyHippy

    DirtyHippy

    Joined:
    Jul 17, 2012
    Posts:
    224
    So this implementation had a few issues that I fixed. The regex was greedy and would match to the end of the references hence removing every reference from your lib to the end rather than just your lib.

    I also added some logic to prevent adding the reference unless it found the existing reference and also to skip rewriting the file if it didn't do anything.

    Code (CSharp):
    1.     /// <summary>
    2.     /// When Unity autogens its project files, it adds file references to external dlls that you have built and copied in.  This means you cannot use various meta related editor
    3.     /// actions like go to definition, refactor, etc because even though you have the actual project that built the DLL in the solution, it doesn't have a project reference to it.
    4.     /// This ties into the build process of unity and will change these csproj files automatically after Unity rebuilds them (which happens after source is added/removed/changed).
    5.     /// </summary>
    6.     public class ReferenceExchange:AssetPostprocessor
    7.     {
    8.         private class ReferenceExchangeMeta
    9.         {
    10.             /// <summary>
    11.             /// Project name.
    12.             /// </summary>
    13.             public string ProjectName;
    14.  
    15.             /// <summary>
    16.             /// Project guid (from the csproj file).
    17.             /// </summary>
    18.             public string TargetProjectGuid;
    19.  
    20.             /// <summary>
    21.             /// Relative path to the project which will be used as the project reference.
    22.             /// </summary>
    23.             public string TargetProjectPath;
    24.  
    25.             public ReferenceExchangeMeta (string projectName, string targetProjectGuid, string targetProjectPath)
    26.             {
    27.                 ProjectName = projectName;
    28.                 TargetProjectGuid = targetProjectGuid;
    29.                 TargetProjectPath = targetProjectPath;
    30.             }
    31.         }
    32.  
    33.         /// <summary>
    34.         /// List of references that will be swapped out.
    35.         /// </summary>
    36.         private static ReferenceExchangeMeta [] References = new ReferenceExchangeMeta []
    37.         {
    38.             new ReferenceExchangeMeta ("PROJECTNAME", "GUID", @"RELATIVEPATH")
    39.         };
    40.  
    41.         [MenuItem ("Window/Modify csproj files with project refs")]
    42.         private static void ModifyProjectFilesWithProjectRefs ()
    43.         {
    44.             OnGeneratedCSProjectFiles ();
    45.         }
    46.  
    47.         private static void OnGeneratedCSProjectFiles ()
    48.         {
    49.             var projectDirectory = Directory.GetParent(Application.dataPath).FullName;
    50.             var projectName = Path.GetFileName(projectDirectory);
    51.             var csProjFile = Path.Combine(projectDirectory, string.Format("{0}.csproj", projectName));
    52.  
    53.             try
    54.             {
    55.                 // read, change and overwrite the csproj file in here
    56.                 var content = File.ReadAllText(csProjFile);
    57.  
    58.                 var matchedAtLeastOne = false;
    59.  
    60.                 foreach (var referenceEntry in References)
    61.                 {
    62.                     var regex = string.Format (@"<Reference Include=\""{0}\"">(.|\n)*?<\/Reference>", referenceEntry.ProjectName);
    63.  
    64.                    var regexEval = new Regex (regex);
    65.                    if (regexEval.IsMatch (content))
    66.                    {
    67.                        content = regexEval.Replace (content, string.Empty);
    68.  
    69.                        // Find the project reference tag.
    70.                        var index = content.IndexOf("<ProjectReference", StringComparison.Ordinal);
    71.                        if(index != -1)
    72.                        {
    73.                            var newReference = @"<ProjectReference Include=""" + referenceEntry.TargetProjectPath + @""">" + Environment.NewLine +
    74.                            "      <Project>{" + referenceEntry.TargetProjectGuid + "}</Project> " + Environment.NewLine +
    75.                            "      <Name>" + referenceEntry.ProjectName + "</Name>" + Environment.NewLine +
    76.                            "    </ProjectReference>" + Environment.NewLine +
    77.                            "    ";
    78.  
    79.                            content = content.Insert(index,newReference);
    80.                        }
    81.  
    82.                        matchedAtLeastOne = true;
    83.                    }
    84.                }
    85.              
    86.                if (matchedAtLeastOne)
    87.                    File.WriteAllText(csProjFile, content);
    88.            }
    89.            catch(Exception e)
    90.            {
    91.                UnityEngine.Debug.Log ("Project file could not be changed.");
    92.                UnityEngine.Debug.Log (e.Message);
    93.            }
    94.        }
    95.    }
    96.  
     
    Kerozard likes this.