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

.NET:C#/C++CLI能否重载/挂钩IUnknown接口?

嘿,这个问题问得挺深入的!刚好我之前研究过.NET COM互操作的底层细节,来给你梳理清楚:

首先明确一点:CLR本身并不允许直接重写它自动生成的CCW(COM Callable Wrapper)里的IUnknown::QueryInterface。毕竟CCW是CLR内部实现的组件,用来处理.NET对象和COM客户端的交互,我们没有办法直接修改它的逻辑。

但你的思路——用DummyGreeter(标记ComVisible但不注册)+DummyWrapper(原生COM包装器)——完全可以实现你要的"挂钩QueryInterface、针对特定IID自定义行为"的效果!本质上是绕开CLR的默认CCW,用自己写的原生COM组件作为中间层,拦截所有COM客户端的调用,包括QueryInterface。

具体实现步骤拆解

1. 先搞定DummyGreeter项目(.NET类库)

创建一个VB.NET类库,把你的Greeter类标记为<ComVisible(True)>,但绝对不要注册这个类库——别勾选项目属性里的"为COM互操作注册",也别用regasm命令注册。这样CLR就不会生成默认的CCW注册表项,所有的COM交互都由我们的DummyWrapper来控制。

示例代码(Greeter.vb):

Imports System.Runtime.InteropServices

<ComVisible(True)>
Public Class Greeter
    Public Function SayHello(name As String) As String
        Return $"Hello, {name}!"
    End Function
End Class

2. 核心:DummyWrapper原生COM DLL

这个项目是实现拦截的关键,我们要写一个原生COM对象,它内部持有.NET对象的CCW引用,然后自己实现IUnknown::QueryInterface,把特定IID的请求拦下来处理,其他请求转发给.NET对象的CCW。

(1)main.cpp的核心逻辑

需要做几件事:加载CLR并创建.NET对象实例、实现自定义的QueryInterface、实现COM类工厂和必要的导出函数。这里给你一个简化的核心示例:

#include <windows.h>
#include <mscoree.h>
#include <unknwn.h>

#pragma comment(lib, "mscoree.lib")

// 定义你要拦截的特定IID(自己随便定义一个就行)
const IID IID_IGreeterEx = {0x12345678, 0x1234, 0x1234, {0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF}};

// 自定义包装器类,实现IUnknown
class DummyGreeterWrapper : public IUnknown {
private:
    LONG m_cRef;
    IUnknown* m_pNetCCW; // 持有.NET对象的CCW指针

public:
    DummyGreeterWrapper(IUnknown* pNetCCW) : m_cRef(1), m_pNetCCW(pNetCCW) {
        m_pNetCCW->AddRef();
    }

    // 重写QueryInterface,实现拦截逻辑
    STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override {
        if (riid == IID_IUnknown) {
            *ppvObject = static_cast<IUnknown*>(this);
            AddRef();
            return S_OK;
        }
        // 拦截我们关心的特定IID
        else if (riid == IID_IGreeterEx) {
            // 这里可以写你的自定义逻辑:比如返回自己实现的IGreeterEx接口,或者直接返回E_NOINTERFACE
            // 示例里直接返回空,实际根据需求调整
            *ppvObject = nullptr;
            return E_NOINTERFACE;
        }
        // 其他所有IID请求,直接转发给.NET对象的CCW
        else {
            return m_pNetCCW->QueryInterface(riid, ppvObject);
        }
    }

    STDMETHODIMP_(ULONG) AddRef() override {
        return InterlockedIncrement(&m_cRef);
    }

    STDMETHODIMP_(ULONG) Release() override {
        ULONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0) {
            m_pNetCCW->Release();
            delete this;
        }
        return cRef;
    }
};

// COM类工厂,用来创建我们的包装器对象
class DummyGreeterClassFactory : public IClassFactory {
private:
    LONG m_cRef;

public:
    DummyGreeterClassFactory() : m_cRef(1) {}

    STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override {
        if (riid == IID_IUnknown || riid == IID_IClassFactory) {
            *ppvObject = static_cast<IClassFactory*>(this);
            AddRef();
            return S_OK;
        }
        return E_NOINTERFACE;
    }

    STDMETHODIMP_(ULONG) AddRef() override {
        return InterlockedIncrement(&m_cRef);
    }

