You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

进程外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

二、问题排查与修复

经过反复核对文档和代码,发现代理/存根的编译参数有两个关键错误:

  1. 不能使用PROXY_CLSID宏来指定代理存根的CLSID,正确的宏应该是PROXY_CLSID_IS
  2. 代理存根的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 19:57:42