尝试实现任务调度程序 COM 处理程序

Trying to implement a task scheduler COM handler

ITaskHandler::Start的原型如下:

HRESULT ( STDMETHODCALLTYPE Start )( 
            __RPC__in ITaskHandler * This,
            /* [in] */ __RPC__in_opt IUnknown *pHandlerServices,
            /* [in] */ __RPC__in BSTR data)

为什么 pHandlerServices 是可选的 - 如果我得到一个 NULL(这是我的情况) - 我如何通知任务调度程序我已经完成了任务。

OK - 这是我实现 class 的 QueryInterface 的交易,总是 return 相同的对象思维 - ITaskHandler 将立即被查询。然而,情况并非如此——第一个查询是针对 IClassFactory 的,CreateInstance 的函数签名具有第二个参数 pUnkOuter NULL,它与我实现的 [=] 的第二个参数重叠19=]。然而,奇怪的是 pHandlerServices 被标记为可选。

这是我当前的处理程序实现,但仍然无法正常工作(最后的 运行 结果是不支持此类接口 (0x80004002))- 我的接口 ITaskHandler 从未被查询过。到目前为止,我什至实现了 ICallFactory 但别名没有运气(CreateCall 从未被调用过) - 这是代码:

#define COBJMACROS
#include <windows.h>
#include <objbase.h>
#include <unknwn.h>

    // {179D1704-49C5-4111-B3CF-C528ABB014D0}
DEFINE_GUID(CLSID_IRmouseHandler, 
0x179d1704, 0x49c5, 0x4111, 0xb3, 0xcf, 0xc5, 0x28, 0xab, 0xb0, 0x14, 0xd0);

#define wstringCLSID_IRmouseHandler L"{179D1704-49C5-4111-B3CF-C528ABB014D0}"
static const GUID CLSID_IRmouseHandler = 
{ 0x179d1704, 0x49c5, 0x4111, { 0xb3, 0xcf, 0xc5, 0x28, 0xab, 0xb0, 0x14, 0xd0 } };

// {D363EF80-5C42-46D8-847B-B3A27A3BD0E3}
DEFINE_GUID(IID_IRmouseHandler, 
0xd363ef80, 0x5c42, 0x46d8, 0x84, 0x7b, 0xb3, 0xa2, 0x7a, 0x3b, 0xd0, 0xe3);

static const GUID IID_IRmouseHandler = 
{ 0xd363ef80, 0x5c42, 0x46d8, { 0x84, 0x7b, 0xb3, 0xa2, 0x7a, 0x3b, 0xd0, 0xe3 } };

#include <taskschd.h>

#include <ObjIdl.h>

#define stub(x)\
\
STDMETHODCALLTYPE x() {\
    MessageBox(\
        NULL,\
        "ITaskHandler_" #x,\
        "Account Details",\
        MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2\
    );}

extern ITaskHandler tskhandler; extern IClassFactory factory; extern ICallFactory callfactory;

stub(CreateCall)

HRESULT ( STDMETHODCALLTYPE CreateInstance )( 
            IClassFactory * This,
            /* [annotation][unique][in] */ 
            _In_opt_  IUnknown *pUnkOuter,
            /* [annotation][in] */ 
            _In_  REFIID riid,
            /* [annotation][iid_is][out] */ 
            _COM_Outptr_  void **ppvObject) { return QueryInterface(This, riid, ppvObject);}

HRESULT STDMETHODCALLTYPE QueryInterface(
            __RPC__in ITaskHandler * This,
            /* [in] */ __RPC__in REFIID riid,
            /* [annotation][iid_is][out] */ 
            _COM_Outptr_  void **ppvObject) {
    if(!ppvObject) return E_POINTER;
    if(!memcmp(riid, &IID_ITaskHandler, sizeof *riid) || !memcmp(riid, &IID_IUnknown, sizeof *riid))*ppvObject = &tskhandler;
    else if(!memcmp(riid, &IID_ICallFactory, sizeof *riid))*ppvObject = &callfactory;
    else if(!memcmp(riid, &IID_IClassFactory, sizeof *riid))*ppvObject = &factory;
    else return E_NOINTERFACE;
    return S_OK;}



