随笔 - 60, 文章 - 0, 评论 - 197, 引用 - 0
数据加载中……

Win32 RPC 编程(一)

我们从一个简单的 RPC “Hello, world!”的例子开始。
参考资料:MSDN: Win32 and COM Development -> Networking -> Network Protocols -> Remote Procedure Calls (RPC)


第1步:编写 IDL(Interface Description Language,接口描述语言)文件
-------------------------------------------------------------------------
IDL 是一个通用的工业标准语言,大家应该不陌生,因为 COM 里面也是用它来描述接口的。
Hello.idl:

[
     uuid("4556509F-618A-46CF-AB3D-ED736ED66477"),   // 唯一的UUID,用 GUIDGen 生成
     version(1.0)
]

interface HelloWorld 
{
     // 我们定义的方法
     void Hello([in,string]const char * psz);
     void Shutdown(void); 
}


一个可选的文件是应用程序配置文件(.acf),它的作用是对 RPC 接口进行配置,例如下面的 Hello.acf 文件:
Hello.acf:


     implicit_handle(handle_t    HelloWorld_Binding) 


interface HelloWorld
{

}

上面定义了 implicit_handle,这样客户端将绑定句柄 HelloWorld_Binding 了,后面的客户端代码中我们会看到。


编译 IDL 文件:
>midl Hello.idl
Microsoft (R) 32b/64b MIDL Compiler Version 6.00.0366
Copyright (c) Microsoft Corporation 1991-2002. All rights reserved.
Processing .\Hello.idl
Hello.idl
Processing .\Hello.acf
Hello.acf

 
我们可以看到自动生成了 Hello.h, Hello_s.c, Hello_c.c 文件,这些叫做 rpc stub 程序,不过我们可以不管这个概念,
我们只需要知道 Hello.h 里面定义了一个

extern RPC_IF_HANDLE HelloWorld_v1_0_s_ifspec;

这个 RPC_IF_HANDLE 将在后面用到。

 
第2步:编写服务端程序
-------------------------------------------------------------------------
第1步中我们已经约定了调用的接口,那么现在我们开始实现其服务端。代码如下:
server.c

#include <stdlib.h>
#include <stdio.h>
#include "Hello.h"     // 引用MIDL 生成的头文件

/**
 * 这是我们在IDL 中定义的接口方法
 * 需要注意一点,IDL 里面的声明是:void Hello([in,string]const char * psz);
 * 但是这里变成了const unsigned char *,为什么呢?
 * 参见MSDN 中的MIDL Command-Line Reference -> /char Switch
 * 默认的编译选项,对 IDL 中的char 按照unsigned char 处理
 */

void Hello(const unsigned char * psz)
{
     printf("%s\n", psz);
}

 
/** 这也是我们在IDL 中定义的接口方法,提供关闭server 的机制*/
void Shutdown(void)
{
     // 下面的操作将导致 RpcServerListen() 退出
     RpcMgmtStopServerListening(NULL);
     RpcServerUnregisterIf(NULL, NULL, FALSE);
}

int main(int argc,char * argv[])
{
     // 用Named Pipe 作为RPC 的通道,这样EndPoint 参数就是Named Pipe 的名字
     // 按照Named Pipe 的命名规范,\pipe\pipename,其中pipename 可以是除了\
     // 之外的任意字符,那么这里用一个GUID 串来命名,可以保证不会重复
     RpcServerUseProtseqEp((unsigned char *)"ncacn_np", 20, (unsigned char *)"\\pipe\\{8dd50205-3108-498f-96e8-dbc4ec074cf9}", NULL);   

     // 注册接口,HelloWorld_v1_0_s_ifspec 是在MIDL 生成的Hello.h 中定义的
     RpcServerRegisterIf(HelloWorld_v1_0_s_ifspec, NULL, NULL);
   
     // 开始监听,本函数将一直阻塞
     RpcServerListen(1,20,FALSE);
     return 0;
}

// 下面的函数是为了满足链接需要而写的,没有的话会出现链接错误
void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len)
{
     return(malloc(len));
}

void __RPC_USER midl_user_free(void __RPC_FAR *ptr)
{
     free(ptr);
}

 

编译:
>cl /D_WIN32_WINNT=0x500 server.c Hello_s.c rpcrt4.lib
用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 14.00.50727.42 版
版权所有(C) Microsoft Corporation。保留所有权利。

server.c
Hello_s.c
正在生成代码...
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:server.exe
server.obj
Hello_s.obj
rpcrt4.lib

