为非托pipeC ++客户端创buildWCF服务
我需要让非托pipe的Windows C ++客户端与WCF服务交谈。 C ++客户端可以在Win2000及更高版本上运行。 我有两个WCF服务和哪个C ++ API正在使用的控制。 由于它是专有应用程序,所以最好在可能的情况下使用Microsoft的东西,绝对不是GNU许可的API。 那些有工作能力的人,你们能分享一步一步的过程吗?
到目前为止,我已经研究了以下选项:
- WWSAPI – 不好,不能在Win 2000客户端上运行。
- ATL Server,使用以下指南作为参考。 我遵循所述的步骤(删除策略引用并将WSDL扁平化),但是由此产生的WSDL仍然不能被sproxy
还有什么想法? 请回答,只有当你真的有自己的工作。
编辑1 :我为任何我可能困惑的人道歉:我正在寻找的是从没有安装.NET框架的客户端调用WCF服务的方法,所以使用基于.NET的帮助程序库不是一个选项,它必须是纯粹的非托pipeC ++
基本的想法是在C#中为客户端编写WCF代码(这样做更简单),并使用C ++桥接dll来弥合C / C ++代码和托pipe的WCF代码之间的差距。
以下是使用Visual Studio 2008和.NET 3.5 SP1的分步过程。
-
首先要做的是创buildWCF服务和一个手段来承载它。 如果您已经有这个,请跳到下面的步骤7。 否则,请按照以下步骤创buildWindows NT服务。 使用VS2008为项目提供的默认名称以及添加到项目中的任何类。 此Windows NT服务将承载WCF服务。
-
将名为HelloService的WCF服务添加到项目中。 为此,请右键单击Solution Explorer窗口中的项目,然后selectAdd | New Item …菜单项。 在Add New Item对话框中,selectC#WCF Service模板,然后单击Addbutton。 这将HelloService以接口文件(IHelloService.cs),类文件(HelloService.cs)和默认服务configuration文件(app.config)的forms添加到项目中。
-
像这样定义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); } }
-
修改上面步骤1中创build的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 { } } }
-
build立这个项目。
-
打开Visual Studio 2008命令提示符。 导航到项目的输出目录。 键入以下内容:`installutil WindowsService1.exe'这将在本地计算机上安装Windows NT服务。 打开服务控制面板并启动Service1服务。 为了使下面的步骤9工作,做到这一点非常重要。
- 打开Visual Studio 2008的另一个实例,并创build一个MFC应用程序,这个应用程序距离WCF很近。 作为一个例子,我简单地创build了一个对话框的MFC应用程序,并添加了Say Hello! button。 右键单击解决scheme资源pipe理器中的项目,然后select“属性”菜单选项。 在常规设置下,将输出目录更改为.. \ bin \ Debug。 在C / C ++常规设置下,将.. \ HelloServiceClientBridge添加到其他包含目录。 在链接器常规设置下,将.. \ Debug添加到其他库目录。 点击确定button。
-
从文件菜单中,select添加|新build项目…菜单项。 selectC#类库模板。 将名称更改为HelloServiceClient,然后单击确定button。 右键单击解决scheme资源pipe理器中的项目,然后select“属性”菜单选项。 在“生成”选项卡中,将输出path更改为.. \ bin \ Debug,以使程序集和app.config文件与MFC应用程序位于同一目录中。 这个库将包含服务引用,即WCF代理类,与Windows NT服务中托pipe的WCF Hello服务。
-
在解决scheme资源pipe理器中,右键单击HelloServiceClient项目的引用文件夹,然后select添加服务引用…菜单选项。 在Address字段中,inputHello Service的地址。 这应该等于上面步骤2中创build的app.config文件中的基址。 点击开始button。 Hello服务应显示在服务列表中。 点击OKbutton自动生成Hello服务的代理类。 注意: 我似乎总是遇到由这个过程生成的Reference.cs文件的编译问题。 我不知道如果我做错了,或者如果有一个错误,但解决这个问题最简单的方法是直接修改Reference.cs文件。 这个问题通常是一个命名空间的问题,可以用最小的努力修复。 只要意识到这是一种可能性。 对于这个例子,我已经将HelloServiceClient.ServiceReference1更改为HelloService(以及其他所需的更改)。
-
为了允许MFC应用程序与WCF服务进行交互,我们需要构build一个托pipe的C ++“桥”DLL。 从文件菜单中,select添加|新build项目…菜单项。 selectC ++ Win32项目模板。 将名称更改为HelloServiceClientBridge,然后单击确定button。 对于应用程序设置,将应用程序types更改为DLL并检查空项目checkbox。 点击完成button。
-
首先要做的是修改项目属性。 右键单击解决scheme资源pipe理器中的项目,然后select“属性”菜单选项。 在常规设置下,将输出目录更改为.. \ bin \ Debug并将公共语言运行时支持选项更改为公共语言运行时支持(/ clr)。 在“框架和引用”设置下,添加对.NET系统,System.ServiceModel和mscorlib程序集的引用。 点击确定button。
-
将以下文件添加到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__
-
修改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__
-
.cpp文件的语法使用托pipeC ++,这需要一些习惯。 修改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; }
-
剩下唯一要做的就是更新MFC应用程序来调用SayHello()WCF服务调用。 在MFC窗体上,双击Say Hello! button来生成ButtonClicked事件处理程序。 使事件处理程序如下所示:
#include "IHelloServiceClientBridge.h" #include <string> void CMFCApplicationDlg::OnBnClickedButton1() { try { std::string message = IHelloServiceClientBridge::SayHello("Your Name Here"); AfxMessageBox(CString(message.c_str())); } catch (...) { } }
-
运行该应用程序,然后单击说你好! button。 这将导致应用程序调用Windows NT服务中托pipe的WCF Hello Service的SayHello()方法(顺便说一下,它应该仍然在运行)。 返回值然后显示在消息框中。
希望你能从这个简单的例子来推断,以适应你的需求。 如果这不起作用,请让我知道,所以我可以修复这个post。
对于那些有兴趣的人,我find了一个半工作的ATL服务器解决scheme。 以下是主机代码,注意它使用BasicHttpBinding,它是唯一一个与ATL服务器一起工作的:
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();
InlineXsdInWsdlBehavior的代码可以在这里find。 InlineXsdInWsdlBehavior需要做一个重要的改变,以便在涉及复杂types的时候使用sproxy正常工作。 这是由sproxy中的错误引起的,它不能正确地限定名称空间别名,所以wsdl不能有重复的名称空间别名,否则sproxy将会废话。 以下是需要改变的function:
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); } } } }
下一步是生成C ++头文件:
sproxy.exe /wsdl http://localhost:8200/Service1?wsdl
然后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;
最终的C ++代码处理复杂的types相当体面,除了它不能为对象分配NULL。
我将创build一个C#托pipe类来执行WCF工作,并将该类作为COM对象公开给C ++客户端。
您可以使用已弃用的MS Soap Toolkit轻松实现SOAP客户端。 不幸的是,除了迁移到.NET外,似乎还没有这个替代品。
您能否发布REST Web服务并使用MSXML COM库 – 应该已经安装,有一个XMLparsing器和一个HTTP库。