和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 然而这么多情况,就是没找到我需要的那一种
和SharpDX坑爹的Variant刚正面的更多相关文章
- .NET解所有相机RAW格式照片
再聊.NET解相机RAW格式照片 上次我发了一篇文章<用.NET解索尼相机ARW格式照片>,提到通过安装Sony Raw File Decoder的方式,然后调用Windows Imagi ...
- Error: 16GU盘变1G,恢复
最近装win10,chromium os之后,删除U盘中的文件,发现不能删除,脑子一热格式化了,发现16G突然变成了1G,这不是坑爹吗,刚买的新U盘呀.立马百度,发现有说是买的被骗了,有的说使用某个软 ...
- UITableView性能优化
关于UITableView的性能优化,网络上也有一些总结.在这里就介绍下我们项目中遇到的问题以及对应的解决方法.相信我们遇到的问题也有一定的普适性,能够作为其他问题的优化方案. Instruments ...
- mongodb操作记录
[User]1.db.addUser("name","pwd","true/false")2.db.auth("name" ...
- 20155222卢梓杰 实验八 Web基础
实验八 Web基础 1.安装apache sudo apt-get install apache2 2.启动apache service apache2 start 3.使用netstat -tupl ...
- 魔兽争霸war3心得体会(一):UD的冰甲蜘蛛流
玩war3好几年了,之前都是打打电脑,随便玩玩的.刚刚在浩方等平台上和人玩的时候,各种被虐,很难赢一局.从去年开始,才认真玩.思考下各种战术. 最初,使用的是兽族orc,后来觉得兽族不够厉害,玩到对战 ...
- .NET Core验证ASP.NET密码
.NET Core验证ASP.NET密码 随着.NET Core的持续更新和完善,越来越多的机构已经选择或者升级为.NET Core.但由于技术不完全相同,不可能所有应用/数据库都能无缝迁移,因此AS ...
- 学习进度04(billbill长评数据提取01)
学习了python写入csv文件自己想了一个小实战,爬取billbill<白色相簿>番剧的长评 网页是动态变化的,往下拉他才会更新出长评,找出关键链接https://api.bilibil ...
- 坑爹CF April Fools Day Contest题解
H - A + B Strikes Back A + B is often used as an example of the easiest problem possible to show som ...
随机推荐
- 利用X-Forwarded-For伪造客户端IP漏洞成因及防范
内容转载自叉叉哥https://blog.csdn.net/xiao__gui/article/details/83054462 问题背景 在Web应用开发中,经常会需要获取客户端IP地址.一个典型的 ...
- 设计模式(十七)Observer模式
在Observer模式中,当观察对象的状态发生变化时,会通知给观察者.Observer模式适用于根据对象状态进行相应处理的场景. 首先看一下示例程序的视图. 然后用实际代码来理解这种设计模式. pac ...
- 数据结构(三十三)最小生成树(Prim、Kruskal)
一.最小生成树的定义 一个连通图的生成树是一个极小的连通子图,它含有图中全部的顶点,但只有足以构成一棵树的n-1条边. 在一个网的所有生成树中,权值总和最小的生成树称为最小代价生成树(Minimum ...
- unity image 设置图片
从任意文件目录下读取文件并在unity中显示: 1)读取目标文件 byte[] imageByte = File.ReadAllBytes(imagePath); 2)转换成纹理 texture.Lo ...
- 修改linux系统history命令的条数和格式
在一次测试环境遇到的情况,发现服务莫名其妙挂了,以为服务有bug,查了一下午,后来一个同事说,是我把服务关了啊.... 是可忍孰不可忍,原生的history命令,只能看到输入的命令历史,看不到什么时候 ...
- vue-music 使用better-scroll遇到轮播图不能自动轮播
根据vue-music视频中slider组建的使用,当安装新版本的better-scroll,轮播组件,不能正常轮播 这是因为,better-scroll发布新版本之后,参数设置发生改变 这是旧版本: ...
- ansible剧本之playbook操作
ansible 剧本 yaml介绍: 是一个编程语言 文件后缀名 yaml yml 数据对应格式: 字典: key: value 列表: [] - ansible-playbook命令格式 执行顺序: ...
- 阿里规范不建议多表Join,可这SQL要怎么写?
阿里开发手册的描述,禁止多表join: 手册上写着[强制],相信很多同学项目里面的代码都不满足这个要求. 但是关键问题是:不用join,这SQL究竟要怎么写?! 分解关联查询 即对每个要关联的表进行单 ...
- vue设置页面标题
使用vue-wechat-title插件对页面标题进行设置 1.安装模块 命令行窗口中运行npm install vue-wechat-title --save PS.如果程序正在运行,ctrl ...
- csps模拟测试92反思
连着挂了三天T1了. 89: SPFA$vst$数组没清空 90:调试的时候多删了一句代码 91:没开$long long$ 我真是废物. 希望以后不要犯SB错误了