Explorer doesn't release IDataObject when doing a drag/drop - c++

I'm implementing drag-and-drop in my application. I'm having a problem with Windows Explorer not releasing my IDataObject after a drag-and-drop operation. To isolate the problem, I've implemented a very simple drag-and-drop source that should compile in most any Win32 compiler. The data object contains no data; as you can see everything is very simple. The data object contains tracing that can be viewed with DebugView to indicate when it is created and when it is destroyed.
To reproduce:
Start the drag by holding down the mouse button.
Drag-and-drop the object into an open Windows Explorer window.
Observe the output in DebugView; sample output:
[4964] gdo ctor
[4964] gds ctor
[4964] gds dtor
This output indicates that the data source was destructed, but somebody is still holding a reference to my IDataObject!
Start dragging a file in the same Explorer window. Even though I'm not at all interacting with my project at this time, it causes gdo dtor to be printed - indicating that the final reference to the IDataObject was released.
I'm running Windows 7 64-bit. It's interesting to note that some Explorer windows do release the data object right away after the drop; others don't seem to do that until you start dragging a different object into the Explorer window as indicated in step #4. It also seems to depend on where in the window I drop the object - some places cause the object to be immediately released and others don't. It's very strange!
My questions are these:
Is this normal for Explorer to do this? Why is this? Or do I have a bug in my code? It's very disconcerting to see COM objects still referenced when my application terminates! Also it means that the resources held by IDataObject are tied up until Explorer decides to release the object.
If this is indeed normal behavior (and even if it isn't, I guess I should cope with ill-behaved drop targets), then what is the best practice for cleaning up this unreleased COM object when the application terminates? I'm writing in C++ Builder and using ATL, and when the user tries to close the application, they get a very unfriendly "There are still active COM objects in this application, blah blah blah. Are you sure you want to close this application?" - presumably generated by ATL which is noticing there are unreleased COM objects - generally a bad thing on application shutdown.
Here's some sample code. It implements an IDataObject that provides no data, and a very basic IDropSource. Of course, the real application provides data via IDataObject but I found this basic implementation is enough to reproduce the issue. I wrote it in C++ Builder but 90% of it is portable Win32 code. Just add a label or other object to the GUI toolkit of choice (MFC, WinForms with C++/CLI, Qt, wxWidgets, straight Win32, whatever) and tie the appropriate code to the MouseDown event.
I can't think of any bugs in this code that would cause this behavior, but that doesn't mean I didn't miss any!
class GenericDataObject : public IDataObject
{
public:
// basic IUnknown implementation
ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); }
ULONG __stdcall Release() {
ULONG nRefCount = InterlockedDecrement(&refcount);
if (nRefCount == 0) delete this;
return nRefCount;
}
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) {
if (!ppvObject) return E_POINTER;
if (riid == IID_IUnknown) {
*ppvObject = static_cast<IUnknown*>(this);
AddRef();
return S_OK;
} else if (riid == IID_IDataObject) {
*ppvObject = static_cast<IDataObject*>(this);
AddRef();
return S_OK;
} else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
}
// IDataObject members
STDMETHODIMP GetData (FORMATETC *pformatetcIn, STGMEDIUM *pmedium) { return DV_E_FORMATETC; }
STDMETHODIMP GetDataHere (FORMATETC *pformatetc, STGMEDIUM *pmedium) { return E_NOTIMPL; }
STDMETHODIMP QueryGetData (FORMATETC *pformatetc) { return DV_E_FORMATETC; }
STDMETHODIMP GetCanonicalFormatEtc (FORMATETC *pformatectIn, FORMATETC *pformatetcOut) { return DV_E_FORMATETC; }
STDMETHODIMP SetData (FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease) { return E_NOTIMPL; }
STDMETHODIMP EnumFormatEtc (DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) { return E_NOTIMPL; }
STDMETHODIMP DAdvise (FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) { return OLE_E_ADVISENOTSUPPORTED; }
STDMETHODIMP DUnadvise (DWORD dwConnection) { return OLE_E_ADVISENOTSUPPORTED; }
STDMETHODIMP EnumDAdvise (IEnumSTATDATA **ppenumAdvise) { return OLE_E_ADVISENOTSUPPORTED; }
public:
GenericDataObject() : refcount(1) {OutputDebugString("gdo ctor");}
~GenericDataObject() {OutputDebugString("gdo dtor");}
private:
LONG refcount;
};
class GenericDropSource : public IDropSource
{
public:
// basic IUnknown implementation
ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); }
ULONG __stdcall Release() {
ULONG nRefCount = InterlockedDecrement(&refcount);
if (nRefCount == 0) delete this;
return nRefCount;
}
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) {
if (!ppvObject) return E_POINTER;
if (riid == IID_IUnknown) {
*ppvObject = static_cast<IUnknown*>(this);
AddRef();
return S_OK;
} else if (riid == IID_IDropSource) {
*ppvObject = static_cast<IDropSource*>(this);
AddRef();
return S_OK;
} else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
}
// IDropSource members
STDMETHODIMP QueryContinueDrag (BOOL fEscapePressed, DWORD grfKeyState) {
if (fEscapePressed) {
return DRAGDROP_S_CANCEL;
}
if (!(grfKeyState & (MK_LBUTTON | MK_RBUTTON))) {
return DRAGDROP_S_DROP;
}
return S_OK;
}
STDMETHODIMP GiveFeedback (DWORD dwEffect) { return DRAGDROP_S_USEDEFAULTCURSORS; }
public:
GenericDropSource() : refcount(1) {OutputDebugString("gds ctor");}
~GenericDropSource() {OutputDebugString("gds dtor");}
private:
LONG refcount;
};
// This is the C++ Builder-specific part; all I did was add a label to the default form
// and tie this event to it.
void __fastcall TForm1::Label1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
OleInitialize(NULL);
GenericDataObject *o = new GenericDataObject;
GenericDropSource *s = new GenericDropSource;
DWORD effect = 0;
DoDragDrop(o, s, DROPEFFECT_COPY, &effect);
o->Release();
s->Release();
}

