Returning COM object to JScript - c++

I am trying to pass a COM object from an ActiveX component to JScript. So far I have tried the following method of doing so:
STDMETHODIMP CHSNetwork::CreateIPPPacket(VARIANT ** ppv)
{
IIPPacket *iipp;
HRESULT hr = CoCreateInstance(CLSID_IPPacket, NULL, CLSCTX_ALL, IID_IIPPacket, (void **)&iipp);
if(SUCCEEDED(hr) && ppv)
{
CComVariant cvar((IUnknown *)iipp);
hr = cvar.Detach(*ppv);
}
return hr;
}
The following JScript causes the error Variable uses an Automation type not supported in JScript:
var hsn = new ActiveXObject("ZIENetwork.HSNetwork");
var ipp = hsn.CreateIPPPacket();
Any help would be greatly appreciated. Thanks.

Ah. I got it. I needed to pass a VARIANT * not a VARIANT **. I guess I still get confused by pointers-to-pointers as it relates to return values with COM.
Thus the correct code is:
STDMETHODIMP CHSNetwork::CreateIPPPacket(VARIANT * ppv)
{
// TODO: Add your implementation code here
IIPPacket *iipp;
HRESULT hr = CoCreateInstance(CLSID_IPPacket, NULL, CLSCTX_ALL, IID_IIPPacket, (void **)&iipp);
if(ppv)
{
CComVariant cvar((IUnknown *)iipp);
hr = cvar.Detach(ppv);
}
return hr;
}

Of course if you only ever pass back an interface you can pass back a IDispatch** or IUnknown**, you don't need the VARIANT, just a thought :)

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?

How to use property pages on unregistrated filter?

I use filter DS LAME for compressing audio. I loaded it from file "lame.ax" as follows:
// pPath - path to LAME "lame.ax"
HRESULT CMyFilter::CreateObjectFromPath(wchar_t *pPath, REFCLSID clsid, IUnknown **ppUnk)
{
// load the target DLL directly
if (!m_hLibFilter) m_hLibFilter = LoadLibrary(pPath);
if (!m_hLibFilter)
{
return HRESULT_FROM_WIN32(GetLastError());
}
// the entry point is an exported function
FN_DLLGETCLASSOBJECT fn = (FN_DLLGETCLASSOBJECT)GetProcAddress(m_hLibFilter, "DllGetClassObject");
if (fn == NULL)
{
return HRESULT_FROM_WIN32(GetLastError());
}
// create a class factory
IUnknownPtr pUnk;
HRESULT hr = fn(clsid, IID_IUnknown, (void**)(IUnknown**)&pUnk);
if (SUCCEEDED(hr))
{
IClassFactoryPtr pCF = pUnk;
if (pCF == NULL)
{
hr = E_NOINTERFACE;
}
else
{
// ask the class factory to create the object
hr = pCF->CreateInstance(NULL, IID_IUnknown, (void**)ppUnk);
}
}
return hr;
}
further
HRESULT hr = 0;
IUnknown *ppUnk = 0;
ULONG lRef = 0;
hr = CreateObjectFromPath(L"lame.ax", CLSID_LAMEDShowFilter, (IUnknown **)&ppUnk);
hr = ppUnk->QueryInterface(&m_pFilter);
lRef = ppUnk->Release();
It works perfectly. LAME encoding audio.
I want to show the filter settings - property page, but this code failed
bool ShowConfigWindow(HWND hParent)
{
ISpecifyPropertyPages *pProp;
HRESULT hr = m_pFilter->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pProp);
if (SUCCEEDED(hr))
{
// Get the filter's name and IUnknown pointer.
FILTER_INFO FilterInfo;
hr = m_pFilter->QueryFilterInfo(&FilterInfo);
IUnknown *pFilterUnk;
m_pFilter->QueryInterface(IID_IUnknown, (void **)&pFilterUnk);
// Show the page.
CAUUID caGUID;
pProp->GetPages(&caGUID);
pProp->Release();
HRESULT hr = OleCreatePropertyFrame(
hParent, // Parent window
0, 0, // Reserved
FilterInfo.achName, // Caption for the dialog box
1, // Number of objects (just the filter)
&pFilterUnk, // Array of object pointers.
caGUID.cElems, // Number of property pages
caGUID.pElems, // Array of property page CLSIDs
0, // Locale identifier
0, NULL // Reserved
);
// Clean up.
pFilterUnk->Release();
FilterInfo.pGraph->Release();
CoTaskMemFree(caGUID.pElems);
}
return true;
}
I find https://groups.google.com/forum/#!topic/microsoft.public.win32.programmer.directx.video/jknSbMenWeM
I should call CoRegisterClassObject for each property page, but how to do it?
Or what the right way?
OleCreatePropertyFrame takes property page class identifiers (CLSIDs) so you need to find a way to make them "visible" for the API.
Use of CoRegisterClassObject is one of the ways to achieve the mentioned task (perhaps the easiest, another method would be reg-free COM). You need to retrieve IClassFactory pointers for property page CLSIDs the same way as you do it in the first snippet. Then instead of calling IClassFactory::CreateInstance you use the interface pointers as arguments for CoRegisterClassObject API. Make sure you do it on the same thread as the following OleCreatePropertyFrame call. CoRevokeClassObject will clean things up afterwards.

