I'm using Visual Studio 2010 on Windows 7 SP1 x64.
I need to make a custom deskband that communicates with a win32 application, compiled as 32bit app.
I used this CodeProject article as a starting point: http://www.codeproject.com/Articles/39189/Shell-Extensibility-Explorer-Desk-Band-Tray-Notifi
The deskband dll (compiled for x64) works fine - I see it in the taskbar, etc.
It is registered using regsvr32 from a cmd.exe that was "Run as administrator". The rgs file is this:
HKCR
{
NoRemove CLSID
{
ForceRemove {8D330AEA-90D1-4632-804B-4710F2358A18} = s 'my deskbar'
{
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
}
}
}
Based on this stackoverflow answer: How to add and implement a new interface to an Inproc COM server
I've created an interface in the IDL file:
import "oaidl.idl";
import "ocidl.idl";
[
object,
uuid(35662650-07D0-4F31-B5F0-3C21FCA4BDA2),
version(1.0),
oleautomation
//dual,
//pointer_default(unique)
]
interface IGEDeskBand : IDispatch
{
HRESULT BadgeSet([in] long arg);
HRESULT BadgeClear(void);
HRESULT BadgeBlink(void);
};
[
uuid(33EB8C00-F66A-493C-8607-529060FAA378),
version(1.0),
helpstring("GamEffective DeskBand Library")
]
library GEDeskBandLib
{
importlib("stdole32.tlb");
[
uuid(8D330AEA-90D1-4632-804B-4710F2358A18),
helpstring("GEDeskBand Class")
]
coclass GEDeskBand
{
[default] interface IGEDeskBand;
};
cpp_quote("const size_t MAX_GUID_STRING_LEN = 39;")
};
, visual studio compiles it using midl producing the generated headers, I include the generated code: #include "GEDeskBand_i.h", and I made my class inherit from
public IDispatchImpl<IGEDeskBand, &__uuidof(IGEDeskBand), &LIBID_GEDeskBandLib, /* wMajor = */ 1, /* wMinor = */ 0>
, added a COM_INTERFACE_ENTRY with interface to the COM MAP and added the methods:
STDMETHOD(BadgeSet)(/* [in] */ long arg);
STDMETHOD(BadgeClear)();
STDMETHOD(BadgeBlink)();
(well, I used the visual studio wizard: class view -> right click class, add -> implement interface), currently the methods' implementation is just return E_NOTIMPL;, and I hope that's not a problem. This is all that I've done with the code on the server, regarding my interface and trying to communicate with this object from another process.
Using #define _ATL_DEBUG_QI from this article (http://msdn.microsoft.com/en-us/library/ezs95xhx%28v=vs.80%29.aspx), I see in the Output window of Visual Studio when attached to explorer.exe (where the deskbar dll is loaded and my COM object lives) that QueryInterface is queried with the right interface and doesn't say "failed".
This is when the deskbar is loaded:
CComClassFactory - IClassFactory
CGEDeskBand - IDeskBand
CGEDeskBand - IPersistStreamInit
CGEDeskBand - IDeskBand2
CGEDeskBand - IDeskBand
CGEDeskBand - {EA5F2D61-E008-11CF-99CB-00C04FD64497} - failed
CGEDeskBand - IObjectWithSite
CGEDeskBand - {7FE80CC8-C247-11D0-B93A-00A0C90312E1} - failed
CGEDeskBand - IOleCommandTarget - failed
CGEDeskBand - IDeskBandInfo - failed
CGEDeskBand - IDeskBand
CGEDeskBand - IOleWindow
CGEDeskBand - IPersist
CGEDeskBand - IDeskBand2
and this is when I run the tester that demonstrates the problem:
CGEDeskBand - IDeskBand
CGEDeskBand - {00000003-0000-0000-C000-000000000046} - failed
CGEDeskBand - {00000003-0000-0000-C000-000000000046} - failed
CGEDeskBand - {0000001B-0000-0000-C000-000000000046} - failed
CGEDeskBand - IUnknown
CGEDeskBand - {00000018-0000-0000-C000-000000000046} - failed
CGEDeskBand - {00000019-0000-0000-C000-000000000046} - failed
CGEDeskBand - {4C1E39E1-E3E3-4296-AA86-EC938D896E92} - failed
CGEDeskBand - IDeskBand
CGEDeskBand - IDeskBand
CGEDeskBand - IPersist
CGEDeskBand - IPersist
CGEDeskBand - {1C733A30-2A1C-11CE-ADE5-00AA0044773D} - failed
CGEDeskBand - {35662650-07D0-4F31-B5F0-3C21FCA4BDA2}
The last one is my IID and it doesn't say failed.
This is a test code that demonstrates the problem:
#include <Shobjidl.h>
#include "GEDeskBand_i.h"
#include "GEDeskBandGuids.h"
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr;
CComPtr<IUnknown> spBandService;
CComPtr<IBandSite> spBandSite;
CoInitialize(NULL);
hr = spBandService.CoCreateInstance(CLSID_TrayBandSiteService);
if (SUCCEEDED(hr)) {
hr = spBandService->QueryInterface(&spBandSite);
if (SUCCEEDED(hr)) {
DWORD dwBandId = 0;
UINT num_bands = spBandSite->EnumBands((UINT)-1, &dwBandId);
for (UINT uBand = 0; uBand < num_bands; uBand++) {
if (SUCCEEDED(spBandSite->EnumBands(uBand, &dwBandId))) {
IDeskBand *band;
if (SUCCEEDED(spBandSite->GetBandObject(dwBandId, IID_IDeskBand, (void**)&band))) {
IPersist* pPersist = NULL;
if (SUCCEEDED(band->QueryInterface(IID_IPersist, (void**)&pPersist))) {
CLSID clsid = CLSID_NULL;
if (SUCCEEDED(pPersist->GetClassID(&clsid))) {
if (clsid == CLSID_GEDeskBand) {
// reaches here fine
IGEDeskBand *pGEDeskBand = NULL;
hr = band->QueryInterface(IID_IGEDeskBand, (void**)&pGEDeskBand);
if (SUCCEEDED(hr)) {
// never reaches here, always get E_NOINTERFACE
pGEDeskBand->BadgeSet(1);
}
break;
}
}
}
}
}
}
spBandSite.Release();
}
spBandService.Release();
}
CoUninitialize();
return 0;
}
The problem is that although I see the IDeskBand or the IPersist of my DeskBand object (the CLSID matches, I know it's my object, and I know it implements that interface), QueryInterface using my interface IID_IGEDeskBand never succeeds.
QueryInterface does find the IID, but it does not reach my client. Because of marshaling, I suppose.
I've tried compiling the tester as x64, but that didn't work.
Raymond Chen wrote about this: http://blogs.msdn.com/b/oldnewthing/archive/2004/12/13/281910.aspx
All I understood from that blog post is that I need to deal with the marshaling. I thought it was supposed to be automatic?
I saw that one way to fix this is to build a midl generated proxy-stub: CoCreateInstance returning E_NOINTERFACE even though interface is found
I've built and registered a proxy-stub dll, a 32bit one, by getting midl to create 32bit proxy code and the recipe from here: http://msdn.microsoft.com/en-us/library/aa366768%28VS.85%29.aspx
This didn't work either - when running the tester, I do not see the proxy-stub DLL loaded in the tester process. Should I? It seems to be registered correctly.
I don't know COM or even general win32 all that well, but I've been googling for hours and I don't understand what I'm doing wrong.
Related
How can I have a Windows tray notification icon for an out-of-process COM server, developed using VS2019?
So far I have tried just adding one with Shell_NotifyIconA(NIM_ADD, &n); as per the MSDN documentation. .However if I set the NOTIFYICONDATA::m_hWnd to 0 then this call is rejected with 0x80004005 (Invalid handle).
So I have to specify a window handle that the icon's messages will go to, but the application currently doesn't have any windows. It does have a message pump which is found at ATL::CAtlExeModule<T>::RunMessageLoop() (that's part of the ATL boilerplate code) but I can't see any mention of where a window handle is to send messages to this loop.
I've tried using a Message-only Window created with CWindowImpl::Create, however when the program runs, the behaviour is unexpected. A blank space appears in the notification tray (the icon does not show properly), and mousing or clicking on the space does not cause the message handler to be entered. The log message appears indicating Shell_NotifyIcon() succeeded and the handles are valid, but no further log messages.
What's the right way to do this in VS2019? (I have done it before in C++Builder which lets you simply add a form, mark it as the main form, and add a notification icon component to it).
Code for the ATLExeModule (this is the boilerplate code plus my modifications):
class CNotifyWnd : public CWindowImpl<CNotifyWnd>
{
public:
BEGIN_MSG_MAP(CMyCustomWnd)
MESSAGE_HANDLER(WM_USER+1, OnMsg)
END_MSG_MAP()
LRESULT OnMsg(UINT, WPARAM, LPARAM, BOOL&)
{
DEBUG_LOG("Received notification");
return 0;
}
};
static void create_notifyicon()
{
auto * pw = new CNotifyWnd;
HWND hwnd = pw->Create(HWND_MESSAGE);
auto hInst = GetModuleHandle(NULL);
NOTIFYICONDATAA n{};
n.cbSize = sizeof n;
n.hIcon = LoadIcon(NULL, IDI_SHIELD);
#pragma warning(disable : 4996)
strcpy(n.szTip, "Tooltip string");
n.dwInfoFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
n.uVersion = NOTIFYICON_VERSION;
n.hWnd = hwnd;
n.uID = 1234;
n.uCallbackMessage = WM_USER + 1;
int hr = Shell_NotifyIconA(NIM_ADD, &n);
DEBUG_LOG("Shell_NotifyIcon = {}; Icon handle {}, window {}",
hr, (uint64_t)n.hIcon, (uint64_t)n.hWnd);
}
class CMyProjectModule : public ATL::CAtlExeModuleT< CMyProjectModule >
{
public :
DECLARE_LIBID(LIBID_MyProjectLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MYPROJECT, "{d0d2e9f7-8578-412a-9311-77ff62291751}")
using Parent = ATL::CAtlExeModuleT< CMyProjectModule >;
HRESULT PreMessageLoop(int nShowCmd) throw()
{
HRESULT hr = Parent::PreMessageLoop(nShowCmd);
create_notifyicon();
return hr;
}
};
CMyProjectModule _AtlModule;
extern "C" int WINAPI _tWinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/,
LPTSTR /*lpCmdLine*/, int nShowCmd)
{
return _AtlModule.WinMain(nShowCmd);
}
The code in the question is mostly correct, however dwInfoFlags should be uFlags. After making that change the notify icon worked as intended.
Thanks to commentors who suggested ways to simplify the original code in the question, and the idea of a "message-only window" created by setting the parent to HWND_MESSAGE.
My friend asked me to write a simple program that captures a fingerprint from the built-in reader on a computer and prints out some kind of identifier. I can choose operating system myself, like a laptop with Windows or Linux, or an Android phone.
I thought that would be simple, surely there are many API:s for this, and I noticed that Microsoft themselves actually provides an API for it. Since I can log into my win10 laptop with the fingerprint reader, I know that the reader works.
For some reason the example that Microsoft themselves provide in their documention does not work for me. https://learn.microsoft.com/en-us/windows/desktop/api/Winbio/nf-winbio-winbiocapturesample
I suppose that the people writing those pages forgot to mention some important aspect or step, perhaps there is a way to add permission somewhere in Visual Studio.
After rewriting and trying many things, at least I get ONE step further in the process, but it still fails.
Here is the current version
#include "pch.h"
#include <iostream>
#include "Windows.h"
#include "Stdio.h"
#include "Conio.h"
#include "Winbio.h"
HRESULT CaptureSample();
void capture(WINBIO_SESSION_HANDLE sessionHandle, int flag);
int main()
{
std::cout << "Hello World!\n";
HRESULT x = CaptureSample();
}
HRESULT CaptureSample()
{
HRESULT hr = S_OK;
WINBIO_SESSION_HANDLE sessionHandle = NULL;
WINBIO_REJECT_DETAIL rejectDetail = 0;
// Connect to the system pool.
hr = WinBioOpenSession(
WINBIO_TYPE_FINGERPRINT, // Service provider
WINBIO_POOL_SYSTEM, // Pool type
WINBIO_FLAG_DEFAULT, // Access: Capture raw data
NULL, // Array of biometric unit IDs
0, // Count of biometric unit IDs
WINBIO_DB_DEFAULT, // Default database
&sessionHandle // [out] Session handle
);
if (FAILED(hr))
{
wprintf_s(L"WinBioOpenSession failed. hr = 0x%x\n", hr);
goto e_Exit;
}
wprintf_s(L"Start my fingerprint capturing...\n");
capture(sessionHandle, WINBIO_DATA_FLAG_INTEGRITY);
capture(sessionHandle, WINBIO_DATA_FLAG_PRIVACY);
capture(sessionHandle, WINBIO_DATA_FLAG_SIGNED);
capture(sessionHandle, WINBIO_DATA_FLAG_OPTION_MASK_PRESENT);
capture(sessionHandle, WINBIO_DATA_FLAG_RAW);
capture(sessionHandle, WINBIO_DATA_FLAG_INTERMEDIATE);
capture(sessionHandle, WINBIO_DATA_FLAG_PROCESSED);
hr = WinBioEnrollCapture(sessionHandle, &rejectDetail);
wprintf_s(L"WinBioEnrollCapture hr=%x rejection = %d\n", hr, rejectDetail);
if (sessionHandle != NULL)
{
WinBioCloseSession(sessionHandle);
sessionHandle = NULL;
}
e_Exit:
wprintf_s(L"\n Press any key to exit...");
_getch();
return hr;
}
void capture(WINBIO_SESSION_HANDLE sessionHandle, int flag) {
WINBIO_UNIT_ID unitId = 0;
WINBIO_REJECT_DETAIL rejectDetail = 0;
PWINBIO_BIR sample = NULL;
SIZE_T sampleSize = 0;
wprintf_s(L"\n Calling WinBioCaptureSample. Flag = %d.\n", flag);
HRESULT hr = WinBioCaptureSample(
sessionHandle,
WINBIO_PURPOSE_IDENTIFY,
flag,
&unitId,
&sample,
&sampleSize,
&rejectDetail
);
if (FAILED(hr))
{
if (hr == WINBIO_E_BAD_CAPTURE)
{
wprintf_s(L"\n Bad capture; reason: %d\n", rejectDetail);
}
else if (hr == E_ACCESSDENIED)
{
wprintf_s(L"\n WinBioCaptureSample failed, access denied.");
}
else
{
wprintf_s(L"\n WinBioCaptureSample failed. hr = 0x%x\n", hr);
}
goto e_Exit;
}
wprintf_s(L"\n Swipe processed - Unit ID: %d\n", unitId);
wprintf_s(L"\n Captured %d bytes.\n", sampleSize);
e_Exit:
if (sample != NULL)
{
WinBioFree(sample);
sample = NULL;
}
}
and here is the result of running it:
Hello World!
Start my fingerprint capturing...
Calling WinBioCaptureSample. Flag = 1.
WinBioCaptureSample failed, access denied.
Calling WinBioCaptureSample. Flag = 2.
WinBioCaptureSample failed, access denied.
Calling WinBioCaptureSample. Flag = 4.
WinBioCaptureSample failed, access denied.
Calling WinBioCaptureSample. Flag = 8.
WinBioCaptureSample failed, access denied.
Calling WinBioCaptureSample. Flag = 32.
WinBioCaptureSample failed, access denied.
Calling WinBioCaptureSample. Flag = 64.
WinBioCaptureSample failed, access denied.
Calling WinBioCaptureSample. Flag = 128.
WinBioCaptureSample failed, access denied.WinBioEnrollCapture hr=8009802c rejection = 0
Press any key to exit...
What am I missing?
I think you should try using another API, though your objective is not clear here. You can check the CloudABIS™ which is superscalar, biometrics-as-a-service (BaaS) matching system for fast deployment at lower costs. The API is provided by M2SYS TEchnology and they have an vast array of hardware also.
The MIDL compiler generates code for a proxy/stub with registration routines that write to HKEY_LOCAL_MACHINE. Is there any way (preferably without hacking the MIDL-generated code and without bypassing all that generated code in favor of custom code) to register a MIDL-generated p/s in HKEY_CURRENT_USER?
Also: Will this work if both the p/s and the COM server are registered per-user like this? I just found (after a very frustrating 48 hours) that a p/s registered machine-wide will not work correctly if the COM server is registered per-user. Specifically, the asynchronous call logic (ICallFactory::CreateCall) of the p/s will fail under these circumstances.
Using RegOverridePredefKey is the right answer.
Then register with:
regsvr32 /n /i:user C:\src\myCode.dll
With "/i:user", regsvr32 calls your the "DllInstall" function instead of DllRegisterServer.
Example implementation of DllInstall:
extern "C" STDAPI DllInstall(BOOL bInstall, _In_opt_ LPCWSTR pszCmdLine)
{
HRESULT hr = E_FAIL;
static const wchar_t szUserSwitch[] = L"user";
if (pszCmdLine != NULL)
{
if (_wcsnicmp(pszCmdLine, szUserSwitch, _countof(szUserSwitch)) == 0)
{
ATL::AtlSetPerUserRegistration(true); // is this really needed??
}
}
LSTATUS status = RegCreateKeyEx(HKEY_CURRENT_USER, L"SOFTWARE\\Classes", 0, 0, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, 0, &hkcu_classes, &disposition);
if (status == ERROR_SUCCESS)
{
status = RegOverridePredefKey(HKEY_CLASSES_ROOT, hkcu_classes);
}
hr = HRESULT_FROM_NT(status);
if (SUCCEEDED(hr))
{
if (bInstall)
{
hr = DllRegisterServer();
if (FAILED(hr))
{
DllUnregisterServer();
}
}
else
{
hr = DllUnregisterServer();
}
}
}
So I'm writing a c++ code that its goal is to add a simple task to the task scheduler.
The application is to be run in windows XP so I'm using Task Scheduler 1.0 Interfaces.
I followed the example, C/C++ Code Example: Creating a Task Using NewWorkItem, and tweaked it a little to fit my needs.
#include <windows.h>
#include <initguid.h>
#include <ole2.h>
#include <mstask.h>
#include <msterr.h>
#include <objidl.h>
#include <wchar.h>
#include <stdio.h>
int main(int argc, char **argv)
{
HRESULT hr = S_OK;
ITaskScheduler *pITS;
/////////////////////////////////////////////////////////////////
// Call CoInitialize to initialize the COM library and then
// call CoCreateInstance to get the Task Scheduler object.
/////////////////////////////////////////////////////////////////
hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_CTaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskScheduler,
(void **) &pITS);
if (FAILED(hr))
{
CoUninitialize();
return 1;
}
}
else
{
return 1;
}
/////////////////////////////////////////////////////////////////
// Call ITaskScheduler::NewWorkItem to create new task.
/////////////////////////////////////////////////////////////////
LPCWSTR pwszTaskName;
ITask *pITask;
IPersistFile *pIPersistFile;
pwszTaskName = L"Test Task";
hr = pITS->NewWorkItem(pwszTaskName, // Name of task
CLSID_CTask, // Class identifier
IID_ITask, // Interface identifier
(IUnknown**)&pITask); // Address of task
// interface
pITS->Release(); // Release object
if (FAILED(hr))
{
CoUninitialize();
fprintf(stderr, "Failed calling NewWorkItem, error = 0x%x\n",hr);
return 1;
}
// set exe path
hr = pITask->SetApplicationName("some_exe.exe");
if (FAILED(hr))
{
CoUninitialize();
return 1;
}
/////////////////////////////////////////////////////////////////
// Call IUnknown::QueryInterface to get a pointer to
// IPersistFile and IPersistFile::Save to save
// the new task to disk.
/////////////////////////////////////////////////////////////////
hr = pITask->QueryInterface(IID_IPersistFile,
(void **)&pIPersistFile);
pITask->Release();
if (FAILED(hr))
{
CoUninitialize();
fprintf(stderr, "Failed calling QueryInterface, error = 0x%x\n",hr);
return 1;
}
hr = pIPersistFile->Save(NULL,
TRUE);
pIPersistFile->Release();
if (FAILED(hr))
{
CoUninitialize();
fprintf(stderr, "Failed calling Save, error = 0x%x\n",hr);
return 1;
}
CoUninitialize();
printf("Created task.\n");
return 0;
}
The tweak is basically adding a call to ITask::SetApplicationName method which I don't think is related to my issue.
The code compiles and runs well. I can see that a task was added.
Problem
When I try to run the task I get:
Could not start
Inspecting file SchedLgU.txt I see:
""Test Task.job" (some_exe.exe) 12/9/2014 2:23:42 AM ** ERROR **
The attempt to retrieve account information for the specified task failed; therefore, the task did not run. Either an error occurred, or no account information existed for the task.
The specific error is:
0x8004130f: No account information could be found in the Task Scheduler security database for the task indicated..job" (some_exe.exe) 12/9/2014 2:23:42 AM ** ERROR **
The attempt to retrieve account information for the specified task failed; therefore, the task did not run. Either an error occurred, or no account information existed for the task.
The specific error is:
0x8004130f: No account information could be found in the Task Scheduler security database for the task indicated.
I can see here that the cause might be due to:
The account credentials are missing.
Any ideas?
I found the issue. This line was missing:
hr = pITask->SetAccountInformation(L"", NULL);
Disclaimer
This only works in administrator user accounts. I haven't figured out a way to make it work in non administrator accounts.
I have a fairly complex requirement. My STA COM object is implemented in a DLL (can't move it to out-of-process EXE). By the means of DllSurrogate I am hosting my object in a dllhost.exe process. My object has an UI attached to it (a plain modeless dialog) but I need the PreTranslateAccelerator mechanism in order for some shortcuts to work, etc. Since COM activates my object and hosts it in the default dllhost.com, I am obviously not controlling the message pump.
Is there still a way to pre-translate messages in this scenario? I doubt COM has foreseen such a specific scenario but maybe I am missing something.
Okay here it is. I hope I didn't leave out anything important. Basically, I have created a custom CMyComCreator instead of the default one. Instead of just creating a COM object and returning an interface pointer, I spin a worker UiThread. I use MyData structure to pass data across threads. Once the worker thread has finished setting up, I use the CComGITPtr to transfer the marshalled interface pointer from the UiThread back to the main. The consumers (out-of-process) end up with interface pointers that talk directly to the UiThread bypassing the main thread. You may think of CMyDialog as a modeless dialog which sends a PostQuitMessage on destruction to terminate the message loop. That's all. May look cumbersome but it works good.
struct MyData
{
ATL::CComGITPtr<IUnknown> Unk;
ATL::CEvent Event;
HRESULT hr;
MyData() : hr(E_OUTOFMEMORY), Event(FALSE, FALSE) { }
};
static CMessageLoop * MessageLoop;
class CMyComCreator
{
public:
static HRESULT WINAPI CreateInstance(
_In_opt_ void* pv,
_In_ REFIID riid,
_COM_Outptr_ LPVOID* ppv)
{
ATLASSERT(ppv != NULL);
if (ppv == NULL)
return E_POINTER;
*ppv = NULL;
HRESULT hRes = E_OUTOFMEMORY;
MyData* data = NULL;
ATLPREFAST_SUPPRESS(6014 28197)
/* prefast noise VSW 489981 */
ATLTRY(data = _ATL_NEW MyData)
ATLPREFAST_UNSUPPRESS()
if (data != NULL)
{
HANDLE thread = (HANDLE)_beginthreadex(NULL, 0, UiThread, (void *)data, 0, NULL);
if (thread)
{
WaitForSingleObject(data->Event, INFINITE);
CloseHandle(thread);
hRes = data->hr;
if (SUCCEEDED(hRes))
{
ATL::CComPtr<IUnknown> unk;
hRes = data->Unk.CopyTo(&unk);
if (SUCCEEDED(hRes))
{
hRes = unk->QueryInterface(riid, ppv);
}
}
}
delete data;
}
return hRes;
}
};
typedef CMyComCreator _CreatorClass;
static unsigned __stdcall UiThread(void * param)
{
CoInitializeEx(0, COINIT_APARTMENTTHREADED);
MyData * data = (MyData *)param;
ATL::CComObject<CMyDialog> * bb;
data->hr = ATL::CComObject<CMyDialog>::CreateInstance(&bb);
ATL::CComPtr<IUnknown> unk((IDispatch *) bb);
data->Unk = unk;
unk.Release();
data->Event.Set();
if (SUCCEEDED(data->hr))
{
CMessageLoop theLoop;
MessageLoop = &theLoop;
int nRet = theLoop.Run();
MessageLoop = NULL;
}
CoUninitialize();
return 0;
}
I needed to pack everything in a single DLL.
In which case, DllSurrogate is not the only way of doing this. There's also Rundll32:
INFO: Windows Rundll and Rundll32 Interface
This would allow you to run your own message loop inside the DLL's EntryPoint and have complete control over message processing, including PreTranslateMessage. You can copy the message loop logic from an ATL EXE server.
Bear in mind, there's still 32-bit and 64-bit version of "RunDll32.exe" in every 64-bit Windows OS. Use the one which matches the bit-ness of your DLL.