• Welcome to Powerbasic Museum 2020-B.
 

News:

Forum in repository mode. No new members allowed.

Main Menu

IRunningObjectTable Interface

Started by Frederick J. Harris, February 12, 2019, 06:45:19 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

Hi Jose!

Hope you are doing well!  I haven't been doing much coding for the past year since I retired, but I'm trying to find some time to get back to it.  I'm trying to help a C++ coder with the IRunningObjectTable interface.  I've only ever used the Running Object Table one time I can remember, when I developed that Out Of Process COM Server in PowerBASIC years ago (posted here).  What I'm wondering is how best to obtain an IDispatch pointer to an already running instance of Excel.  Of course, in PowerBASIC that is easily done for us by the GetCom call, which yields an object reference to an already running instance.  I guess what I'm asking is what do you think Bob Z's internal implementation for that was?

I looked at your IRunningObjectTable example and that seemed to provide a good start.  In studying up on the interfaces I noted the IMoniker interface has a IMoniker::BindToObject() method that allows passing in an IID and yields upon success an [OUT]  interface pointer in the normal fashion.  In the C/C++ code below I used your IRunningObjectTable example and in the loop which iterates through the ROT I call IMoniker::BindToObject for each entry in the table.  If Excel is running it yields S_OK when it hits "Book1".  I'm not sure if this is the best way to go.  I was hoping for your input on this. 

It seems to me that if I were to continue in this fashion I would need some other tests to make sure it was an object reference to Excel I got.  Afterall, if both Excel and Word were running, I'd get an S_OK return for IDispatch on both.  Can you shed some light on this for me?

Fred


// cl Test7.cpp /O1 /Os /GS- TCLib.lib kernel32.lib ole32.lib oleaut32.lib
#include <windows.h>
#include "stdio.h"

int main()
{
const IID            IID_IDispatch     = {0x00020400,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};   // IID_IDispatch
IBindCtx*            pBindCtx          = NULL;
IRunningObjectTable* pROT              = NULL;
IEnumMoniker*        pEnumMoniker      = NULL;
IMoniker*            pMoniker          = NULL;
IDispatch*           pXLApp            = NULL;
ULONG                iFetched          = 0;
LPOLESTR             pDisplayName      = NULL;
HRESULT              hr;
int                  iResult           = 0;

CoInitialize(NULL);
hr = CreateBindCtx(0, &pBindCtx);
if(hr!=S_OK)
    goto Bad_Ending;
hr = pBindCtx->GetRunningObjectTable(&pROT);
if(hr!=S_OK)
    goto Bad_Ending;
hr = pROT->EnumRunning(&pEnumMoniker);
if(hr!=S_OK)
    goto Bad_Ending;
   
while(true)

    hr = pEnumMoniker->Next(1, &pMoniker, &iFetched);   
    if(hr!=S_OK)
       break;
    hr = pMoniker->GetDisplayName(pBindCtx, 0, &pDisplayName);
    if(hr!=S_OK)
       break;
    wprintf(L"pDisplayName = %s\n",pDisplayName);
         
    hr=pMoniker->BindToObject(pBindCtx,NULL,IID_IDispatch,(void**)&pXLApp); 
    if(hr==S_OK)
    {
       printf("pMoniker->BindToObject() Succeeded!\n");
       iResult=wcscmp(pDisplayName,L"Book1");
       if(iResult==0)
       {
          printf("Found Excel App!\n");       
          printf("pXLApp = %u\n",pXLApp);
          pXLApp->Release();
       }   
    }
    else
       printf("pMoniker->BindToObject() Failed!\n");     
    if(pDisplayName)
       CoTaskMemFree(pDisplayName);   
    pMoniker->Release();
    pMoniker=NULL;   
}; 
   
Bad_Ending:
if(pMoniker)
    pMoniker->Release();
if(pEnumMoniker)
    pEnumMoniker->Release();
if(pROT)
    pROT->Release();
if(pBindCtx)
    pBindCtx->Release(); 
CoUninitialize();

return 0;
}

José Roca

Hi, Fred,

Welcome back!

The function you're looking for is GetActiveObject:
https://docs.microsoft.com/en-us/windows/desktop/api/oleauto/nf-oleauto-getactiveobject

Currently, I'm collaborating with Paul Squires in a framework and a visual designer for FreeBasic:
http://www.planetsquires.com/protect/forum/index.php

For this compiler I wrote the functions below, which remain untested because I don't have Office installed.


