Dmitry Dmitry - 3 months ago 26
C++ Question

How do I instantiate a Scripting.Dictionary using CoCreateInstance?

I have been reading up on COM and VBScript and I am now curious if I can create a Scripting.Dictionary object from C++ using CoCreateInstance function.

What I want to accomplish is something like this:

script.vbs

' reminder: run with "cscript script.vbs"
Option Explicit

Dim myDictionary

Set myDictionary = CreateObject("Scripting.Dictionary")

myDictionary.Add "Foo", "Bar"

MsgBox myDictionary.Item("Foo")


I don't actually understand how the process of instantiating COM Objects works, but from what I understood, it has to look something like:

main.cpp:

/* reminder: use Unicode */
#include <Windows.h>
#include <stdio.h>

/**
* Takes a guid and dumps it out to stdin as a string.
* @param guid guid to dump to stdin.
*/
void DumpGUID(GUID guid)
{

char dumpData[81];
sprintf_s (
&dumpData[0],
81,
"{"
"\"Data1\": \"%u\","
"\"Data2\": \"%u\","
"\"Data3\": \"%u\","
"\"Data4\": \"%u\""
"}",
guid.Data1,
guid.Data2,
guid.Data3,
guid.Data4
);
puts(dumpData);
}

int main(int argc, char **argv)
{
GUID guid;
const wchar_t *ScriptingDictionaryGUID = L"EE09B103-97E0-11CF-978F-00A02463E06F";

void *myDictionary = NULL;

/* anonymous scope: set guid to 0 so I can tell if it has changed */
{
memset(&guid, 0, sizeof(guid));
}

DumpGUID(guid);

/* anonymous scope: convert my string to guid */
{
HRESULT whyNoConvert = CLSIDFromString((LPCOLESTR)ScriptingDictionaryGUID, &guid);
if (FAILED(whyNoConvert)) {
printf("can't convert string to guid, got error %d.\n", whyNoConvert);

/* ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680589(v=vs.85).aspx */
switch (whyNoConvert) {
case NOERROR:
puts("The CLSID was obtained successfully.");
break;
case CO_E_CLASSSTRING:
puts("The class string was improperly formatted.");
break;
case REGDB_E_CLASSNOTREG:
puts("The CLSID corresponding to the class string was not found in the registry.");
break;
case REGDB_E_READREGDB:
puts("The registry could not be opened for reading.");
break;
}
system("pause");
}
}
DumpGUID(guid);

HRESULT instantiationResult = CoCreateInstance (
guid,
NULL,
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
IID_IDispatch,
&myDictionary
);
DumpGUID(guid);

if (FAILED(instantiationResult)) {
puts("Can't create a dictionary :(");
} else {
puts("I HAVE DONE IT!");
}

system("pause");
return 0;
}


My code fails to create the dictionary object(but it does compile)...
It also fails to actually convert from my String to GUID with error "The class string was improperly formatted.".

I have no idea what I am doing wrong(or what I am doing right for that matter).

EDIT: Working code(still need to figure out how to interface with the object once it's created though)

#ifndef UNICODE
#define UNICODE
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef STRICT
#define STRICT
#endif
#pragma comment(lib, "uuid.lib")
#pragma comment(lib, "ole32.lib")

#include <windows.h>
#include <stdio.h>

int main(int argc, char **argv)
{
GUID dictionary_guid = {0xEE09B103, 0x97E0, 0x11CF, {0x97, 0x8F, 0x00, 0xA0, 0x24, 0x63, 0xE0, 0x6F}};
IDispatch* p_dictionary = nullptr;

/* anonymous scope: initializing COM */
{
HRESULT result = CoInitialize(NULL);
if (!SUCCEEDED(result)) {
printf("CoInitialize failed: %u.\n", result);
system("pause");
exit(0);
}
}

/* anonymous scope: creating instance and checking */
{
HRESULT result = CoCreateInstance (
dictionary_guid,
nullptr,
CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
IID_IDispatch,
reinterpret_cast<void**>(&p_dictionary)
);
if (!SUCCEEDED(result)) {
puts("CoCreateInstance failed");
system("pause");
exit(0);
}

puts ("I HAVE DONE IT!");
printf("HRESULT = %u.\n", result);
printf("p_dictionary: %u.\n", p_dictionary);
}
p_dictionary->Release();
CoUninitialize();

system("pause");
return EXIT_FAILURE;
}


Thanks ahead of time!

Answer

This code works for me:

#undef UNICODE
#define UNICODE
#undef NOMINMAX
#define NOMINMAX
#undef STRICT
#define STRICT
#include <windows.h>

#include <iostream>
#include <stdexcept>
#include <string>

auto fail( std::string const& s )
    -> bool
{ throw std::runtime_error( s ); }

struct Succeeded {};

auto operator>>( HRESULT const hr, Succeeded )
    -> bool
{ return SUCCEEDED( hr ); }     // A macro from <windows.h>.

struct Com_library
{
    ~Com_library()
    { CoUninitialize(); }

    Com_library()
    {
        CoInitialize( 0 )
            >> Succeeded() || fail( "CoInitialize failed" );
    }
};

namespace sys {
    using String = std::wstring;
    auto& out = std::wcout;
    auto& err = std::wcerr;
}  // namespace sys

void cppmain()
{
    Com_library const using_Com;

    GUID const dictionary_guid =
    {0xEE09B103, 0x97E0, 0x11CF, {0x97, 0x8F, 0x00, 0xA0, 0x24, 0x63, 0xE0, 0x6F}};

    IDispatch* p_dictionary = nullptr;
    CoCreateInstance (
        dictionary_guid,
        nullptr,
        CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
        IID_IDispatch,
        reinterpret_cast<void**>( &p_dictionary )
        )
        >> Succeeded() || fail( "CoCreateInstance failed" );

    sys::out << "I HAVE DONE IT!\n";
    /* figure out how to use the dictionary handle */

    p_dictionary->Release();        // You want to automate this, e.g. smart pointer.
}

auto main()
    -> int
{
    using std::exception;
    try
    {
        cppmain();
        return EXIT_SUCCESS;
    }
    catch( exception const& x )
    {
        sys::err << x.what() << "\n";
    }
    return EXIT_FAILURE;
}

Compiling with g++:

g++ com_example.cpp -lole32 -luuid

Compiling with Visual C++:

cl com_example.cpp ole32.lib uuid.lib

One important thing is to remember to initialize COM, via a call to CoInitialize. Also, be aware that using IDispatch directly is a PITA in C++ (very simple in a scripting language, for which it's designed, but ungood in C++), so if the dictionary class supports a dual interface you'd better query for the ordinary more C++-friendly interface. Or use some 3rd party's C++ IDispatch wrapper, such may exist (since I made one once I guess that also others may have, and placed it on the net).