Why COM doesn't work in a new thread? - c++

My problems started after converting my VS2003 project to VS2008. Solution contains 3 projects. Projects are DLL's. There were A LOT of compilation errors, then some linker errors... Well, I fought them off. Now it just simply doesn't work ;)
So, one of this DLL's is suppoused to communicate with Word by COM.
Word::_ApplicationPtr d_pApp;
Word::_DocumentPtr d_pDoc;
void MSWord2003::init()
{
free();
HRESULT hr;
CLSID clsid;
CLSIDFromProgID(L"Word.Application", &clsid);
// Get an interface to the running instance, if any..
IUnknown *pUnk;
hr = GetActiveObject(clsid, NULL, (IUnknown**)&pUnk);
if(hr!=S_OK)
throw MSWord::MSWordException("Nie znaleziono działającej aplikacji MSWord.");
IDispatch* d_pDispApp;
hr = pUnk->QueryInterface(IID_IDispatch, (void**)&d_pDispApp);
if(hr!=S_OK)
throw MSWord::MSWordException("Nie udało się połączyć z aplikacją MSWord.");
pUnk->Release();
pUnk = 0;
d_pApp = d_pDispApp;
d_pDoc = d_pApp->ActiveDocument;
d_pDispApp->AddRef();
d_currIdx = -1;
paragraphsCount = d_pDoc->GetParagraphs()->Count;
footnotesCount = d_pDoc->GetFootnotes()->Count;
endnotesCount = d_pDoc->GetEndnotes()->Count;
}
void MSWord2003::free()
{
if(d_pApp!=0)
{
d_pApp->Release();
d_pApp=0;
}
}
This code works on VS2003 (and different machine, I don't have VS2003 on my computer) while in VS2008 it works only if it is called by main thread.
When called by a new thread (wich is initialized by CoInitialize) d_pApp is not initialized properly - its ptr shows 0.
While debugging I reached code in comip.h:
template<typename _InterfacePtr> HRESULT _QueryInterface(_InterfacePtr p) throw()
{
HRESULT hr;
// Can't QI NULL
//
if (p != NULL) {
// Query for this interface
//
Interface* pInterface;
hr = p->QueryInterface(GetIID(), reinterpret_cast<void**>(&pInterface));
// Save the interface without AddRef()ing.
//
Attach(SUCCEEDED(hr)? pInterface: NULL);
}
else {
operator=(static_cast<Interface*>(NULL));
hr = E_NOINTERFACE;
}
return hr;
}
In a new thread, QueryInterface returns E_NOINTERFACE, although GetIID() returns the same thing for both threads. And that is where I got stuck - I have no idea, what causes this behaviour...

IMO you should initialize COM not with CoInitialize, but with CoInitializeEx, specifying COINIT_MULTITHREADED. Otherwise you'll have separate single-threaded COM apartment for every thread.

Related

IGlobalInterfaceTable::RegisterInterfaceInGlobal returns E_NOTIMPL

Does anyone know why IGlobalInterfaceTable::RegisterInterfaceInGlobal might return E_NOTIMPL?
I'm using ATL's CComGITPtr and simply assigning it to a COM pointer. The assignment, in turn, calls the following ATL code:
HRESULT Attach(_In_ T* p) throw()
{
if (p)
{
CComPtr<IGlobalInterfaceTable> spGIT;
HRESULT hr = E_FAIL;
hr = AtlGetGITPtr(&spGIT);
ATLASSERT(spGIT != NULL);
ATLASSERT(SUCCEEDED(hr));
if (FAILED(hr))
return hr;
if (m_dwCookie != 0)
hr = spGIT->RevokeInterfaceFromGlobal(m_dwCookie);
if (FAILED(hr))
return hr;
return spGIT->RegisterInterfaceInGlobal(p, __uuidof(T), &m_dwCookie);
}
else
{
return Revoke();
}
}
Apparently that last call to spGIT->RegisterInterfaceInGlobal results in E_NOTIMPL.
I'm performing the assignment in the context of an in-proc Explorer extension, and I'm doing it on the main UI thread.
Also, I'm not sure if this is related, but I get the same error when trying to use RoGetAgileReference (on Windows 8.1), doing something like this (with an IShellItemArray):
HRESULT hr;
CComPtr<IAgileReference> par;
hr = RoGetAgileReference(AGILEREFERENCE_DEFAULT, IID_IShellItemArray, psia, &par);
Any ideas what might be going wrong?

Linking and communication to AutoCAD in C++

How can i open the AutoCAD application and send commands to that in C++?
In the VB it's possible by CreateObject and GetObject functions.
In C++, you need to use CoCreateInstance instead of CreateObject and CoGetObject instead of GetObject.
Here is some sample code, adapted from this sample of Microsoft:
// Initialize COM for this thread...
CoInitialize(NULL);
// Get CLSID for our server...
CLSID clsid;
HRESULT hr = CLSIDFromProgID(L"AutoCAD.Application", &clsid);
if(FAILED(hr)) {
return -1;
}
// Start server and get IDispatch...
IDispatch *pAcadApp;
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pAcadApp);
if(FAILED(hr)) {
return -2;
}
You may want to use C# instead of C++, the .Net syntax is much more friendly than the C++ if you start to deal with COM pointers.
Here is an example. You can always create a .Net CLI library and wrap your C++ code so you can use it from .Net.
void LaunchACAD()
{
try
{
//Connect to a running instance
AcadApp = (AcadApplication)System.Runtime.InteropServices.Marshal.GetActiveObject(
"AutoCAD.Application");
}
catch(Exception ex)
{
// starts last run acad version
System.Type acType = System.Type.GetTypeFromProgID("AutoCAD.Application", true);
// ("AutoCAD.Application.17.1"); // starts 2008
// ("AutoCAD.Application.17.2"); // starts 2009
AcadApp = (AcadApplication)System.Activator.CreateInstance(acType);
}
AcadApp.Visible = true; // by the time this is reached AutoCAD is fully functional
}

