对于某些文件操作,我们使用 VB6 的 PropertyBag 对象通过 Content 属性将各种项目转换为字节数组。然后我们将字节数组保存为二进制文件的一部分。稍后,当我们加载文件时,我们读回文件,读入字节数组并使用 propertybag 的 readproperty 重建项目。我们经常使用它来存储客户徽标等图像。
.NET 框架对使用 PropertyBags 提供哪些支持?除了编写 COM 帮助程序 DLL 之外。属性包是 VB6 独有的构造还是通用 OLE/COM 自动化框架的一部分?
我希望避免编写 VB6 辅助 DLL 并直接访问(通过声明)所需的 COM 函数。我不想复制功能,而是将存储在 bytearray 中的对象转换为 .NET 等效项。
注意: 我对在 .NET 中实现属性包的功能不感兴趣。仅能够读取已保存的 VB6 属性包的字节。
.NET 中对 PropertyBags 的支持为 0。对象的序列化取代了 PropertyBag 构造。
所以在我看来你有两个选择。
我知道我要去哪一个。
您可以在他的旧VB6示例页面上查看Edanmo的将图片加载并保存到字节数组示例。它非常简单,可用于“序列化”任何实现 IPersistStream 接口的对象,例如 ADODB.Recordset。 VB6 的 PropertyBag 对对象使用 IPersistStream,并且可能实现“自定义”VT_Xxx 变体类型序列化。
顺便说一句,Edanmo 的代码片段允许您读取通过复制/粘贴到访问网格的图像列存储的图像。
嗯,我尝试添加对建议的 C:\WINDOWS\SysWow64\MSVBVM60.DLL 的引用,但在对象浏览器中没有得到任何PropertyBag。也许我做错了什么,但我想解析属性包二进制数据。
我已经分析了属性包的结构,并且有了部分实现。有一些东西我不知道它们代表什么(我想这可能是某种校验和,但看起来不像)。
这是我读取 VB6 属性包的实现:
public class PropertyBag
{
private Dictionary<string, object> _values;
public PropertyBag(byte[] data)
{
_values = new Dictionary<string, object>();
if (data != null && data.Length > 12
&& data[0] == 147 && data[1] == 178)
{
int offset = 4;
int totalSize = BitConverter.ToInt32(data, offset);
if (data.Length != totalSize)
throw new IndexOutOfRangeException("The PropertyBag total size does not match the actual data size");
offset += 4;
while (data.Length > offset + 4)
{
PropertyBagValueType valueType = (PropertyBagValueType)BitConverter.ToUInt16(data, offset);
offset += 2;
var valueNameLen = BitConverter.ToUInt16(data, offset);
offset += 2;
string valueName;
offset += 8; // Some data I don't understand
valueName = UnicodeEncoding.Unicode.GetString(data, offset, valueNameLen * 2);
offset += valueNameLen * 2; // Unicode take 2 bytes per char
int valueLen = 0;
switch (valueType)
{
case PropertyBagValueType.Bool:
valueLen = 2;
if (data[offset] == 0)
_values.Add(valueName, false);
else
{
Debug.Assert(data[offset] == 255);
_values.Add(valueName, true);
}
break;
case PropertyBagValueType.Byte:
valueLen = 2;
_values.Add(valueName, data[offset]);
break;
case PropertyBagValueType.Int16:
valueLen = 2;
_values.Add(valueName, BitConverter.ToInt16(data, offset));
break;
case PropertyBagValueType.Int32:
valueLen = 4;
_values.Add(valueName, BitConverter.ToInt32(data, offset));
break;
case PropertyBagValueType.Single:
valueLen = 4;
_values.Add(valueName, BitConverter.ToSingle(data, offset));
break;
case PropertyBagValueType.Double:
valueLen = 8;
_values.Add(valueName, BitConverter.ToDouble(data, offset));
break;
case PropertyBagValueType.Currency:
valueLen = 8;
var i64Value = BitConverter.ToInt64(data, offset);
_values.Add(valueName, i64Value / 10000.0);
break;
case PropertyBagValueType.String:
valueLen = BitConverter.ToInt32(data, offset) * 2; // Unicode
offset += 4;
_values.Add(valueName, UnicodeEncoding.Unicode.GetString(data, offset, valueLen));
break;
case PropertyBagValueType.ByteArray:
offset += 2; // The value in the current offset seem to be 1. It might be the size per item in the array. Other array types are not seem to be supported
valueLen = BitConverter.ToInt32(data, offset);
offset += 4;
offset += 4; // I have no idea why extra 4 bytes padding. Maybe it's for supporting arrays with size larger than 4GB, but VB is 32 bit, so it can't support more than 4GB in RAM
_values.Add(valueName, data.Skip(offset).Take(valueLen).ToArray());
break;
case PropertyBagValueType.Date:
valueLen = 8;
/* A Date is stored as an IEEE 64-bit (8-byte) floating point number, just like a Double
Digits to the left of the decimal point (when converted to decimal) are interperated as a date between 1 January 100 and 31 December 9999. Values to the right of the decimal point indicate time between 0:00:00 and 23:59:59.
The date section of the Date data type (before the decimal point), is a count of the number of days that have passed since 1 Jan 100, offset by 657434. That is, 1 Jan 100 is denoted by a value of -657434; 2 Jan 100 is denoted -657433; 31 Dec 1899 is 1; 1 Jan 2000 is 36526, and so on.
The time section of the date (after the decimal point) is the fraction of a day, expressed as a time. For example, 1.5 indicates the date 31 Dec 1899 (as above) and half a day, i.e. 12:00:00. So, an hour is denoted by an additional 4.16666666666667E-02, a minute by 6.94444444444444E-04, and a second by 1.15740740740741E-05.
Taken from: https://www.codeguru.com/visual-basic/how-visual-basic-6-stores-data/
*/
var num = BitConverter.ToDouble(data, offset);
DateTime dateTime = new DateTime(1899, 12, 30);
var days = Math.Floor(num);
var seconds = (num - days) * (3600 * 24);
dateTime = dateTime.AddDays(days);
dateTime = dateTime.AddSeconds(seconds);
_values.Add(valueName, dateTime);
break;
default:
throw new NotSupportedException("Unsupported value type");
}
offset += valueLen;
if (offset % 4 != 0)
offset += 4 - offset % 4;
}
}
}
public IEnumerable<string> Keys
{
get
{
return _values.Keys.ToList();
}
}
public object ReadValue(string key, object defaultValue = null)
{
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
var keyToUse = key.ToLowerInvariant();
if (_values.ContainsKey(keyToUse))
return _values[keyToUse];
return defaultValue;
}
}
internal enum PropertyBagValueType
{
Bool = 11,
Byte = 17, // 0x11
Int16 = 2,
Int32 = 3,
Single = 4,
Double = 5,
Currency = 6,
String = 8,
Date = 7,
ByteArray = 8209, //0x2011
}
如您所见,我能够读取大多数类型的值,包括日期和时间。 我不确定值类型1、9、10、12-16是否被占用。 我原本假设 1 将用于位/布尔值,但事实并非如此,并且 Byte 的枚举高于 Int16 和 Int32
由于 PropertyBag 不支持内部属性包、类和集合,所以我不确定我还没有涵盖哪些其他类型。
还有什么要补充的吗? (就像我错过的类型?)