ULONG STDMETHODCALLTYPE AddRef(){}
stub(Release)
HRESULT ( STDMETHODCALLTYPE Start )( 
            __RPC__in ITaskHandler * This,
            /* [in] */ __RPC__in_opt IUnknown *pHandlerServices,
            /* [in] */ __RPC__in BSTR data) {ITaskHandlerStatus *pHandlerStatus;
            IUnknown_QueryInterface(pHandlerServices,&IID_ITaskHandlerStatus,&pHandlerStatus),
            ITaskHandlerStatus_TaskCompleted(pHandlerStatus,S_OK);return S_OK;}
stub(Stop)
stub(Pause)
stub(Resume)

ITaskHandler tskhandler = {.lpVtbl = &(struct ITaskHandlerVtbl){.QueryInterface=QueryInterface,.Resume=Resume,
.AddRef=AddRef,.Release=Release,.Start=Start,.Stop=Stop,.Pause=Pause}};

IClassFactory factory = {.lpVtbl = &(struct IClassFactoryVtbl){.QueryInterface=QueryInterface,
.AddRef=AddRef,.Release=Release,.CreateInstance=CreateInstance}};

ICallFactory callfactory = {.lpVtbl = &(struct ICallFactoryVtbl){.QueryInterface=QueryInterface,
.AddRef=AddRef,.Release=Release,.CreateCall=CreateCall}};

int WinMain(
  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPSTR     lpCmdLine,
  int       nShowCmd
) { DWORD dwToken; //AddVectoredExceptionHandler(1,PvectoredExceptionHandler);
    CoInitializeEx(NULL,0), CoRegisterClassObject(&CLSID_IRmouseHandler,&tskhandler,CLSCTX_LOCAL_SERVER,REGCLS_MULTIPLEUSE,&dwToken),Sleep(INFINITE);}

正如@HansPassant 指出的那样,关键在于任务计划程序只会 运行 COM 对象退出进程。为此,您需要注册您的 COM 对象以使用系统提供的 DllSurrogate。

这是我的示例的 COM 注册码

HKEY_CLASSES_ROOT\AppID\{6B9279D0-D220-4288-AFDF-E424F558FEF2}
   DllSurrogate   REG_SZ ""
Computer\HKEY_CLASSES_ROOT\CLSID\{36A976F4-698B-4B50-BE2C-83F815575199}
   Default        REG_SZ Path\To\your_com.dll
   AppID          REG_SZ {6B9279D0-D220-4288-AFDF-E424F558FEF2}
   ThreadingModel REG_SZ Both

工作示例代码(对不起 C++)- 它基本上只是添加了 ITaskHandler 的默认 COM 实现。它至少会调用 Start。如果您想使用自己的代码,我建议您在担心任务计划程序之前先在一个简单的测试程序中测试 COM 对象加载。

// {36A976F4-698B-4B50-BE2C-83F815575199}
DEFINE_GUID(CLSID_TestObject,
    0x36a976f4, 0x698b, 0x4b50, 0xbe, 0x2c, 0x83, 0xf8, 0x15, 0x57, 0x51, 0x99);

int main()
{
    CoInitialize(nullptr);
    ITaskHandler* handler = nullptr;
    HRESULT hr = CoCreateInstance(CLSID_TestObject, nullptr, CLSCTX_LOCAL_SERVER, IID_ITaskHandler, (LPVOID*)&handler);
    fprintf(stderr, "CoCreateInstance %08x\r\n", hr);
    return 0;
}

DllMain.cpp

extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    return TRUE;
}

// {36A976F4-698B-4B50-BE2C-83F815575199}
DEFINE_GUID(CLSID_TestObj,
    0x36a976f4, 0x698b, 0x4b50, 0xbe, 0x2c, 0x83, 0xf8, 0x15, 0x57, 0x51, 0x99);

long g_nComObjsInUse;

STDAPI DllGetClassObject(const CLSID& clsid,
    const IID& iid,
    void** ppv)
{
    OutputDebugStringW(L"DllGetClassObject");
    if (IsEqualGUID(clsid, CLSID_TestObj))
    {
        TestClassFactory *pAddFact = new TestClassFactory();
        if (pAddFact == NULL)
            return E_OUTOFMEMORY;
        else
        {
            return pAddFact->QueryInterface(iid, ppv);
        }
    }
    return CLASS_E_CLASSNOTAVAILABLE;
}