编译时为什么要指定 _WIN32_WINNT=0x500 呢?因为如果没有的话会报告下面的错误:
Hello_s.c(88) : fatal error C1189: #error :  You need a Windows 2000 or later to
run this stub because it uses these features:

 
第3步:编写客户端程序
-------------------------------------------------------------------------
客户端的代码:
client.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Hello.h"     // 引用MIDL 生成的头文件

int main(int argc, char * argv[])
{
     unsigned char * pszStringBinding = NULL;
     if ( argc != 2 )
     {
         printf("Usage:%s <Hello Text>\n", argv[0]);
         return 1;
     }   

     // 用Named Pipe 作为RPC 的通道。参见server.c 中的RpcServerUseProtseqEp() 部分
     // 第3 个参数NetworkAddr 如果取NULL,那么就是连接本机服务
     // 否则要取\\\\servername 这样的格式,例如你的计算机名为jack,那么就是\\jack
     RpcStringBindingCompose( NULL, (unsigned char*)"ncacn_np", /*(unsigned char*)"\\\\servername"*/ NULL, (unsigned char*)"\\pipe\\{8dd50205-3108-498f-96e8-dbc4ec074cf9}", NULL, &pszStringBinding );

     // 绑定接口,这里要和 Hello.acf 的配置一致,那么就是HelloWorld_Binding
     RpcBindingFromStringBinding(pszStringBinding, & HelloWorld_Binding );   

     // 下面是调用服务端的函数了
     RpcTryExcept
     {
         if ( _stricmp(argv[1], "SHUTDOWN") == 0 )
         {
              Shutdown();
         }
         else
         {
              Hello((unsigned char*)argv[1]);
         }
     }
     RpcExcept(1)
     {
         printf( "RPC Exception %d\n", RpcExceptionCode() );
     }
     RpcEndExcept

 
     // 释放资源
     RpcStringFree(&pszStringBinding);
     RpcBindingFree(&HelloWorld_Binding);
     return 0;
}

 
// 下面的函数是为了满足链接需要而写的,没有的话会出现链接错误
void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len)
{
     return(malloc(len));
}

void __RPC_USER midl_user_free(void __RPC_FAR *ptr)
{
     free(ptr);
}

 

编译:
>cl /D_WIN32_WINNT=0x500 client.c Hello_c.c rpcrt4.lib
用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 14.00.50727.42 版
版权所有(C) Microsoft Corporation。保留所有权利。

client.c
Hello_c.c
正在生成代码...
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:client.exe
client.obj
Hello_c.obj
rpcrt4.lib

 

第4步:测试:
-------------------------------------------------------------------------
运行 server.exe,将弹出一个 console 窗口,等待客户端调用。
运行客户端 client.exe:

>client hello
可以看到 server.exe 的 console 窗口出现 hello 的字符串。


>client shutdown
server.exe 退出。

示例下载
 

posted on 2008-04-28 18:50 Normandy 阅读(22792) 评论(16)  编辑 收藏 引用 所属分类: Networking

评论

# re: Win32 RPC 编程(一)  回复  更多评论   

你好,你的Win32 RPC 编程(一)让我对Windows RPC编程有了一个很形象地认识,非常感谢。不过我在用你的代码做实验时发现编译有些问题,之今不知道为什么。还望赐教。

我用2005新建了一个c++ Console Application,然后按照你的顺序,依次新建了Hello.idl和Hello.acf文件,编译后,的确看到生成出来的Hello_c.c,Hello_h.h和Hello_s.c,但在Solution Explorer中看不到这些生成出来的文件。接着我复制完服务端程序后编译后,出现下列错误:
1>RPCServer.obj : error LNK2019: unresolved external symbol __imp__RpcServerUnregisterIf@12 referenced in function _Shutdown
1>RPCServer.obj : error LNK2019: unresolved external symbol __imp__RpcMgmtStopServerListening@4 referenced in function _Shutdown
1>RPCServer.obj : error LNK2019: unresolved external symbol __imp__RpcServerListen@12 referenced in function _wmain
1>RPCServer.obj : error LNK2019: unresolved external symbol __imp__RpcServerRegisterIf@12 referenced in function _wmain
1>RPCServer.obj : error LNK2001: unresolved external symbol _HelloWorld_v1_0_s_ifspec
1>RPCServer.obj : error LNK2019: unresolved external symbol __imp__RpcServerUseProtseqEpW@16 referenced in function _wmain

如果我将那几个生成出来的文件手动加入到Solution Explore中后,编译后报下列错误:

1>Hello_s.c
1>c:\zhongwei\rpc\rpcserver\rpcserver\hello_s.c(226) : fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source?
1>Hello_c.c
1>c:\zhongwei\rpc\rpcserver\rpcserver\hello_c.c(225) : fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source?