c++ MS Word - OleAutomation

I am creating IDispatch like this:
IDispatch *m_pWApp = NULL;
CoInitialize(NULL);
CLSID clsid;
m_hr = CLSIDFromProgID(L"Word.Application", &clsid);
if(SUCCEEDED(m_hr))
{
m_hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&m_pWApp);
if(FAILED(m_hr)) m_pWApp=NULL;
}
I am using this to automate word like in example
But now I need to use method from interface IOleCommandTarget::Exec for hide menu bar for opening word. I know that with this method it is possible.
My question is, how can I get IOleCommandTarget to use it for calling Exec and of course to have possibility to use IDispatch line in example.
Your code is correct, just add:
LPOLECOMMANDTARGET lpOleCommandTarget = NULL;
lpDispatch->QueryInterface(IID_IOleCommandTarget, (void**)&lpOleCommandTarget);
or
IOleCommandTarget* target;
disp->QueryInterface (IID_IOleCommandTarget, (void **) &target);
Then **lpOleCommandTarget->Exec(...);**
For example:
lpOleCommandTarget->Exec(NULL, OLECMDID_PRINT, 0, NULL,NULL);

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;
}

How to load & call a VBScript function from within C++?

We have customers asking for VBScript functions to be called when particular actions occur within our product. I've been trying to research the Windows Scripting technologies but I'm having difficulty finding exactly what I need. Hopefully some of you can help.
Our product is a native C++ Windows product. The customer would specify a VBScript file, which we would load, and whenever a particular event occurs, we'd call a particular function in the VBScript and let it do its thing. We may provide objects within the script's namespace for it to access information about our product as well.
I found some information on MSDN about the IActiveScript interface, and some related things, but cannot find any examples of instantiating a COM object that implements this interface for VBScript.
I know that PowerShell would probably be a better option for this these days, but our customers are stuck in a lot of legacy systems and VBScript is what they know.
Any help you can provide (links or otherwise) would be appreciated!
I've put together a "Hello World" IActiveScript C++ ATL console application that:
Define CSimpleScriptSite class
Implement IActiveScriptSite interface (mandatory)
Implement IActiveScriptSiteWindow interface (optional)
Minimum implementation with most functions implemented with a dummy stub
Has no error handling. Consult MSDN IActiveScriptError.
Use CoCreateInstance a new IActiveSite object
Create instances of both VBScript and JScript
Link the IActiveSite to IActiveScriptSite using IActiveSite::SetScriptSite
Call QueryInterface to get an IActiveScriptParse interface
Use IActiveScriptParse to execute VBScript or JScript code
The sample:
Evaluates an expression in JScript
Evaluates an expression in VBScript
Runs a command in VBScript
Code:
#include "stdafx.h"
#include <atlbase.h>
#include <activscp.h>
class CSimpleScriptSite :
public IActiveScriptSite,
public IActiveScriptSiteWindow
{
public:
CSimpleScriptSite() : m_cRefCount(1), m_hWnd(NULL) { }
// IUnknown
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject);
// IActiveScriptSite
STDMETHOD(GetLCID)(LCID *plcid){ *plcid = 0; return S_OK; }
STDMETHOD(GetItemInfo)(LPCOLESTR pstrName, DWORD dwReturnMask, IUnknown **ppiunkItem, ITypeInfo **ppti) { return TYPE_E_ELEMENTNOTFOUND; }
STDMETHOD(GetDocVersionString)(BSTR *pbstrVersion) { *pbstrVersion = SysAllocString(L"1.0"); return S_OK; }
STDMETHOD(OnScriptTerminate)(const VARIANT *pvarResult, const EXCEPINFO *pexcepinfo) { return S_OK; }
STDMETHOD(OnStateChange)(SCRIPTSTATE ssScriptState) { return S_OK; }
STDMETHOD(OnScriptError)(IActiveScriptError *pIActiveScriptError) { return S_OK; }
STDMETHOD(OnEnterScript)(void) { return S_OK; }
STDMETHOD(OnLeaveScript)(void) { return S_OK; }
// IActiveScriptSiteWindow
STDMETHOD(GetWindow)(HWND *phWnd) { *phWnd = m_hWnd; return S_OK; }
STDMETHOD(EnableModeless)(BOOL fEnable) { return S_OK; }
// Miscellaneous
HRESULT SetWindow(HWND hWnd) { m_hWnd = hWnd; return S_OK; }
public:
LONG m_cRefCount;
HWND m_hWnd;
};
STDMETHODIMP_(ULONG) CSimpleScriptSite::AddRef()
{
return InterlockedIncrement(&m_cRefCount);
}
STDMETHODIMP_(ULONG) CSimpleScriptSite::Release()
{
if (!InterlockedDecrement(&m_cRefCount))
{
delete this;
return 0;
}
return m_cRefCount;
}
STDMETHODIMP CSimpleScriptSite::QueryInterface(REFIID riid, void **ppvObject)
{
if (riid == IID_IUnknown || riid == IID_IActiveScriptSiteWindow)
{
*ppvObject = (IActiveScriptSiteWindow *) this;
AddRef();
return NOERROR;
}
if (riid == IID_IActiveScriptSite)
{
*ppvObject = (IActiveScriptSite *) this;
AddRef();
return NOERROR;
}
return E_NOINTERFACE;
}
int _tmain(int argc, _TCHAR* argv[])
{
HRESULT hr = S_OK;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Initialize
CSimpleScriptSite* pScriptSite = new CSimpleScriptSite();
CComPtr<IActiveScript> spJScript;
CComPtr<IActiveScriptParse> spJScriptParse;
hr = spJScript.CoCreateInstance(OLESTR("JScript"));
hr = spJScript->SetScriptSite(pScriptSite);
hr = spJScript->QueryInterface(&spJScriptParse);
hr = spJScriptParse->InitNew();
CComPtr<IActiveScript> spVBScript;
CComPtr<IActiveScriptParse> spVBScriptParse;
hr = spVBScript.CoCreateInstance(OLESTR("VBScript"));
hr = spVBScript->SetScriptSite(pScriptSite);
hr = spVBScript->QueryInterface(&spVBScriptParse);
hr = spVBScriptParse->InitNew();
// Run some scripts
CComVariant result;
EXCEPINFO ei = { };
hr = spJScriptParse->ParseScriptText(OLESTR("(new Date()).getTime()"), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
hr = spVBScriptParse->ParseScriptText(OLESTR("Now"), NULL, NULL, NULL, 0, 0, SCRIPTTEXT_ISEXPRESSION, &result, &ei);
hr = spVBScriptParse->ParseScriptText(OLESTR("MsgBox \"Hello World! The current time is: \" & Now"), NULL, NULL, NULL, 0, 0, 0, &result, &ei);
// Cleanup
spVBScriptParse = NULL;
spVBScript = NULL;
spJScriptParse = NULL;
spJScript = NULL;
pScriptSite->Release();
pScriptSite = NULL;
::CoUninitialize();
return 0;
}
A version of the above code can be found here:
https://github.com/stephenquan/RunScriptDemo
IActiveScript and related interfaces work very well. I use them in my product exactly the same way you have described. Some of out customers write their own VBScript and JScript scripts to analyze and update application data before it gets posted to a database.
You use CoCreateInstance() to instantiate IActiveScript, like you would any other COM object. You would then call its QueryInterface() method to obtain an IActiveScriptParse interface for loading snippets of scripting code, and then you update the IActiveScript's state to execute the code.
You can add custom objects to the script by implementing IDispatch-derived classes and then passing them to the engine using IActiveScript::AddNamedItem() and an IActiveScriptSite::GetItemInfo() callback.
There are examples of IActiveScript usage available on MSDN.