dotpcap / sharppcap

Official repository - Fully managed, cross platform (Windows, Mac, Linux) .NET library for capturing packets
1.31k stars 267 forks source link

SharpPcap is not trimmable because of dependency on NativeLibrary.SetDllImportResolver #517

Closed landerverhacklansweeper closed 1 month ago

landerverhacklansweeper commented 1 month ago

I recently got an issue using SharpPcap 9.0.0 on linux-x64 (.NET 8.0). It would not load correctly until I turned off Trimming during publish. The culprit is the following class: NativeLibraryHelper.

For some reason, it uses reflection to get the SetDllImportResolver method of the NativeLibrary class in System.Runtime.InteropServices. Because of this, the compiler is unaware of this dependency. Can we add something like

[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(NativeLibrary))]
public static void SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
{
    // existing code
}

So the trimming would not throw out this required method?

I added this to my own method, and it does seem to work.

I have no idea why NativeLibraryHelper uses reflection, but whatever the reason, it might also be the reason why this attribute might not be set.

Could we avoid the reflection all together? It also hinders my plan to use AOT. I want to be able to run on weaker hardware.

kayoub5 commented 1 month ago

The problem could be fixed by writing the same logic without reflection inside a #if NET6_0_OR_GREATER, while keeping the old logic for backward compatibility in the #else block, then compiling the library for .NET standard, .NET 6 and 8

PR welcome if someone wants to take a swing at this.

landerverhacklansweeper commented 1 month ago

I would if I could, but I'm not a contributer. Here is the required code.

// Copyright 2021 Ayoub Kaanich <kayoub5@live.com>
// SPDX-License-Identifier: MIT

using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace SharpPcap.LibPcap
{
    class NativeLibraryHelper
    {
#if NET6_0_OR_GREATER
        public static void SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
        {
            NativeLibrary.SetDllImportResolver(assembly, resolver);
        }

        public static bool TryLoad(string libraryPath, out IntPtr handle)
        {
            return NativeLibrary.TryLoad(libraryPath, out handle);
        }
#else
        public delegate IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath);
        private static readonly Type NativeLibraryType;
        static NativeLibraryHelper()
        {
            NativeLibraryType = typeof(DllImportSearchPath).Assembly
                .GetType("System.Runtime.InteropServices.NativeLibrary");
        }
        public static void SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
        {
            if (NativeLibraryType == null)
            {
                return;
            }

            var dllImportResolverType = typeof(DllImportSearchPath).Assembly
                .GetType("System.Runtime.InteropServices.DllImportResolver");

            var setDllImportResolverMethod = NativeLibraryType
                .GetMethod(
                    "SetDllImportResolver",
                    BindingFlags.Public | BindingFlags.Static,
                    null,
                    new[] { typeof(Assembly), dllImportResolverType },
                    null
                );

            setDllImportResolverMethod.Invoke(null, new object[] {
                assembly,
                Delegate.CreateDelegate(dllImportResolverType, resolver, "Invoke")
            });
        }

        public static bool TryLoad(string libraryPath, out IntPtr handle)
        {
            var tryLoadMethod = NativeLibraryType
                ?.GetMethod(
                    "TryLoad",
                    BindingFlags.Public | BindingFlags.Static,
                    null,
                    new[] { typeof(string), typeof(IntPtr).MakeByRefType() },
                    null
                );
            if (tryLoadMethod == null)
            {
                handle = IntPtr.Zero;
                return false;
            }
            var args = new object[] { libraryPath, IntPtr.Zero };
            var res = (bool)tryLoadMethod.Invoke(null, args);
            handle = (IntPtr)args[1];
            return res;
        }
#endif
    }
}