Costura: Confluent.Kafka doesn't get the correct Costura temp path

Please check all of the platforms you are having the issue on (if platform is not listed, it is not supported)

  • WPF
  • UWP
  • iOS
  • Android
  • .NET Standard
  • .NET Core

Component

Costura.Fody

Version of Library

5.3.0

Version of OS(s) listed above with issue

Windows 10

Steps to Reproduce

  1. Create any app with .NET 4.7;
  2. Add Confluent.Kafka;
  3. Add Costura.Fody;
  4. Edit the FodyWeavears.xml to:
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Costura>
    <Unmanaged32Assemblies>
      librdkafka
      librdkafkacpp
      libzstd
      msvcp120
      msvcr120
      zlib
    </Unmanaged32Assemblies>
    <Unmanaged64Assemblies>
      librdkafka
      librdkafkacpp
      libzstd
      msvcp120
      msvcr120
      zlib
    </Unmanaged64Assemblies>
  </Costura>
</Weavers>
  1. Add any library you want, to prove that Costura works;
  2. Call any method of Confluent.Kafka;
  3. Run, and get you error:
Unhandled Exception: System.InvalidOperationException: Error while loading librdkafka.dll or its dependencies from *current.dir*\librdkafka.dll. Check the directory exists, if not check your deployment process. You can also load the library and its dependencies by yourself before any call to Confluent.Kafka ---> System.ComponentModel.Win32Exception: The specified module could not be found
   --- End of inner exception stack trace ---
   at Confluent.Kafka.Impl.Librdkafka.LoadNetFrameworkDelegates(String userSpecifiedPath)
   at Confluent.Kafka.Impl.Librdkafka.Initialize(String userSpecifiedPath)
   at Confluent.Kafka.Producer`2..ctor(ProducerBuilder`2 builder)
   at Confluent.Kafka.ProducerBuilder`2.Build()
   at ActiveDirectory.Infrastructure.Helpers.QueueHelper.<SendMessage>d__1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

Expected Behavior

I would like that Confluent.Kafka to load correctly the library from the path that Costura changed.

Actual Behavior

Confluent.Kafka doesn’t know how to search in Costura.Fody temp path.

Important: I opened an issue inside Confluent.Kafka to there staff answer some this question too. I would like to know where is the main problem. If you could, please, read it too: https://github.com/confluentinc/confluent-kafka-dotnet/issues/1660

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 48 (31 by maintainers)

Commits related to this issue

Most upvoted comments

Stable version is being deployed right now. Please let us know if any other issues arise.

Special thanks to @tom-englert for fixing this!

… and to @0xced for the msbuild part!

I think that we can release this as stable. @tom-englert you agree that we can release this and the other PR you did?

msvc*.dll come with the “Microsoft Visual C++ ??? Redistributable” - so they might not be present always.

I once used this workaround to ensure these get not loaded by Costura if they are already present:

FodyWeavers.xml

 <Costura LoadAtModuleInit="false" ...>

Source code:

// Try to pre-load runtime from system directory, if it's already installed there.
NativeMethods.LoadLibrary("vcruntime140.dll");
if (IntPtr.Size == 4)
    NativeMethods.LoadLibrary("msvcp140.dll");

CosturaUtility.Initialize();

There are many things at play in this scenario. You can find a working solution for both 32-bit and 64-bit systems in my embed-native-dlls branch. All the native dlls are embedded in the single executable and automatically loaded at runtime by Costura but you still need to call Confluent.Kafka.Library.Load with the path where the native librdkafka dll was extracted.

The missing pieces were:

  1. Proper PreloadOrder so that all native dlls are loaded successfully
  2. Some MSBuild magic (i.e. target that I named EmbedLibrdkafkaRedistNativeLibraries) to prevent native dlls from being copied to the output directory and being embedded in virtual costura32 and costura64 directories instead.
  3. ~Use the right path when calling Confluent.Kafka.Library.Load, this required to configure CreateTemporaryAssemblies="true" so that we can know the directory where native dlls are extracted by Costura with typeof(Confluent.Kafka.Library).Assembly.GetName().EscapedCodeBase~ (see Tom’s comment below)

In addition, I have

  • Added <AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects> and <GenerateSupportedRuntime>false</GenerateSupportedRuntime> to prevent ConsoleApp1.exe.config from being generated. (Binding redirects are not needed thanks to Costura)
  • Set <DebugType>embedded</DebugType> to embed the pdb inside the exe instead of having it in a separate file
  • Used <PlatformTarget>AnyCPU</PlatformTarget> so that the app runs in 64-bit by default

When Costura has already loaded the libraries, no path is needed for LoadLibrary, just specifying only the file name will simply return the handle of the loaded library.

My guess is that the PreLoadOrder is wrong, and Costura did not load all of the libraries sucessfully,

@GeertvanHorrik in Costura SetDllDirectory + LoadLibrary is used. Using LoadLibraryEx(..., LOAD_WITH_ALTERED_SEARCH_PATH) might be the better solution, and PreLoadOrder would not be required here. Any idea why it was done this way?

can you also create a stand alone github repository that contains a minimal repro for this issue