- Home /
Circular references between Dlls. firstpass and a generated Dll
EDIT: I ended up using Delegate.Create delegate and hook up my dll dynamically and not statically.
public void ConnectAssembly(string assemblyPath, string serializerTypeName)
{
if (!File.Exists(assemblyPath))
throw new IOException("Assembly path doesn't exist: " + assemblyPath);
var asm = Assembly.LoadFrom(assemblyPath);
var serializerType = asm.GetType(serializerTypeName);
if (serializerType == null)
throw new NullReferenceException("Serializer type was not found: " + serializerTypeName);
var serializeMethod = serializerType.GetMethod("Serialize", BindingFlags.Static | BindingFlags.Public);
var deserializeMethod = serializerType.GetMethod("Deserialize", BindingFlags.Static | BindingFlags.Public);
_serialize = (SerializationDelegate)Delegate.CreateDelegate(typeof(SerializationDelegate), null, serializeMethod);
_deserialize = (DeserializationDelegate)Delegate.CreateDelegate(typeof(DeserializationDelegate), null, deserializeMethod);
}
EDIT: Here's a video showing it in action:
https://www.youtube.com/watch?v=e-3a6qnVjmM&feature=youtu.be
In my firstpass dll, I have type X
- I have a dynamically generated dll 'SerTest.dll' that I emit at in the firstpass, that references 'X'. So when Unity tries to compile firstpass, it tries to compile the dll 'SerTest' which references 'X' in firstpass, compilation fails.
-----Compiler Commandline Arguments:
Filename: "C:\Program Files\Unity\Editor\Data\Mono\bin\mono.exe"
Arguments: "C:\Program Files\Unity\Editor\Data\Mono\lib\mono\unity\smcs.exe" @Temp/UnityTempFile-71d970fa2565f064492790f199c48fd7
MONO_PATH: C:\Program Files\Unity\Editor\Data\Mono\lib\mono\unity
MONO_CFG_DIR: C:\Program Files\Unity\Editor\Data\Mono\etc
index: 68
Responsefile: Temp/UnityTempFile-71d970fa2565f064492790f199c48fd7 Contents:
-debug
-target:library
-nowarn:0169
-out:Temp/Assembly-CSharp-firstpass.dll
-r:"C:/Program Files/Unity/Editor/Data/Managed/UnityEngine.dll"
-r:"C:/Program Files/Unity/Editor/Data/UnityExtensions/Unity/GUISystem/UnityEngine.UI.dll"
-r:Assets/SerTest.dll <<<<<<<<<< !!!!!
-r:Assets/Dll/FastSerializer.dll
-r:"C:/Program Files/Unity/Editor/Data/UnityExtensions/Unity/GUISystem/UnityEngine.UI.dll"
-r:"C:/Program Files/Unity/Editor/Data/Managed/UnityEditor.dll"
-r:"C:\Program Files\Unity\Editor\Data\PlaybackEngines\iossupport\UnityEditor.iOS.Extensions.Xcode.dll"
-define:UNITY_5_0_0
-define:UNITY_5_0
-define:UNITY_5
-define:ENABLE_LICENSE_RENAME
-define:ENABLE_NEW_BUGREPORTER
-define:ENABLE_2D_PHYSICS
-define:ENABLE_4_6_FEATURES
-define:ENABLE_AUDIO
-define:ENABLE_CACHING
-define:ENABLE_CLOTH
-define:ENABLE_DUCK_TYPING
-define:ENABLE_FRAME_DEBUGGER
-define:ENABLE_GENERICS
-define:ENABLE_HOME_SCREEN
-define:ENABLE_IMAGEEFFECTS
-define:ENABLE_LIGHT_PROBES_LEGACY
-define:ENABLE_MICROPHONE
-define:ENABLE_MULTIPLE_DISPLAYS
-define:ENABLE_NEW_HIERARCHY
-define:ENABLE_PHYSICS
-define:ENABLE_PHYSICS_PHYSX3
-define:ENABLE_PLUGIN_INSPECTOR
-define:ENABLE_SHADOWS
-define:ENABLE_SINGLE_INSTANCE_BUILD_SETTING
-define:ENABLE_SPRITES
-define:ENABLE_TERRAIN
-define:ENABLE_UNITYEVENTS
-define:ENABLE_WEBCAM
-define:ENABLE_WWW
-define:ENABLE_AUDIOMIXER_SUSPEND
-define:ENABLE_NONPRO
-define:INCLUDE_DYNAMIC_GI
-define:INCLUDE_GI
-define:INCLUDE_IL2CPP
-define:PLATFORM_SUPPORTS_MONO
-define:RENDER_SOFTWARE_CURSOR
-define:UNITY_STANDALONE_WIN
-define:UNITY_STANDALONE
-define:ENABLE_SUBSTANCE
-define:ENABLE_TEXTUREID_MAP
-define:ENABLE_RUNTIME_GI
-define:ENABLE_MOVIES
-define:ENABLE_NETWORK
-define:ENABLE_MONO
-define:ENABLE_PROFILER
-define:UNITY_EDITOR
-define:UNITY_EDITOR_64
-define:UNITY_EDITOR_WIN
Assets/Plugins/DemoBehaviour.cs
-r:"C:\Program Files\Unity\Editor\Data\Mono\lib\mono\unity\System.Runtime.Serialization.dll"
-r:"C:\Program Files\Unity\Editor\Data\Mono\lib\mono\unity\System.Xml.Linq.dll"
-----CompilerOutput:-stdout--exitcode: 1--compilationhadfailure: True--outfile: Temp/Assembly-CSharp-firstpass.dll
Internal compiler error at Assets/Plugins/DemoBehaviour.cs(111,10):: exception caught while emitting MethodBuilder [DemoBehaviour::Demo]
The following assembly referenced from C:\Users\vexe\Desktop\FastSerializer\Assets\SerTest.dll could not be loaded:
Assembly: Assembly-CSharp-firstpass (assemblyref_index=1)
Version: 0.0.0.0
Public Key: (none)
The assembly was not found in the Global Assembly Cache, a path listed in the MONO_PATH environment variable, or in the location of the executing assembly (C:\Users\vexe\Desktop\FastSerializer\Assets\).
Could not load file or assembly 'Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
-----CompilerOutput:-stderr----------
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies.
File name: 'Assembly-CSharp-firstpass, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
To put it in a simplified way, here's what I have:
public class DemoBehaviour : MonoBehaviour
{
void Demo()
{
// access code in SerTest.dll
}
public class TestClass
{
// some fields....
}
}
That goes into the firstpass.
Then I emit a dll that references TestClass
and does stuff with 'some fields'. The dll is called 'SerTest.dll' and is also located in the firstpass.
I really need to put that dll in the firstpass because I generate serialization code for user types, which could be in any pass, firstpass, plugins etc.
NOTE: I only get the errors when I try to do something with my generated Dll. So say there's a type called "SERIALIZER" in it, in the 'Demo' method, if I do SERIALIZER.Serialize
(access the dll statically) I get those errors. I looked around and found this answer - apparently, if I reference the dll dynamically via reflection, it works! so instead of SERIALIZER.Serialize, I do typeof(SERIALIZER).GetMethod("Serialize").Invoke(....) which is really weird...
I really appreciate any help.
Answer by Bunny83 · Mar 16, 2015 at 01:51 PM
I'm not sure if i fully understood your setup ^^. What seems a bit strange is that your SerText.dll accesses a class / method in a script file (which of course has to be compiled first).
The solution is: Don't reference TestClass from your SerTest.dll.
Now it depends on when and where you need / want to access that TestClass from the SerTest dll. The usual way, the way most native plugins solve "callbacks", is to provide an "Init" method in your SerTest dll that takes an parameter of an interface which your script can implement. So at runtime the first thing you do is to pass an implementation of that interface to your DLL and it's going to store that in a static var. This initialization could even be done from a constructor / static constructor. Of course don't use classes derived from a Unity type for the interface implementation as this will force you to wait for Awake.
If you just need a few simple callbacks, you can also use static delegates in your DLL which will be set from your script classes.
Another way is, like you already mentioned in your question, use reflection from the DLL to access the TestClass (not the other way round).
So it's perfectly fine to statically link / access your "SERIALIZER" from the TestClass as the SerTest dll is already compiled. It makes no sense to introduce a static dependency in your SerTest dll to "Assembly-CSharp-firstpass" as the latter actually isn't compiled yet.
edit
Ok, i just saw your video ^^. This is a very special case. Can't you just "generate" your serializer code as text into script files? :) That way it would be compiled along with the first pass.
If you can't "generate" your code as text, i would suggest that you use reflection to create a delegate to your serialize method at runtime. Or, as said above, use an interface which your serializer implements. That way you just need to call a static method in your SerTest dll via reflection to create an instance and pass back the interface reference.
Thanks for your reply! :) - have you seen the video I linked? It explains the setup and why I need to reference user scripts from the generated dll. It's basically a serializer, it 'needs' to know about the user types so it can generate the serialization code for them. I would really like to avoid reflection at all costs, the point of this is to build a very fast serializer that writes directly to a Stream, no temp buffers, no reflection etc.
I don't understand why it works when I invoke it via reflection, and not statically... worse case I could use Delegate.CreateDelegate I guess...
The problem is, when you compile a dll, all referenced DLLs and used Types must be available at the time of compilation. Since your SerTest.dll references the firstpass DLL, the firstpass DLL has to be available when compiling it. Circular assembly references aren't possible in C#. The only two solutions are:
merge them into one assembly (so create your serializaion code as text / script files so they compile along with the rest)
use dynamic dispatch via reflection.
Read your edit. To give more context. So initially I didn't want to emit to a dll. I use Dynamic$$anonymous$$ethod
and emit IL directly. Using the Dynamic$$anonymous$$ethods approach, and creating delegates to those have no problems whatsoever (this is what the #DYNA$$anonymous$$IC define is for at the top of my Demo script) - But I'm also planning to support AOT platforms which don't allow emitting IL. This is why I thought of emitting to an assembly ins$$anonymous$$d of Dynamic$$anonymous$$ethod. Which worked and I got the code I had in $$anonymous$$d. But then I ran into this issue. I'm not sure if I could emit to a file, if that's a thing. One thing I tried to do was to emit to a dll, decompile it and then copy/paste the code to a script lol which 'kinda' worked, except VS hung up due to the large amount of code generated (10k or so), I can't imagine myself asking my users to do that... I will look up emitting to a file, if not I'll just use Delegate.CreateDelegate on the Serialize method in SerTest.dll
Responding to your comment, I see. But when we're using reflection, we're doing typeof(SERIALIZER).Get$$anonymous$$ethod(...)
aren't we also referring to the dll? I mean I'm still using a type from that assembly, why didn't it complain?