BinaryFormatter 反序列化在通过 COM 互操作使用时抛出异常

BinaryFormatter Deserialisation throws exception when used through COM interop

我必须更新一些旧应用程序 (vb6),并且我一直在使用 COM 互操作在 c# 中编写新代码 (Visual Studio 2010)。它大部分工作正常,但我遇到了一个问题,我不确定是什么原因造成的。

我使用下面的方法对对象进行深拷贝

    public static T CloneObject<T>(T source)
    {
        T destination = default(T);

        if (!typeof(T).IsSerializable)
        {
            throw new ArgumentException("The type must be serialisable.", "source");
        }

        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        using (Stream ms = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(ms, source);
            ms.Position = 0;

            destination = (T)formatter.Deserialize(ms);
        }

        return destination;
    }

由于某种原因,调用反序列化函数时代码出错。它抛出的异常是找不到代码所在的程序集。

Unable to find assembly 'AssemblyBeingUsed, Version = 1.0.0.0, Culture=neutral, PublicKeyToken=null'

这让我有点困惑,因为程序集已经被访问并且它说找不到它。格式化错误消息的代码是同一程序集的一部分!

这是异常的堆栈跟踪。

at System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly() at System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name) at System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum) at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run() at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage) at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)

编辑:更有用的信息。

从不直接从 vb6 调用此函数。因为它使用泛型,所以我很确定无论如何这是不可能的。从从 vb6 应用程序加载的表单调用此函数。当从 c# 应用程序使用此表单时,即使它在做完全相同的事情,也没有问题。

Visual Studio项目使用"Register for COM interop"选项,程序集加载到vb6项目中作为参考。

编辑:来自fuslogvw.exe

的输出

fuslogvw.exe 的输出显示了 5 个与我感兴趣的程序集相关的条目 (BarcodeAndOperatorDatabase)。由于它们合并起来很长,我已将所有输出上传到 this file.

老实说,我不确定自己在看什么。我认为有 3 个操作对应于程序集绑定发生的时间:

15:29:06: VB6 应用程序启动,操作成功。

15:29:14(2 个条目):从程序集加载的表单(我认为),操作失败。这有点令人困惑,因为表单可以正确加载并且可以与之交互。

15:29:50(2条):调用CloneObject方法时按钮点击失败,操作失败。

TLDR 版本:

系统无法定位程序集。解决方案是为 AppDomain.CurrentDomain.AssemblyResolve 事件和 return 添加对所需程序集的引用。

长版:

堆栈跟踪以 System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly() 结束。如果您进一步跟踪源代码中的方法调用,您将看到调用了以下代码。

    internal static Assembly LoadAssemblyFromString(String assemblyName) {
        //
        // Try using the stringized assembly name to load from the fusion cache.
        //
        BCLDebug.Trace("SER", "[LoadAssemblyFromString]Looking for assembly: ", assemblyName);
        Assembly found = Assembly.Load(assemblyName);
        return found;
    }

本次通话融合日志为:

*** Assembly Binder Log Entry  (14/06/2017 @ 15:29:50) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Running under executable  C:\Program Files (x86)\Microsoft Visual Studio\VB98\vb6.exe
--- A detailed error log follows. 

=== Pre-bind state information ===
LOG: DisplayName = BarcodeAndOperatorDatabase, Version=1.0.4.0, Culture=neutral, PublicKeyToken=null
 (Fully-specified)
LOG: Appbase = file:///C:/Program Files (x86)/Microsoft Visual Studio/VB98/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = vb6.exe
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).
LOG: The same bind was seen before, and was failed with hr = 0x80070002.
ERR: Unrecoverable error occurred during pre-download check (hr = 0x80070002).

问题是系统正在探测“Appbase”下列出的目录 (C:\Program Files (x86)\Microsoft Visual Studio\VB98),并且由于所需的程序集不在该目录下,它未找到。

最初COM加载CLR和CLR加载程序集时,都是基于注册表中存储的路径。从这个日志条目可以看出这一点。

=== Pre-bind state information ===
LOG: Where-ref bind. Location = D:/Development/Library/C Sharp/BarcodeAndOperatorDatabase/BarcodeAndOperatorDatabase/bin/x86/ComInterop/BarcodeAndOperatorDatabase.dll
LOG: Appbase = file:///C:/Program Files (x86)/Microsoft Visual Studio/VB98/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = vb6.exe
Calling assembly : (Unknown).
===
LOG: This bind starts in LoadFrom load context.
WRN: Native image will not be probed in LoadFrom context. Native image will only be probed in default load context, like with Assembly.Load().
LOG: No application configuration file found.
LOG: Using host configuration file: 
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Attempting download of new URL file:///D:/Development/Library/C Sharp/BarcodeAndOperatorDatabase/BarcodeAndOperatorDatabase/bin/x86/ComInterop/BarcodeAndOperatorDatabase.dll.
LOG: Assembly download was successful. Attempting setup of file: D:\Development\Library\C Sharp\BarcodeAndOperatorDatabase\BarcodeAndOperatorDatabase\bin\x86\ComInterop\BarcodeAndOperatorDatabase.dll
LOG: Entering run-from-source setup phase.
LOG: Assembly Name is: BarcodeAndOperatorDatabase, Version=1.0.4.0, Culture=neutral, PublicKeyToken=null
LOG: Re-apply policy for where-ref bind.
LOG: Where-ref bind Codebase does not match what is found in default context. Keep the result in LoadFrom context.
LOG: Binding succeeds. Returns assembly from D:\Development\Library\C Sharp\BarcodeAndOperatorDatabase\BarcodeAndOperatorDatabase\bin\x86\ComInterop\BarcodeAndOperatorDatabase.dll.
LOG: Assembly is loaded in LoadFrom load context.

最初该程序集仅存在于 LoadFrom load context 中,在探测 default load context 时无法找到。因此有必要在探测default load context时提供一种方法来通过LoadFrom程序集。这可以通过处理 AppDomain.AssemblyResolve 事件来完成。

以下摘录自:Best Practices for Assembly Loading 提供了对各种上下文的描述。

Within an application domain, assemblies can be loaded into one of three contexts, or they can be loaded without context:

The default load context contains assemblies found by probing the global assembly cache, the host assembly store if the runtime is hosted (for example, in SQL Server), and the ApplicationBase and PrivateBinPath of the application domain. Most overloads of the Load method load assemblies into this context.

The load-from context contains assemblies that are loaded from locations that are not searched by the loader. For example, add-ins might be installed in a directory that is not under the application path. System.Reflection.Assembly.LoadFrom, System.AppDomain.CreateInstanceFrom, and System.AppDomain.ExecuteAssembly are examples of methods that load by path.

The reflection-only context contains assemblies loaded with the ReflectionOnlyLoad and ReflectionOnlyLoadFrom methods. Code in this context cannot be executed, so it is not discussed here. For more information, see How to: Load Assemblies into the Reflection-Only Context.