和SharpDX坑爹的Variant剛正面
和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
然而這麼多情況,就是沒找到我需要的那一種