I am trying to build Connection Points in existing COM component. I derive the class from CCmdTarget and call MFC macros in class declaration and implementation as described in MSDN. Component is correctly build, linked, and run. However, when I debug my client application I see the following issue:
Client locates the correct component, finds the connection point container and appropriate connection point; however Advise method fails to establish connection.
It is assumed that this Advise calls the QueryInterface with IID__IAdHocPresenceEvents.
Do not understand why this calls the QueryInterface with IID_IMarshal.
Later this becomes reason for not getting event at ClientSink::OnAdHocPresenceQuery
So, please help me to resolve this issue or give a hint where else to look for the issue.
Thanks in advance,
Hovo
class ClientSink : public _IAdHocPresenceEvents
{
private:
DWORD m_dwRefCount;
public:
ClientSink();
virtual ~CClientSink();
STDMETHODIMP OnAdHocPresenceQuery(int Result)
{
CString strTemp ("OnAdHocPresenceQuery");
AfxMessageBox(strTemp);
return S_OK;
};
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject)
{
HRESULT hr = S_OK;
if (iid == IID_IUnknown)
{
m_dwRefCount++;
*ppvObject = (IUnknown *)this;
}
else if (iid == IID__IAdHocPresenceEvents)
{
m_dwRefCount++;
*ppvObject = (_IAdHocPresenceEvents *)this;
}
else
{
*ppvObject = NULL;
hr = E_NOINTERFACE;
}
return hr;
}
ULONG STDMETHODCALLTYPE AddRef()
{
m_dwRefCount++;
return m_dwRefCount;
}
ULONG STDMETHODCALLTYPE Release()
{
ULONG l;
l = m_dwRefCount--;
if ( 0 == m_dwRefCount)
{
delete this;
}
return l;
}
};
int SendRequest()
{
CLSID clientCLSID;
if (FAILED(CLSIDFromProgID(A2BSTR("SMC.SMCLink"), &clientCLSID)))
{
clientCLSID = SMC::CLSID_SMCLink;
}
LPUNKNOWN lpUnk;
SMC::ISMCLink* m_pSMCLink = NULL;
if (m_pSMCLink == NULL)
{
if (GetActiveObject(clientCLSID, NULL, &lpUnk) == NOERROR)
{
hr = lpUnk->QueryInterface(SMC::IID_ISMCLink, (LPVOID*)&m_pSMCLink);
lpUnk->Release();
}
}
if(hr != S_OK)
{
return -1;
}
DWORD dwAdvise = 0; //variable,shown here for completeness
IConnectionPoint *pCntPoint = NULL;
IConnectionPointContainer *pConnPtContainer = NULL;
//check if this interface supports connectible objects
hr = m_pSMCLink->QueryInterface(IID_IConnectionPointContainer,(void **)&pConnPtContainer);
if ( !SUCCEEDED(hr) )
{
return hr;
}
// find the specific connection point
hr = pConnPtContainer->FindConnectionPoint(IID__IAdHocPresenceEvents, &pCntPoint);
if ( !SUCCEEDED(hr) )
{
return hr;
}
//we are done with the connection point container interface
pConnPtContainer->Release();
IUnknown *pSinkUnk = NULL;
CSink *pSink = NULL;
pSink = new CSink;
if ( NULL == pSink )
{
return E_FAIL;
}
//Get the pointer to CSink's IUnknown pointer
hr = pSink->QueryInterface (IID_IUnknown,(void **)&pSinkUnk);
if ( !SUCCEEDED(hr) )
{
return hr;
}
// It is assumed that this should call the QueryInterface with IID__IAdHocPresenceEvents
// Do not understand why this calls the QueryInterface with IID_IMarshal
// Later this becomes reason for not getting event at ClientSink::OnAdHocPresenceQuery
hr = pCntPoint->Advise(pSinkUnk, &dwAdvise);
if ( !SUCCEEDED(hr) )
{
return hr;
}
hr = m_pSMCLink->RequestService(BSTR("hov#moco"));
pCntPoint->Unadvise(dwAdvise); //disconnect from server
pCntPoint->Release();
return hr;
}
[
helpstring("Interface to control My Product"),
uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
dual,
oleautomation
]
interface ISMCLink : IDispatch
{
// ...
HRESULT RequestService([in] BSTR user);
// ...
};
// Primary dispatch interface for My Component
[ uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) ]
dispinterface IDispSMCLink
{
interface ISMCLink;
};
[
helpstring("ISMCLink2, Interface to access My Product"),
uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
dual,
oleautomation
]
interface ISMCLink2 : ISMCLink
{
//..
};
[ uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx), version(1.0) ]
library SMC
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
helpstring("_IAdHocPresenceEvents Interface")
]
interface _IAdHocPresenceEvents : IUnknown
{
[id(1), helpstring("method OnAdHocPresenceQuery")] HRESULT OnAdHocPresenceQuery(int Result);
};
#include "ISMCLink.idl"
[ uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) ]
coclass SMCLink
{
dispinterface IDispSMCLink;
[default] interface ISMCLink;
interface ISMCLink2;
[default, source] interface _IAdHocPresenceEvents;
};
};
class SMCLink : public CCmdTarget
{
protected:
DECLARE_MESSAGE_MAP()
DECLARE_DISPATCH_MAP()
DECLARE_INTERFACE_MAP()
// Connection point for ISMCLink interface
BEGIN_CONNECTION_PART(SMCLink, AdHocPresenceEvents)
CONNECTION_IID(IID__IAdHocPresenceEvents)
END_CONNECTION_PART(AdHocPresenceEvents)
DECLARE_CONNECTION_MAP()
public:
SMCLink(void);
virtual ~SMCLink(void);
// to be OLE creatable, it must be DYNCREATE and OLECREATE
DECLARE_DYNCREATE(SMCLink)
DECLARE_OLECREATE(SMCLink)
// Generated OLE dispatch map functions
//{{AFX_DISPATCH(SMCLink)
afx_msg HRESULT RequestAdHocPresence(BSTR sipAddr);
//}}AFX_DISPATCH
BEGIN_DUAL_INTERFACE_PART(DualSMCLink, ISMCLink)
//..
STDMETHOD(RequestService)(THIS_ BSTR user);
END_DUAL_INTERFACE_PART(DualSMCLink)
};
//..
BEGIN_MESSAGE_MAP(SMCLink, CCmdTarget)
//{{AFX_MSG_MAP(CAutoProxy)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
IMPLEMENT_DYNCREATE(SMCLink, CCmdTarget)
BEGIN_DISPATCH_MAP(SMCLink, CCmdTarget)
//{{AFX_DISPATCH_MAP(SMCLink)
//..
DISP_FUNCTION(SMCLink, "RequestService", RequestService, VT_ERROR, VTS_BSTR)
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
BEGIN_INTERFACE_MAP(SMCLink, CCmdTarget)
INTERFACE_PART(SMCLink, IID_ISMCLink, DualSMCLink)
INTERFACE_PART(SMCLink, IID_ISMCLink2, DualSMCLink)
INTERFACE_PART(SMCLink, IID_IConnectionPointContainer, ConnPtContainer)
END_INTERFACE_MAP()
BEGIN_CONNECTION_MAP(SMCLink, CCmdTarget)
CONNECTION_PART(SMCLink, IID__IAdHocPresenceEvents, AdHocPresenceEvents)
END_CONNECTION_MAP()
SMCLink::SMCLink(void)
{
// enable this object for OLE automation
EnableAutomation();
// enable this object for connection points
EnableConnections();
}
//..
afx_msg HRESULT SMCLink::RequestService(BSTR sipAddr)
{
HRESULT hr = E_FAIL;
int status = 1;
const CPtrArray* pConnections = m_xAdHocPresenceEvents.GetConnections ();
ASSERT (pConnections != NULL);
int nConnections = pConnections->GetSize ();
if (nConnections) {
for (int i=0; i<nConnections; i++)
{
_IAdHocPresenceEvents* pInterface = (_IAdHocPresenceEvents*) (pConnections->GetAt (i));
ASSERT (pInterface != NULL);
// Outgoing!
hr = pInterface->OnAdHocPresenceQuery (status);
}
}
return hr;
}
// delegate standard IDispatch methods to MFC IDispatch implementation
DELEGATE_DUAL_INTERFACE(SMCLink, DualSMCLink)
STDMETHODIMP SMCLink::XDualSMCLink::RequestService(BSTR user)
{
METHOD_PROLOGUE(SMCLink, DualSMCLink)
return pThis->RequestService(user);
}
//..
IMarshal is queried because COM needs to marhsal the calls between processes. While you can obviously implement the interface yourself, there is no point in bothering with this. Instead, make sure your interface is OLE Automation compatible, and is on a type library and the type library is registered (you can check this with COM/OLE Viewer tool. COM will supply automatic proxy/stub pair for you and won't require that IMarshal is implemented.
Related
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.
Windows explorer has the ability to show a custom banner when doing a search and indexing is disabled:
I would like to display a similar banner in a custom namespace extension with a custom message and custom link handler. Is there an namespace extension interface I can implement that will allow me to provide this functionality? I have searched for various terms in the namespace extension documentation , but I can't figure out the correct terms to use.
That feature is not documented by Microsft. And i found how to show information bar from namespace extension.
Picture
First you need declare 3 undocumented interface
IInfobarHost
MIDL_INTERFACE("e38fe0f3-3db0-47ee-a314-25cf7f4bf521")
IInfoBarHost : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Inform(IInfoBarMessage* msg) = 0;
virtual HRESULT STDMETHODCALLTYPE CancelInform(GUID guid) = 0;
};
IInfobarMessage
MIDL_INTERFACE("819d1334-9d74-4254-9ac8-dc745ebc5386")
IInfoBarMessage : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetMessageID(GUID* guid,INT* intVal) = 0;
virtual HRESULT STDMETHODCALLTYPE GetMessageW(LPWSTR* message) = 0;
virtual HRESULT STDMETHODCALLTYPE CreateMenu(HMENU* pMwnu) = 0;
virtual HRESULT STDMETHODCALLTYPE HandleMenu(HWND hwnd,int intVal) = 0;
};
IBrowserProgressSessionProvider
MIDL_INTERFACE("18140CBD-AA23-4384-A38D-6A8D3E2BE505")
IBrowserProgressSessionProvider : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE BeginSession() = 0; //Unknown
virtual HRESULT STDMETHODCALLTYPE EndSession() = 0;//Unknown
virtual HRESULT STDMETHODCALLTYPE GetCurrentSession(PDWORD sessionId) = 0;
virtual HRESULT STDMETHODCALLTYPE ActivateSession() = 0;//Unknown
};
IID_IInfoBarHost : e38fe0f3-3db0-47ee-a314-25cf7f4bf521
IID_IBrowserProgressConnection : 20174539-B2C7-4EC7-970B-04201F9CDBAD
IID_IBrowserProgressAggregator : 5EA8EEC4-C34B-4DE0-9B56-0E15FD8C8F80
IID_IBrowserProgressSessionProvider : 18140CBD-AA23-4384-A38D-6A8D3E2BE505
2.Create one class and implement IInfoBarMessage.
GetMessageID(GUID* guid,INT* pIntVal)
Get Infobar guid. Generate own random guid for current infobar message.
HRESULT STDMETHODCALLTYPE CInfoBarMessageImpl::GetMessageID(GUID* guid, INT* intVal) {
//Our InformationBar GUID (generated in constructor)
*guid = this->guid;
return S_OK;
}
GetMessageW(LPWSTR* message)
Get InformationBar message
allocate memory and copy unicode string to parameter (using CoTaskMemAlloc)
HRESULT STDMETHODCALLTYPE CInfoBarMessageImpl::GetMessageW(LPWSTR* message) {
if (!this->message) { //message set at constructor or set at our own initialize function
return E_FAIL;
}
*message = (LPWSTR)CoTaskMemAlloc(sizeof(WCHAR) * (this->messageLen + 1));
wcscpy_s(*message, this->messageLen + 1, this->message);
return S_OK;
}
Get IServiceProvider from ShellView(IShellView) and Get IBrowserProgressSessionProvider interface to get dwCookie value.
After that , Get IBrowserProgressConnection From GIT(GlobalInterfaceTable) using ‘dwCookie’ from ‘GetCurrentSession’ and Get IInfoBarHost interface From BrowserProgressConnection
//CInfobarManger is own class (does not implement anyone)
void CInfoBarManager::ShowInfoBar(IShellView* shview) {
HRESULT hr;
DWORD dwCookie = 0;
if (!this->sprovider) {
hr = shview->QueryInterface(IID_IServiceProvider, (PVOID*)&sprovider);
if (FAILED(hr)) {
goto escapeArea;
}
}
if (!this->sessionProvider) {
hr = this->sprovider->QueryService(IID_IBrowserProgressAggregator, IID_IBrowserProgressSessionProvider, (PVOID*)&this->sessionProvider);
if (FAILED(hr)) {
goto escapeArea;
}
}
hr = sessionProvider->GetCurrentSession(&dwCookie);
if (FAILED(hr)) {
goto escapeArea;
}
//Get GIT
if (!this->git) {
hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, CLSCTX_INPROC_SERVER,
IID_IGlobalInterfaceTable, (PVOID*)&this->git);
if (FAILED(hr)) {
goto escapeArea;
}
}
hr = this->git->GetInterfaceFromGlobal(dwCookie, IID_IBrowserProgressConnecion, (PVOID*)&this->browserProgress);
if (hr == S_OK) {
hr = this->browserProgress->QueryInterface(IID_IInfoBarHost, (PVOID*)&this->host);
if (hr == S_OK) {
//Call Relase in explorer inside
this->infoMsg->AddRef();
this->host->Inform(this->infoMsg);
}
}
escapeArea:
return;
}
Show Infobar at MessageSFVCB if uMsg is 17 (onrefresh) or other place if you want.
//in MessageSFVCB
else if (uMsg == 17) { //OnRefresh
if (!this->infobar) {
this->infobar = new CInfoBarManager();
infobar->InitializeSimple(L"NSE Information Bar Message");
}
infobar->ShowInfoBar(this->shview); //IShellView
}
sample codes : https://github.com/bho3538/NSEInformationBar
I'm trying to load a font from a file at runtime and display it with DirectWrite. The following code should initialize a IDWriteTextFormat object with that font:
hr = pDWriteFactory->CreateTextFormat(
L"Font Family", // Font family name
NULL, // Font collection (NULL sets it to use the system font collection)
// Somehow, a custom font collection should be used here, but I don't know how to do that
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
16.0f,
L"en-us",
&pTextFormat // IDWriteTextFormat object
);
It works perfectly with system fonts but I don't know how to load a custom font file. I would appreciate any examples on how to achieve that.
What I tried so far:
• I read this article and this question on Stackoverflow but I don't see where one should pass the file path of the font file (therefore, I completely failed to implement any of the code provided on those two pages)
• I also read this article but if I'm right, it has nothing to do with DirectWrite (?) (I tried to implement the AddFontResourceEx-method, but to no avail)
• Finally, the answer to this question suggested using ResourceFontContext::CreateFontCollection could solve the problem but after reading this page (it's in German but the screenshot is in English) I believe that it can only be used with fonts embedded as resources (which is not an option in my case)
Sure, it's possible to do that. You'll need to:
implement IDWriteFontCollectionLoader interface in your code;
You should also implement IDWriteFontFileEnumerator obviously, but this should be trivial.
register loader with the factory using RegisterFontCollectionLoader
create a collection using CreateCustomFontCollection and same factory;
pass it to CreateTextFormat called on same factory.
When you call CreateCustomFontCollection you have to provide a collection key and its size, it's a blob only meaningful to you, could be anything. Later on your loader will be called with this key, so you can identify it. For example you can use a string 'myspecialkey' as a key, and in CreateEnumeratorFromKey check if it's called with that key, rejecting any other key.
If you only wanted to create a fontface object from a path to a file, you don't need any of above, just use CreateFontFileReference followed by CreateFontFace.
If anyone is interested in the code which finally worked:
Add Common.h, FontLoader.h and FontLoader.cpp to your project (code is given below) and add the following lines to your application:
#include "FontLoader.h"
// ...
IDWriteFactory* pDWriteFactory;
IDWriteFontCollection *fCollection;
IDWriteTextFormat* pTextFormat;
// ...
MFFontContext fContext(pDWriteFactory);
std::vector<std::wstring> filePaths; // vector containing ABSOLUTE file paths of the font files which are to be added to the collection
std::wstring fontFileFilePath = L"C:\\xyz\\abc.ttf";
filePaths.push_back(fontFileFilePath);
HRESULT hr = fContext.CreateFontCollection(filePaths, &fCollection); // create custom font collection
hr = pDWriteFactory->CreateTextFormat(
L"Font Family", // Font family name
fCollection,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
16.0f,
L"en-us",
&pTextFormat // IDWriteTextFormat object
);
FontLoader.h
#pragma once
#include <string>
#include "Common.h"
typedef std::vector<std::wstring> MFCollection;
class MFFontCollectionLoader : public IDWriteFontCollectionLoader
{
public:
MFFontCollectionLoader() : refCount_(0)
{
}
// IUnknown methods
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
// IDWriteFontCollectionLoader methods
virtual HRESULT STDMETHODCALLTYPE CreateEnumeratorFromKey(
IDWriteFactory* factory,
void const* collectionKey, // [collectionKeySize] in bytes
UINT32 collectionKeySize,
OUT IDWriteFontFileEnumerator** fontFileEnumerator
);
// Gets the singleton loader instance.
static IDWriteFontCollectionLoader* GetLoader()
{
return instance_;
}
static bool IsLoaderInitialized()
{
return instance_ != NULL;
}
private:
ULONG refCount_;
static IDWriteFontCollectionLoader* instance_;
};
class MFFontFileEnumerator : public IDWriteFontFileEnumerator
{
public:
MFFontFileEnumerator(
IDWriteFactory* factory
);
HRESULT Initialize(
UINT const* collectionKey, // [resourceCount]
UINT32 keySize
);
~MFFontFileEnumerator()
{
SafeRelease(¤tFile_);
SafeRelease(&factory_);
}
// IUnknown methods
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
// IDWriteFontFileEnumerator methods
virtual HRESULT STDMETHODCALLTYPE MoveNext(OUT BOOL* hasCurrentFile);
virtual HRESULT STDMETHODCALLTYPE GetCurrentFontFile(OUT IDWriteFontFile** fontFile);
private:
ULONG refCount_;
IDWriteFactory* factory_;
IDWriteFontFile* currentFile_;
std::vector<std::wstring> filePaths_;
size_t nextIndex_;
};
class MFFontContext
{
public:
MFFontContext(IDWriteFactory *pFactory);
~MFFontContext();
HRESULT Initialize();
HRESULT CreateFontCollection(
MFCollection &newCollection,
OUT IDWriteFontCollection** result
);
private:
// Not copyable or assignable.
MFFontContext(MFFontContext const&);
void operator=(MFFontContext const&);
HRESULT InitializeInternal();
IDWriteFactory *g_dwriteFactory;
static std::vector<unsigned int> cKeys;
// Error code from Initialize().
HRESULT hr_;
};
class MFFontGlobals
{
public:
MFFontGlobals() {}
static unsigned int push(MFCollection &addCollection)
{
unsigned int ret = fontCollections.size();
fontCollections.push_back(addCollection);
return ret;
}
static std::vector<MFCollection>& collections()
{
return fontCollections;
}
private:
static std::vector<MFCollection> fontCollections;
};
FontLoader.cpp
#include "FontLoader.h"
IDWriteFontCollectionLoader* MFFontCollectionLoader::instance_(
new(std::nothrow) MFFontCollectionLoader()
);
HRESULT STDMETHODCALLTYPE MFFontCollectionLoader::QueryInterface(REFIID iid, void** ppvObject)
{
if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontCollectionLoader))
{
*ppvObject = this;
AddRef();
return S_OK;
}
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
}
ULONG STDMETHODCALLTYPE MFFontCollectionLoader::AddRef()
{
return InterlockedIncrement(&refCount_);
}
ULONG STDMETHODCALLTYPE MFFontCollectionLoader::Release()
{
ULONG newCount = InterlockedDecrement(&refCount_);
if (newCount == 0)
delete this;
return newCount;
}
HRESULT STDMETHODCALLTYPE MFFontCollectionLoader::CreateEnumeratorFromKey(
IDWriteFactory* factory,
void const* collectionKey, // [collectionKeySize] in bytes
UINT32 collectionKeySize,
OUT IDWriteFontFileEnumerator** fontFileEnumerator
)
{
*fontFileEnumerator = NULL;
HRESULT hr = S_OK;
if (collectionKeySize % sizeof(UINT) != 0)
return E_INVALIDARG;
MFFontFileEnumerator* enumerator = new(std::nothrow) MFFontFileEnumerator(
factory
);
if (enumerator == NULL)
return E_OUTOFMEMORY;
UINT const* mfCollectionKey = static_cast<UINT const*>(collectionKey);
UINT32 const mfKeySize = collectionKeySize;
hr = enumerator->Initialize(
mfCollectionKey,
mfKeySize
);
if (FAILED(hr))
{
delete enumerator;
return hr;
}
*fontFileEnumerator = SafeAcquire(enumerator);
return hr;
}
// ------------------------------ MFFontFileEnumerator ----------------------------------------------------------
MFFontFileEnumerator::MFFontFileEnumerator(
IDWriteFactory* factory
) :
refCount_(0),
factory_(SafeAcquire(factory)),
currentFile_(),
nextIndex_(0)
{
}
HRESULT MFFontFileEnumerator::Initialize(
UINT const* collectionKey, // [resourceCount]
UINT32 keySize
)
{
try
{
// dereference collectionKey in order to get index of collection in MFFontGlobals::fontCollections vector
UINT cPos = *collectionKey;
for (MFCollection::iterator it = MFFontGlobals::collections()[cPos].begin(); it != MFFontGlobals::collections()[cPos].end(); ++it)
{
filePaths_.push_back(it->c_str());
}
}
catch (...)
{
return ExceptionToHResult();
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE MFFontFileEnumerator::QueryInterface(REFIID iid, void** ppvObject)
{
if (iid == IID_IUnknown || iid == __uuidof(IDWriteFontFileEnumerator))
{
*ppvObject = this;
AddRef();
return S_OK;
}
else
{
*ppvObject = NULL;
return E_NOINTERFACE;
}
}
ULONG STDMETHODCALLTYPE MFFontFileEnumerator::AddRef()
{
return InterlockedIncrement(&refCount_);
}
ULONG STDMETHODCALLTYPE MFFontFileEnumerator::Release()
{
ULONG newCount = InterlockedDecrement(&refCount_);
if (newCount == 0)
delete this;
return newCount;
}
HRESULT STDMETHODCALLTYPE MFFontFileEnumerator::MoveNext(OUT BOOL* hasCurrentFile)
{
HRESULT hr = S_OK;
*hasCurrentFile = FALSE;
SafeRelease(¤tFile_);
if (nextIndex_ < filePaths_.size())
{
hr = factory_->CreateFontFileReference(
filePaths_[nextIndex_].c_str(),
NULL,
¤tFile_
);
if (SUCCEEDED(hr))
{
*hasCurrentFile = TRUE;
++nextIndex_;
}
}
return hr;
}
HRESULT STDMETHODCALLTYPE MFFontFileEnumerator::GetCurrentFontFile(OUT IDWriteFontFile** fontFile)
{
*fontFile = SafeAcquire(currentFile_);
return (currentFile_ != NULL) ? S_OK : E_FAIL;
}
// ---------------------------------------- MFFontContext ---------------------------------------------------------
MFFontContext::MFFontContext(IDWriteFactory *pFactory) : hr_(S_FALSE), g_dwriteFactory(pFactory)
{
}
MFFontContext::~MFFontContext()
{
g_dwriteFactory->UnregisterFontCollectionLoader(MFFontCollectionLoader::GetLoader());
}
HRESULT MFFontContext::Initialize()
{
if (hr_ == S_FALSE)
{
hr_ = InitializeInternal();
}
return hr_;
}
HRESULT MFFontContext::InitializeInternal()
{
HRESULT hr = S_OK;
if (!MFFontCollectionLoader::IsLoaderInitialized())
{
return E_FAIL;
}
// Register our custom loader with the factory object.
hr = g_dwriteFactory->RegisterFontCollectionLoader(MFFontCollectionLoader::GetLoader());
return hr;
}
HRESULT MFFontContext::CreateFontCollection(
MFCollection &newCollection,
OUT IDWriteFontCollection** result
)
{
*result = NULL;
HRESULT hr = S_OK;
// save new collection in MFFontGlobals::fontCollections vector
UINT collectionKey = MFFontGlobals::push(newCollection);
cKeys.push_back(collectionKey);
const void *fontCollectionKey = &cKeys.back();
UINT32 keySize = sizeof(collectionKey);
hr = Initialize();
if (FAILED(hr))
return hr;
hr = g_dwriteFactory->CreateCustomFontCollection(
MFFontCollectionLoader::GetLoader(),
fontCollectionKey,
keySize,
result
);
return hr;
}
std::vector<unsigned int> MFFontContext::cKeys = std::vector<unsigned int>(0);
// ----------------------------------- MFFontGlobals ---------------------------------------------------------
std::vector<MFCollection> MFFontGlobals::fontCollections = std::vector<MFCollection>(0);
Common.h
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
//----------------------------------------------------------------------------
#pragma once
// The following macros define the minimum required platform. The minimum required platform
// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run
// your application. The macros work by enabling all features available on platform versions up to and
// including the version specified.
// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef WINVER // Minimum platform is Windows 7
#define WINVER 0x0601
#endif
#ifndef _WIN32_WINNT // Minimum platform is Windows 7
#define _WIN32_WINNT 0x0601
#endif
#ifndef _WIN32_WINDOWS // Minimum platform is Windows 7
#define _WIN32_WINDOWS 0x0601
#endif
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#ifndef UNICODE
#define UNICODE
#endif
// Windows header files
#include <windows.h>
#include <dwrite.h>
#include <d2d1.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <memory>
#include <vector>
// Ignore unreferenced parameters, since they are very common
// when implementing callbacks.
#pragma warning(disable : 4100)
////////////////////////////////////////
// COM inheritance helpers.
// Releases a COM object and nullifies pointer.
template <typename InterfaceType>
inline void SafeRelease(InterfaceType** currentObject)
{
if (*currentObject != NULL)
{
(*currentObject)->Release();
*currentObject = NULL;
}
}
// Acquires an additional reference, if non-null.
template <typename InterfaceType>
inline InterfaceType* SafeAcquire(InterfaceType* newObject)
{
if (newObject != NULL)
newObject->AddRef();
return newObject;
}
// Sets a new COM object, releasing the old one.
template <typename InterfaceType>
inline void SafeSet(InterfaceType** currentObject, InterfaceType* newObject)
{
SafeAcquire(newObject);
SafeRelease(¤tObject);
currentObject = newObject;
}
// Maps exceptions to equivalent HRESULTs,
inline HRESULT ExceptionToHResult() throw()
{
try
{
throw; // Rethrow previous exception.
}
catch (std::bad_alloc&)
{
return E_OUTOFMEMORY;
}
catch (...)
{
return E_FAIL;
}
}
The code is certainly not perfect but it worked for me.
If you have any questions concerning this code just leave a comment (even though I probably won't be able to give a satisfying answer anymore). Note that I copied large parts of the code from here.
In advance sorry for the TOO Long post, but given that there are many parts involved, and I am not sure where I can be making a mistake, hence, I need to post all the parts involved. Please do not assume anything, I can be making any type of error here.
Problem:
1) Real problem: Just learning COM.
2) I need to pass a IDispatch pointer from my console client (ConsoleClient) in C++ as parameter to a COM function in a Windows Service (WinService). This IDispatch pointer acts as callback. The WinService knows the name of the function to be called. All my attempts have been unsuccessulf. I am getting different errors like: No implemented, RPC is unavailable, etc..
NOTE: The following error is correct! I edited after #Igor pointed out my error: to have a do-while (which I removed from the main()) in a STA thread. I added a message pump (GetMessage, TranslateMessage and DispatchMessage) in the main() thread (instead of the do-while) and my problem was fixed!
I have a WinService exposing the following COM interface (WinService's idl file):
[
object,
uuid(DBE8BC31-9D2B-4F4B-903A-B40473408DE9),
dual,
nonextensible,
pointer_default(unique)
]
interface IWinService : IDispatch
{
///Here there are more functions..
[id(4), helpstring("Interface HELP")]
HRESULT WinServiceCOMfunction( [in] VARIANT vCallback, [out, retval] LONG* pReturn );
//Here there even more functions...
};
[
uuid(DEF3BFAE-ADF4-493B-8D01-E47A279225C5),
version(1.0),
helpstring("LIB HELP")
]
library WinServiceLib
{
importlib("stdole2.tlb");
[
uuid(AC290DC9-8CB4-4502-A73C-2BDAEC4B215A)
]
coclass CoWinService
{
[default] interface IWinService;
};
}
The WinService internally expects that vCallBack.vt = VT_DISPATCH, i.e., that is the callback pointer I need to pass in when I invoke the WinServiceCOMfunction from my ConsoleClient.EXE app. WinService knows the name of the function that acts as callback. I.e., WinService calls from the IDispatch-pointer-callback GetIDsOfName with "FunctionCallback" as parameter. However, here I am getting different errors in the WinService like: No implemented, RPC is unavailable, etc.. Then no callback is executed (the ConsoleClient does not receive anything back).
What I have done so far (all the following source code is found in the ConsoleClient.EXE)
ConsoleClient.EXE main.cpp:
int _tmain(int argc, _TCHAR* argv[])
{
CoInitialize(NULL);
//To be brief: Let's think that I already have an IDispatch pointer to the WinService interface IWinService:
CComPtr<IDispatch> pIWinService; //pIWinService
//Here I queried the IWinService interface...It was successful!!!
OLECHAR * winServiceNameFunction = L"WinServiceCOMfunction";
DISPID dispid;
hr = pIWinService->GetIDsOfNames( IID_NULL, &winServiceNameFunction, 1, LOCALE_USER_DEFAULT, &dispid );
if ( FAILED( hr ) )
{
wprintf( L"GetIDsOfNames failed" );
return 1;
}
else
{
wprintf( L"GetIDsOfNames succeeded!" );
}
CComPtr<IDispatch> consoleClientCallback( new CConsoleClientInterface() );
CConsoleClientInterface *sanity = dynamic_cast<CConsoleClientInterface *>( consoleClientCallback.p );
if ( nullptr == pAutoCallback )
{
wprintf( L"CConsoleClientInterface pointer failed\n" );
return 1;
}
else
{
wprintf( L"CConsoleClientInterface pointer succeeded\n" );
}
DWORD dwRegister;
hr = CoRegisterClassObject(CLSID_CoConsoleClient, consoleClientCallback.p,
CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &dwRegister);
if(FAILED(hr))
{
wprintf(L"CoRegisterClassObject Failed\n");
return 1;
}
else
{
wprintf(L"CoRegisterClassObject Succeeded\n");
}
CComVariant paramCallback( consoleClientCallback.Detach() );
VARIANTARG varParams[] = { paramCallback };
DISPPARAMS dispparams = { vArgs, NULL, 1, 0 };
hr = pPtr->Invoke(
dispid,
IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD,
&dispparams,
NULL,
NULL,
NULL);
if ( FAILED( hr ) )
{
wprintf( L"Invoke failed" );
return 1;
}
else
{
wprintf( L"Invoke Succeeded" );
}
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CoRevokeClassObject(dwRegister);
CoUninitialize();
return 0;
}
All the main is succesfully executed. However, I never get anything from the callback. When the WinService tries to QueryInterface from teh IDispatch pointer callback, it throws erros like: No implemented, RPC is unavailable, among others...
Template class to create the ConsoleClient IDispatch interface
From: http://blogs.msdn.com/b/oldnewthing/archive/2013/06/12/10425215.aspx
disInterfaceBase.h:
template<typename DispInterface>
class CDispInterfaceBase : public DispInterface
{
public:
CDispInterfaceBase() : m_cRef(1), m_dwCookie(0) { }
/* IUnknown */
IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv)
{
*ppv = nullptr;
HRESULT hr = E_NOINTERFACE;
if (riid == IID_IUnknown || riid == IID_IDispatch ||
riid == __uuidof(DispInterface))
{
*ppv = static_cast<DispInterface *>
(static_cast<IDispatch*>(this));
AddRef();
hr = S_OK;
}
return hr;
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&m_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
LONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0) delete this;
return cRef;
}
// *** IDispatch ***
IFACEMETHODIMP GetTypeInfoCount(UINT *pctinfo)
{
*pctinfo = 0;
return E_NOTIMPL;
}
IFACEMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid,
ITypeInfo **ppTInfo)
{
*ppTInfo = nullptr;
return E_NOTIMPL;
}
IFACEMETHODIMP GetIDsOfNames(REFIID, LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
return E_NOTIMPL;
}
IFACEMETHODIMP Invoke(
DISPID dispid, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS *pdispparams, VARIANT *pvarResult,
EXCEPINFO *pexcepinfo, UINT *puArgErr)
{
if (pvarResult) VariantInit(pvarResult);
return SimpleInvoke(dispid, pdispparams, pvarResult);
}
// Derived class must implement SimpleInvoke
virtual HRESULT SimpleInvoke(DISPID dispid,
DISPPARAMS *pdispparams, VARIANT *pvarResult) = 0;
public:
HRESULT Connect(IUnknown *punk)
{
HRESULT hr = S_OK;
CComPtr<IConnectionPointContainer> spcpc;
if (SUCCEEDED(hr)) {
hr = punk->QueryInterface(IID_PPV_ARGS(&spcpc));
}
if (SUCCEEDED(hr)) {
hr = spcpc->FindConnectionPoint(__uuidof(DispInterface), &m_spcp);
}
if (SUCCEEDED(hr)) {
hr = m_spcp->Advise(this, &m_dwCookie);
}
return hr;
}
void Disconnect()
{
if (m_dwCookie) {
m_spcp->Unadvise(m_dwCookie);
m_spcp.Release();
m_dwCookie = 0;
}
}
private:
LONG m_cRef;
CComPtr<IConnectionPoint> m_spcp;
DWORD m_dwCookie;
};
The actual IDispatch implementation in the ConsoleClient
ConsoleClient.h
class CConsoleClientInterface : public CDispInterfaceBase<ICONSOLEINTERFACE>
{
public:
CConsoleClientInterface() { }
~CConsoleClientInterface() { }
STDMETHODIMP GetIDsOfNames(REFIID, LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
HRESULT hr = E_FAIL;
if(_wcsicmp(*rgszNames, L"FunctionCallback") == 0)
{
*rgDispId = 1;
hr= S_OK;
}
else
{
hr= DISP_E_UNKNOWNINTERFACE;
}
if(FAILED(hr))
{
std::cout << L"FAILED\n";
}
return hr;
}
HRESULT SimpleInvoke(
DISPID dispid, DISPPARAMS *pdispparams, VARIANT *pvarResult)
{
// switch (dispid)
// {
// case 4:
std::cout << L"SimpleInvoke" << std::endl; //This is never printed (for the error that I am trying to figure out)
HRESULT hr = FunctionCallback( pdispparams->rgvarg[1].intVal, pdispparams->rgvarg[0].parray );
// break;
// }
return hr;
}
HRESULT FunctionCallback( LONG longValue, LPSAFEARRAY safearray )
{
//So simple, I just want that this value is printed in the ConsoleClient's console! Unfortunately this is not happening!
std::cout << longValue << std::endl;
return S_OK;
}
};
And the ConsoleClient.idl
[
object,
uuid(CD08B160-558A-4251-885C-173A08A461F1),
dual,
nonextensible,
helpstring("ICONSOLEINTERFACE Interface"),
pointer_default(unique)
]
interface ICONSOLEINTERFACE : IDispatch{
[id(1), helpstring("method FunctionCallback")]
HRESULT FunctionCallback([in] LONG longValue, [in] LPSAFEARRAY safeArray );
};
[
uuid(F3445A9E-555B-4729-952B-8B72B8DB2E37),
version(1.0),
helpstring("ConsoleClientLib Help Lib")
]
library ConsoleClientLib
{
importlib("stdole2.tlb");
[
uuid(EFF9EC78-3031-4558-9BA3-5B2641CCB304),
helpstring("CoConsoleClient Class")
]
coclass ConsoleClientInterface
{
[default] interface ICONSOLEINTERFACE;
};
};
I have created:
HKEY_CLASSES_ROOT\CLSID\{EFF9EC78-3031-4558-9BA3-5B2641CCB304} //CLSID
(Default) REG_SZ = C:\Program Files\Common Files\ConsoleClient.EXE
HKEY_CLASSES_ROOT\CLSID\{EFF9EC78-3031-4558-9BA3-5B2641CCB304}\LocalServer32
ThreadingModel REG_SZ = Both
Also I have registered the tlb using regtlibv12.exe
Sorry for the TOO LONG post, but COM forces me to do this, in particular that I am new with COM and I do not know where I can be making a mistake.
NOTE: I am sure that the Invoke (from the ConsoleClient.EXE) to the WinService COM function (WinServiceCOMfunction) is successful (I debugged it and hits inside this function after the Invoke).
NOTE2: I am sure that the WinService using the callback works. There are other implementations (in different programming languages) making use of this mechanism via the same Function (WinServiceCOMfunction) in WinService.
Any help? Thanks in advance!
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.