进程外COM服务器运行异常排查及解决方法
进程外COM服务器搭建遇0x80004002错误的排查与解决
最近折腾进程外COM服务器的时候踩了个大坑,调用CoCreateInstance一直返回错误码0x80004002(也就是E_NOINTERFACE),不管是指定进程外还是尝试进程内调用都失败。折腾了好一阵才找到问题根源,下面把整个操作流程、问题点和修复方案给大家理清楚:
一、我的初始实现流程
1. 编写IDL定义文件(classic.idl)
import "objidlbase.idl"; [ uuid(CF86E2E0-B12D-4C6A-9C5A-D7AA65101E91), ] coclass CClassic { [default] interface IClassic; } [ object, uuid(CF86E2E0-B12D-4C6A-9C5A-D7AA65101E90), pointer_default(unique) ] interface IClassic : IUnknown { HRESULT Hello(); }
2. 生成代理/存根文件
用midl命令生成所需的头文件和代理存根源码:
midl /nologo /out classic /I"D:\EWDK\Program Files\Windows Kits\10\Include\10.0.19041.0\um" /I"D:\EWDK\Program Files\Windows Kits\10\Include\10.0.19041.0\shared" classic.idl
3. 实现COM接口(comobj.cpp)
用ATL实现IClassic接口和对应的COM类:
#include <atlbase.h> #include <atlcom.h> #include "classic/classic.h" CComModule _Module; extern "C" CLSID CLSID_CClassic; struct Toast : CComObjectRoot,CComCoClass<Toast,&CLSID_CClassic>,IClassic { DECLARE_REGISTRY(CLSID_CClassic,"Toaster.1.0","Toaster","Toast Server",THREADFLAGS_BOTH); DECLARE_CLASSFACTORY(); DECLARE_OBJECT_DESCRIPTION("Toast Server"); BEGIN_COM_MAP(Toast) COM_INTERFACE_ENTRY(IClassic) END_COM_MAP(); HRESULT STDMETHODCALLTYPE Hello( void) { printf("Hello\n"); return S_OK; } }; OBJECT_ENTRY_AUTO(CLSID_CClassic,Toast);
4. 编写进程外服务器入口(comexe.cpp)
基于ATL的CAtlExeModuleT实现进程外模块:
#include <atlbase.h> #include <atlcom.h> struct MyModule : CAtlExeModuleT<MyModule> { }; MyModule mod; int main(int argc, char *args[]) { mod.WinMain(SW_HIDE); }
5. 编译服务器可执行文件
cl /nologo /EHsc comexe.cpp comobj.cpp classic/classic_i.c /link /debug
6. 初始编译代理/存根(这里犯了关键错误)
一开始我用了错误的宏定义来编译代理存根DLL,这就是导致后续错误的根源:
cl /nologo /EHsc /DREGISTER_PROXY_DLL /DPROXY_CLSID=CLSID_CClassic classic/classic_p.c classic/classic_i.c classic/dlldata.c /link /dll /out:comproxy.dll /export:DllGetClassObject /export:DllCanUnloadNow /export:DllRegisterServer /export:DllUnregisterServer /export:GetProxyDllInfo rpcrt4.lib
7. 注册服务器和代理/存根
用管理员权限执行以下命令:
# 注册进程外COM服务器 comexe /RegServer # 注册代理/存根DLL regsvr32 comproxy.dll
8. 编写测试客户端(test.cpp)
写了个简单的客户端来测试进程内和进程外调用:
#include <Unknwn.h> #include <stdio.h> #include "classic\classic.h" #pragma comment(lib, "ole32") extern "C" CLSID CLSID_CClassic; int main(int argc, char *args[]) { CoInitialize(nullptr); IClassic *ifc = nullptr; HRESULT hr; if(argc<2) return 1; switch(args[1][0]) { case 'I': case 'i': hr = CoCreateInstance(CLSID_CClassic,nullptr,CLSCTX_INPROC_SERVER,IID_IClassic,(void**)&ifc); break; case 'O': case 'o': hr = CoCreateInstance(CLSID_CClassic,nullptr,CLSCTX_LOCAL_SERVER,IID_IClassic,(void**)&ifc); default: ; } printf("0x%x\n",hr); }
9. 编译测试程序
cl /nologo /std:c++latest /EHsc test.cpp classic/classic_i.c /link /debug
10. 初始测试结果
不管是进程外还是进程内调用,都返回了0x80004002错误:
test O 0x80004002 test I 0x80004002
二、问题排查与修复
经过反复核对文档和代码,发现代理/存根的编译参数有两个关键错误:
- 不能使用
PROXY_CLSID宏来指定代理存根的CLSID,正确的宏应该是PROXY_CLSID_IS - 代理存根的CLSID必须和COM服务器的
CLSID_CClassic完全不同,不能复用同一个ID
修正后的代理/存根编译命令如下(我重新生成了一个新的CLSID用于代理存根):
cl /nologo /EHsc /DREGISTER_PROXY_DLL /DPROXY_CLSID_IS={0xCF86E2E0,0xB12D,0x4C6A,{0x9C,0x5A,0xD7,0xAA,0x65,0x10,0x1E,0x93}} classic/classic_p.c classic/classic_i.c classic/dlldata.c /link /dll /out:comproxy.dll /export:DllGetClassObject /export:DllCanUnloadNow /export:DllRegisterServer /export:DllUnregisterServer /export:GetProxyDllInfo rpcrt4.lib
三、修正后的测试结果
重新编译代理存根DLL、注册后再次测试,结果完全符合预期:
test O返回0x0,进程外调用成功,服务器正常输出"Hello"test I仍然失败,这是正常的,因为我们实现的是进程外服务器,并没有提供进程内组件,所以CLSCTX_INPROC_SERVER自然找不到对应的实现
内容的提问来源于stack exchange,提问作者yushang
相关产品推荐
相关产品推荐