Related

Copying a virtual file to the clipboard

By slightly modifying the code from the Raymond Chen's article, I got a class for copying a vector to the clipboard as a file:
typedef std::wstring String;
class CFileDataObject : public IDataObject
{
public:
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
IUnknown *punk = NULL;
if (riid == IID_IUnknown)
punk = static_cast<IUnknown*>(this);
else if (riid == IID_IDataObject)
{
punk = static_cast<IDataObject*>(this);
}
*ppv = punk;
if (punk)
{
punk->AddRef();
return S_OK;
}
else return E_NOINTERFACE;
};
STDMETHODIMP_(ULONG) AddRef()
{
return ++m_cRef;
};
STDMETHODIMP_(ULONG) Release()
{
ULONG cRef = --m_cRef;
if (cRef == 0)
{
delete this;
}
return cRef;
}
// IDataObject
STDMETHODIMP GetData(FORMATETC *pfe, STGMEDIUM *pmed)
{
ZeroMemory(pmed, sizeof(*pmed));
switch (GetDataIndex(pfe))
{
case DATA_FILEGROUPDESCRIPTOR:
{
FILEGROUPDESCRIPTOR fgd;
ZeroMemory(&fgd, sizeof(fgd));
fgd.cItems = 1;
fgd.fgd[0].dwFlags = FD_FILESIZE | FD_WRITESTIME;
fgd.fgd[0].nFileSizeLow = DataSize & 0xFFFFFFFFULL;
fgd.fgd[0].nFileSizeHigh = (DataSize & 0xFFFFFFFF00000000ULL) >> 32;
fgd.fgd[0].ftLastWriteTime.dwLowDateTime = 0x256d4000;
fgd.fgd[0].ftLastWriteTime.dwHighDateTime = 0x01bf53eb;
StringCchCopy(fgd.fgd[0].cFileName,ARRAYSIZE(fgd.fgd[0].cFileName),
m_FileName.c_str());
pmed->tymed = TYMED_HGLOBAL;
return CreateHGlobalFromBlob(&fgd, sizeof(fgd),GMEM_MOVEABLE, &pmed->hGlobal);
}
case DATA_FILECONTENTS:
pmed->tymed = TYMED_HGLOBAL;
pmed->hGlobal = hData;
return S_OK;
}
return DV_E_FORMATETC;
};
STDMETHODIMP GetDataHere(FORMATETC *pfe, STGMEDIUM *pmed)
{
return E_NOTIMPL;
};
STDMETHODIMP QueryGetData(FORMATETC *pfe)
{
return GetDataIndex(pfe) == DATA_INVALID ? S_FALSE : S_OK;
};
STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *pfeIn,FORMATETC *pfeOut)
{
*pfeOut = *pfeIn;
pfeOut->ptd = NULL;
return DATA_S_SAMEFORMATETC;
};
STDMETHODIMP SetData(FORMATETC *pfe, STGMEDIUM *pmed,BOOL fRelease)
{
return E_NOTIMPL;
};
STDMETHODIMP EnumFormatEtc(DWORD dwDirection,LPENUMFORMATETC *ppefe)
{
if (dwDirection == DATADIR_GET)
return SHCreateStdEnumFmtEtc(ARRAYSIZE(m_rgfe), m_rgfe, ppefe);
*ppefe = NULL;
return E_NOTIMPL;
}
STDMETHODIMP DAdvise(FORMATETC *pfe, DWORD grfAdv,IAdviseSink *pAdvSink, DWORD *pdwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
};
STDMETHODIMP DUnadvise(DWORD dwConnection)
{
return OLE_E_ADVISENOTSUPPORTED;
};
STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *ppefe)
{
return OLE_E_ADVISENOTSUPPORTED;
};
CFileDataObject(const String& FileName, const std::vector<uint8_t>& Data)
: m_cRef(1),m_FileName(FileName)
{
SetFORMATETC(&m_rgfe[DATA_FILEGROUPDESCRIPTOR],
RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR));
SetFORMATETC(&m_rgfe[DATA_FILECONTENTS],
RegisterClipboardFormat(CFSTR_FILECONTENTS),TYMED_HGLOBAL, 0);
HRESULT hres = CreateHGlobalFromBlob(Data.data(), Data.size(), GMEM_MOVEABLE, &hData);
if (!SUCCEEDED(hres)) { /* Some error handling goes here */ };
DataSize = Data.size();
}
private:
enum {
DATA_FILEGROUPDESCRIPTOR,
DATA_FILECONTENTS,
DATA_NUM,
DATA_INVALID = -1,
};
int GetDataIndex(const FORMATETC *pfe)
{
for (int i = 0; i < ARRAYSIZE(m_rgfe); i++)
{
if (pfe->cfFormat == m_rgfe[i].cfFormat && (pfe->tymed & m_rgfe[i].tymed) &&
pfe->dwAspect == m_rgfe[i].dwAspect && pfe->lindex == m_rgfe[i].lindex)
return i;
}
return DATA_INVALID;
}
private:
ULONG m_cRef;
FORMATETC m_rgfe[DATA_NUM];
String m_FileName;
HGLOBAL hData = 0;
size_t DataSize = 0;
};
Usage:
std::vector<uint8_t> FileData;
// Filling the vector with some data
IDataObject* fdo = new CFileDataObject(FileName, FileData);
if (fdo)
{
HRESULT hres = OleSetClipboard(fdo);
fdo->Release();
}
The class serves its purpose, but there is one detail: the file can only be pasted only once. All further attempts to paste the file fail. Is this normal or is there something wrong with the class?
Your GetData() is returning the original hData object for DATA_FILECONTENTS instead of a copy. Since the pmed->pUnkForRelease field is being returned as NULL, the caller of GetData() will free the returned HGLOBAL when done using it.
https://learn.microsoft.com/en-us/windows/win32/api/objidl/ns-objidl-ustgmedium-r1
pUnkForRelease
Pointer to an interface instance that allows the sending process to control the way the storage is released when the receiving process calls the ReleaseStgMedium function. If pUnkForRelease is NULL, ReleaseStgMedium uses default procedures to release the storage; otherwise, ReleaseStgMedium uses the specified IUnknown interface.
https://learn.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-releasestgmedium
The provider indicates that the receiver of the medium is responsible for freeing the medium by specifying NULL for the punkForRelease structure member. Then the receiver calls ReleaseStgMedium, which makes a call as described in the following table depending on the type of storage medium being freed.
Medium
ReleaseStgMedium Action
TYMED_HGLOBAL
Calls the GlobalFree function on the handle.
So, to keep that from happening, set pmed->pUnkForRelease to an AddRef'ed IUnknown that is responsible for freeing hData when noone is using it anymore. In your case, you can use the this pointer for your CFileDataObject object for that IUnknown interface.
When the original provider of the medium is responsible for freeing the medium, the provider calls ReleaseStgMedium, specifying the medium and the appropriate IUnknown pointer as the punkForRelease structure member. Depending on the type of storage medium being freed, one of the following actions is taken, followed by a call to the IUnknown::Release method on the specified IUnknown  pointer.
Medium
ReleaseStgMedium Action
TYMED_HGLOBAL
None.
Otherwise, another option is to implement DATA_FILECONTENTS as an IStream instead of an HGLOBAL, where the IStream accesses the original vector data. This option is even described in the Shell Clipboard Formats documentation:
CFSTR_FILECONTENTS
This format identifier is used with the CFSTR_FILEDESCRIPTOR format to transfer data as if it were a file, regardless of how it is actually stored. The data consists of an STGMEDIUM structure that represents the contents of one file. The file is normally represented as a stream object, which avoids having to place the contents of the file in memory. In that case, the tymed member of the STGMEDIUM structure is set to TYMED_ISTREAM, and the file is represented by an IStream interface. The file can also be a storage or global memory object (TYMED_ISTORAGE or TYMED_HGLOBAL). The associated CFSTR_FILEDESCRIPTOR format contains a FILEDESCRIPTOR structure for each file that specifies the file's name and attributes.