    STDMETHODIMP_(ULONG) Release() override {
        ULONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0) {
            delete this;
        }
        return cRef;
    }

    STDMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppvObject) override {
        if (pUnkOuter != nullptr) {
            return CLASS_E_NOAGGREGATION;
        }

        // 加载CLR,创建.NET对象的实例
        ICLRMetaHost* pMetaHost = nullptr;
        ICLRRuntimeInfo* pRuntimeInfo = nullptr;
        ICLRRuntimeHost* pRuntimeHost = nullptr;
        HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
        if (FAILED(hr)) goto Cleanup;

        // 这里指定.NET版本,要和DummyGreeter的版本匹配
        hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&pRuntimeInfo);
        if (FAILED(hr)) goto Cleanup;

        hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&pRuntimeHost);
        if (FAILED(hr)) goto Cleanup;

        hr = pRuntimeHost->Start();
        if (FAILED(hr)) goto Cleanup;

        // 创建.NET对象,拿到它的CCW指针
        IUnknown* pNetCCW = nullptr;
        hr = pRuntimeHost->CreateDomainAndExecuteAssembly(
            L"DummyGreeter.dll",
            L"DummyGreeter.Greeter",
            L"CreateInstance",
            nullptr,
            (DWORD*)&pNetCCW
        );
        if (FAILED(hr)) goto Cleanup;

        // 创建我们的包装器对象,返回给客户端
        DummyGreeterWrapper* pWrapper = new DummyGreeterWrapper(pNetCCW);
        hr = pWrapper->QueryInterface(riid, ppvObject);
        pWrapper->Release();

    Cleanup:
        // 别忘了释放所有COM对象,避免内存泄漏
        if (pRuntimeHost) pRuntimeHost->Release();
        if (pRuntimeInfo) pRuntimeInfo->Release();
        if (pMetaHost) pMetaHost->Release();
        if (pNetCCW) pNetCCW->Release();
        return hr;
    }

    STDMETHODIMP LockServer(BOOL fLock) override {
        return S_OK;
    }
};

// 导出COM必须的函数
extern "C" HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppvObject) {
    // 这里要定义你的包装器CLSID,和注册表注册的一致
    const CLSID CLSID_DummyGreeter = {0x87654321, 0x4321, 0x4321, {0xAB, 0xCD, 0xEF, 0x90, 0x78, 0x56, 0x34, 0x12}};
    if (rclsid != CLSID_DummyGreeter) {
        return CLASS_E_CLASSNOTAVAILABLE;
    }

    DummyGreeterClassFactory* pFactory = new DummyGreeterClassFactory();
    HRESULT hr = pFactory->QueryInterface(riid, ppvObject);
    pFactory->Release();
    return hr;
}

extern "C" HRESULT __stdcall DllCanUnloadNow() {
    // 简单示例,实际需要跟踪组件的引用计数,确定是否可以卸载
    return S_FALSE;
}

extern "C" HRESULT __stdcall DllRegisterServer() {
    // 这里需要实现注册表注册逻辑:写入CLSID对应的InprocServer32等项
    return S_OK;
}

extern "C" HRESULT __stdcall DllUnregisterServer() {
    // 注销注册表项
    return S_OK;
}
(2)DummyWrapper.def文件

必须导出COM要求的几个函数,所以要加一个.def文件:

LIBRARY "DummyWrapper"
EXPORTS
    DllGetClassObject PRIVATE
    DllCanUnloadNow PRIVATE
    DllRegisterServer PRIVATE
    DllUnregisterServer PRIVATE

几个关键注意点

  • CLR版本匹配:代码里指定的.NET版本要和DummyGreeter项目的目标版本完全一致,不然加载会失败。
  • 引用计数要严谨:原生COM对象和.NET CCW的引用计数必须正确管理,不然会出现内存泄漏或者对象提前释放的问题。
  • 自定义IID的处理:在QueryInterface里,你可以针对特定IID返回自己的接口实现——比如写一个IGreeterEx接口,让客户端调用这个接口时执行你想要的逻辑,而不是用.NET对象的默认实现。
  • 注册问题:只有DummyWrapper需要注册为COM组件,DummyGreeter只需要和DummyWrapper放在同一个目录下,或者放在CLR能找到的路径里就行。

总结

虽然CLR不让我们直接修改它的CCW的QueryInterface,但用你想到的"原生包装器+未注册的.NET类库"的方案,完全可以实现拦截特定IID的效果。核心就是用自己的原生COM组件接管所有COM客户端的请求,从而插入自定义逻辑,完美解决你的需求!

内容的提问来源于stack exchange,提问作者Dmytro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:49:35