1. 起源:
VCU10之视频下载模块,采用纯python编码实现,c++代码调用pythonrun.h配置python运行环境启动python模块,编译为dll给c#调用,以使界面UI能够使用其中功能。
不要问为什么不用IronPython,它不是正统Python,且下载模块亦要为Mac产品所用。
棘手问题!用去一天时间反复打印日志,验证所传字串区别,以期望发现问题定位问题,直至下班前始有灵感。
验证发现,非中文字符可以正常下载,中文字符下载解析失败,当时即想到可能是字串不统一所致,就在python代码试遍字串编码转换,均不奏效。
原接口封装代码如下:
[DllImport("VideoDownloader", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private extern static bool VDDownload(IntPtr instance, string savePath, string imageSavePath,
string quality, string ext, string subtitleLang);
比如传savePath为[d:\vcu新],dll调用python模块,打印其编码信息:
print 'dir: ' + dir + ', code: ' + repr(dir))
则打印日志为:
dir: d:\kvd新, code: 'd:\\kvd\xd0\xc2'
而直接运行python代码,其输出却是:
dir: d:\kvd新, code: 'd:\\kvd\xe6\x96\xb0'
用c#建Demo用Encoding反解析验知\xd0\xc2为[新]字的gb2312编码,\xe6\x96\xb0为[新]字的utf-8编码。
2、字串编码
Win7 64位简体中文版中c#的默认编码:
做如此简单验证,发现其默认编码为gb2312;而所用python代码,为兼顾中文等多字节字符,默认采用utf-8编码。
问题大抵就在这里了,dll接口字串传递方式,需改为utf-8字串进行传递,但c#内置没有utf-8封装器,自定义实现它。
3、UTF8Marshaler
//接口数据为utf-8编码所设置
public class UTF8Marshaler : ICustomMarshaler
{
public void CleanUpManagedData(object managedObj)
{
} public void CleanUpNativeData(IntPtr pNativeData)
{
Marshal.FreeHGlobal(pNativeData);
} public int GetNativeDataSize()
{
return -;
} public IntPtr MarshalManagedToNative(object managedObj)
{
if (object.ReferenceEquals(managedObj, null))
return IntPtr.Zero;
if (!(managedObj is string))
throw new InvalidOperationException(); byte[] utf8bytes = Encoding.UTF8.GetBytes(managedObj as string);
IntPtr ptr = Marshal.AllocHGlobal(utf8bytes.Length + );
Marshal.Copy(utf8bytes, , ptr, utf8bytes.Length);
Marshal.WriteByte(ptr, utf8bytes.Length, );
return ptr;
} public object MarshalNativeToManaged(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return null; List<byte> bytes = new List<byte>();
for (int offset = ; ; offset++)
{
byte b = Marshal.ReadByte(pNativeData, offset);
if (b == )
break;
else
bytes.Add(b);
}
return Encoding.UTF8.GetString(bytes.ToArray(), , bytes.Count);
} private static UTF8Marshaler instance = new UTF8Marshaler();
public static ICustomMarshaler GetInstance(string cookie)
{
return instance;
}
}
4、更新接口字串封送样式
[DllImport("VideoDownloader", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private extern static bool VDDownload(IntPtr instance,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]
string savePath,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]
string imageSavePath,
string quality, string ext, string subtitleLang);
验证工作OK!运行效果如下:
至此,一个技术难点解决,记录于此。