你能知道是什么原因造成的么?
2008-10-19 13:56 | 金中伟

# re: Win32 RPC 编程(一)  回复  更多评论   

我的EMAIL:juniorzhong@gmail.com
MSN:junior_zhong@tom.com

期待你的指点,谢谢!
2008-10-19 14:08 | 金中伟

# re: Win32 RPC 编程(一)  回复  更多评论   

@金中伟
其实没有这么复杂,我是通过 Makefile 编译的, 没有用 IDE。你打开 Visual Studio 2005 Command Prompt 或 Visual Studio 2008 Command Prompt ,然后进到源码目录下 敲一个 nmake 命令, 所有的都会为你自动生成。细节可查看源码目录下的 Makefile 文件。
2008-10-20 10:09 | Normandy

# re: Win32 RPC 编程(一)  回复  更多评论   

@金中伟
你出错的原因貌似是有些函数库没有添加到引用中,所以编译时有无法解析的标志错误。或者跟工程编码有关,自己察看下吧
2009-08-03 18:17 | qb

# re: Win32 RPC 编程(一)  回复  更多评论   

你的问题是没有添加RPC Runtime Library,你可以在工程属性的'连接器'下的输入 添加'附加依赖项' Rpcrt4.lib,或者在你的CPP文件开头添加如下语句
#pragma comment(lib, "Rpcrt4.lib")
2009-08-24 23:15 | Quincy, Hu

# re: Win32 RPC 编程(一)  回复  更多评论   

RPC 学好了,大有用处。
2009-10-14 21:49 | jc_ontheroad

# re: Win32 RPC 编程(一)  回复  更多评论   

问下大家,在windows下编译器cl.exe 用/I来说明头文件的路径,用什么参数来说明库文件的路径啊?
2011-04-27 08:03 | liweihua

# re: Win32 RPC 编程(一)  回复  更多评论   

@liweihua
/LIBPATH:"X:\xxx\xxx"
2011-04-27 09:28 | 溪流

# re: Win32 RPC 编程(一)  回复  更多评论   

windows下有没有像RPCGEN这样的工具啊?
2011-08-24 16:38 | tal

# re: Win32 RPC 编程(一)  回复  更多评论   

呵呵,楼主,感谢你的文章啊。
我也喜欢用命令行编译程序。呵呵
2012-05-25 15:16 | 凭凡

# re: Win32 RPC 编程(一)  回复  更多评论   

我在2010编译的。结果是1714结果,
服务器起不来
2012-09-03 14:23 | 董香升

# re: Win32 RPC 编程(一)  回复  更多评论   

我把程序改成相应的wchar_t的unicode版本。可是server.cpp中RpcServerUseProtseqEp返回1703号错误。找不到愿意,希望指点一下。我在开头#define UNICODE了。
2012-09-04 11:31 |

# re: Win32 RPC 编程(一)  回复  更多评论   

我把程序改成相应的wchar_t的unicode版本。可是server.cpp中RpcServerUseProtseqEp返回1703号错误。找不到愿意,希望指点一下。我在开头#define UNICODE了。
2012-09-04 11:32 | 董香升

# re: Win32 RPC 编程(一)  回复  更多评论   

请教原因和解决方法:
网上获取RPC实例,比如http://blog.163.com/junior_zhong/blog/static/27871180200891810561138/
在main()中的:
status = RpcServerUseProtseqEp(
reinterpret_cast <unsigned char*>("ncacn_np"),
nMaxCalls,
reinterpret_cast <unsigned char*>("//pipe//{a5194558-21a6-4978-9610-2072fcf1dc6e}"),
NULL );

编译时出现错误:
error C2664: 'RpcServerUseProtseqEpW' : cannot convert parameter 1 from 'unsigned char *' to 'RPC_WSTR'
按照网上方法,reinterpret_cast <unsigned char*>可以解决两个字符串问题,但是不能搞定最后一个参数NULL一直报错C2664。
2012-09-06 14:34 | iHyy

# re: Win32 RPC 编程(一)  回复  更多评论   

@董香升
请问后来怎么解决的啊?
2015-07-13 15:58 | 范范

# re: Win32 RPC 编程(一)  回复  更多评论   

如果你没有使用Makefile编译而是采用vs的属性中配置来编译,server.cpp中RpcServerUseProtseqEp返回1703号错误, 或是RpcServerListen报错。请在属性页->常规->字符集中选择未设置,也就是disable UNICODE
2016-02-17 13:55 | zhang_luo@qq.com

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理