Last active
August 20, 2018 15:25
-
-
Save derrickturk/ef9620ae75a529b777ceb4e530cdb64f to your computer and use it in GitHub Desktop.
An IDispatch client that launches Excel for 25 seconds.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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, ¶ms, result, | |
nullptr, nullptr); | |
if (invoke_result != S_OK) | |
throw COMException(invoke_result); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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