Jump to content

How to manage optional dependencies


Recommended Posts

I recently added support for ContractConfigurator.  This entailed adding a reference to ContractConfigurator's DLL so that I could pick up some base classes.  I never intended to make ContractConfigurator a straight-up requirement for the mod - more I wanted to make it so that if you had ContractConfigurator, you'd get some extra content.

Generally speaking, .Net doesn't load dependent dll's until a class that actually uses that DLL gets loaded...  So I thought that because the contract classes have no Unity stuff in them and nothing in the rest of my DLL references the contract classes that I'd be okay.

But... Apparently not.  I should perhaps have figured this since Unity is reflecting over all the types:

[ERR 13:27:03.459] ADDON BINDER: Cannot resolve assembly: ContractConfigurator, Culture=neutral, PublicKeyToken=null

[ERR 13:27:03.464] AssemblyLoader: Exception loading 'ProgressiveColonizationSystem': System.Reflection.ReflectionTypeLoadException: Exception of type 'System.Reflection.ReflectionTypeLoadException' was thrown.
  at (wrapper managed-to-native) System.Reflection.Assembly.GetTypes(System.Reflection.Assembly,bool)
  at System.Reflection.Assembly.GetTypes () [0x00000] in <ad04dee02e7e4a85a1299c7ee81c79f6>:0 
  at AssemblyLoader.LoadAssemblies () [0x000e6] in <06f13185617646e5bc801baeab53ab75>:0 

Additional information about this exception:

Seems like I got a couple of choices, and I thought I'd see if anybody has an opinion or maybe a better alternative.

  1. I could create a second mod for the contracts, and have that have a dependency on ContractConfigurator and my mod.
  2. I could create a second DLL which depends on my mod and ContractConfigurator.  This one will not load correctly if CC is not present.

I'm not fond of #2 because that means my mod will generate a nastygram in the log for folks without ContractsConfigurator.  That wouldn't hardly be the first one, but it's bad form.  I'm not fond of #1 because I feel like it makes for more work for the user (heh, and some more work for me too!)

Link to comment
Share on other sites

For the sake of posterity, I went with the option of splitting it out into a separate DLL, but not a separate mod.  Work was done in this commit:


Break out Contracts code into its own DLL · SteveBenz/ProgressiveColonizationSystem@66d8a63 (github.com)

Edited by NermNermNerm
Link to comment
Share on other sites

  • 3 months later...
On 4/4/2021 at 12:13 PM, peteletroll said:

The clean solution is to use reflection: big topic!

Reflexion quickly become a mess to work with, it is prone to breakage when the target mod is updated, and it comes with a very noticable performance cost.

The best way to handle this is to make a separate binary (dll) that reference both mods, and to conditonally load it from your main mod when the other mod is detected.

Example : https://github.com/Kerbalism/Kerbalism/blob/master/src/KerbalismBootstrap/Bootstrap.cs

Edited by Gotmachine
Link to comment
Share on other sites

  • 5 months later...

I'm noob at this topic but thought I'd share my solution in case anyone else struggled figuring it out like I did, there seems to be very little info around about it so it took a while to work out and was very necessary when making modular mods with optional dependencies

If u add a reference to another mod's DLL then try to access those classes in ur mod, the KSP "Addon Binder" (which controls the loading of mod DLLs) will throw an exception if the other mod is absent - even if u hide ur code behind a System.IO.File.Exists check for whether the other mod is present. My solution was to find the mod in the list of loaded assemblies, then reflect on the classes and methods in order to run a method from the other mod.

Here is a one line sample (replacing "OtherMod" etc with whatever the mod name, namespace, class and methods are)

if (System.IO.File.Exists(KSPUtil.ApplicationRootPath + "GameData/OtherModName/OtherMod.dll")) {
	try {
		AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "OtherModName").GetType("OtherModNameSpace.OtherModClass").GetMethod("OtherModMethod", BindingFlags.Public | BindingFlags.Static).Invoke(null, null);
	}
	finally { }
}

maybe that will help someone idk

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...