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:\kvdxd0xc2'
而直接运行python代码,其输出却是:
dir: d:kvd新, code: 'd:\kvdxe6x96xb0'
用c#建Demo用Encoding反解析验知xd0xc2为[新]字的gb2312编码,xe6x96xb0为[新]字的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 -1; } 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 + 1); Marshal.Copy(utf8bytes, 0, ptr, utf8bytes.Length); Marshal.WriteByte(ptr, utf8bytes.Length, 0); return ptr; } public object MarshalNativeToManaged(IntPtr pNativeData) { if (pNativeData == IntPtr.Zero) return null; List<byte> bytes = new List<byte>(); for (int offset = 0; ; offset++) { byte b = Marshal.ReadByte(pNativeData, offset); if (b == 0)
break; else
bytes.Add(b); } return Encoding.UTF8.GetString(bytes.ToArray(), 0, 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!运行效果如下:
至此,一个技术难点解决,记录于此。