' ========================================================================================
' If the requested object is in an EXE (out-of-process server), such Office applications,
' and it is running and registered in the Running Object Table (ROT), AfxGetCom will
' return a pointer to its interface. AfxAnyCom will first try to use an existing, running
' application if available, or it will create a new instance if not.
' Be aware that AfxGetCom can fail under if Office is running but not registered in the ROT.
' When an Office application starts, it does not immediately register its running objects.
' This optimizes the application's startup process. Instead of registering at startup, an
' Office application registers its running objects in the ROT once it loses focus. Therefore,
' if you attempt to use GetObject or GetActiveObject to attach to a running instance of an
' Office application before the application has lost focus, you might receive an error.
' See: https://support.microsoft.com/en-us/help/238610/getobject-or-getactiveobject-cannot-find-a-running-office-application
' ========================================================================================
PRIVATE FUNCTION AfxGetCom OVERLOAD (BYREF wszProgID AS CONST WSTRING) AS IDispatch PTR
   DIM classID AS CLSID, pUnk AS ANY PTR
   CLSIDFromProgID(wszProgID, @classID)
   IF IsEqualGuid(@classID, @IID_NULL) THEN RETURN NULL
   GetActiveObject(@classID, NULL, @pUnk)
   RETURN pUnk
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION AfxGetCom OVERLOAD (BYREF classID AS CONST CLSID) AS IDispatch PTR
   DIM pUnk AS ANY PTR
   GetActiveObject(@classID, NULL, @pUnk)
   RETURN pUnk
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION AfxAnyCom OVERLOAD (BYREF wszProgID AS CONST WSTRING) AS IDispatch PTR
   DIM classID AS CLSID, pUnk AS ANY PTR
   pUnk = AfxGetCom(wszProgID)
   IF pUnk THEN RETURN pUnk
   CLSIDFromProgID(wszProgID, @classID)
   IF IsEqualGuid(@classID, @IID_NULL) THEN RETURN NULL
   IF GetActiveObject(@classID, NULL, @pUnk) <> S_OK THEN
      CoCreateInstance(@classID, NULL, CLSCTX_INPROC_SERVER, @IID_IUnknown, @pUnk)
   END IF
   RETURN pUnk
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION AfxAnyCom OVERLOAD (BYREF classID AS CONST CLSID) AS IDispatch PTR
   DIM pUnk AS ANY PTR
   IF GetActiveObject(@classID, NULL, @pUnk) <> S_OK THEN
      CoCreateInstance(@classID, NULL, CLSCTX_INPROC_SERVER, @IID_IUnknown, @pUnk)
   END IF
   RETURN pUnk
END FUNCTION
' ========================================================================================


Hope it helps!

Frederick J. Harris

Thanks very much for the help Jose.  GetActiveObject() did it.  The thread at www.cplusplus.com involved was....

http://www.cplusplus.com/forum/windows/249570/

C++ coders for some reason don't fool with this OLE/COM stuff much it seems.  I referenced your help and site at about the 15th post in that thread. 

That's Freebasic code you posted above, right?

I'm still struggling to find time to code, but when my life settles down a bit I'll give Freebasic a look. 

Odd thing about that GetActiveObject() call that threw me for a bit was the last [out] pointer to pointer parameter was typed as returning an IUnknown pointer rather than a (**void) which I'm more used to.  But since dual interface objects still have IUnknown as the first three methods, followed by the last four of IDispatch, I figurred I could just go and use it instead of a QueryInterface cast to IDispatch.  Didn't work!  It crashed!  I had to do a QueryInterface to IDispatch, and the address returned was different from the IUnknown pointer returned by GetActiveObject.  I'm not  sure what's going on there.  It seems to me they should have the same address.  It must be a conceptual misunderstanding on my part, or its just the total wierdness of Excel.


José Roca

#3
I hate to write code that I can't test because there is always something missing. I will modify the posted functions to do a call to QueryInterface and return a true IDispatch pointer.

GetActiveObject is a wrapper function that uses the IMoniker and IRunningObjectTable interfaces. This is what it does:


/***********************************************************************
  *      GetActiveObject (OLEAUT32.35)
  *
  * Gets an object from the global item table.
  *
  * PARAMS
  *  rcid        [I] CLSID of the object.
  *  preserved   [I] Reserved. Set to NULL.
  *  ppunk       [O] Address to store object into.
  *
  * RETURNS
  *  Success: S_OK.
  *  Failure: HRESULT code.
  */
HRESULT WINAPI GetActiveObject(REFCLSID rcid,LPVOID preserved,LPUNKNOWN *ppunk)
{
     WCHAR           guidbuf[80];
     HRESULT         ret;
     LPRUNNINGOBJECTTABLE    runobtable;
     LPMONIKER       moniker;

     StringFromGUID2(rcid,guidbuf,39);
     ret = CreateItemMoniker(pdelimiter,guidbuf,&moniker);
     if (FAILED(ret))
         return ret;
     ret = GetRunningObjectTable(0,&runobtable);
     if (FAILED(ret)) {
         IMoniker_Release(moniker);
         return ret;
     }
     ret = IRunningObjectTable_GetObject(runobtable,moniker,ppunk);
     IRunningObjectTable_Release(runobtable);
     IMoniker_Release(moniker);
     return ret;
}


