HOWTO: Use Microsoft Word's CAPI Messaging Interface (CMI)

ID: Q190057


The information in this article applies to:
  • Microsoft Office 2000 Developer
  • Microsoft Visual C++, 32-bit Editions, version 5.0
  • Microsoft Word Developer's Kit, version 95
  • Microsoft Word 97 for Windows


SUMMARY

As an alternative to automation using COM, you can use the CAPI Messaging Interface (CMI) to control Microsoft Word. CMI uses the same structures and functions as a Microsoft Word DLL add-in (WLL), but allows cross-process and asynchronous calls. To use CMI, you should have a copy of the latest Microsoft Word Developer's Kit (ISBN: 1-55615-880-7), and a 32-bit version of Microsoft Visual C++. This article lists the steps you need to take to build a CMI project, and demonstrates using CMI.


MORE INFORMATION

Step By Step Example

  1. Create a new dialog-based Win32 AppWizard (exe) project.


  2. Add a button to your dialog and a handler function for it.


  3. Follow steps 2 through 8 in the following Microsoft Knowledge Base article:
    Q183758 WD: Build a Microsoft Word Add-in (WLL) Using Visual C++


  4. At the top of your dialog boxes .cpp, add the following lines to the end of #include list:
    
    #include "capilib.h"
    #include "wdcmds.h"
    #include "wdfid.h"
    
    extern "C" {
       _declspec(dllimport) short WINAPI cmiCommandDispatch(short
       CommandID, short DlgOptions, short cArgs, LPWDOPR lpwdoprArgs,
       LPWDOPR lpwdoprReturn);
    } 


  5. In your button handler function, add the following code:
    
    // Start Word.
    STARTUPINFO si;
    ::ZeroMemory(&si, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    PROCESS_INFORMATION pi;
    
    if(::CreateProcess(
          NULL, "c:/progra~1/micros~1/office/winword.exe", NULL, NULL,
          FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {
       // Wait for Word to become Idle.
       WaitForInputIdle(pi.hProcess, 3000);
    }
    else {
       ::MessageBeep(0);
       ::MessageBox(
          NULL, "CreateProcess() failed.\nCheck Path for WinWord.Exe.",
          "Error", MB_SETFOREGROUND);
       return;
    }
    
    // Put your window back in the foreground.
    SetForegroundWindow();
    
    WCB wcb; // Your local WCB structure.
    int err; // Error/result.
    
    // Call wdDrawLine - Creates a graphical line object in the document.
    InitWCB(&wcb, 0, 0, 0);
    err = cmiCommandDispatch(
       wdDrawLine, 0, wcb.cArgs, wcb.wdoprArgs,
       (LPWDOPR)&wcb.wdoprReturn);
    if(err) { ShowCMIError(err); return; }
    
    // Call wdFormatDrawingObject - Changes shape & position of the line.
    InitWCB(&wcb, 0, 0, 0);
    AddShortDlgField(&wcb, 40, fidHorizontalPos, INPUT);
    AddShortDlgField(&wcb, 40, fidVerticalPos, INPUT);
    AddShortDlgField(&wcb, 180, fidHeight, INPUT);
    AddShortDlgField(&wcb, 80, fidWidth, INPUT);
    err = cmiCommandDispatch(
       wdFormatDrawingObject, 2, wcb.cArgs, wcb.wdoprArgs,
       (LPWDOPR)&wcb.wdoprReturn);
    if(err) { ShowCMIError(err); return; }
    
    // *** Draw another line with opposite height & width.
    // Call wdDrawLine - Creates a graphical line object in the document.
    InitWCB(&wcb, 0, 0, 0);
    err = cmiCommandDispatch(
       wdDrawLine, 0, wcb.cArgs, wcb.wdoprArgs,
       (LPWDOPR)&wcb.wdoprReturn);
    if(err) { ShowCMIError(err); return; }
    
    // Call wdFormatDrawingObject - Changes shape & position of the line.
    InitWCB(&wcb, 0, 0, 0);
    AddShortDlgField(&wcb, 40, fidHorizontalPos, INPUT);
    AddShortDlgField(&wcb, 40, fidVerticalPos, INPUT);
    AddShortDlgField(&wcb, 80, fidHeight, INPUT);
    AddShortDlgField(&wcb, 180, fidWidth, INPUT);
    err = cmiCommandDispatch(
       wdFormatDrawingObject, 2, wcb.cArgs, wcb.wdoprArgs,
       (LPWDOPR)&wcb.wdoprReturn);
    if(err) { ShowCMIError(err); return; }
    
    // Sleep for a little bit so user can see what happened.
    ::Sleep(1000);
    
    ::MessageBox(NULL, "Cmi-Test: Click me to continue...",
      "Notice", MB_SETFOREGROUND);
    
    // Close Word.
    ::PostThreadMessage(pi.dwThreadId, WM_QUIT, 0, 0);
    
    // Wait for Word to exit.
    ::WaitForSingleObject(pi.hProcess, 2000);
    
    // Release handles to the process (these come from CreateProcess).
    // Note: The process will not close later, unless these are closed.
    ::CloseHandle(pi.hThread);
    ::CloseHandle(pi.hProcess);
    
    // Done.
    SetForegroundWindow();
    
    ::MessageBox(NULL, "Cmi-Test: All Done.", "Notice",
      MB_SETFOREGROUND); 


  6. Just before your button handler function, add this function:
    
          void ShowCMIError(int err)
          {
          static char txt[1024];
          char *msg;
    
          // Check for general CMI errors.
          if((err >= 5001) && (err <= 5034)) {
             msg = "General CAPI error.\n"
                   "More information: This code is defined and described in
                   WdError.h\n"
                   "Suggestion: Lookup error in WdError.h, and modify
                   parameters to cmiCommandDispatch().";
          }
          // Check for Win32Cmi.dll errors.
          else {
             switch (err) {
                case  0: msg = "Success"; break;
                case -1: msg = "Microsoft Word is invisible, not running, or"
                              " hasn't completly loaded.\n"
                              "Suggestion: Start Microsoft Word before"
                              "executing.";
                         break;
                case -2: msg = "Win32Cmi.dll's shared memory block not big"
                              " enough.\n"
                              "More Information: The default size is 131072.\n"
                              "Suggestion: Recompile the win32cmi project with"
                              " a bigger buffer.";
                         break;
                case -3: msg = "Cannot obtain Microsoft Word's thread Id.";
                         break;
                case -4: msg = "Win32Cmi.dll could not install its
                               WH_GETMESSAGE"
                              " hook.";
                         break;
                case -5: msg = "The current instance of Microsoft Word was not"
                             " the original instance hooked by Win32Cmi.dll.\n"
                             "More Information: Win32Cmi.dll's hooked thread"
                             " id doesn't match the current Microsoft Word\n"
                             " thread id. Win32Cmi.dll is only designed to"
                             " work with a specific instance of Microsoft"
                             " Word.\n"
                             "Suggestion 1: Use CMI with only one Microsoft"
                             " Word instance.\n"
                             "Suggestion 2: Use OLE Automation as an"
                             " alternative to CMI.\n"
                             "Suggestion 3: Modify the Win32Cmi project"
                             " yourself to support multiple instances of"
                             " Microsoft Word.";
                         break;
                case -6: msg = "Win32Cmi.dll's call to CreateEvent() failed.";
                         break;
                case -7: msg = "Win32Cmi.dll's call to CreateFileMapping()"
                               " failed.";
                         break;
                case -8: msg = "Win32Cmi.dll's call to MapViewOfFile()
                               failed.";
                         break;
                            case -9: msg = "Timeout (default=60s).
                                            Win32Cmi.dll's call to"
                             " WaitForSingleObject() did not return "
                             "WAIT_OBJECT_0.\n"
                             "Suggestion 1: Try calling on a separate "
                             " thread.\n"
                             "Suggestion 2: Increase time-out value if "
                             "applicable.\n"
                             "Suggestion 3: Win32Cmi uses hooks for its "
                             "implementation. If you also use hooks, or \n"
                             " synchronization objects, watch out for deadlock"
                             " scenarios.";
                         break;
                default: msg = "Unknown error.\n"
                              "More Information: Error was not a CMI error.\n"
                              "Suggestion: Check for error and description in "
                              "WdError.h";
                         break;
             }
          }
    
          sprintf(txt, "CMI Error %d\n%s", err, msg);
          ::MessageBox(NULL, txt, "CMI Error", MB_SETFOREGROUND);
          } 


  7. Copy Win32cmi.lib from the Word Developer's Kit disk to your project directory.


  8. On the Link tab in the Project Settings dialog box, add Win32cmi.lib to the "Object/Library modules" section.


  9. Copy Win32cmi.dll from the Word Developer's Kit disk to your Windows directory.


  10. Compile and test.


When your application runs, click your dialog button. You should see Microsoft Word start and the two lines added to the default document.


REFERENCES

Microsoft Word Developer's Kit: ISBN:1-55615-880-7

For additional information, please see the following article(s) in the Microsoft Knowledge Base:

Q183165 What Do the cmiCommandDispatch() Errors Mean?

© Microsoft Corporation 1998, All Rights Reserved.
Contributions by Joe Crump, Microsoft Corporation

Additional query words: wdCommandDispatch wordbasic

Keywords : kbnokeyword kbVC500 kbWord kbGrpDSO kbOffice2000
Version : WINDOWS:95,97; winnt:5.0
Platform : WINDOWS winnt
Issue type : kbhowto


Last Reviewed: June 1, 1999
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.