STDAPI DllCanUnloadNow()
{
    OutputDebugStringW(L"DllCanUnloadNow");
    if (g_nComObjsInUse == 0)
    {
        return S_OK;
    }
    else
    {
        return S_FALSE;
    }
}

TestObj.h

extern long g_nComObjsInUse;

class CTestObj :
    public ITaskHandler
{
public:
    CTestObj();
    virtual ~CTestObj();

    //IUnknown interface 
    HRESULT __stdcall QueryInterface( REFIID riid,void **ppObj) override;
    ULONG   __stdcall AddRef() override;
    ULONG   __stdcall Release() override;

    //IAdd interface
    HRESULT __stdcall Start(IUnknown* handler, BSTR data) override;
    HRESULT __stdcall Stop(HRESULT* retCode) override;
    HRESULT __stdcall Pause() override;
    HRESULT __stdcall Resume() override;
private:
    long m_nRefCount;   //for managing the reference count
};

TestObj.cpp

HRESULT __stdcall CTestObj::Start(IUnknown* handler, BSTR data)
{
    OutputDebugStringW(L"Start");
    return S_OK;
}

HRESULT __stdcall CTestObj::Stop(HRESULT* retCode)
{
    OutputDebugStringW(L"Stop");
    return S_OK;
}

HRESULT __stdcall CTestObj::Pause()
{
    OutputDebugStringW(L"Pause");
    return S_OK;
}

HRESULT __stdcall CTestObj::Resume()
{
    OutputDebugStringW(L"Resume");
    return S_OK;
}

CTestObj::CTestObj()
{
    InterlockedIncrement(&g_nComObjsInUse);
}

CTestObj::~CTestObj()
{
    InterlockedDecrement(&g_nComObjsInUse);
}

HRESULT __stdcall CTestObj::QueryInterface(REFIID riid, void **ppObj)
{
    OutputDebugStringW(L"QueryInterface");
    if (IsEqualGUID(riid,IID_IUnknown))
    {
        *ppObj = static_cast<IUnknown*>(this);
        AddRef();
        return S_OK;
    }
    if (IsEqualGUID(riid,IID_ITaskHandler))
    {
        *ppObj = static_cast<ITaskHandler*>(this);
        AddRef();
        return S_OK;
    }
    *ppObj = NULL;
    return E_NOINTERFACE;
}

ULONG   __stdcall CTestObj::AddRef()
{
    OutputDebugStringW(L"AddRef");
    return InterlockedIncrement(&m_nRefCount);
}

ULONG   __stdcall CTestObj::Release()
{
    OutputDebugStringW(L"Release");
    long nRefCount = 0;
    nRefCount = InterlockedDecrement(&m_nRefCount);
    if (nRefCount == 0) delete this;
    return nRefCount;
}

TestClassFactory.h

class TestClassFactory : IClassFactory
{
public:
    TestClassFactory();
    ~TestClassFactory();
    HRESULT __stdcall QueryInterface(
        REFIID riid,
        void **ppObj) override;
    ULONG   __stdcall AddRef() override;
    ULONG   __stdcall Release() override;

    HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter,
        const IID& iid,
        void** ppv) override;
    HRESULT __stdcall LockServer(BOOL bLock) override;

private:
    long m_nRefCount;
};

TestClassFactory.cpp

extern long g_nComObjsInUse;

TestClassFactory::TestClassFactory()
{
    InterlockedIncrement(&g_nComObjsInUse);
}


TestClassFactory::~TestClassFactory()
{
    InterlockedDecrement(&g_nComObjsInUse);
}

HRESULT __stdcall TestClassFactory::CreateInstance(IUnknown* pUnknownOuter,
    const IID& iid,
    void** ppv)
{
    OutputDebugStringW(L"CreateInstance");
    if (pUnknownOuter != NULL)
    {
        return CLASS_E_NOAGGREGATION;
    }

    CTestObj* pObject = new CTestObj();
    if (pObject == NULL)
    {
        return E_OUTOFMEMORY;
    }

    return pObject->QueryInterface(iid, ppv);
}