The pdelimiter parameter in the call to CreateIMoniker


static const WCHAR  _delimiter[] = {'!',0}; /* default delimiter apparently */
static const WCHAR  *pdelimiter = &_delimiter[0];


José Roca

> That's Freebasic code you posted above, right?

Yes, it is. I wanted to play with 64-bit code and started using this compiler. There were several problems, mainly the lack of native support for dynamic unicode strings, but I have written a framework that provides all that I missed from PowerBasic and much more.

Since you're a C programmer, you will be at home with FreeBasic, as it is a C compiler with a basic syntax, with many of the C nuisances like strict type checking (having to use casting frequently), all these C aliases (HANDLE, HWND, WPARAM, LPARAM, etc.), parameters passed by value by default, etc.

My framework is available in GitHub:

https://github.com/JoseRoca/WinFBX

Afx framework: https://github.com/JoseRoca/WinFBX/tree/master/Afx

Documentation: https://github.com/JoseRoca/WinFBX/tree/master/docs

The easiest way to get started is to install Paul Squire's WinFBE editor, that comes with everything, including my framework and the FreeBasic compilers and headers.


José Roca

I have modified the above posted functions to return an IDispatch pointer.


' ========================================================================================
' If the requested object is in an EXE (out-of-process server), such Office applications,
' and it is running and registered in the Running Object Table (ROT), AfxGetCom will
' return a pointer to its interface. AfxAnyCom will first try to use an existing, running
' application if available, or it will create a new instance if not.
' Be aware that AfxGetCom can fail under if Office is running but not registered in the ROT.
' When an Office application starts, it does not immediately register its running objects.
' This optimizes the application's startup process. Instead of registering at startup, an
' Office application registers its running objects in the ROT once it loses focus. Therefore,
' if you attempt to use GetObject or GetActiveObject to attach to a running instance of an
' Office application before the application has lost focus, you might receive an error.
' See: https://support.microsoft.com/en-us/help/238610/getobject-or-getactiveobject-cannot-find-a-running-office-application
' ========================================================================================
PRIVATE FUNCTION AfxGetCom OVERLOAD (BYREF wszProgID AS CONST WSTRING) AS IDispatch PTR
   DIM classID AS CLSID, pUnk AS IUnknown PTR, pDisp AS IDispatch PTR
   CLSIDFromProgID(wszProgID, @classID)
   IF IsEqualGuid(@classID, @IID_NULL) THEN RETURN NULL
   GetActiveObject(@classID, NULL, @pUnk)
   IF pUnk THEN
      pUnk->lpVtbl->QueryInterface(pUnk, @IID_IDispatch, @pDisp)
      pUnk->lpVtbl->Release(pUnk)
   END IF
   RETURN pDisp
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION AfxGetCom OVERLOAD (BYREF classID AS CONST CLSID) AS IDispatch PTR
   DIM pUnk AS IUnknown PTR, pDisp AS IDispatch PTR
   GetActiveObject(@classID, NULL, @pUnk)
   IF pUnk THEN
      pUnk->lpVtbl->QueryInterface(pUnk, @IID_IDispatch, @pDisp)
      pUnk->lpVtbl->Release(pUnk)
   END IF
   RETURN pDisp
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION AfxAnyCom OVERLOAD (BYREF wszProgID AS CONST WSTRING) AS IDispatch PTR
   DIM classID AS CLSID, pUnk AS IUnknown PTR, pDisp AS IDispatch PTR
   CLSIDFromProgID(wszProgID, @classID)
   IF IsEqualGuid(@classID, @IID_NULL) THEN RETURN NULL
   ' // Check if there is an instance already running
   IF GetActiveObject(@classID, NULL, @pUnk) = S_OK THEN
      pUnk->lpVtbl->QueryInterface(pUnk, @IID_IDispatch, @pDisp)
      pUnk->lpVtbl->Release(pUnk)
      RETURN pDisp
   END IF
   ' // Otherwise, create a new instance
   CoCreateInstance(@classID, NULL, CLSCTX_INPROC_SERVER, @IID_IUnknown, @pUnk)
   IF pUnk THEN
      pUnk->lpVtbl->QueryInterface(pUnk, @IID_IDispatch, @pDisp)
      pUnk->lpVtbl->Release(pUnk)
   END IF
   RETURN pDisp
