Link to home
Start Free TrialLog in
Avatar of mgonyea
mgonyeaFlag for Canada

asked on

Accessing a COM DLL in C++

In Microsoft Visual C++ 6.0, I want to access a COM DLL (written in VFoxpro 6.0).  In VB and VFP the syntax was CREATEOBJECT("test.class").  How do I go about doing this?  

Thanks.
ASKER CERTIFIED SOLUTION
Avatar of jkr
jkr
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I just ran into this solution about 2 days ago.  I asked a similar question about a month ago, but it wasn't answered quite how I wanted, so I continued to research it myself.  It has taken me over a year to not only use, but also understand what I'm going to post here.  It seems this is sorely undocumented, and what little is documented, is buried deep within pages of specs and nothingness.

So here's how to do Automation in C++.  This example would be equivalent to VB:

wordApplication=CreateObject("Word.Application")
wordDocuments=wordApplication.Documents
currentDocument=wordDocuments.Open('file',0,0,0,'','',0,'','',wdOpenFormatAuto)


Now, in C++ (this is a roller coaster ride...):

//got this definition from the .idl file
int wdOpenFormatAuto=0;

void main(void)
{
  CLSID wordCLSID;
  IDispatch *wordApplication,*wordDocuments,*currentDocument;
  DISPID dispid;
  HRESULT hr;
  DISPPARAMS dp;
  VARIANT res;
  VARIANTARG va[11];
  OLECHAR *oc;
  int x;

  OleInitialize(NULL);
  //get the CLSID of Word.Application
  CLSIDFromProgID(L"Word.Application",&wordCLSID);

  hr = CoCreateInstance(wordCLSID,NULL,CLSCTX_LOCAL_SERVER,IID_IDispatch,(void **)&wordApplication);

  //find the dispatch id for "Documents"
  oc=L"Documents";
  hr=wordApplication->GetIDsOfNames(IID_NULL,&oc,1,LOCALE_SYSTEM_DEFAULT,&dispid);
  printf("Documents DispID=0x%x\n",dispid);

  //setup for 0 arguments
  dp.rgvarg=NULL;
  dp.cArgs=0;
  dp.rgdispidNamedArgs=NULL;
  dp.cNamedArgs=0;

  //now get the Documents interface.  Note, this is NOT a COM function, it is a property.
  hr=wordApplication->Invoke(dispid,IID_NULL,LOCALE_SYSTEM_DEFAULT,DISPATCH_PROPERTYGET,&dp,&res,NULL,NULL);

  //now save the IDispatch interface pointer for Documents into our wordDocuments variable
  wordDocuments=res.pdispVal;


  //now let's use this new interface to Open() a .doc file

  //get the Open() dispatch ID
  oc=L"Open";
  hr=wordDocuments->GetIDsOfNames(IID_NULL,&oc,1,LOCALE_SYSTEM_DEFAULT,&dispid);

  //now set up the parameters needed for the open function.
  //note, there are 10 variants that need to be set up!
  dp.rgvarg=va;
  dp.cArgs=10;
  dp.cNamedArgs=0;
  dp.rgdispidNamedArgs=NULL;

  VariantInit(&dp.rgvarg[0]);
  dp.rgvarg[0].vt=VT_I4;
  dp.rgvarg[0].lVal=wdOpenFormatAuto;

  VariantInit(&dp.rgvarg[1]);
  dp.rgvarg[1].vt=VT_BSTR;
  oc=L"";
  dp.rgvarg[1].bstrVal=SysAllocString(oc);

  VariantInit(&dp.rgvarg[2]);
  dp.rgvarg[2].vt=VT_BSTR;
  oc=L"";
  dp.rgvarg[2].bstrVal=SysAllocString(oc);

  VariantInit(&dp.rgvarg[3]);
  dp.rgvarg[3].vt=VT_BOOL;
  dp.rgvarg[3].boolVal=0x0000;

  VariantInit(&dp.rgvarg[4]);
  dp.rgvarg[4].vt=VT_BSTR;
  oc=L"";
  dp.rgvarg[4].bstrVal=SysAllocString(oc);

  VariantInit(&dp.rgvarg[5]);
  dp.rgvarg[5].vt=VT_BSTR;
  oc=L"";
  dp.rgvarg[5].bstrVal=SysAllocString(oc);

  VariantInit(&dp.rgvarg[6]);
  dp.rgvarg[6].vt=VT_BOOL;
  dp.rgvarg[6].boolVal=0x0000;

  VariantInit(&dp.rgvarg[7]);
  dp.rgvarg[7].vt=VT_BOOL;
  dp.rgvarg[7].boolVal=0x0000;

  VariantInit(&dp.rgvarg[8]);
  dp.rgvarg[8].vt=VT_BOOL;
  dp.rgvarg[8].boolVal=0x0000;

  VariantInit(&dp.rgvarg[9]);
  dp.rgvarg[9].vt=VT_BSTR;
  oc=L"test.doc"
  dp.rgvarg[9].bstrVal=SysAllocString(oc);

  //now call the Open() function.  Note that this IS a method, not a property like above
  hr=wordDocuments->Invoke(dispid,IID_NULL,LOCALE_SYSTEM_DEFAULT,DISPATCH_METHOD,&dp,&res,NULL,NULL);

  //now free up variables if needed
  for(x=0;x<10;x++) VariantClear(&dp.rgvarg[x]);

  //now save the IDispatch Interface to the newly opened document
  currentDocument=res.pdispVal;


  currentApplication->Release();
  wordDocuments->Release();
  wordApplication->Release();

  OleUninitialize();
}
I forgot to mention one VERY important thing.  If you're compiling this yourself with a makefile, you will pull out your hair wondering why IID_IDispatch and IDispatch is not declared anywhere.  To get it to compile, also include uuid.lib

