Мне нужна альтернатива `Assembly.GetEntryAssembly()`, которая никогда не возвращает null

Мне нужно найти сборку, в которой запущено исполнение управляемого кода.

// using System.Reflection;
Assembly entryAssembly = Assembly.GetEntryAssembly();

Это похоже на способ, но страница MSDN для Assembly.GetEntryAssembly утверждает, что этот метод "[c] возвращает null при вызове из неуправляемого кода."

В этом случае я хотел бы знать, какая сборка вызывается неуправляемым кодом.

Есть ли надежный способ сделать это, то есть тот, который всегда возвращает ссылку на ненулевое Assembly?

Ответы

Ответ 1

Лучшее, о чем я мог догадаться, - это следующее, которое должно работать в однопоточном сценарии:

// using System.Diagnostics;
// using System.Linq; 
Assembly entryAssembly = new StackTrace().GetFrames().Last().GetMethod().Module.Assembly;

(приведенный выше фрагмент оптимизирован для удобства понимания, а не для скорости выполнения или эффективности памяти.)

Ответ 2

Я пробовал оба метода stakx.

Метод, основанный на MainModule, не работает в некоторых особых случаях (например, динамические сборки).

Метод, основанный на StackTrace, может вернуть слишком высокую (или низкую) сборку в иерархии, например mscorlib.

Я сделал небольшой вариант, который хорошо работает в моих случаях использования:

// using System.Diagnostics;
// using System.Linq;
var methodFrames = new StackTrace().GetFrames().Select(t => t.GetMethod()).ToArray();
MethodBase entryMethod = null;
int firstInvokeMethod = 0;
for (int i = 0; i < methodFrames.Length; i++)
{
    var method = methodFrames[i] as MethodInfo;
    if (method == null)
        continue;
    if (method.Name == "Main" && method.ReturnType == typeof(void))
        entryMethod = method;
    else if (firstInvokeMethod == 0 && method.Name == "InvokeMethod" && method.IsStatic && method.DeclaringType == typeof(RuntimeMethodHandle))
        firstInvokeMethod = i;
}

if (entryMethod == null)
    entryMethod = firstInvokeMethod != 0 ? methodFrames[firstInvokeMethod - 1] : methodFrames.Last();

Assembly entryAssembly = entryMethod.Module.Assembly;

В принципе, я иду по стеку, пока не найду обычный метод с именем "Main" с типом возврата void. Если такой метод не найден, я ищу метод, вызываемый посредством отражения. Например, NUnit использует этот вызов для загрузки модульных тестов.

Конечно, я делаю это, только если Assembly.GetEntryAssembly() возвращает null.

Ответ 3

Другая (в основном непроверенная) отправная точка для рабочего решения может быть примерно такой:

// using System;
// using System.Diagnostics;
// using System.Linq;
ProcessModule mainModule = Process.GetCurrentProcess().MainModule;
Assembly entryAssembly = AppDomain.CurrentDomain.GetAssemblies()
                         .Single(assembly => assembly.Location == mainModule.FileName);

Остаются некоторые неопределенности:

  • Модули и сборки - это не одно и то же. ProcessModule может даже концептуально отличаться от Module. Описанный выше код всегда работает в присутствии многомодульных (то есть многофайловых) сборок, особенно когда точка входа сборки не находится в модуле манифеста?

  • Гарантируется, что Process.MainModule всегда возвращает ненулевую ссылку?