背景
公司的支付平台最近对接了西安移动的支付接口,接口中签名的方法是对方提供了一个com组件,组件中包含了一个签名的方法和一个验签的方法,注册了签名之后,在vs中进行了引用,引用之后,查看组件的定义如下:
using System; using System.Runtime.InteropServices; namespace UMPAYLib { [ClassInterface(0)] [Guid("E92EB0AA-00CC-4F93-A76D-632BEA94E980")] [TypeLibType(2)] [ComConversionLoss] public class SignClass : ISign, Sign { public SignClass(); [DispId(1)] public virtual string Sign(string str, string certfile, string keyfile); [DispId(2)] public virtual int Verify(string str, string sig, IntPtr certfile); } }
首先先看一下签名的方法:Sign(string str,string certfile,string keyfile);
三个参数分别是用于签名的串、公钥证书的路径和私钥证书的路径。
再看验签的方法:Verify(string str,string sig,IntPtr certfile);
三个参数分别是用于签名的串,要验证的签名值和公钥证书的路径。
那么问题来了,验签方法的第三个参数,证书的路径怎么是IntPtr类型呢?IntPtr到底是个什么类型呢?我该怎么调用这个方法呢?
解决过程
首先我问题了接口方,接口放的对接人员倒是挺负责任,帮我看文档,问同事,可接口方看过他们自己的文档之后,也郁闷了,他们也不清楚文档咋和组件里的方法定义不一样,他们说要请示总部,而请示总部要用邮件,而且半天也不见回复,可接口后天就要上线测试了,等回复看来不靠谱,还得靠自己,于是就开始了求助度娘。
首先我们来看看IntPtr到底是个什么类型?
MSDN的解释:
用于表示指针或句柄的平台特定类型。 备注: IntPtr 类型被设计成整数,其大小适用于特定平台。 即是说,此类型的实例在 32 位硬件和操作系统中将是 32 位,在 64 位硬件和操作系统上将是 64 位。 IntPtr 类型可以由支持指针的语言使用,并可作为在支持与不支持指针的语言间引用数据的一种通用方式。 IntPtr 对象也可用于保持句柄。 例如,IntPtr 的实例广泛地用在 System.IO.FileStream 类中来保持文件句柄。 IntPtr 类型符合 CLS,而 UIntPtr 类型却不符合。 只有 IntPtr 类型可用在公共语言运行时中。 UIntPtr 类型大多数是提供来维护与 IntPtr 类型之间的体系结构上的对称性。 此类型实现 ISerializable 接口。
其中一行是这样说的:
IntPtr类型可以由支持指针的语言使用,并可作为在支持与不支持指针的语言间引用数据的一种通用方式。
引用数据的通用方式?指针?看到这个之后,我就在度娘里输入了“IntPtr传字符串“几个字,搜索结果中看到了一篇园子里一个仁兄写的博客,http://www.cnblogs.com/jxsoft/archive/2011/07/06/2099061.html,正式这篇博客,让我豁然开朗,找到了问题最终的解决办法,虽然解决的方法不是用的这位仁兄的方法,但思路是从这儿而来,所以还是要谢谢”许明吉博客“了。
我先是用了这篇博客中的如下这个方法进行测试:
/// <summary> /// 根据数据的长度申请非托管空间 /// </summary> /// <param name="strData">要申请非托管空间的数据</param> /// <returns>指向非拖管空间的指针</returns> private static IntPtr mallocIntptr( string strData ) { //先将字符串转化成字节方式 Byte[] btData = System.Text.Encoding.Default.GetBytes(strData); //申请非拖管空间 IntPtr m_ptr = Marshal.AllocHGlobal(btData.Length); //给非拖管空间清0 Byte[] btZero = new Byte[btData .Length+ 1]; //一定要加1,否则后面是乱码,原因未找到 Marshal.Copy(btZero, 0, m_ptr, btZero.Length); //给指针指向的空间赋值 Marshal.Copy(btData, 0, m_ptr, btData.Length); return m_ptr; }
测试的代码如下:
UMPAYLib.SignClass signClass = new UMPAYLib.SignClass();IntPtr ptrCertFile = mallocIntptr(certFile); int b = signClass.Verify(prestr, SIGN, ptrCertFile);
运行测试的页面,当执行到signClass.Verify这个com组件验签的方法的时候,报了一个”没有足够的内存来继续运行程序“的异常,组件内部的代码也看不到,所以也不知道里面怎么处理导致了这个内存溢出的异常,好不容易找到了思路,却有出现了这个问题,该怎么办呢?于是我又看了看ptrCertFile的属性和方法,我输出了ptrCertFile的.ToString(),发现得到了一个很大的数字,可还是不知道为什么会内存溢出,我又仔细看了看,mallocIntptr这个方法,方法里有一个分配内存的类引起了我的兴趣,它就是Marshal,我们再来看看Mashal这个类具体是干啥的?有啥方法?
MSDN的解释:
提供了一个方法集,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。
从解释中可以看出,该类主要是用于分配非托管内存和在托管类型和非托管类型之间进行转换。
于是我就浏览了一下Mashal类的成员,发现了一个方法:
而这个方法也本身就能实现上面mallocIntptr这个方法的功能,于是我就把代码修改为如下:
UMPAYLib.SignClass signClass = new SignClass(); string certPath = MobileWapPayConfig.CertFile; IntPtr ptrCertFile = Marshal.StringToBSTR(certPath); int result = signClass.Verify(prestr, sign, ptrCertFile); Marshal.FreeBSTR(ptrCertFile); return result == 0;
重新运行测试页面,一切正常。
至此,最初遇到的IntPtr不知如何调用的问题已经解决了,但遗留了一个小问题,那就是为什么mallocIntptr这个方法会导致内存溢出,望了解的朋友不吝赐教!
总结
对于一些com组件的方法参数IntPtr类型的,可以使用Marshal类的相关方法来处理。