HRESULT __stdcall TestClassFactory::LockServer(BOOL bLock)
{
    OutputDebugStringW(L"LockServer");
    return E_NOTIMPL;
}

HRESULT __stdcall TestClassFactory::QueryInterface(
    REFIID riid,
    void **ppObj)
{
    OutputDebugStringW(L"QueryInterface");
    if (IsEqualGUID(riid, IID_IUnknown))
    {
        *ppObj = static_cast<IUnknown*>(this);
        AddRef();
        return S_OK;
    }
    if (IsEqualGUID(riid, IID_IClassFactory))
    {
        *ppObj = static_cast<IClassFactory*>(this);
        AddRef();
        return S_OK;
    }
    *ppObj = NULL;
    return E_NOINTERFACE;
}

ULONG   __stdcall TestClassFactory::AddRef()
{
    OutputDebugStringW(L"AddRef");
    return InterlockedIncrement(&m_nRefCount);
}

ULONG   __stdcall TestClassFactory::Release()
{
    OutputDebugStringW(L"Release");
    long nRefCount = 0;
    nRefCount = InterlockedDecrement(&m_nRefCount);
    if (nRefCount == 0) delete this;
    return nRefCount;
}

任务登记XML

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.6" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2006-11-10T14:29:55.5851926</Date>
    <Author>a</Author>
    <URI>\b</URI>
    <SecurityDescriptor>D:(A;;FA;;;BA)(A;;FA;;;SY)(A;;FRFX;;;WD)</SecurityDescriptor>
  </RegistrationInfo>
  <Triggers>
    <LogonTrigger id="06b3f632-87ad-4ac0-9737-48ea5ddbaf11">
      <Enabled>false</Enabled>
      <Delay>PT1H</Delay>
    </LogonTrigger>
  </Triggers>
  <Principals>
    <Principal id="AllUsers">
      <GroupId>S-1-1-0</GroupId>
      <RunLevel>LeastPrivilege</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>false</AllowHardTerminate>
    <StartWhenAvailable>true</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>true</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>PT1H</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="AllUsers">
    <ComHandler>
      <ClassId>{36A976F4-698B-4B50-BE2C-83F815575199}</ClassId>
    </ComHandler>
  </Actions>
</Task>

有 3 个问题:

1) AddRef 和 stub 必须有 1 个参数,比如 IUnknown* This (or LPVOID This)。如果您的函数是 __cdecl 并不重要,但 COM 仅适用于 __stdcall 函数。 并且系统期望函数自己从堆栈中清除参数。

2) AddRef 至少必须 return 而不是 0,存根函数必须 return S_OK 或 0。也许这不是什么大问题..

3) 调查显示 class 工厂的系统查询 IUnknown,可能它执行 QueryInterface 而不是 AddRef。但是无论如何,对于每个 IUnknown 查询, return taskhandler 都是不正确的。 return "factory" 就更好了。但是最好 return 这在 IUnknown 上,反正每个接口都是 IUnknown 的后代。

额外的问题,如果你在 Unicode 下编译消息框显示奇怪的中文文本,因为需要使文本参数 Unicode,或显式使用 MessageBoxA

#define COBJMACROS
#include <windows.h>
#include <objbase.h>
#include <unknwn.h>

// {179D1704-49C5-4111-B3CF-C528ABB014D0}
DEFINE_GUID(CLSID_IRmouseHandler,
    0x179d1704, 0x49c5, 0x4111, 0xb3, 0xcf, 0xc5, 0x28, 0xab, 0xb0, 0x14, 0xd0);

#define wstringCLSID_IRmouseHandler L"{179D1704-49C5-4111-B3CF-C528ABB014D0}"
static const GUID CLSID_IRmouseHandler =
{ 0x179d1704, 0x49c5, 0x4111,{ 0xb3, 0xcf, 0xc5, 0x28, 0xab, 0xb0, 0x14, 0xd0 } };