Watch for new explorer windows in Win32 API

I'm currently making a program to add tabs to the Windows file explorer using the win32 API since I'm not satisfied by any of the programs that currently do that (Clover, Groupy to name a few).
To do that I obviously need to get all explorer windows that are currently opened and to make the program watch for new windows being created.
The way I currently do it is by calling EnumWindows in my messages loop, and attaching to my program's main window any explorer window that isn't attached yet.
while(GetMessage(&uMsg, NULL, 0, 0) > 0)
{
TranslateMessage(&uMsg);
DispatchMessage(&uMsg);
EnumWindows((WNDENUMPROC)findExplorerWindows, (LPARAM)mainWindow);
}
This is obviously not optimal (new windows only get attached to my program when a message is sent to my program, and overall it slows everything down quite a bit especially when there's already a lot of opened windows) and I'd like to know if there is a way to watch the opened windows list to fire an event whenever a new window is created or anything of that sort.
Here is some sample Console code (uses COM) that uses the IShellWindows interface.
First it dumps the current explorer windows ("views"), then it hooks events raised by the companion interface DShellWindowsEvents
#include <atlbase.h>
#include <atlcom.h>
#include <shobjidl_core.h>
#include <shlobj_core.h>
#include <exdispid.h>
// a COM class that handles DShellWindowsEvents
class WindowsEvents : public IDispatch
{
// poor man's COM object... we don't care, we're basically a static thing here
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject)
{
if (IsEqualIID(riid, IID_IUnknown))
{
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
if (IsEqualIID(riid, IID_IDispatch))
{
*ppvObject = static_cast<IDispatch*>(this);
return S_OK;
}
*ppvObject = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) AddRef() { return 1; }
STDMETHODIMP_(ULONG) Release() { return 1; }
// this is what's called by the Shell (BTW, on the same UI thread)
// there are only two events "WindowRegistered" (opened) and "WindowRevoked" (closed)
STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
// first parameter is window's registration cookie
int cookie = V_I4(&pDispParams->rgvarg[0]);
if (dispIdMember == DISPID_WINDOWREGISTERED) // needs exdispid.h
{
wprintf(L"Window registered, cookie:%u\n", cookie);
}
else if (dispIdMember == DISPID_WINDOWREVOKED)
{
wprintf(L"Window revoked, cookie:%u\n", cookie);
}
// currently the cookie is not super useful, it's supposed to be usable by FindWindowSW
CComVariant empty;
long hwnd;
CComPtr<IDispatch> window;
HRESULT hr = Windows->FindWindowSW(&pDispParams->rgvarg[0], &empty, 0, &hwnd, SWFO_COOKIEPASSED, &window);
// always returns S_FALSE... it does not seem to work
// so, you'll have to ask for all windows again...
return S_OK;
}
// the rest is left not implemented
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) { return E_NOTIMPL; }
STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { return E_NOTIMPL; }
STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { return E_NOTIMPL; }
public:
CComPtr<IShellWindows> Windows;
};
int main()
{
CoInitialize(NULL);
{
CComPtr<IShellWindows> windows;
if (SUCCEEDED(windows.CoCreateInstance(CLSID_ShellWindows)))
{
// dump current windows
long count = 0;
windows->get_Count(&count);
for (long i = 0; i < count; i++)
{
CComPtr<IDispatch> window;
if (SUCCEEDED(windows->Item(CComVariant(i), &window)))
{
// get the window handle
CComPtr<IWebBrowserApp> app;
if (SUCCEEDED(window->QueryInterface(&app)))
{
HWND hwnd = NULL;
app->get_HWND((SHANDLE_PTR*)&hwnd);
wprintf(L"HWND[%i]:%p\n", i, hwnd);
}
}
}
// now wait for windows to open
// get the DShellWindowsEvents dispinterface for events
CComPtr<IConnectionPointContainer> cpc;
if (SUCCEEDED(windows.QueryInterface(&cpc)))
{
// https://learn.microsoft.com/en-us/windows/win32/shell/dshellwindowsevents
CComPtr<IConnectionPoint> cp;
if (SUCCEEDED(cpc->FindConnectionPoint(DIID_DShellWindowsEvents, &cp)))
{
WindowsEvents events;
events.Windows = windows;
DWORD cookie = 0;
// hook events
if (SUCCEEDED(cp->Advise(&events, &cookie)))
{
// pump COM messages to make sure events arrive
do
{
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} while (TRUE);
// normally we should get here if someone sends a PostQuitMessage(0) to the current thread
// but this is a console sample...
// unhook events
cp->Unadvise(cookie);
}
}
}
}
}
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.

Waiting for URLDownloadToFile() to end

I want to make the program that downloads page from internet and makes some parsing on it. Second part is easy, problem is first.
I want to use URLDownloadToFile() function. But by default it doesn't wait for completing the download. MSDN says that the last param is sort of callback function, but I can't find any info of how to use it (when it is called and what it must do, even what type of function it is). Can someone explain me what is that last parameter and how use it (in C++) to make my app wait?
You have to create a class that implements the IBindStatusCallback interface. You can return E_NOTIMPL for most of the methods. Use OnProgress() to show progress. Here's a sample program that gets this done:
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#pragma comment(lib, "urlmon.lib")
using namespace std;
class DownloadProgress : public IBindStatusCallback {
public:
HRESULT __stdcall QueryInterface(const IID &,void **) {
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef(void) {
return 1;
}
ULONG STDMETHODCALLTYPE Release(void) {
return 1;
}
HRESULT STDMETHODCALLTYPE OnStartBinding(DWORD dwReserved, IBinding *pib) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE GetPriority(LONG *pnPriority) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE OnLowResource(DWORD reserved) {
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE OnStopBinding(HRESULT hresult, LPCWSTR szError) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE GetBindInfo(DWORD *grfBINDF, BINDINFO *pbindinfo) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pformatetc, STGMEDIUM *pstgmed) {
return E_NOTIMPL;
}
virtual HRESULT STDMETHODCALLTYPE OnObjectAvailable(REFIID riid, IUnknown *punk) {
return E_NOTIMPL;
}
virtual HRESULT __stdcall OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
{
wcout << ulProgress << L" of " << ulProgressMax;
if (szStatusText) wcout << " " << szStatusText;
wcout << endl;
return S_OK;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
DownloadProgress progress;
HRESULT hr = URLDownloadToFile(0,
L"http://sstatic.net/stackoverflow/img/sprites.png?v=3",
L"c:/temp/test.png", 0,
static_cast<IBindStatusCallback*>(&progress));
return 0;
}
Output:
0 of 0 sstatic.net
0 of 0 64.34.119.12
0 of 0
0 of 0 image/x-png
3550 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
3550 of 16542 C:\Users\hpassant\AppData\Local\Microsoft\Windows\Temporary Inter
et Files\Content.IE5\NRPH4KHK\sprites[1].png
7330 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
8590 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
12370 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
13630 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
16542 of 16542 http://sstatic.net/stackoverflow/img/sprites.png?v=3
Probably function immediatelly returns because of error.
URLDownloadToFile() is definitely syncronous function, if you set LPBINDSTATUSCALLBACK lpfnCB as NULL.
It is so "syncronous", what it will never end until its download completion, even if network connection fails and will block your thread. Killing thread with URLDownloadToFile() in progress by TerminateThread() function will cause resources leak and child calls to system dlls unfinished and after couple of times URLDownloadToFile() will refuse to work in context of current process.
The only way of reliable usage of URLDownloadToFile() without callback function is to fork separate process to it and kill that process if download stalls which is resource consuming.
URLDownloadToFile() download behaviours exactly the same way as IE, all IE proxy and network settings in user profile in which context this function is running will apply to this function also.
Also URLDownloadToFile() doesn't return immediately even with callback function. I consider to start URLDownloadToFile() in separate thread to safely control and abort network download.
There is simple example of callback function at https://github.com/choptastic/OldCode-Public/blob/master/URLDownloadToFile/URLDownloadToFile.cpp
To get safe download you should upgrade code at least with something like:
private:
int progress, filesize;
int AbortDownload;
public:
STDMETHOD(OnStartBinding)(
{
AbortDownload=0;
progress=0;
filesize=0;
return E_NOTIMPL; }
STDMETHOD(GetProgress)()
{ return progress; }
STDMETHOD(GetFileSize)()
{ return filesize; }
STDMETHOD(AbortDownl)()
{
AbortDownload=1;
return E_NOTIMPL; }
HRESULT DownloadStatus::OnProgress ( ULONG ulProgress, ULONG ulProgressMax,ULONG ulStatusCode, LPCWSTR wszStatusText )
{
progress=ulProgress;
filesize=ulProgressMax;
if (AbortDownload) return E_ABORT;
return S_OK;
}
so you can always abort download and check progress of download.
Even after download have been indicated as completed by S_OK returned by URLDownloadToFile() function you have to compare progress==filesize values, because URLDownloadToFile() can drop download with S_OK by mistake, for example if connection is made via network bridge of local network interfaces and bridge have fallen down for some reason.
Also you have to pay attention to DeleteUrlCacheEntry() function in pair with URLDownloadToFile() to free disk space after download, because all donloaded content is cached at disk by default according to IE caching policy.
Something as simple as the sample below should do the trick if you want to just download the file synchronously:
HRESULT hRez = URLDownloadToFile( NULL, _T(<url>), _T(<file>), 0, NULL );
if( hRez == 0 ){
// download ok
}
else{
// download failed
}
The documentation says the final parameter is a pointer to "the IBindStatusCallback interface of the caller." That means you, as the caller, need to provide a pointer to something that implements that interface. You could start with an implementation like this:
class CBindStatusCallback: public IBindStatusCallback
{
public:
STDMETHODIMP OnProgress(ULONG ulProgress, ULONG ulProgressMax,
ULONG ulStatusCode, LPCWSTR szStatusText)
{
// write your implementation here
}
// Override GetBindInfo and the other IBindStatusCallback methods
// by simply returning E_NOTIMPL, like this:
STDMETHODIMP GetBindInfo(DWORD* /*grfBINDF*/, BINDINFO* /*pbindinfo*/)
{
return E_NOTIMPL;
}
// Provide the usual implementations for these IUnknown methods.
STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
};
Create an instance of that, get its IBindStatusCallback interface pointer, and pass it to the API function. Something like this:
CBindStatusCallback* obj = new CBindStatusCallback;
IBindStatusCallback* callback = NULL;
HResult hr = obj->QueryInterface(IID_IBindStatusCallback, &callback);
obj = NULL;
hr = URLDownloadToFile(..., callback);
callback->Release();
callback = NULL;
You'll probably want to pass some sort of information to the object's constructor so that it knows how to notify the rest of your program that the download has terminated. Until your program receives that notification, you can just let it sit in the usual idle state in its message pump.
This might help.
Using Internet Explorer to download files for you

Flash ActiveX: How to Load Movie from memory or resource or stream?

I'm embedding a Flash ActiveX control in my C++ app (Flash.ocx, Flash10a.ocx, etc depending on your Flash version).
I can load an SWF file by calling LoadMovie(0, filename), but the file needs to physically reside in the disk. How to load the SWF from memory (or resource, or stream)? I'm sure there must be a way, because commercial solutions like f-in-box's feature Load flash movies from memory directly also uses Flash ActiveX control.
Appearantly I a going to need to supply details for a vote 'up'.. OK.
The internal flash buffer when first initiailized indicates if a movie is loaded or if the buffer hold properties in the buffer fisrt four bytes.
gUfU -- no movie loaded. properties to follow ....
fUfU -- .. [4bytes] size as integer.
then the UNCOMPRESSED movie or SWF as it were.
Write a IStream class.
fill with above.
save as szFile
TFlashStream *fStream = new TFlashStream(szFile);
// QI flash player
IPersistStreamInit * psStreamInit = 0;
shock->QueryInterface(::IID_IPersistStreamInit,
(LPVOID*)&psStreamInit);
if(psStreamInit)
{
psStreamInit->InitNew();
psStreamInit->Load(fStream);
psStreamInit->Release();
}
delete fStream;
Things to note :
When psStreamInit->Load(fStream);
will call IStream::Read looking for the header 'fUfU'.
if the return is correct psStreamInit then calls IStream::Read for the buffer size.
If everthing looks good so far, psStreamInit then reads in 1024 byte chunks until the read is exhausted.
However. for the header and file size.
STDMETHOD(Read)(void *pv, ULONG cb, ULONG *pcbRead)
pcbRead is invalid. you may want to use something like
IsBadReadPtr
--
Michael
To spare you some typing. It works for me in this way (just works not extensively tested):
void flash_load_memory(FlashWidget* w, void* data, ULONG size) {
FlashMemoryStream fStream = FlashMemoryStream(data, size);
IPersistStreamInit* psStreamInit = NULL;
w->mFlashInterface->QueryInterface(IID_IPersistStreamInit,(LPVOID*) &psStreamInit);
if(psStreamInit) {
psStreamInit->InitNew();
psStreamInit->Load((LPSTREAM)&fStream);
psStreamInit->Release();
}
}
class FlashMemoryStream : IStream {
public:
FlashMemoryStream(void* data,ULONG size) {
this->data = data;
this->size = size;
this->pos = 0;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppv) {
return E_NOTIMPL;
}
ULONG STDMETHODCALLTYPE AddRef() {
return E_NOTIMPL;
}
ULONG STDMETHODCALLTYPE Release() {
return E_NOTIMPL;
}
// IStream methods
STDMETHOD(Read) (void *pv,ULONG cb,ULONG *pcbRead) {
if(pos == 0 && cb == 4) {
memcpy(pv,"fUfU",4);
pos += 4;
return S_OK;
}
else if(pos == 4 && cb == 4) {
memcpy(pv,&size,4);
size += 8;
pos += 4;
return S_OK;
}
else {
if(pos + cb > size) cb = size - pos;
if(cb == 0) return S_FALSE;
memcpy(pv,(char*)data + pos - 8,cb);
if(pcbRead) (*pcbRead) = cb;
pos += cb;
return S_OK;
}
}
STDMETHOD(Write) (void const *pv,ULONG cb,ULONG *pcbWritten) { return E_NOTIMPL; }
STDMETHOD(Seek) (LARGE_INTEGER dlibMove,DWORD dwOrigin,ULARGE_INTEGER *plibNewPosition) { return E_NOTIMPL; }
STDMETHOD(SetSize) (ULARGE_INTEGER libNewSize) { return E_NOTIMPL; }
STDMETHOD(CopyTo) (IStream *pstm,ULARGE_INTEGER cb,ULARGE_INTEGER *pcbRead,ULARGE_INTEGER *pcbWritten) { return E_NOTIMPL; }
STDMETHOD(Commit) (DWORD grfCommitFlags) { return E_NOTIMPL; }
STDMETHOD(Revert) (void) { return E_NOTIMPL; }
STDMETHOD(LockRegion) (ULARGE_INTEGER libOffset,ULARGE_INTEGER cb,DWORD dwLockType) { return E_NOTIMPL; }
STDMETHOD(UnlockRegion) (ULARGE_INTEGER libOffset,ULARGE_INTEGER cb,DWORD dwLockType) { return E_NOTIMPL; }
STDMETHOD(Stat) (STATSTG *pstatstg,DWORD grfStatFlag) { return E_NOTIMPL; }
STDMETHOD(Clone) (IStream **ppstm) { return E_NOTIMPL; }
void* data;
ULONG size;
ULONG pos;
};
Being a flash guy I don't know any details on the C++ side, but if you made a request on the Flash side to a fake protocol, on the C side can you intercept that request and answer it with a data stream? I mean something like:
var mc:MovieClip = createEmptyMovieClip( "mc", 0 );
mc.loadMovie( "fakeprotocol://"+filename )
As long as the response looks (to Flash) like an HTTP stream, that ought to work. (Apologies in advance if the "intercept the request and return a data stream" is the part you're asking for help with.)
in addition....
Flash player promotes IPersistStorage.
flash.QI IPersistStorage
pStorage.load (mystorage_as_stream)
.. in theory.
Sorry for the above.. I intended to post
Flash player promotes IPersistStreamInit.
flash.QI IPersistStreamInit
pStream.load (my_stream)
Michael
This method don't work when you try to load a movie via the MovieclipLoader or LoadMovie from another movie!!!
The result is to replace the calling SWF file!! ...so this method work only for loading the base file.
Someone know a better method that work also with MovieClipLoader and LoadMovie?
Thanks.
MS VC ATL sample (have built with VS 2010 SP1 + Windows SDK 7.1 and tested on Windows 7 SP1 64-bit with Flash64_11_3_300_257.ocx / Flash32_11_3_300_257.ocx and on Windows XP SP3 32-bit with Flash32_11_3_300_257.ocx):
#pragma pack(push, 1)
typedef struct _FLASH_STREAM_HEADER
{
DWORD m_dwSignature;
DWORD m_dwDataSize;
} FLASH_STREAM_HEADER, *PFLASH_STREAM_HEADER;
#pragma pack(pop)
static HRESULT LoadFlashMovieFromResource(ATL::CComPtr<IShockwaveFlash>& spShockwaveFlash,
UINT nResourceID, LPCTSTR pszResourceType = RT_RCDATA)
{
HMODULE hModule = ATL::_AtlBaseModule.GetModuleInstance();
ATLASSUME(hModule != NULL);
//HINSTANCE hResourceInstance = ATL::AtlFindResourceInstance(nResourceID, pszResourceType);
//HRSRC hResource = ::FindResource(hResourceInstance, MAKEINTRESOURCE(nResourceID),
// pszResourceType);
HRSRC hResource = ::FindResource(hModule, MAKEINTRESOURCE(nResourceID), pszResourceType);
if (hResource == NULL)
return HRESULT_FROM_WIN32(::GetLastError());
DWORD dwResourceDataSize = ::SizeofResource(hModule, hResource);
if (dwResourceDataSize == 0)
return HRESULT_FROM_WIN32(::GetLastError());
HGLOBAL hResourceLoaded = ::LoadResource(hModule, hResource);
if (hResourceLoaded == NULL)
return HRESULT_FROM_WIN32(::GetLastError());
ATL::CComPtr<IStream> spStream;
HRESULT hResult = ::CreateStreamOnHGlobal(NULL, TRUE, &spStream);
if (FAILED(hResult))
return hResult;
FLASH_STREAM_HEADER fsh = {0x55665566, dwResourceDataSize};
ULARGE_INTEGER uli = {sizeof (fsh) + dwResourceDataSize};
hResult = spStream->SetSize(uli);
if (FAILED(hResult))
return hResult;
hResult = spStream->Write(&fsh, sizeof (fsh), NULL);
if (FAILED(hResult))
return hResult;
hResult = spStream->Write(reinterpret_cast<void*>(hResourceLoaded), dwResourceDataSize, NULL);
if (FAILED(hResult))
return hResult;
uli.QuadPart = 0;
hResult = spStream->Seek(*reinterpret_cast<PLARGE_INTEGER>(&uli), STREAM_SEEK_SET, NULL);
if (FAILED(hResult))
return hResult;
ATL::CComPtr<IPersistStreamInit> spPersistStreamInit;
hResult = spShockwaveFlash.QueryInterface(&spPersistStreamInit);
if (SUCCEEDED(hResult))
hResult = spPersistStreamInit->Load(spStream);
return hResult;
}