Problems accessing a COM interface in C++

What I want to do is access a COM interface and then call the "Open" method of that interface.
I have a sample code in Visual Basic which works fine, but I need to write it in C++ and I can't seem to get it to work.
First of all, this is the working VB code:
Dim CANapeApplication As CANAPELib.Application
CANapeApplication = CreateObject("CANape.Application")
Call CANapeApplication.Open("C:\Users\Public\Documents\Vector\CANape\12\Project", 0)
CANape.Application is the ProgID which selects the interface I need.
After reading some docs at msdn.microsoft.com and this question, I wrote this code:
void ErrorDescription(HRESULT hr); //Function to output a readable hr error
int InitCOM();
int OpenCANape();
// Declarations of variables used.
HRESULT hresult;
void **canApeAppPtr;
IDispatch *pdisp;
CLSID ClassID;
DISPID FAR dispid;
UINT nArgErr;
OLECHAR FAR* canApeWorkingDirectory = L"C:\\Users\\Public\\Documents\\Vector\\CANape\\12\\Project";
int main(){
// Instantiate CANape COM interface
if (InitCOM() != 0) {
std::cout << "init error";
return 1;
}
// Open CANape
if (OpenCANape() != 0) {
std::cout << "Failed to open CANape Project" << std::endl;
return 1;
}
CoUninitialize();
return 0;
}
void ErrorDescription(HRESULT hr) {
if(FACILITY_WINDOWS == HRESULT_FACILITY(hr))
hr = HRESULT_CODE(hr);
TCHAR* szErrMsg;
if(FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&szErrMsg, 0, NULL) != 0)
{
_tprintf(TEXT("%s"), szErrMsg);
LocalFree(szErrMsg);
} else
_tprintf( TEXT("[Could not find a description for error # %#x.]\n"), hr);
}
int InitCOM() {
// Initialize OLE DLLs.
hresult = OleInitialize(NULL);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// Get CLSID from ProgID
//hresult = CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID);
hresult = CLSIDFromProgID(OLESTR("CanapeCom.CanapeCom"), &ClassID);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// OLE function CoCreateInstance starts application using GUID/CLSID
hresult = CoCreateInstance(ClassID, NULL, CLSCTX_LOCAL_SERVER,
IID_IDispatch, (void **)&pdisp);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
// Call QueryInterface to see if object supports IDispatch
hresult = pdisp->QueryInterface(IID_IDispatch, (void **)&pdisp);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
std::cout << "success" << std::endl;
return 0;
}
int OpenCANape() {
//Method name
OLECHAR *szMember = L"Open";
// Retrieve the dispatch identifier for the Open method
// Use defaults where possible
DISPID idFileExists;
hresult = pdisp->GetIDsOfNames(
IID_NULL,
&szMember,
1,
LOCALE_SYSTEM_DEFAULT,
&idFileExists);
if (!SUCCEEDED(hresult)) {
std::cout << "GetIDsOfNames: ";
ErrorDescription(hresult);
return 1;
}
unsigned int puArgErr = 0;
VARIANT VarResult;
VariantInit(&VarResult);
DISPPARAMS pParams;
memset(&pParams, 0, sizeof(DISPPARAMS));
pParams.cArgs = 2;
VARIANT Arguments[2];
VariantInit(&Arguments[0]);
pParams.rgvarg = Arguments;
pParams.cNamedArgs = 0;
pParams.rgvarg[0].vt = VT_BSTR;
pParams.rgvarg[0].bstrVal = SysAllocString(canApeWorkingDirectory);
pParams.rgvarg[1].vt = VT_INT;
pParams.rgvarg[1].intVal = 0; // debug mode
// Invoke the method. Use defaults where possible.
hresult = pdisp->Invoke(
dispid,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD,
&pParams,
&VarResult,
NULL,
&puArgErr
);
SysFreeString(pParams.rgvarg[0].bstrVal);
if (!SUCCEEDED(hresult)) {
ErrorDescription(hresult);
return 1;
}
return 0;
}
There are several problems with this.
Using the ClassID received from CLSIDFromProgID as the first parameter of CoCreateInstance does not work, it returns the error: class not registered
If i use the ProgID CanapeCom.CanapeCom (I found it by looking in the Registry), CoCreateInstance works. However, when I use pdisp->GetIDsOfNames I get the error message: Unkown name. Which I think means that the method was not found. That seems logical because I've used a different ProgID, but I just can't figure out how to get to the interface I'm looking for.
I have also tried to use the resulting CLSID from CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID); as the 4th argument of CoCreateInstance but that resulted in a "No such interface supported" error.
Do I need the dll file of the software? In the VB example the dll file is used to get the interface and then create a new object using the ProgID. I'm not sure if I need to do the same in C++ or how this should work.
I'm really stuck here and hope that someone can help me.
Thanks for your comments.
I've fixed the problem, although the solution is kind of embarrassing...
In my defense, I'm still a student and new to this kind of stuff.
I've used the Process Monitor to check what happens when I execute the VB script.
I saw that the CLSID used there is the ID returned by CLSIDFromProgID(OLESTR("CANape.Application"), &ClassID);, which meant that this had to be the right one and the problem had to be somewhere else. I've looked again at the CoCreateInstance and then took a look at the other parameters. Turns out that the context CLSCTX_LOCAL_SERVER was wrong, it has to be CLSCTX_INPROC_SERVER. I don't know why I've set it to local_server in the first place or why I've never questioned it. I wrote that part of the code a few days ago and then focused too much on the CLSID and IID rather than on the other parameters.
I've also taken the first comment from Alex into account and created a tlb file.
This is a simplified version of the code that works:
#import "CANape.tlb"
int _tmain(int argc, _TCHAR* argv[])
{
_bstr_t path = "C:\\Users\\Public\\Documents\\Vector\\CANape\\12\\Project";
CLSID idbpnt;
CoInitialize(NULL);
HRESULT hr = CLSIDFromProgID (L"CANape.Application", &idbpnt);
CANAPELib::IApplication *app;
hr = CoCreateInstance(idbpnt,NULL,CLSCTX_INPROC_SERVER,__uuidof(CANAPELib::IApplication),(LPVOID*)&app );
app->Open(path,0);
CoUninitialize();
return 0;
}

