项目中,用C#开发了一个web服务后台,提供接口以供外部调用。最近功能更新,更改了某个接口的实现,发版并测试之后,发现在vs中调试一切正常,但是发布到IIS中运行,就会出现问题,该接口调用不了。由于是java web调用C#后台,调用后台的代码,是由web服务后台的wsdl文件生成,现在接口调用出错,加上只有此次更改后的接口调用出错,其它接口一切正常,而且在vs编译器下又一切正常。所以,第一直观肯定是这里的java调用出现问题。
第1步,确定是java调用问题。
尝试重新生成java调用web后台的代码。因为VS中一切正常,IIS中就不行,于是发布到IIS之后,利用运行起来的wsdl文件,生成新的调用代码,试了之后,还是不行。通过解析java war包后的文件夹内容,然后进行比较,发现其实war包的内容是一样的,也就是说生成java调用C# web后台的代码没有变化,问题不在这里。
第2步,确定是IIS配置问题。
这个解决方式百度上有很多,基本上就是修改IIS配置,同时对调用的dll,通过右键属性->安全,授予IIS用户权限。处理之后,依然没有解决问题。
第3步,确定是tomocat配置问题。
查看tomocat下的日志,发现有日志记录,意思是调用接口的类中,出现了javax.xml.ws.WebServiceException: java.net.SocketException: Connection reset错误,网上有建议将生成的wsdl文件里面的接口地址处理成要发布的IP地址,没有解决此问题。另外tomocat的配置本来就没有修改,之前就没有问题,所以紧接着确定是代码问题。
第4步,调用的dll,编译生成的代码有问题。
跟写dll的C++沟通,基本确定没问题,毕竟,如果有问题的话,那么在编译器调试时就会出现问题。
第5步,调用dll的C#代码有问题。
好不容易来到了第四步,问题真就出在这里!发现的方式是,在调用dll前面和后面直接返回接口调用,实际效果是一个可以一个不可以,由此锁定问题。在进行C#调用C++中,代码如下:
[DllImport("SDK.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern string TEST_SDK_GetVersion();
上面是一个简单的调用代码,获取版本号,返回的是string类型。但实际在跟C++工程师沟通后,得知该函数在C++中,返回的是string类型的指针。这里涉及到内存管理的问题,因为VS和IIS应用程序池的内存管理方式存在差别,所以导致VS里面运行正常,发步IIS后出现问题。
修改方式:返回值不用string,采用IntPtr。IntPtr用于表示指针或句柄的平台特定类型,C#中的IntPtr类型称为“平台特定的整数类型”,你也可以理解成变相的指针,这里由于我们需要的还是string类型的值,所以返回值不能直接使用,还需要转换。所以修改该函数返回值类型,并添加一个方法来间接调用:
[DllImport("SDK.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr TEST_SDK_GetVersion();
private string Get_TEST_SDK_GetVersion()
{
return Marshal.PtrToStringAnsi(TEST_SDK_GetVersion()); //转换为C#托管环境的string
}
现在,改为调用Get_TEST_SDK_GetVersion方法即可。
总结:查看MSDN文档,Marshal.PtrToStringAnsi(IntPtr)方法为托管字符串的副本从非托管 ANSI 第一个空字符之前的所有字符 String, 并且每个将 ANSI 字符扩展为 Unicode,对于自定义封送处理或混合托管和非托管代码时很有用。通过该方法,将非托管C++的string指针的值,在托管代码C#中获取到。这样,也就理解了为啥在VS中调试或运行一切正常,而发布IIS上就有问题了。