END FUNCTION
' ========================================================================================
' ========================================================================================
PRIVATE FUNCTION AfxAnyCom OVERLOAD (BYREF classID AS CONST CLSID) AS IDispatch PTR
   DIM pUnk AS IUnknown PTR, pDisp AS IDispatch PTR
   ' // Check if there is an instance already running
   IF GetActiveObject(@classID, NULL, @pUnk) = S_OK THEN
      pUnk->lpVtbl->QueryInterface(pUnk, @IID_IDispatch, @pDisp)
      pUnk->lpVtbl->Release(pUnk)
      RETURN pDisp
   END IF
   ' // Otherwise, create a new instance
   CoCreateInstance(@classID, NULL, CLSCTX_INPROC_SERVER, @IID_IUnknown, @pUnk)
   IF pUnk THEN
      pUnk->lpVtbl->QueryInterface(pUnk, @IID_IDispatch, @pDisp)
      pUnk->lpVtbl->Release(pUnk)
   END IF
   RETURN pDisp
END FUNCTION
' ========================================================================================


Frederick J. Harris

About FreeBASIC...

If I install Pauls WinFBE I don't need to install the compilers first?  From the GitHub description of WinFBE....

The WinFBE Suite package (attached to each release as a compressed RAR file), is an all-in-one package including the latest WinFBE editor, FreeBASIC compiler (32 and 64 bit compilers), Jose Roca's WinFBX library, several Help files and all necessary Include files.

But furthyer on down in Requirements it states...

Requirements:

·   Windows Vista or later operating system. Windows XP is not supported because many newer API's are used in the source code.
·   FreeBASIC Compiler http://www.freebasic.net/

I downloaded FreeBASIC-1.06.0-win32.exe and that is about 11 megabytes, and Paul's WinFBE-master.zip and that's about 18 megabytes.  So the latter includes the compilers?

I'm using an ancient version of Office and Excel myself - Office 2000.  It still works fine.  Where I worked before I retired they had front row seats on the Microsoft Upgrade Bandwagon, so I had access to the latest of everything.  But on my personal computers which I used exclusively for development, I just used the old version of Office.  There weren't very many issues with that - testament to the power of COM.  So what I'm saying is that its possible - though in my mind unlikely, that the IUnknown** returned by GetActiveObject() might work in IDispatch calls.  It absolutely didn't though in the version I have, like I said.

Just as soon as I get a better internet connection I'm hoping to get the latest versions of Excel and Word.  It isn't that I need them for myself, but I still hope to support all the work I did over the years for the Pennsylvania Bureau of Forestry.  All my apps interacted heavily with Word and Excel.

Right now I only have a cellular internet connection and am limited to 15 gigs per month.  And I'm happy to have it, as for the first couple months I lived up here I didn't have any internet at all.  I built a log cabin way up in the mountains in a remote area and its amazing I have a cellular connection at all.  Most remote areas in the US don't have cellular access.  But I just about have to use an old Windows 7 machine because as soon as I connect a Windows 10 machine to the internet it blows through the monthly allotment like almost immediately what with all the connections it makes to the internet as it scours my personal information for anything saleable and sends it all over the world.

Its a winter wonderland up here now (I'm at 7500 feet elevation) and the ground is frozen solid as a rock.  As soon as it thaws I need to dig a 300 foot trench up to the dirt road above me and string cable down to my cabin.  I need to put an antennae up there where I'll have line of sight to a tower from which I can get a better internet connection.  That's why I'm saying I have a hard time finding time or coding right now - too much to do just staying alive!  But its good for my soul!

José Roca

If you install the WinFBE suite, you don't need to install anything else. It comes with the latest compilers and tools. Yesterday, version 1.06 of the compilers was released, so I guess that Paul will prepare a new version of the WinFBE suite in a few days, after the needed checks.

> Its a winter wonderland up here now (I'm at 7500 feet elevation) and the ground is frozen solid as a rock.

Just the opposite that me. I live at sea level and Spring will come next Friday (one month before that it used to be, courtesy of climate change), with temperatures of 19º C (66.2º F).

> But its good for my soul!

This is the more important. I still do some programming because I like it and it is good for my brain activity, but mainly as a hobbie.

Paul Squires

Quote from: José Roca on February 19, 2019, 04:52:32 PM
This is the more important. I still do some programming because I like it and it is good for my brain activity, but mainly as a hobbie.

That is exactly my situation as well. I would hate to never program again because it keeps my mind so very active. It is nice building things and feeling that sense of satisfaction from completing a project that others use.
Paul Squires
FireFly Visual Designer SQLitening Database System JellyFish Pro Editor
http://www.planetsquires.com