DirectShow CLR having issue with global variables

I am trying to code up GMFBridge and DirectShow in CLR C++. I am trying to compare its performance against the GMFBridgeLib and the DirectShowLib in the same solution to see which is more efficient.
Right now I am following the GMFBridge source code for setting up C++ capture. One issue I am having is in objects that need to be global so that they can be accessed across the GUI buttons. The GMFBridge code does that as follows:
private:
IGMFBridgeControllerPtr m_pBridge;
that is then used in the setup code as follows:
HRESULT hr = m_pBridge.CreateInstance(__uuidof(GMFBridgeController));
if (FAILED(hr))
{
return hr;
}
// init to video-only, in discard mode (ie when source graph
// is running but not connected, buffers are discarded at the bridge)
hr = m_pBridge->AddStream(true, eMuxInputs, true);
My current problem is that CLR states that any global has to be a pointer of some form, * or ^ depending on managed or unmanaged. It will not just let me add in a global variable such as the GMFBridge source code does. If I create a pointer:
IGMFBridgeControllerPtr* pBridge2;
and try to use that in my GUI code:
(*pBridge2).CreateInstance(__uuidof(GMFBridgeController));
(*pBridge2).AddStream(true, eMuxInputs, true);
It does compile, but when i run it, the code crashes with
An unhandled exception of type 'System.NullReferenceException' occurred in Program.exe.
Addidional information: Object reference not set to an instance of an object.
on the block of code
void _Release() throw()
{
if (m_pInterface != NULL) { <--------------
m_pInterface->Release();
}
}
in comip.h line 823 called from:
HRESULT CreateInstance(const CLSID& rclsid, IUnknown* pOuter = NULL, DWORD dwClsContext = CLSCTX_ALL) throw()
{
HRESULT hr;
_Release();
if (dwClsContext & (CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER)) { <----------
IUnknown* pIUnknown;
hr = CoCreateInstance(rclsid, pOuter, dwClsContext, __uuidof(IUnknown), reinterpret_cast<void**>(&pIUnknown));
if (SUCCEEDED(hr)) {
hr = OleRun(pIUnknown);
if (SUCCEEDED(hr)) {
hr = pIUnknown->QueryInterface(GetIID(), reinterpret_cast<void**>(&m_pInterface));
}
pIUnknown->Release();
}
}
else {
hr = CoCreateInstance(rclsid, pOuter, dwClsContext, GetIID(), reinterpret_cast<void**>(&m_pInterface));
}
if (FAILED(hr)) {
// just in case refcount = 0 and dtor gets called
m_pInterface = NULL;
}
return hr;
}
comip.h line 626 called from this line of code
(*pBridge2).CreateInstance(__uuidof(GMFBridgeController));
the only thing that seems to work is creating a local variable that is not a point object, but then i cant set it to a global, or use it across GUI objects.
if I make it local:
IGMFBridgeControllerPtr pBridge;
pBridge.CreateInstance(__uuidof(GMFBridgeController));
that works.
The problem seems to be that you do not assign anything to the pointer you declare:
IGMFBridgeControllerPtr* pBridge2;
You have to do something like:
pBridge2 = &m_pBridge;
Or just skip the use of pBridge2 completely and use &m_pBridge instead.

