gal*_*ets 59 c++ wcf soap wsdl web-services
我需要让非托管的Windows C++客户端与WCF服务进行通信.C++客户端可以在Win2000及更高版本上运行.我可以控制WCF服务和正在使用的C++ API.由于它是专有应用程序,因此最好尽可能使用Microsoft的东西,绝对不是GNU许可的API.那些有它工作的人,你能分享一个循序渐进的过程如何让它运作起来吗?
到目前为止,我已经研究了以下选项:
还有什么想法吗?只有在你真正让自己工作的时候才能回答.
编辑1:我为任何我可能感到困惑的人道歉:我正在寻找的是一种从没有安装.NET框架的客户端调用WCF服务的方法,因此使用基于.NET的帮助库不是一种选择,它必须是纯粹的非托管C++
Mat*_*vis 58
基本思想是在C#中为客户端编写WCF代码(这种方式更简单)并使用C++桥接DLL来弥合非托管C++代码与用C#编写的托管WCF代码之间的差距.
以下是使用Visual Studio 2008和.NET 3.5 SP1的分步过程.
首先要做的是创建WCF服务和托管它的方法.如果您已经拥有此功能,请跳至下面的步骤7.否则,请按照此处的步骤创建Windows NT服务.使用VS2008为项目提供的默认名称以及添加到项目中的任何类.此Windows NT服务将承载WCF服务.
将名为HelloService的WCF服务添加到项目中.要执行此操作,请在"解决方案资源管理器"窗口中右键单击项目,然后选择"添加|新项..."菜单项.在"添加新项"对话框中,选择C#WCF服务模板,然后单击"添加"按钮.这会以接口文件(IHelloService.cs),类文件(HelloService.cs)和默认服务配置文件(app.config)的形式将HelloService添加到项目中.
像这样定义HelloService:
``
[ServiceContract]
public interface IHelloService
{
[OperationContract]
string SayHello(string name);
}
public class HelloService : IHelloService
{
public string SayHello(string name)
{
return String.Format("Hello, {0}!", name);
}
}
Run Code Online (Sandbox Code Playgroud)
修改上面步骤1中创建的Service1类,如下所示:
using System.ServiceModel;
using System.ServiceProcess;
public partial class Service1 : ServiceBase
{
private ServiceHost _host;
public Service1()
{
InitializeComponent();
}
protected override void OnStart( string [] args )
{
_host = new ServiceHost( typeof( HelloService ) );
_host.Open();
}
protected override void OnStop()
{
try {
if ( _host.State != CommunicationState.Closed ) {
_host.Close();
}
} catch {
}
}
}
Run Code Online (Sandbox Code Playgroud)建立项目.
打开Visual Studio 2008命令提示符.导航到项目的输出目录.键入以下内容:`installutil WindowsService1.exe'这将在本地计算机上安装Windows NT服务.打开"服务"控制面板,然后启动Service1服务.执行此操作非常重要,以便下面的步骤9能够正常工作.
从"文件"菜单中,选择"添加"|"新建项目..."菜单项.选择C#类库模板.将名称更改为HelloServiceClient,然后单击"确定"按钮.在解决方案资源管理器中右键单击该项目,然后选择"属性"菜单选项.在"构建"选项卡中,将输出路径更改为..\bin\Debug,以使程序集和app.config文件与MFC应用程序位于同一目录中.此库将包含服务引用(即WCF代理类)到Windows NT服务中托管的WCF Hello Service.
在Solution Explorer中,右键单击HelloServiceClient项目的References文件夹,然后选择Add Service Reference ...菜单选项.在"地址"字段中,键入Hello Service的地址.这应该等于上面步骤2中创建的app.config文件中的基址.单击"Go"按钮.Hello Service应显示在"服务"列表中.单击"确定"按钮以自动生成Hello Service的代理类. 注意: 我似乎总是遇到此过程生成的Reference.cs文件的编译问题.我不知道我做错了还是有错误,但解决这个问题的最简单方法是直接修改Reference.cs文件.问题通常是命名空间问题,可以轻松修复.请注意,这是一种可能性.对于此示例,我已将HelloServiceClient.ServiceReference1更改为HelloService(以及任何其他必需的更改).
为了允许MFC应用程序与WCF服务交互,我们需要构建一个托管C++"桥"DLL.从"文件"菜单中,选择"添加"|"新建项目..."菜单项.选择C++ Win32项目模板.将名称更改为HelloServiceClientBridge,然后单击"确定"按钮.对于Application Settings,将Application Type更改为DLL并选中Empty project复选框.单击"完成"按钮.
首先要做的是修改项目属性.在解决方案资源管理器中右键单击该项目,然后选择"属性"菜单选项.在常规设置下,将输出目录更改为..\bin\Debug,并将公共语言运行时支持选项更改为公共语言运行时支持(/ clr).在"框架和引用"设置下,添加对.NET系统,System.ServiceModel和mscorlib程序集的引用.单击"确定"按钮.
将以下文件添加到HelloServiceClientBridge项目 - HelloServiceClientBridge.h,IHelloServiceClientBridge.h和HelloServiceClientBridge.cpp.
将IHelloServiceClientBridge.h修改为如下所示:
#ifndef __IHelloServiceClientBridge_h__
#define __IHelloServiceClientBridge_h__
#include <string>
#ifdef HELLOSERVICECLIENTBRIDGE_EXPORTS
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#pragma comment (lib, "HelloServiceClientBridge.lib") // if importing, link also
#endif
class DLLAPI IHelloServiceClientBridge
{
public:
static std::string SayHello(char const *name);
};
#endif // __IHelloServiceClientBridge_h__
Run Code Online (Sandbox Code Playgroud)将HelloServiceClientBridge.h修改为如下所示:
#ifndef __HelloServiceClientBridge_h__
#define __HelloServiceClientBridge_h__
#include <vcclr.h>
#include "IHelloServiceClientBridge.h"
#ifdef _DEBUG
#using<..\HelloServiceClient\bin\Debug\HelloServiceClient.dll>
#else
#using<..\HelloServiceClient\bin\Release\HelloServiceClient.dll>
#endif
class DLLAPI HelloServiceClientBridge : IHelloServiceClientBridge
{ };
#endif // __HelloServiceClientBridge_h__
Run Code Online (Sandbox Code Playgroud).cpp文件的语法使用托管C++,这需要一些时间来习惯.将HelloServiceClientBridge.cpp修改为如下所示:
#include "HelloServiceClientBridge.h"
using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::ServiceModel;
using namespace System::ServiceModel::Channels;
std::string IHelloServiceClientBridge::SayHello(char const *name)
{
std::string rv;
gcroot<Binding^> binding = gcnew WSHttpBinding();
gcroot<EndpointAddress^> address = gcnew EndpointAddress(gcnew String("http://localhost:8731/Design_Time_Addresses/WindowsService1/HelloService/"));
gcroot<HelloService::HelloServiceClient^> client = gcnew HelloService::HelloServiceClient(binding, address);
try {
// call to WCF Hello Service
String^ message = client->SayHello(gcnew String(name));
client->Close();
// marshal from managed string back to unmanaged string
IntPtr ptr = Marshal::StringToHGlobalAnsi(message);
rv = std::string(reinterpret_cast<char *>(static_cast<void *>(ptr)));
Marshal::FreeHGlobal(ptr);
} catch (Exception ^) {
client->Abort();
}
return rv;
}
Run Code Online (Sandbox Code Playgroud)剩下要做的就是更新MFC应用程序以调用SayHello()WCF服务调用.在MFC表单上,双击Say Hello!按钮生成ButtonClicked事件处理程序.使事件处理程序看起来像这样:
#include "IHelloServiceClientBridge.h"
#include <string>
void CMFCApplicationDlg::OnBnClickedButton1()
{
try {
std::string message = IHelloServiceClientBridge::SayHello("Your Name Here");
AfxMessageBox(CString(message.c_str()));
} catch (...) {
}
}
Run Code Online (Sandbox Code Playgroud)运行应用程序,然后单击Say Hello!按钮.这将导致应用程序调用Windows NT服务中托管的WCF Hello Service的SayHello()方法(顺便说一下,它应该仍在运行).然后返回值显示在消息框中.
希望您能从这个简单的例子中推断出适合您的需求.如果这不起作用,请告诉我,以便我可以修复帖子.
gal*_*ets 12
对于那些感兴趣的人,我找到了一个半工作的ATL Server解决方案.以下是主机代码,注意它使用BasicHttpBinding,它是唯一一个与ATL Server一起使用的代码:
var svc = new Service1();
Uri uri = new Uri("http://localhost:8200/Service1");
ServiceHost host = new ServiceHost(typeof(Service1), uri);
var binding = new BasicHttpBinding();
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IService1), binding, uri);
endpoint.Behaviors.Add(new InlineXsdInWsdlBehavior());
host.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpGetEnabled = true });
var mex = host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
host.Open();
Console.ReadLine();
Run Code Online (Sandbox Code Playgroud)
可以在此处找到InlineXsdInWsdlBehavior的代码.需要对InlineXsdInWsdlBehavior进行一项重要更改,以便在涉及复杂类型时使其与sproxy一起正常工作.它是由sproxy中的错误引起的,它没有正确定义命名空间别名,因此wsdl不能具有重复的命名空间别名或者sproxy会废弃.这是需要改变的功能:
public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
{
int tnsCount = 0;
XmlSchemaSet schemaSet = exporter.GeneratedXmlSchemas;
foreach (WsdlDescription wsdl in exporter.GeneratedWsdlDocuments)
{
//
// Recursively find all schemas imported by this wsdl
// and then add them. In the process, remove any
// <xsd:imports/>
//
List<XmlSchema> importsList = new List<XmlSchema>();
foreach (XmlSchema schema in wsdl.Types.Schemas)
{
AddImportedSchemas(schema, schemaSet, importsList, ref tnsCount);
}
wsdl.Types.Schemas.Clear();
foreach (XmlSchema schema in importsList)
{
RemoveXsdImports(schema);
wsdl.Types.Schemas.Add(schema);
}
}
}
private void AddImportedSchemas(XmlSchema schema, XmlSchemaSet schemaSet, List<XmlSchema> importsList, ref int tnsCount)
{
foreach (XmlSchemaImport import in schema.Includes)
{
ICollection realSchemas = schemaSet.Schemas(import.Namespace);
foreach (XmlSchema ixsd in realSchemas)
{
if (!importsList.Contains(ixsd))
{
var new_namespaces = new XmlSerializerNamespaces();
foreach (var ns in ixsd.Namespaces.ToArray())
{
var new_pfx = (ns.Name == "tns") ? string.Format("tns{0}", tnsCount++) : ns.Name;
new_namespaces.Add(new_pfx, ns.Namespace);
}
ixsd.Namespaces = new_namespaces;
importsList.Add(ixsd);
AddImportedSchemas(ixsd, schemaSet, importsList, ref tnsCount);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
下一步是生成C++标头:
sproxy.exe /wsdl http://localhost:8200/Service1?wsdl
Run Code Online (Sandbox Code Playgroud)
然后C++程序看起来像这样:
using namespace Service1;
CoInitializeEx( NULL, COINIT_MULTITHREADED );
{
CService1T<CSoapWininetClient> cli;
cli.SetUrl( _T("http://localhost:8200/Service1") );
HRESULT hr = cli.HelloWorld(); //todo: analyze hr
}
CoUninitialize();
return 0;
Run Code Online (Sandbox Code Playgroud)
得到的C++代码非常合理地处理复杂类型,除了它不能为对象分配NULL.