日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

分享一次与SharpDX坑爹Bug刚正面的过程

發布時間:2023/12/4 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 分享一次与SharpDX坑爹Bug刚正面的过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

和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實際本質是一個字節數組,其內容為?31000,代表無限循環?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) { /* ... */ }

  • 原來?objectvalue的本質是它內部騷操作創建了一個?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?然而這么多情況,就是沒找到我需要的那一種????。

    最重要的是,直接傳?IntPtr的另一個重載它居然是?internal的,導致我幾乎沒什么操作空間了。

    逢山開路,遇水搭橋

    我花了很多時間怎么在?SharpDX已有提供的?Variant對象上做手腳,但感覺代碼會比較復雜,所以我走向了另一條路,從那個?internal,傳?IntPtr的接口出發解決。

    調用私有方法

    傳?IntPtr的方法是私有的,不能直接調用,但可以使用反射來創建一個指向該私有方法的委托,然后通過該委托調用這個方法:

  • var setMetadataMethod = encoder.MetadataQueryWriter

  • .GetType()

  • .GetMethod(nameof(WIC.MetadataQueryWriter.SetMetadataByName), BindingFlags.NonPublic | BindingFlags.Instance);

  • var setMetadata = (Action<string, IntPtr>)setMetadataMethod

  • .CreateDelegate(typeof(Action<string, IntPtr>), encoder.MetadataQueryWriter);

  • 這樣一來即可通過?setMetadata委托來調用。

    不一樣的Variant

    SharpDX提供了一個?Variant,不代表必須要用他的?Variant,他想通過高達?300行代碼的?if/else搞定一切,導致代碼超級復雜。而通過查詢?C++接口,我能將?Variant簡化為如下一個?struct:

  • [StructLayout(LayoutKind.Explicit)]

  • struct PV

  • {

  • [FieldOffset(0)] short VT;

  • [FieldOffset(8)] int Length;

  • [FieldOffset(8)] IntPtr StringBuffer;

  • [FieldOffset(8)] ushort UShortValue;

  • [FieldOffset(16)] IntPtr Buffer;

  • public static PV CreateUByteVector(int length, IntPtr buffer) => new PV

  • {

  • VT = (short)VariantType.Vector + (short)VariantElementType.UByte,

  • Length = length,

  • Buffer = buffer

  • };

  • public static PV CreateString(IntPtr buffer) => new PV

  • {

  • VT = (short)VariantElementType.StringPointer,

  • StringBuffer = buffer,

  • };

  • public static PV CreateUI2(ushort val) => new PV

  • {

  • VT = (short)VariantElementType.UShort,

  • UShortValue = val,

  • };

  • }

  • 指定/appext/Application

    這是一個指向字符串的指針,正常可能要用?fixed關鍵字或者?Marshal.AllocHGlobal方法,但這里有更簡單的辦法,我可以使用?stackalloc,因為此處我具備該內存的所有權,且能精準控制它的生命周期,而且它性能也更好:

  • // /appext/Application: NETSCAPE2.0

  • byte* bytes = stackalloc byte[11] { 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48 };

  • PV pv = PV.CreateUByteVector(11, (IntPtr)bytes);

  • setMetadata("/appext/Application", (IntPtr)(void*)&pv);

  • 指定/appext/Data

    同樣的道理,將?/appext/data的?31000分配在棧上代碼最簡單,性能也最好:

  • // /appext/Data: 3, 1, [0, 0], 0

  • byte* bytes2 = stackalloc byte[5] { 3, 1, 0, 0, 0, };

  • PV pv2 = PV.CreateUByteVector(5, (IntPtr)bytes2);

  • setMetadata("/appext/Data", (IntPtr)(void*)&pv2);

  • 注意,不是所有內存都能分配到棧上,它有兩大限制,首先棧內存是有限的,其次,該內存必須立即被使用,否則棧銷毀后這些內存指針也會立即失效。

    優化

    其實有了這個,還能做更多的操作。默認圖片的運行速度比較慢,可以將圖片的速度調快一點,這也是通過設置?/grctlext/Delay實現的,只是該?metadata是存在于幀上(而不是圖片容器上):

  • // frame delay by 50ms.

  • var setMetadata = (Action<string, IntPtr>)setMetadataMethod

  • .CreateDelegate(typeof(Action<string, IntPtr>), frame.MetadataQueryWriter);

  • var pv = PV.CreateUI2(5);

  • setMetadata("/grctlext/Delay", (IntPtr)(void*)&pv);

  • 我設置的是?5,代表?50ms,該值的意思就是乘以?10ms,如?10就代表?100ms,每一幀都可以不一樣。如果不設置,那就由呈現器決定?gif播放的速度,大部分播放器都是?100ms。如果設置為?1或者?0,則會被呈現器忽略,如果想要在瀏覽器上運行,我親測最低可以設置的值為?2。

    下圖為默認效果:?

    下圖為?50ms效果:?

    下圖為?20ms效果:?

    還能如指定?Gif的?Comment字段,可以給自己做一個簽名:

  • // "/commentext/TextEntry": "Created by Flysha.Zhou\0"

  • var commentsBytes = Encoding.UTF8.GetBytes("Created by Flysha.Zhou\0");

  • fixed (byte* p = commentsBytes)

  • {

  • var pv3 = PV.CreateString((IntPtr)p);

  • setMetadata("/commentext/TextEntry", (IntPtr)(void*)&pv3);

  • }

  • 結語

    上述代碼都已經上傳到了我的博客?Github中:?https://github.com/sdcb/blog-data/tree/master/2019/20191127-the-variant-bug-in-sharpdx

    由于?SharpDX官方已經停止維護,但遲遲又沒有其它包頂上,官方是不可能修這些東西????,這些坑只能自己默默踩了(這波“福報”只能默默領好了)。有時我覺得要是脾氣來了,我甚至可以?SharpDX拿過來自己維護一個發行版,因為?SharpDX在平時的生活工作中實在是太常用、太常見了。

    微信不能評論,有想法的朋友可以點擊閱讀原文,或者到這個地址(博客園)進行點贊/評論:https://www.cnblogs.com/sdflysha/p/20191127-the-variant-bug-in-sharpdx.html

    喜歡的朋友 請關注我的微信公眾號:【DotNet騷操作】

    總結

    以上是生活随笔為你收集整理的分享一次与SharpDX坑爹Bug刚正面的过程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。