How to globally mute and unmute sound in Vista and 7, and to get a mute state?

I'm using the old good Mixer API right now, but it does not work as expected on Windows Vista & 7 in the normal, not in XP compatibility mode. It mutes the sound for the current app only, but I need a global (hardware) mute. How to rearch the goal? Is there any way to code this w/o COM interfaces and strange calls, in pure C/C++?
The audio stack was significantly rewritten for Vista. Per-application volume and mute control was indeed one of the new features. Strange calls will be required to use the IAudioEndpointVolume interface.
I recently dealt with this same issue. We have a Windows application that uses the sound system for alarms. We cannot abide the user muting the sound system inadvertently. Here is how I was able to use the interface suggested above to address this issue:
During initialization I added a function to initialize a member of type IAudioEndpointVolume. It was a bit tricky and the help wasn't as helpful as it could be. Here's how to do it:
/****************************************************************************
** Initialize the Audio Endpoint (Only for post XP systems)
****************************************************************************/
void CMuteWatchdog::InitAudioEndPoint(void)
{
HRESULT hr;
IMMDeviceEnumerator * pDevEnum;
IMMDevice * pDev;
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&pDevEnum);
m_pIaudEndPt = NULL;
if(hr == S_OK)
{
hr = pDevEnum->GetDefaultAudioEndpoint(eRender, eConsole, &pDev);
if(hr == S_OK)
{
DWORD dwClsCtx;
const IID iidAEV = __uuidof(IAudioEndpointVolume);
dwClsCtx = 0;
hr = pDev->Activate(iidAEV, dwClsCtx, NULL, (void**) &m_pIaudEndPt);
if(hr == S_OK)
{
// Everything is groovy.
}
else
{
m_pIaudEndPt = NULL; // Might mean it's running on XP or something. Don't use.
}
pDev->Release();
}
pDevEnum->Release();
}
}
...
About once per second I added a simple call to the following:
////////////////////////////////////////////////////////////////////////
// Watchdog function for mute.
void CMuteWatchdog::GuardMute(void)
{
if(m_pIaudEndPt)
{
BOOL bMute;
HRESULT hr;
bMute = FALSE;
hr = m_pIaudEndPt->GetMute(&bMute);
if(hr == S_OK)
{
if(bMute)
{
m_pIaudEndPt->SetMute(FALSE, NULL);
}
}
}
}
Finally, when the program exits just remember to release the allocated resource.
////////////////////////////////////////////////////////////////////////
// De-initialize the watchdog
void CMuteWatchdog::OnClose(void)
{
if(m_pIaudEndPt)
{
m_pIaudEndPt->Release();
m_pIaudEndPt = NULL;
}
}