// {D363EF80-5C42-46D8-847B-B3A27A3BD0E3}
DEFINE_GUID(IID_IRmouseHandler,
    0xd363ef80, 0x5c42, 0x46d8, 0x84, 0x7b, 0xb3, 0xa2, 0x7a, 0x3b, 0xd0, 0xe3);

static const GUID IID_IRmouseHandler =
{ 0xd363ef80, 0x5c42, 0x46d8,{ 0x84, 0x7b, 0xb3, 0xa2, 0x7a, 0x3b, 0xd0, 0xe3 } };

#include <taskschd.h>

#include <ObjIdl.h>

// Vano101: Make This parameter, Return S_OK, Use MessageBoxA or L before text
#define stub(x)\
\
STDMETHODCALLTYPE x(IUnknown* This) {\
    MessageBoxA(\
        NULL,\
        "ITaskHandler_" #x,\
        "Account Details",\
        MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2\
    ); \
    return S_OK; \
}

extern ITaskHandler tskhandler; 
extern IClassFactory factory; 
extern ICallFactory callfactory;

stub(CreateCall)

HRESULT(STDMETHODCALLTYPE CreateInstance)(
    IClassFactory * This,
    /* [annotation][unique][in] */
    _In_opt_  IUnknown *pUnkOuter,
    /* [annotation][in] */
    _In_  REFIID riid,
    /* [annotation][iid_is][out] */
    _COM_Outptr_  void **ppvObject) {
    return QueryInterface(This, riid, ppvObject);
}

HRESULT STDMETHODCALLTYPE QueryInterface(
    __RPC__in ITaskHandler * This,
    /* [in] */ __RPC__in REFIID riid,
    /* [annotation][iid_is][out] */
    _COM_Outptr_  void **ppvObject) {
    if (!ppvObject) return E_POINTER;
    if (!memcmp(riid, &IID_ITaskHandler, sizeof *riid)) *ppvObject = &tskhandler;
    else if (!memcmp(riid, &IID_IUnknown, sizeof *riid)) *ppvObject = &factory; // Vano101: Return factory on IUnknown
    else if (!memcmp(riid, &IID_ICallFactory, sizeof *riid))*ppvObject = &callfactory;
    else if (!memcmp(riid, &IID_IClassFactory, sizeof *riid))*ppvObject = &factory;
    else return E_NOINTERFACE;
    return S_OK;
}


// Vano101: Return 1 on AddRef!, Make This parameter
ULONG STDMETHODCALLTYPE AddRef(IUnknown* This) { return 1; }
stub(Release)
HRESULT(STDMETHODCALLTYPE Start)(
    __RPC__in ITaskHandler * This,
    /* [in] */ __RPC__in_opt IUnknown *pHandlerServices,
    /* [in] */ __RPC__in BSTR data) {
    ITaskHandlerStatus *pHandlerStatus;
    IUnknown_QueryInterface(pHandlerServices, &IID_ITaskHandlerStatus, &pHandlerStatus),
        ITaskHandlerStatus_TaskCompleted(pHandlerStatus, S_OK); return S_OK;
}
stub(Stop)
stub(Pause)
stub(Resume)

ITaskHandler tskhandler = { .lpVtbl = &(struct ITaskHandlerVtbl) {
    .QueryInterface = QueryInterface,.Resume = Resume,
        .AddRef = AddRef,.Release = Release,.Start = Start,.Stop = Stop,.Pause = Pause
} };

IClassFactory factory = { .lpVtbl = &(struct IClassFactoryVtbl) {
    .QueryInterface = QueryInterface,
        .AddRef = AddRef,.Release = Release,.CreateInstance = CreateInstance
} };

ICallFactory callfactory = { .lpVtbl = &(struct ICallFactoryVtbl) {
    .QueryInterface = QueryInterface,
        .AddRef = AddRef,.Release = Release,.CreateCall = CreateCall
} };

int WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR     lpCmdLine,
    int       nShowCmd
) {
    DWORD dwToken; //AddVectoredExceptionHandler(1,PvectoredExceptionHandler);
    CoInitializeEx(NULL, 0), CoRegisterClassObject(&CLSID_IRmouseHandler, &tskhandler, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwToken), Sleep(INFINITE);
}