i.e.

cl test.cpp ole32.lib oleaut32.lib uuid.lib
Avatar of mgonyea

ASKER

In MS Visual C++ 6, the first parameter expected in CLSISFromProgID is an unsigned short *, and won't accept a char[x].
>>the first parameter expected in CLSISFromProgID is an
>>unsigned short *

Thus the 'TEXT()' macro, which will expand to a 'L' string prefix that instructs the compiler to handle the following string as UNICODE, which in fact is 'unsigned short*'

The macro is sth. like that:

#ifdef UNICODE
#define TEXT(x) L##x
#else
#define TEXT(x) x

You could of course hard-code it:

CLSIDFromProgID ( L"test.class", &clsid);
Yes, what he said.
Avatar of mgonyea

ASKER

One last thing, and I'll bump the points up to 200.  The "VARIANT res;" now contains the results of my OLE functions (Yay!).  How do I convert it BACK from a BSTR, into a char *?

Thanks everyone.
As BSTR* is the same as OLECHAR* (which is the same as LPOLESTR ;-)

(see wtypes.h, i just left out the RPC stuff:
typedef OLECHAR *BSTR;
)

this function does what you need:
char* ANSIStringFromUNICODE( LPOLESTR pszOLE)
{
HRESULT hResult;

static char acBuffer[ BUFFER_MAX];


    hResult =   ::WideCharToMultiByte   (   CP_ACP,
                                            0,
                                            pszOLE,
                                            -1,
                                            acBuffer,
                                            sizeof  (   acBuffer),
                                            NULL,
                                            NULL
                                        );


    if  (   FAILED  (   hResult))
            return  (   NULL)

    return ( acBuffer);
}

So, just call it like

char* pc = ANSIStringFromUNICODE ( res.bstrVal);
Also, don't forget to call VariantClear() when you're done so you don't get memory leaks.
So who gets the points on this one?

jkr proposed the original answer and locked the question, but I answered your question much more thorough.  But then he answered your second part.  I think it should split 100/100, but that's a pain.
Avatar of mgonyea

ASKER

Unfortunately, I'll grade the answer.  Works great!  I'm posting another question Brain, so you can answer that one and I'll give you the same amount of points.  The question is "Declaring absolute globals in MSVC++6"

Thanks to you to, jkr!
Fine that I could be of some help!
mgonyea, you should grade this one so that it moves to the PAQ section...