Skip to content

Instantly share code, notes, and snippets.

@caiorss
Forked from derrickturk/excel_dispatch_client.cpp
Created August 20, 2018 15:25
Show Gist options
  • Save caiorss/b2f2f1ff241effd0673c6ffbb570931d to your computer and use it in GitHub Desktop.
Save caiorss/b2f2f1ff241effd0673c6ffbb570931d to your computer and use it in GitHub Desktop.
An IDispatch client that launches Excel for 25 seconds.
// build with g++ -std=c++14 -O2 -Wall -Wextra -pedantic -static -o excel_dispatch_client excel_dispatch_client.cpp -lole32 -luuid
#include <cstdlib>
#include <iostream>
#include <exception>
#include <memory>
#include <string>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <thread>
#include <chrono>
#include "objbase.h"
class COMException : public std::runtime_error {
public:
COMException(HRESULT result);
private:
std::string message(HRESULT result);
};
class COMLibrary {
public:
COMLibrary(DWORD options = COINIT_MULTITHREADED);
COMLibrary(const COMLibrary&) = delete;
COMLibrary& operator=(const COMLibrary&) = delete;
~COMLibrary() noexcept;
};
class COMDispatchable {
public:
COMDispatchable(const std::wstring& prog_id)
: COMDispatchable(prog_id.c_str()) { }
COMDispatchable(const wchar_t *prog_id);
enum class MemberType : WORD {
Method = DISPATCH_METHOD,
PropertyGet = DISPATCH_PROPERTYGET,
PropertyLet = DISPATCH_PROPERTYPUT,
PropertySet = DISPATCH_PROPERTYPUTREF,
};
VARIANT invoke(const std::wstring& member, MemberType type,
std::vector<VARIANT> args)
{
VARIANT result;
invoke_impl(member, type, args, &result);
return result;
}
void invoke_action(const std::wstring& member, MemberType type,
std::vector<VARIANT> args)
{
invoke_impl(member, type, args, nullptr);
}
private:
DISPID dispid(const std::wstring& name) const;
void invoke_impl(const std::wstring& member, MemberType type,
std::vector<VARIANT> args, VARIANT *result);
struct deleter {
inline void operator()(IDispatch *obj) noexcept
{
if (obj)
obj->Release();
}
};
std::unique_ptr<IDispatch, deleter> obj_;
mutable std::unordered_map<std::wstring, DISPID> dispids_;
};
int main()
{
COMLibrary com_lib;
COMDispatchable excel(OLESTR("Excel.Application"));
VARIANT visible = excel.invoke(OLESTR("Visible"),
COMDispatchable::MemberType::PropertyGet, {});
if (visible.vt != VT_BOOL) {
std::cerr << "That's no bool...\n";
return 0;
}
std::cout << "Visible? "
<< (visible.iVal == VARIANT_TRUE ? "True" : "False") << '\n';
VARIANT vt_true;
vt_true.vt = VT_BOOL;
vt_true.iVal = VARIANT_TRUE;
excel.invoke_action(OLESTR("Visible"),
COMDispatchable::MemberType::PropertyLet, { vt_true });
visible = excel.invoke(OLESTR("Visible"),
COMDispatchable::MemberType::PropertyGet, {});
if (visible.vt != VT_BOOL) {
std::cerr << "That's no bool...\n";
return 0;
}
std::cout << "Visible? " << (visible.lVal == 0xFFFF ? "True" : "False")
<< '\n';
std::this_thread::sleep_for(std::chrono::seconds(30));
}
COMException::COMException(HRESULT result)
: std::runtime_error(message(result)) { }
std::string COMException::message(HRESULT result)
{
std::ostringstream msg;
msg << "COM Exception: 0x" << std::hex << result;
return msg.str();
}
COMLibrary::COMLibrary(DWORD options)
{
HRESULT result = CoInitializeEx(nullptr, options);
if (result != S_OK && result != S_FALSE)
throw COMException(result);
}
COMLibrary::~COMLibrary() noexcept
{
CoUninitialize();
}
COMDispatchable::COMDispatchable(const wchar_t *prog_id)
{
HRESULT result;
CLSID clsid;
result = CLSIDFromProgID(prog_id, &clsid);
if (result != S_OK)
throw COMException(result);
IDispatch *dispatch;
result = CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER,
IID_IDispatch, reinterpret_cast<void **>(&dispatch));
if (result != S_OK)
throw COMException(result);
obj_.reset(dispatch);
}
DISPID COMDispatchable::dispid(const std::wstring& name) const
{
auto cached = dispids_.find(name);
if (cached != dispids_.end())
return cached->second;
const wchar_t *name_ptr = name.c_str();
DISPID dispid;
HRESULT result = obj_->GetIDsOfNames(IID_NULL,
const_cast<wchar_t **>(&name_ptr), 1, LOCALE_SYSTEM_DEFAULT,
&dispid);
if (result != S_OK)
throw COMException(result);
dispids_[name] = dispid;
return dispid;
}
void COMDispatchable::invoke_impl(const std::wstring& member, MemberType type,
std::vector<VARIANT> args, VARIANT *result)
{
static const DISPID dispid_named_put = DISPID_PROPERTYPUT;
DISPPARAMS params { args.data(), nullptr,
static_cast<UINT>(args.size()), 0 };
DISPID member_dispid = dispid(member);
WORD wtype = static_cast<WORD>(type);
if (wtype & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF)) {
params.cNamedArgs = 1;
params.rgdispidNamedArgs = const_cast<DISPID *>(&dispid_named_put);
}
HRESULT invoke_result = obj_->Invoke(member_dispid, IID_NULL,
LOCALE_SYSTEM_DEFAULT, wtype, &params, result,
nullptr, nullptr);
if (invoke_result != S_OK)
throw COMException(invoke_result);
}
// cl /EHsc vc_excel_client.cpp
// MSVC can do this, but it's all IDispatch behind the scenes.
// It also uses runtime magic (_com_dispatch_wrapper) that we can't easily
// forward to from g++; additionally the #import generated code is non-standard
// (like, the feature, but also the code it generates: forward declares of
// enums, cats lying down with dogs, etc.)
#include <string>
#include <sstream>
#include <memory>
#include <functional>
#include <thread>
#include <chrono>
#include "objbase.h"
#import "C:/Program Files (x86)/Microsoft Office/root/vfs/ProgramFilesCommonX86/Microsoft Shared/OFFICE16/MSO.DLL" \
no_dual_interfaces
#import "C:/Program Files (x86)/Microsoft Office/root/vfs/ProgramFilesCommonX86/Microsoft Shared/VBA/VBA6/VBE6EXT.OLB" \
no_dual_interfaces
#import "C:/Program Files (x86)/Microsoft Office/root/Office16/EXCEL.EXE" \
no_dual_interfaces, \
rename("DialogBox", "xlDialogBox"), \
rename("RGB", "xlRGB")
class COMException : public std::runtime_error {
public:
COMException(HRESULT result);
private:
std::string message(HRESULT result);
};
class COMLibrary {
public:
COMLibrary(DWORD options = COINIT_MULTITHREADED);
COMLibrary(const COMLibrary&) = delete;
COMLibrary& operator=(const COMLibrary&) = delete;
~COMLibrary() noexcept;
};
template<class T> auto make_unique_COM_instance(const std::wstring& progid)
{
CLSID clsid;
HRESULT result;
result = CLSIDFromProgID(progid.c_str(), &clsid);
if (result != S_OK)
throw COMException(result);
T *obj;
result = CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER,
__uuidof(T), reinterpret_cast<void **>(&obj));
if (result != S_OK)
throw COMException(result);
auto deleter = std::mem_fn(&T::Release);
return std::unique_ptr<T, decltype(deleter)>(obj, deleter);
}
int main()
{
COMLibrary com_library;
auto app = make_unique_COM_instance<Excel::_Application>
(L"Excel.Application");
VARIANT vt_true;
vt_true.vt = VT_BOOL;
vt_true.iVal = VARIANT_TRUE;
app->PutVisible(VARIANT_TRUE);
std::this_thread::sleep_for(std::chrono::seconds(30));
}
COMException::COMException(HRESULT result)
: std::runtime_error(message(result)) { }
std::string COMException::message(HRESULT result)
{
std::ostringstream msg;
msg << "COM Exception: 0x" << std::hex << result;
return msg.str();
}
COMLibrary::COMLibrary(DWORD options)
{
HRESULT result = CoInitializeEx(nullptr, options);
if (result != S_OK && result != S_FALSE)
throw COMException(result);
}
COMLibrary::~COMLibrary() noexcept
{
CoUninitialize();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment