和SharpDX坑爹的Variant刚正面

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

和SharpDX坑爹的Variant刚正面

.NET骚操作   2019-12-02 我要评论

和SharpDX坑爹的Variant刚正面

几个月前我写了和篇文章《.NET中生成动态验证码》文章,其实里面藏着一个大坑。运行里面的代码,会发现运行的gif图片并没有循环播放:

细心的网友也注意到了这个问题:

……但后来他备注说“已解决”,我当时也不知道该怎么解决的,所以我追问了一下,但他一直没有回复。但思路肯定是有的,再不济,也可以将保存为字节数组的数据,用其它的库进行重新解析,然后指定循环次数即可,当然这个方法肯定很搓,想象中较好的办法应该是调用SharpDX内置的API来完成。

踩坑之路

就此我开始了SharpDX的踩坑之路,我找到了许多资料,找到不少示例代码,最后不断实验,最终成功。

C++示例

首先我在网上找到了SharpDX生成循环gif文件的C++开源代码示例,代码源自于https://github.com/GarethRichards/GifSaver/blob/master/GifSaver.cpp:

PROPVARIANT propValue; PropVariantInit(&propValue);
propValue.vt = VT_UI1 | VT_VECTOR;
propValue.caub.cElems = 11;

DX::ThrowIfFailed(m_imagingFactory->CreateEncoder(GUID_ContainerFormatGif, &GUID_VendorMicrosoft, &m_wicBitmapEncoder));
DX::ThrowIfFailed(m_wicBitmapEncoder->Initialize(m_stream.Get(), WICBitmapEncoderNoCache));
ComPtr<IWICMetadataQueryWriter> pEncoderMetadataQueryWriter;
DX::ThrowIfFailed(m_wicBitmapEncoder->GetMetadataQueryWriter(&pEncoderMetadataQueryWriter));
string elms = "NETSCAPE2.0";
propValue.caub.pElems = const_cast<UCHAR *>(reinterpret_cast<const UCHAR *>(elms.c_str()));
DX::ThrowIfFailed(pEncoderMetadataQueryWriter->SetMetadataByName(L"/appext/Application", &propValue));

// Set animated GIF format 
propValue.vt = VT_UI1 | VT_VECTOR;
propValue.caub.cElems = 5;
UCHAR buf[5];
propValue.caub.pElems = &buf[0];
*(propValue.caub.pElems) = 3; // must be > 1, 
*(propValue.caub.pElems + 1) = 1; // defines animated GIF 
*(propValue.caub.pElems + 2) = 0; // LSB 0 = infinite loop. 
*(propValue.caub.pElems + 3) = 0; // MSB of iteration count value 
*(propValue.caub.pElems + 4) = 0; // NULL == end of data 

DX::ThrowIfFailed(pEncoderMetadataQueryWriter->SetMetadataByName(L"/appext/Data", &propValue));
// ...

注意其中/appext/Data实际本质是一个字节数组,其内容为3 1 0 0 0,代表无限循环gif(注释中说得很清楚),这就应该是循环gif的关键所在。

可见,首先需要创建一个PROPVARIANT对象,然后调用IWICBitmapEncoder中的GetMetadataQueryWriter方法,获取IWICMetadataQueryWriter,然后通过该方法中的SetMetadataByName,将/appext/Application以及/appext/Data按照指定的格式传入即可,还能有问题什么呢?

问题可大咯!

坑人的Variant

刚好SharpDX对这些C++/COM接口有看似正确的移植,首先GifBitmapEncoder提供了MetadataQueryWriter属性(而不是Get函数),非常贴心,该类中也包含了名字相同的设置函数方法,其签名如下:

public unsafe void SetMetadataByName(string name, object value) { /* ... */ }

嗯,非常合理,一个键值对而已,能有什么问题?我便用了起来,我自以为代码可能也许大概应该长这个样子:

encoder.MetadataQueryWriter.SetMetadataByName("/appext/Application", "NETSCAPE2.0");
encoder.MetadataQueryWriter.SetMetadataByName("/appext/Data", new byte[] { 3, 1, 0, 0, 0 });

然而运行报错了,错误信息为:

SharpDX.SharpDXException: HRESULT: [0x80070057], Module: [General], ApiCode: [E_INVALIDARG/Invalid Arguments], Message: 参数错误。

   at SharpDX.Result.CheckError() in C:\projects\sharpdx\Source\SharpDX\Result.cs:line 197
   at SharpDX.WIC.MetadataQueryWriter.SetMetadataByName(String name, IntPtr varValueRef) in C:\projects\sharpdx\Source\SharpDX.Direct2D1\Generated\REFERENCE\WIC\Interfaces.cs:line 4923
   at SharpDX.WIC.MetadataQueryWriter.SetMetadataByName(String name, Object value) in C:\projects\sharpdx\Source\SharpDX.Direct2D1\WIC\MetadataQueryWriter.cs:line 91
   at UserQuery.SaveD2DBitmap(Int32 width, Int32 height, String text) in C:\Users\sdfly\AppData\Local\Temp\LINQPad6\_uciwedks\kncljn\LINQPadQuery:line 79
   at UserQuery.Main() in C:\Users\sdfly\AppData\Local\Temp\LINQPad6\_uciwedks\kncljn\LINQPadQuery:line 5

反编译它这个SetMetadataByName一看,其代码如下:

public unsafe void SetMetadataByName(string name, object value)
{
    byte* variant = stackalloc byte[512];
    Variant* variantStruct = (Variant*)variant;
    variantStruct->Value = value;
    SetMetadataByName(name, (IntPtr)(void*)variant);
}

internal unsafe void SetMetadataByName(string name, IntPtr varValueRef) { /* ... */ }

原来object value的本质是它内部骚操作创建了一个Variant,该Variant就对应了C++中的PROPVARIANT,然后后续调用的正确性取决于Variant.Value属性的设置方法,该属性的源代码如下:

// SharpDX.Win32.Variant
using SharpDX.Mathematics.Interop;
using System;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;

public unsafe object Value
{
    get { /* ... */ }
    set
    {
        if (value == null)
        {
            Type = VariantType.Default;
            ElementType = VariantElementType.Null;
            return;
        }
        Type type = value.GetType();
        Type = VariantType.Default;
        if (type.GetTypeInfo().get_IsPrimitive())
        {
            if ((object)type == typeof(byte)) // ...
            if ((object)type == typeof(sbyte)) // ...
            if ((object)type == typeof(int)) // ...
            if ((object)type == typeof(uint)) // ...
            if ((object)type == typeof(long)) // ...
            if ((object)type == typeof(ulong)) // ...
            if ((object)type == typeof(short)) // ...
            if ((object)type == typeof(ushort)) // ...
            if ((object)type == typeof(float)) // ...
            if ((object)type == typeof(double)) // ...
        }
        else
        {
            if (value is ComObject) // ...
            if (value is DateTime) // ...
            if (value is string) // ...
        }
        throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Type [{0}] is not handled", new object[1]
        {
            type.get_Name()
        }));
    }
}

在原代码,该属性全部代码长达约300行,可见作者是花了些心思的,判断了那个object的“各种”情况,根据上文中的C++代码,我需要的是VT_UI1 | VT_VECTOR 然而这么多情况,就是没找到我需要的那一种

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们