The prototype of ITaskHandler::Start
is the following:
HRESULT ( STDMETHODCALLTYPE Start )(
__RPC__in ITaskHandler * This,
/* [in] */ __RPC__in_opt IUnknown *pHandlerServices,
/* [in] */ __RPC__in BSTR data)
How come pHandlerServices
is optional - and if I get a NULL (as it's my case) - how can I notify the task scheduler that I've completed the task.
OK - here is the deal I implemented QueryInterface
of the class to always return the same object thinking - ITaskHandler
will immediately be queried. However this wasn't the case - the first query was for IClassFactory
and the function signature of CreateInstance
had the second parameter pUnkOuter
NULL which overlaps with the second parameter of my implementation of ITaskHandler_Start
. Nevertheless it's weird that pHandlerServices
is marked as optional.
Here is my current implementation of the handler which is still not working (the last run result is No such interface supported (0x80004002)) - my interface ITaskHandler
is never queried for. I even went so far implementing ICallFactory
but alias with no luck (CreateCall
is never called) - here is the code:
#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);}
The key to this as @HansPassant notes is that Task Scheduler will only run COM objects out of process. In order to do this you need register your COM object to use the system supplied DllSurrogate.
Here are the COM registration keys for my example
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
Working example code (sorry C++) - it basically just a default COM implementation with the addition of ITaskHandler. It at least calls Start. If you want to use you own code, I suggest that you test the COM object loads in a simple test program before worrying about the Task Scheduler.
// {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;
}
Task Registration 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>
There are 3 issues:
1) AddRef and stub must have 1 parameter, like IUnknown* This (or LPVOID This). It would does not matter if your function were __cdecl but COM works only with __stdcall functions. And system expects functions clean parameters from stack by own.
2) AddRef must return not 0 at least, and stub function must return S_OK or 0. Maybe it's not large issue..
3) Investigations shows system queries IUnknown for class factory, maybe it performs QueryInterface instead AddRef. But anyway it's not correct to return taskhandler for every IUnknown query. It's even better to return "factory". However it's better to return This on IUnknown, anyway every interface is descendant of IUnknown.
Bonus issue, if you compile under Unicode message box shows strange Chinese text, because there is needed to make text parameters Unicode, or use MessageBoxA explicitly
#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);
}
User contributions licensed under CC BY-SA 3.0