home *** CD-ROM | disk | FTP | other *** search
/ Windows 95 v2.4 Fix / W95-v2.4fix.iso / ACADWIN / ADS / WIN / DDEWIN.C < prev    next >
Encoding:
C/C++ Source or Header  |  1995-02-08  |  53.4 KB  |  1,846 lines

  1. /*
  2.  
  3.  
  4.         DdeWin
  5.  
  6.         Windows Dynamic Data Exchange Client Functions
  7.           Interface to Microsoft's DDEML.DLL
  8.           (DDE Management Library)
  9.  
  10.           by Phil Ford
  11.      ________________________________________________________________________
  12.  
  13.       (C) Copyright 1990-1994 by Autodesk, Inc.
  14.  
  15.       Permission to use, copy, modify, and distribute this software and its
  16.       documentation for any purpose and without fee is hereby granted.  
  17.  
  18.       THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. 
  19.       ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF 
  20.       MERCHANTABILITY ARE HEREBY DISCLAIMED.                                
  21.      ________________________________________________________________________
  22.  
  23.  
  24. */
  25.  
  26.  
  27. /* See DDE.DOC.
  28.    This is the DDE Client library that interfaces to ddeml.dll.
  29.    This module also manages DDE channels by integer handle to 
  30.    allow use by AutoLISP.
  31. */
  32.  
  33. #include "options.h"
  34. #include <stdlib.h>
  35. #include <stdio.h>
  36. #include <string.h>
  37. #include <memory.h>
  38. #include <malloc.h>
  39.  
  40. #include "winutil.h"
  41. #include "ddewin.h"
  42. #include "ddedlg.h"
  43. #include <shellapi.h>
  44. #include <stdarg.h>
  45.  
  46. extern HWND adsw_hwndAcad;            /* AutoCAD window handle */       
  47.  
  48.  
  49. #define MSG_TITLE "AutoCAD ADS"
  50. #define QCONV_WORKING 1               /* Is our code to check for a valid DDE
  51.                                          channel working */     
  52.  
  53. static DWORD idInst = 0;              /* DDE id for DDEML library */
  54.  
  55. #ifndef EOS
  56. #define EOS '\0'
  57. #endif
  58.  
  59. static char profMain[] = "AutoCAD DDE";
  60. static char profDdeApp[] = "DDE App";
  61. static char profDdeTopic[] = "DDE Topic";
  62. static char profDdePath[] = "DDE Path";
  63. static char profAdvise[] = "DDE Advise";
  64. static char profFile[] = "ACAD_ADS.INI";
  65. static char profSpreadsheet[] = "DDE Spreadsheet";
  66. static char profRowSpec[] = "DDE RowSpec";
  67. static char profColSpec[] = "DDE ColSpec";
  68. static char profRangeSpec[] = "DDE RangeSeparator";
  69. static char profOpenCmd[] = "DDE OpenCmd";
  70. static char profDefaultDoc[] = "DDE DefaultDoc";
  71.  
  72. typedef struct tagAPPSFLAGS {
  73.     unsigned regDbFound : 1;          /* This app was found in Windows 
  74.                                          registration database */       
  75.     unsigned defTopic : 1;            /* default topic has been set based
  76.                                          on default path--Quatro only */
  77. } APPSFLAGS;
  78.  
  79. typedef struct tagAPPS {
  80.     char appName[MAXNAME+1];          /* app name without ext or path */        
  81.  
  82.     /* Windows Registration Database names.  NOTE: At this time,
  83.        only Excel has been tested. */
  84.     char className[MAXNAME+1];
  85.  
  86.     char defPath[MAXPATH+120];        /* default exe path and command line */
  87.     char defTopic[MAXPATH+1];         /* default topic (path name) */   
  88.     int apptype;                      /* Cell range descriptor type 
  89.                                          "R1C1:R3C20" or "A1..C20" */   
  90.     char openCmd[40];                 /* DDE command to open a work file.
  91.                                          E.g., for Excel:
  92.                                          [OPEN("shaft.xls")] */ 
  93.     APPSFLAGS flags;
  94. } APPS;
  95.  
  96.  
  97. #define NUM_APPS 3
  98. #define APP_EXCEL 0
  99. #define APP_123W  1
  100. #define APP_QPW   2
  101. APPS ddeApps[NUM_APPS] = {
  102.     { "Excel" , "ExcelWorksheet"    , "c:\\excel\\" , "Sheet1"   , SSEXCEL,
  103.                                                         "[OPEN(\"%1\")]" },
  104.     { "123w"  , "123w"              , "c:\\123w\\"  , "Untitled" , SSLOTUS },
  105.     { "QPW"   , "QuattroProNotebook", "c:\\qpw\\"   , "Notebk1.wb1", SSLOTUS,
  106.                                                         "{FileOpen \"%1\"}" },
  107.     /* RegDB "QuattroProNotebook-shell-open-command-g:\qpw\qpw.exe" */
  108. };
  109.  
  110.  
  111. ddeDATA DdeData = {0};                /* DDE globals and defaults. */
  112.  
  113. USHORT ChannelMap[MAPLEN] = {0};      /* bit map of channel 
  114.                                          numbers in use. */
  115.  
  116. void AppType(int ss_type);
  117.  
  118. /* Global Function Prototypes for ddewin.c */
  119. HDDEDATA CALLBACK DdeCB(UINT uType, UINT uFmt, HCONV hconv, HSZ hsz1, HSZ hsz2,
  120.     HDDEDATA hdata, DWORD dwData1, DWORD dwData2);
  121.  
  122. /* Static Function Prototypes for ddewin.c */
  123. static PDDE DdeCreateChannel(char *app, char *topic);
  124. static PDDE DdeCreateLink(char *AppName, char *TopicName,
  125.                    HCONV hConv, PDDE *List);
  126. static PDDE DdeRemoveLink(PDDE pdde, int SendTermFlag, PDDE *List);
  127. static int DdeAppIndex(char *AppName);
  128. static int DdeExpandOpenCmd(char *openCmd, char *topic, char *expCmd);
  129. static int findRegValue(char *key1, char *key2, char *key3, char *key4, 
  130.                 char *szValue);
  131.  
  132. int debugPrintf(char *format, ...);
  133.  
  134.  
  135. /* Initialize the Dde Manager.   Pointer to structure
  136.    of DDE global data is returned.
  137.    ProgName is our name, used by other apps to get our
  138.    attention; e.g. "AutoCAD" or "DdeApp".
  139.    "idinst" is 0 if DdeInitialize hasn't been called elsewhere;
  140.    otherwise, it's the value.
  141.    Call DdeQuit() to send TERMINATE msg to apps,
  142.    destroy DDE channel windows, and free all memory. 
  143. */
  144. ddeDATA *DdeInit(char *ProgName, HANDLE hInst, DWORD idinst)
  145. {
  146.     int appIdx;
  147.  
  148.     if (ProgName)
  149.         strzcpy(DdeData.progname, ProgName, MAXNAME);
  150.     else 
  151.         strcpy(DdeData.progname, profFile);
  152.     strcpy(DdeData.ProfFile, DdeData.progname);
  153.     strcat(DdeData.ProfFile, ".ini");
  154.  
  155.     DdeData.hInst = hInst;
  156.     DdeData.flags.advise = ADVISEFLAG;
  157.  
  158.     /* Take these from the resource file so they can be localized 
  159.        to the commands used by the local spreadsheet */
  160.  
  161.     LoadString(hInst, IDS_SS_ROW,      DdeData.rowspec,   MAXSPEC);
  162.     LoadString(hInst, IDS_SS_COL,      DdeData.colspec,   MAXSPEC);
  163.     LoadString(hInst, IDS_SS_RANGE,    DdeData.rangespec, MAXSPEC);
  164.     LoadString(hInst, IDS_SS_OPEN,     DdeData.opencmd,   MAXCMD);
  165.     LoadString(hInst, IDS_SS_DOCUMENT, DdeData.worksheet, MAXNAME);
  166.     
  167.     if (_stricmp (DdeData.rowspec, /*MSG0*/"R") == 0 &&
  168.         _stricmp (DdeData.colspec, /*MSG0*/"C") == 0 &&
  169.         _stricmp (DdeData.rangespec, /*MSG0*/":") == 0 &&
  170.         _stricmp (DdeData.opencmd, /*MSG0*/"[OPEN(\"%1\")]") == 0 &&
  171.         _stricmp (DdeData.worksheet, /*MSG0*/"Sheet1") == 0)
  172.     {
  173.         DdeData.spreadsheet = 0;   /* Microsoft Excel (english) */
  174.     } else {
  175.         DdeData.spreadsheet = 1;   /* Other spreadsheet */
  176.     }
  177.         
  178.  
  179.     /* Find paths and open commands by scanning registration database
  180.        using the ddeApps array. */
  181.     appIdx = DdeFindApps();
  182.  
  183.     /* Get user's DDE choices from INI file */
  184.     if (DdeGetProfile(NULL))
  185.         DdeSetApp(DdeData.app, FALSE);
  186.     else if (appIdx >= 0) {
  187.         strcpy(DdeData.app, ddeApps[appIdx].appName);
  188.         strcpy(DdeData.path, ddeApps[appIdx].defPath);
  189.         DdeSetApp(DdeData.app, TRUE);
  190.     } else
  191.         /* If none, set up defaults for DDE dialog to Excel. */
  192.         DdeSetApp(ddeApps[0].appName, TRUE);
  193.  
  194.     if (idinst == 0) {
  195.         /* Change APPCMD_CLIENTONLY to APPCMD_STANDARD to function as 
  196.            client and server. */
  197.         DWORD afCmd = APPCMD_CLIENTONLY;
  198.         if (DdeInitialize(&idInst, (PFNCALLBACK)MakeProcInstance(
  199.                 (FARPROC)DdeCB, hInst), afCmd, 0L))
  200.             return NULL;
  201.         DdeData.flags.init = TRUE;
  202.     } else {
  203.         idInst = idinst;
  204.         DdeData.flags.init = FALSE;
  205.     }
  206.     return &DdeData;
  207. }
  208.  
  209. /* Program about to quit.  Unregister all services, free memory */
  210.  
  211. void
  212. DdeQuit(void)
  213. {
  214.     DdeRemoveAll(TRUE);
  215.     if (DdeData.flags.init && idInst != 0) {
  216.         DdeUninitialize(idInst);
  217.         idInst = 0;
  218.     }
  219. }
  220.  
  221. int DdeSendAcadCmd(char *cmd)
  222. {
  223.     static PDDE pAcadDde = 0;
  224.  
  225.     /* Interrupt AutoCAD to execute our function */
  226.         if (!pAcadDde) {
  227.             HCONV hConv = 0;
  228.             static char AppName[] = "AutoCAD.r13.DDE";
  229.             static char TopicName[] = "system";
  230.  
  231.             pAcadDde = DdeCreateChannel(AppName, TopicName);
  232.             if (pAcadDde) {
  233.                 pAcadDde->hAppName = 
  234.                     DdeCreateStringHandle(idInst, AppName, CP_WINANSI);
  235.                 pAcadDde->hTopicName = 
  236.                     DdeCreateStringHandle(idInst, TopicName, CP_WINANSI);
  237.                 if (pAcadDde->hAppName && pAcadDde->hTopicName) {
  238.                     hConv = DdeConnect(idInst, pAcadDde->hAppName, 
  239.                         pAcadDde->hTopicName, (LPVOID)NULL);
  240.                     pAcadDde->hConv = hConv;
  241.                 }
  242.             }
  243.             /* Msg is received by our DdeMsgInInit function before
  244.                returning to here. */
  245.             if (!hConv) {
  246.                 DdeTerminate(pAcadDde, FALSE);
  247.                 pAcadDde = 0;
  248.                 MessageBox(NULL, "can't talk to acad\n", "???", MB_OK);
  249.             }
  250.         }
  251.         if (pAcadDde) {
  252.             DdeExec(pAcadDde, cmd);
  253.         }
  254.  
  255.     return 0;
  256. }
  257.  
  258. #if USE_DDEDLG
  259.  
  260. /* Set defaults for DDE conversation init dialog.  The appexe is
  261.    the command line used to start the app and have it load a 
  262.    work file.  Set it to NULL to mean search the Windows Registration
  263.    Database for the application path.  For the default work 
  264.    file, no command line should
  265.    follow the exe name.  For work files in the current directory,
  266.    no path is needed for the work file.  Eg., the first 2 of
  267.    the following would attempt to start Excel and use the default
  268.    work file, "Sheet1.xls".
  269.  
  270.         "Excel"         (Excel directory on PATH)
  271.         "c:\\excel\\excel"     (NOT on PATH)
  272.         "c:\\excel\\excel shaft.xls"     (xls file in current dir)
  273.         "c:\\excel\\excel c:\\excel\\shaft.xls"
  274.    
  275. */
  276.  
  277. void DdeDefaults(char *app, char *topic, char *appexe)
  278. {
  279.     int setPath = FALSE;
  280.  
  281.     if (appexe != NULL && appexe[0])
  282.         strzcpy(DdeData.path, appexe, sizeof(DdeData.path)-1);
  283.     else
  284.         setPath = TRUE;
  285.     DdeSetApp(app, setPath);
  286.     if (setPath && topic != NULL && topic[0] && DdeData.path[0]) {
  287.         if (!DdeIsDefTopic(topic)) {
  288.             strcat(DdeData.path, " ");
  289.             strcat(DdeData.path, topic);
  290.         }
  291.     }
  292.     if (topic != NULL && topic[0])
  293.         strzcpy(DdeData.topic, topic, sizeof(DdeData.topic)-1);
  294. }
  295.  
  296.  
  297.  
  298.  
  299.  
  300. /* Try to link to default application and work file.  If fail,
  301.    put up a dialog asking for app and topic name, try to 
  302.    start app and link up with DDE.  Set forcedlg TRUE to enter
  303.    the dialog immediately without the attempt to find a 
  304.    channel */
  305.  
  306. PDDE DdeDlgStart(HWND hwnd, int forcedlg)
  307. {
  308.     PDDE pdde;
  309.  
  310.     /* First see if we have a channel already */
  311.     if (!forcedlg) {
  312.         pdde = DdeAppInit(DdeData.app, DdeData.topic, DdeData.path);
  313.         if (pdde != NULL)
  314.             return pdde;
  315.     }
  316.  
  317.     /* Prompt user with dialog */
  318.     for (pdde = NULL; pdde == NULL; ) {
  319.         if (!DdeDialog(hwnd))
  320.             return NULL;
  321.         pdde = DdeAppInit(DdeData.app, DdeData.topic, DdeData.path);
  322.         if (pdde == NULL) {
  323.             char msg[80];
  324.  
  325.             /* App started, channel init failed */
  326.             sprintf(msg, "Unable to open DDE channel to %s|%s", 
  327.                         DdeData.app, DdeData.topic);
  328.             MsgBox(DdeData.progname, msg);
  329.         }
  330.     }
  331.     return pdde;
  332. }
  333. #endif  /* USE_DDEDLG */
  334.  
  335.  
  336.  
  337.  
  338.  
  339. /* Start up an application and have it load a work file.  Initialize
  340.    a DDE channel to the app and topic.  Returns a pointer to the DDE 
  341.    channel object.  AppPath is the application name.  Set AppExe
  342.    to NULL to search the Windows Registration Database for 
  343.    application path. 
  344.    E.g: 
  345.    pdde = DdeAppInit("Excel", "Sheet1", "c:\\excel\\excel");
  346.    pdde = DdeAppInit("Excel", "Sheet1", NULL);
  347. */
  348. PDDE DdeAppInit(char *AppName, char *TopicName, char *AppExe)
  349. {
  350.     int status, started;
  351.     PDDE pdde = NULL;
  352.     char *commandLine;
  353.  
  354.     /* See if we have a channel already. */
  355.     pdde = DdeChannel(AppName, TopicName);
  356.     if (pdde != NULL)
  357.         return pdde;
  358.  
  359.     /* Set defaults, search Windows Registration DB */
  360.     DdeDefaults(AppName, TopicName, AppExe);
  361.  
  362.     if (!DdeInitSystem(AppName, TopicName)) {
  363.         commandLine = NULL;
  364.         /* App not running yet--start it with the 
  365.            command line, AppExe, or default. */
  366.         if (AppExe == NULL || AppExe[0] == EOS) {
  367.             AppExe = DdeData.app;
  368.             if (!DdeIsDefTopic(TopicName))
  369.                 /* Not default topic */
  370.                 commandLine = TopicName;
  371.         } 
  372.         status = StartApp(AppExe, commandLine);
  373.         if (status < 32)
  374.             return NULL;
  375.         started = TRUE;
  376.     } else
  377.         started = FALSE;
  378.  
  379.     pdde = DdeInitiate(AppName, TopicName);
  380.     if (pdde == NULL && started) {
  381.        /* We started app but couldn't link up to topic--give 
  382.           user a chance to open topic file. */
  383.         DdeInitSystem(AppName, TopicName);
  384.         return DdeInitiate(AppName, TopicName);
  385.     } else
  386.         return pdde;
  387. }
  388.  
  389.  
  390. /* See if the application is running but the work file is 
  391.    not loaded.  Tell app to open work file or put up message 
  392.    box telling user to open work file.  NOTE: this is application
  393.    specific.  Excel accepts "[OPEN("filename")]" as a command
  394.    string to open a spreadsheet file.
  395. */
  396. int DdeInitSystem(char *AppName, char *TopicName)
  397. {
  398.     PDDE pdde;
  399.     char *openCmd;
  400.     int appIdx;
  401.  
  402.     pdde = DdeInitiate(AppName, "System");
  403.     if (pdde != NULL) {
  404.         char omsg[100];
  405.         appIdx = DdeAppIndex(AppName);
  406.         openCmd = DdeData.opencmd;
  407.         if (! openCmd[0])
  408.                 openCmd = ddeApps[appIdx].openCmd;
  409.         if (openCmd[0]) {
  410.             char estr[144];
  411.  
  412.             /* Open topic file with command to Excel, eg:
  413.                [OPEN("%1")] */
  414.             DdeExpandOpenCmd(openCmd, TopicName, estr);
  415.             DdeExec(pdde, estr);
  416.             DdeTerminate(pdde, TRUE);
  417.         } else {
  418.             DdeTerminate(pdde, TRUE);
  419.             sprintf(omsg, "Open the file %.40s in %s\nand then click OK", 
  420.                                                 TopicName, AppName);
  421.             MsgBox(MSG_TITLE, omsg);
  422.         }
  423.         return TRUE;
  424.     } else
  425.         return FALSE;
  426. }
  427.  
  428.  
  429. /* Check if a channel to app and topic is already open.  If not, 
  430.    try to start one.  Copies app and topic into the defaults
  431.    for the initiate dialog.
  432. */
  433. PDDE DdeChannel(char *app, char *topic)
  434. {
  435.     PDDE pdde;
  436.  
  437.     if ((pdde = DdeFindChnl(app, topic)) != NULL)
  438.         /* Have a channel open already. */
  439.         return pdde;
  440.     return DdeInitiate(app, topic);
  441. }
  442.  
  443.  
  444.  
  445. /* Start an application */
  446.  
  447. int StartApp(char *appname, char *commline)
  448. {
  449.     char buf[MAXPATH+120];
  450.  
  451.     strcpy(buf, appname);
  452.     if (commline != NULL && commline[0] != 0) {
  453.         strcat(buf, " ");
  454.         strcat(buf, commline);
  455.     }
  456.     return WinExec(buf, SW_SHOWNORMAL);
  457. }
  458.  
  459.  
  460.  
  461. /* Initiate a DDE link on application and topic names.
  462.    Sets channel number to be used on subsequent DDE
  463.    functions.  0 return means not enough memory, or other
  464.    window creation problem.  When done with this channel, 
  465.    call DdeTerminate().  
  466. */
  467. PDDE DdeInitiate(char *AppName, char *TopicName)
  468. {
  469.     PDDE pdde;
  470.     char MsgLine[80];
  471.     HCONV hConv;
  472.     HANDLE hInst = DdeData.hInst;
  473.  
  474.     pdde = DdeCreateChannel(AppName, TopicName);
  475.     if (!pdde)
  476.         return 0;
  477.  
  478.     if (AppName != NULL && AppName[0] != EOS) {
  479.         /* Make sure apptype is set, but don't overwrite other
  480.            settings with defaults (FALSE) */
  481.         DdeSetApp(AppName, FALSE);
  482.         pdde->apptype = DdeData.apptype;
  483.     }
  484.     if (!AppName[0] || !TopicName[0])
  485.         pdde->ChannelState.wildcard = TRUE;
  486.  
  487.     pdde->hAppName = DdeCreateStringHandle(idInst, AppName, CP_WINANSI);
  488.     if (pdde->hAppName == 0) {
  489.         strhandle_msg(AppName);
  490.         return NULL;
  491.     }    
  492.  
  493.     pdde->hTopicName = DdeCreateStringHandle(idInst, TopicName, CP_WINANSI);
  494.     if (pdde->hTopicName == 0) {
  495.         strhandle_msg(TopicName);
  496.         return NULL;
  497.     }
  498.  
  499.     hConv = DdeConnect(idInst, pdde->hAppName, pdde->hTopicName, (LPVOID)NULL);
  500.     pdde->hConv = hConv;
  501.  
  502.     /* Msg is received by our DdeMsgInInit function before
  503.        returning to here. */
  504.     if (!hConv) {
  505.         if (DdeData.flags.show) {
  506.             strcpy(MsgLine, AppName);
  507.             strcat(MsgLine, " | ");
  508.             strcat(MsgLine, TopicName);
  509.             strcat(MsgLine, " not responding.");
  510.             MsgBox("DDE Initiate", MsgLine);
  511.         }
  512.         DdeTerminate(pdde, FALSE);
  513.         return NULL;
  514.     }
  515.     return pdde;
  516. }
  517.  
  518.  
  519. /* Set defaults based on spreadsheet application name.  Try
  520.    to find exe path in Windows Registration Database.  Returns
  521.    TRUE if app is found in Reg DB.  Set pathFlag TRUE if
  522.    you want the default names and paths to be reset.
  523. */
  524. int DdeSetApp(char *AppName, int pathFlag)
  525. {
  526.     int stat = FALSE;
  527.     int appIdx;
  528.     char *className;
  529.  
  530.     if (DdeData.app != AppName)
  531.         strzcpy(DdeData.app, AppName, sizeof(DdeData.app)-1);
  532.     appIdx = DdeAppIndex(AppName);
  533.  
  534.     DdeData.appIdx = appIdx;
  535.     DdeData.apptype = ddeApps[appIdx].apptype;
  536.     AppType(DdeData.apptype);
  537.     if (pathFlag) {
  538.         className = ddeApps[appIdx].className;
  539.         if (!ddeApps[appIdx].flags.regDbFound) {
  540.             if (DdeData.path[strlen(DdeData.path)-1] == '\\')
  541.                 strcat(DdeData.path, AppName);
  542.         } else
  543.             strcpy(DdeData.path, ddeApps[appIdx].defPath);
  544.  
  545.         strcpy(DdeData.topic, ddeApps[appIdx].defTopic);
  546. #ifdef DEBUG
  547.         debugPrintf("DdeSetApp App: %s, Topic %s, Path: %s, Type: %d\r\n.",
  548.                 DdeData.app, 
  549.                 DdeData.topic, 
  550.                 DdeData.path, 
  551.                 DdeData.apptype);
  552. #endif
  553.     }
  554.     return appIdx;
  555. }
  556.  
  557. /* Return ddeApps array index of a certain app name */
  558.  
  559. static int DdeAppIndex(char *AppName)
  560. {
  561.     int appIdx;
  562.  
  563.     if (!_strnicmp(AppName, "123", 3)) {
  564.         /* Lotus' "123w" */
  565.         appIdx = APP_123W;
  566.     } else if (!_strnicmp(AppName, "qua", 3)) {
  567.         /* Borland's "Quattro Pro" */
  568.         appIdx = APP_QPW;
  569.     } else if (!_strnicmp(AppName, "qpw", 3)) {
  570.         /* Borland's "Quattro Pro" */
  571.         appIdx = APP_QPW;
  572.     } else {
  573.         /* Microsoft's "Excel" */
  574.         appIdx = APP_EXCEL;
  575.     }
  576.     return appIdx;
  577. }
  578.  
  579.  
  580. /* Terminate any open DDE channels with remote processes.
  581.    Set SendTermFlag TRUE to actually send a terminate message, FALSE
  582.    to just remove our link in the DDE Manager list. 
  583. */
  584. int DdeRemoveAll(int SendTermFlag)
  585. {
  586.     PDDE NextDde;
  587.     PDDE pdde;
  588.     int ItemCnt = 0;
  589.     int ChCnt = 0;
  590.  
  591.     for (pdde = DdeData.ChnlList; pdde != NULL; pdde = NextDde) {
  592.         NextDde = pdde->next;
  593.         ++ChCnt;
  594.         DdeTerminate(pdde, SendTermFlag); /* send terminate if not
  595.                                       sent already, free remote 
  596.                                       data */
  597.     }
  598.  
  599.     DdeData.CurChnl = NULL;
  600.  
  601.     /*    
  602.      * RemoveText(&TopicLst, NULL);
  603.      * RemoveText(&SysItemList, NULL);
  604.      */
  605.     return ChCnt;
  606. }
  607.  
  608.  
  609. /* Get the current channel data structure and verify conversation
  610.    still alive */
  611. PDDE DdeGetCurChnl(void)
  612. {
  613.     if (DdeData.CurChnl == NULL)
  614.         return NULL;
  615. #if QCONV_WORKING
  616.     if (!DdeCheckConv(DdeData.CurChnl->hConv))
  617.         return NULL;
  618. #endif
  619.     return DdeData.CurChnl;
  620. }
  621.  
  622.  
  623. /* Is this conversation still alive? */
  624. int 
  625. DdeCheckConv(HCONV hConv)
  626. {
  627.     CONVINFO ci;
  628.     WORD wError;
  629.  
  630.     if (hConv == 0)
  631.         return FALSE;
  632. #if QCONV_WORKING
  633.     if (DdeQueryConvInfo(hConv, QID_SYNC, &ci) == 0) {
  634.         wError = DdeGetLastError(idInst);
  635.         if (wError == DMLERR_NO_CONV_ESTABLISHED) {
  636. #ifdef CV
  637.             MsgBox(MSG_TITLE, "DDE Conversation closed");       
  638. #endif
  639.             return FALSE;
  640.         } else
  641.             return TRUE;
  642.     }
  643. #if 0
  644.     /* This wasn't reliable in 2/92 */
  645.     if (ci.wStatus & ST_TERMINATED || !(ci.wStatus & ST_CONNECTED))
  646.         return FALSE;
  647. #endif
  648. #endif  /* QCONV_WORKING */
  649.     return TRUE;
  650. }
  651.  
  652.  
  653. /* Build DDE text data from a linked list of text, where
  654.    NumCol is the number of columns (can be 1).   Call FreeFar
  655.    when done with the returned pointer.  E.g., in 2 Cols,
  656.                 "First Text",
  657.                 "Text 2",
  658.                 "Text 3", 
  659.                 "Text 4"
  660.  
  661.                 First Text<TAB>Text 2<CR><LF>
  662.                 Text 3<TAB>Text 4<CR><LF>
  663.                 NULL
  664.    Globals FldSepStr and RecSepStr can be changed before calling
  665.    this, if different separators are desired.
  666. */
  667. PHDATA DdeFormatList(PLINK TextList, int NumCol, ULONG *DataLength)
  668. {
  669.     PLINK Link;
  670.     ULONG DataLen = 0L;
  671.     PHDATA CellDataStr;
  672.     PHDATA ptr;
  673.     int cCol = 1;
  674.     int Extra = 0;
  675.     int EndingTab = FALSE;
  676.     int FsepLen, RsepLen;
  677.  
  678.     if (!TextList)
  679.         return NULL;
  680.     if (NumCol == 0)
  681.         NumCol = 0xffff;              /* all cols, no rows
  682.                                       text<TAB>text<TAB>text<TAB>text */
  683.     FsepLen = strlen(FldSepStr);
  684.     RsepLen = strlen(RecSepStr);
  685.     for (Link = TextList; Link != NULL; Link = Link->next) {
  686.         if (cCol < NumCol) {
  687.             Extra = FsepLen;          /* add tab */
  688.             ++cCol;
  689.         }
  690.         else {
  691.             Extra = RsepLen;          /* Add CrLf */
  692.             cCol = 1;
  693.         }
  694.         DataLen += (strlen(Link->text) + Extra);
  695.     }
  696.     ++DataLen;                        /* terminating NULL */
  697.     ptr = CellDataStr = AllocHuge(DataLen, FALSE);
  698.     if (!CellDataStr)
  699.         return NULL;
  700.  
  701.     cCol = 1;
  702.     for (Link = TextList; Link != NULL; Link = Link->next) {
  703.         strcpy(ptr, Link->text);
  704.         ptr += strlen(Link->text);
  705.         if (cCol < NumCol) {
  706.             strcpy(ptr, FldSepStr);
  707.             ptr += FsepLen;
  708.             ++cCol;
  709.             EndingTab = TRUE;
  710.         }
  711.         else {
  712.             cCol = 1;
  713.             strcpy(ptr, RecSepStr);
  714.             ptr += RsepLen;
  715.             EndingTab = FALSE;
  716.         }
  717.     }
  718.     if (EndingTab)                    /* all cols, no rows: remove trailing TAB. */
  719.         --ptr;
  720.     *ptr++ = 0;
  721.     *DataLength = ptr - CellDataStr;
  722.     return CellDataStr;
  723. }
  724.  
  725.  
  726.  
  727. /* Terminate a DDE channel.  Inform the remote app, remove
  728.    link from DdeData.ChnlList.  Fix globals DdeData.CurChnl and 
  729.    DdeData.channel. 
  730. */
  731. void DdeTerminate(PDDE pdde, int SendTermFlag)
  732. {
  733.     PDDE Neighbor;
  734.     int stat = 1;
  735.     int channel = 0;
  736.  
  737.     if (!pdde)
  738.         return;
  739.  
  740.     channel = pdde->channel;
  741.     if (pdde->hConv && SendTermFlag) {  /* Client cancel */
  742.         if (pdde->ChannelState.advise_on)
  743.             if (pdde->item)
  744.                 DdeUnAdvise(pdde, pdde->item, CF_TEXT);
  745.         stat = DdeSendTerm(pdde);
  746.     }
  747.     if (channel > 0 && channel <= MAXCHNL)
  748.         ClearBit(ChannelMap, channel);
  749.  
  750.     Neighbor = DdeRemoveLink(pdde, SendTermFlag, &DdeData.ChnlList);
  751.     if (pdde == DdeData.CurChnl) {
  752.         DdeData.CurChnl = Neighbor;
  753.         if (DdeData.CurChnl)
  754.             DdeData.ChnlNum = DdeData.CurChnl->channel;
  755.         else
  756.             DdeData.ChnlNum = 0;
  757.     }
  758. }
  759.  
  760.  
  761. /*
  762.             D D E   Transaction Functions   
  763. */
  764.  
  765. /* Request string data on item.  Returns TRUE if data is received.
  766.    Data pointer is pdde->pData.  Call DdeFreeData to free it.
  767. */
  768. DWORD DdeReqString(PDDE pdde, char *item)
  769. {
  770.     DWORD stat;
  771.  
  772.     stat = DdeSend(pdde, item, XTYP_REQUEST, 0L, NULL, CF_TEXT, 0);
  773.     return stat;
  774. }
  775.  
  776.  
  777. /* Request data on item.  Returns TRUE if data is received.
  778.    Data pointer is pdde->pData.  Call DdeFreeData to free it.
  779. */
  780. DWORD DdeRequest(PDDE pdde, char *item, UINT uFmt, ULONG WaitTime)
  781. {
  782.     DWORD stat;
  783.  
  784.     stat = DdeSend(pdde, item, XTYP_REQUEST, 0L, NULL, uFmt, WaitTime);
  785.     return stat;
  786. }
  787.  
  788.  
  789.  
  790. /* Force string data on server.  Returns DDE_FACK if OK. 
  791. */
  792. DWORD DdePokeString(PDDE pdde, char *item, char *pString)
  793. {
  794.     return DdePoke(pdde, item, (long)strlen(pString)+1, pString, CF_TEXT);
  795. }
  796.  
  797.  
  798.  
  799. /* Force data on server.
  800. */
  801. DWORD DdePoke(PDDE pdde, char *item, ULONG DataLen, char *pData, UINT uFmt)
  802. {
  803.     DWORD stat;
  804.  
  805.     stat = DdeSend(pdde, item, XTYP_POKE, DataLen, pData, uFmt, ACKWAIT);
  806.     return stat;
  807.  
  808. }
  809.  
  810.  
  811.  
  812. /* Execute a command in the remote program. 
  813. */
  814. DWORD DdeExec(PDDE pdde, char *pString)
  815. {
  816.     DWORD stat;
  817.     ULONG DataLen;
  818.  
  819.     DataLen = strlen(pString) + 1;
  820.     stat = DdeSend(pdde, NULL, XTYP_EXECUTE, DataLen, pString,
  821.                    CF_TEXT, ACKWAIT);
  822.     return stat;
  823. }
  824.  
  825.  
  826. /* Send the TERMINATE message--all done with channel. hConv
  827.    is NULL in this case only.  Returns TERMOK if remote returned
  828.    TERMINATE msg, 0 otherwise. 
  829. */
  830. DWORD DdeSendTerm(PDDE pdde)
  831. {
  832.     DWORD stat;
  833.  
  834.     if (!pdde)
  835.         return 0;
  836.     if (pdde->ChannelState.localterm) /* already terminated. */
  837.         return 0;
  838.  
  839.     stat = DdeDisconnect(pdde->hConv);
  840.     pdde->ChannelState.localterm = TRUE;
  841.     pdde->hConv = 0;
  842.     return stat;                      /* TERMOK = good response. */
  843. }
  844.  
  845.  
  846.  
  847. /* Set advise hot link function and AutoCAD command for when hot 
  848.    link data comes in.  
  849.    DdeSetAdvise(pdde, "R3C4:R13C6", "[(moddrawing 3 20000) ] ", advfunc);
  850. */
  851. void DdeSetAdvise(PDDE pdde, char *item, char *command, ADVISEFUNC advfunc)
  852. {
  853.     if (pdde == NULL)
  854.         return;
  855.     if (item)
  856.         store_string(&pdde->item, item);
  857.     if (command)
  858.         store_string(&pdde->acadadvise, command);
  859.     if (advfunc)
  860.         pdde->AdviseFunc = advfunc;
  861. }
  862.  
  863.  
  864. /* Start or stop hot link in current channel, using info from
  865.    DdeSetAdvise.
  866. */
  867. void DdeAdviseCur(PDDE pdde, int yes, int linktype)
  868. {
  869.     if (pdde == NULL)
  870.         if (!(pdde = DdeData.CurChnl))
  871.             return;
  872.     if (yes) {
  873.         if (!pdde->ChannelState.advise_on)
  874.             DdeAdvise(pdde, pdde->item, CF_TEXT, linktype);
  875.     } else
  876.         DdeUnAdvise(pdde, pdde->item, CF_TEXT);
  877. }
  878.  
  879.  
  880. /* Set up hot link.  Set NoData TRUE if you want only notice of 
  881.    data change, not data itself.  Set item to NULL to use last
  882.    item used for a POKE message on this channel.  The "advcommand"
  883.    is the string to send to AutoCAD when new data comes in, such 
  884.    as "[import]".
  885. */
  886. DWORD DdeAdvise(PDDE pdde, char *item, UINT uFmt, int linktype)
  887. {
  888.     DWORD stat;
  889.     int Xtype = XTYP_ADVSTART;
  890.     char *pitem;
  891.  
  892.     if (!DdeData.flags.advise)
  893.         return 0;
  894.     if (item && !item[0])
  895.         item = NULL;
  896.     pitem = (item == NULL ? pdde->item : item);
  897.     if (pitem == NULL)
  898.         return 0;
  899.     if (pdde->AdviseFunc == NULL)
  900.         return 0;
  901.     if (linktype != HOTLINK)
  902.         Xtype |= XTYPF_NODATA;
  903.     stat = DdeSend(pdde, pitem, Xtype, 0, NULL, uFmt, ACKWAIT);
  904.     /* ADS stores the "hot link" function handler */
  905.     if (stat)
  906.         pdde->ChannelState.advise_on = TRUE;
  907.     return stat;
  908. }
  909.  
  910.  
  911. /* Remove hot link.  Send NULL item to use the one stored in pdde->item
  912. */
  913. DWORD DdeUnAdvise(PDDE pdde, char *item, UINT uFmt)
  914. {
  915.     DWORD stat;
  916.     char *pitem;
  917.  
  918.     if (item && !item[0])
  919.         item = NULL;
  920.     if (uFmt == 0)
  921.         uFmt = CF_TEXT;
  922.     pitem = (item == NULL ? pdde->item : item);
  923.     if (pitem == NULL)
  924.         return 0;
  925.     stat = DdeSend(pdde, pitem, XTYP_ADVSTOP, 0, NULL, uFmt, ACKWAIT);
  926.     if (stat) {
  927.         pdde->ChannelState.advise_on = FALSE;
  928. #ifdef DEBUG
  929.         debugPrintf("Advise off for %s\r\n", pitem);
  930. #endif
  931.     }
  932.     return stat;
  933.  
  934. }
  935.  
  936.  
  937.  
  938. /*
  939.     Send: Execute  (Xtype = XTYP_EXECUTE)
  940.           Poke     (Xtype = XTYP_POKE)
  941.           Request  (Xtype = XTYP_REQUEST)
  942.           Advise   (Xtype = XTYP_ADVREQ)
  943.           Unadvise (Xtype = XTYP_ADVSTOP)
  944.  
  945.     Returns zero on error.
  946.     For "Request", the data will be in pdde->pData, the handle will
  947.     be returned (from which pdde->pData is derived using DdeAccessData).
  948. */
  949.  
  950. DWORD DdeSend(PDDE pdde, char *ItemName, int Xtype, ULONG DataLen,
  951.               char *pData, UINT uFmt, ULONG WaitTime)
  952. {
  953.     HSZ hszItem = (HSZ)0;
  954.     HDDEDATA hData, hReqData;
  955.     DWORD result;
  956.     DWORD len;
  957.     char *pReq;
  958.  
  959.     if (pdde == NULL)
  960.         return 0;
  961.     if (!IsValidPdde(pdde))
  962.         return 0;
  963.     if (uFmt == 0)
  964.         uFmt = CF_TEXT;
  965.     if (Xtype == XTYP_EXECUTE || ItemName == NULL)
  966.         hszItem = (HSZ)0;
  967.     else {
  968.         hszItem = DdeCreateStringHandle(idInst, ItemName, CP_WINANSI);
  969.         if (hszItem == (HSZ)0)
  970.             return 0;
  971.     }
  972.     if (WaitTime == 0)
  973.         WaitTime = DATAWAIT;
  974.  
  975. #define USE_HDATA 1
  976. #if USE_HDATA
  977.     if (pData != NULL) {
  978.         hData = DdeCreateDataHandle(idInst, pData, DataLen,
  979.                                     (DWORD)0, hszItem, uFmt, 0);
  980.         if (hData == 0)
  981.             return 0;
  982.         DataLen = (ULONG)-1;
  983.     } else
  984.         hData = 0;
  985.  
  986. #endif
  987.     if (pData == NULL)
  988.         DataLen = 0;
  989.  
  990.     /* 
  991.                 Start transaction with DDEML here 
  992.     */
  993. #if USE_HDATA
  994.     hReqData = DdeClientTransaction((LPBYTE)hData, (UINT)-1L, pdde->hConv, 
  995.         hszItem, uFmt, Xtype, WaitTime, (LPDWORD)&result);
  996.     /* DdeFreeDataHandle(hData); done by ddeml */
  997. #else
  998.     hReqData = DdeClientTransaction(pData, DataLen, pdde->hConv, hszItem,
  999.                 uFmt, Xtype, WaitTime, (LPDWORD)&result);
  1000. #endif
  1001.     /* Old method DDE constants for low word of result field (not guaranteed
  1002.        to work in future versions).  The recommended error check now
  1003.        is for a non-0 return from DdeClientTransaction. 
  1004.     DDE_FACK            0x8000
  1005.     DDE_FBUSY           0x4000
  1006.     DDE_FDEFERUPD       0x4000
  1007.     DDE_FACKREQ         0x8000
  1008.     DDE_FRELEASE        0x2000
  1009.     DDE_FREQUESTED      0x1000
  1010.     DDE_FACKRESERVED    0x3ff0
  1011.     DDE_FADVRESERVED    0x3fff
  1012.     DDE_FDATRESERVED    0x4fff
  1013.     DDE_FPOKRESERVED    0xdfff
  1014.     DDE_FAPPSTATUS      0x00ff
  1015.     DDE_FNOTPROCESSED   0x0000
  1016.     */
  1017.  
  1018.     if (hszItem != (HSZ)0)
  1019.         DdeFreeStringHandle(idInst, hszItem);
  1020.  
  1021.     if (Xtype == XTYP_REQUEST) {
  1022.         pdde->DataSize = 0;
  1023.         pdde->pData = NULL;
  1024.         if (hReqData == (HDDEDATA)0)
  1025.             return 0;
  1026.         pReq = DdeAccessData(hReqData, &len);
  1027.         if (len == 0 || pReq == NULL)
  1028.             return 0;
  1029.  
  1030.         pdde->pData = pReq;
  1031.         pdde->DataSize = len;
  1032.         pdde->hReqData = hReqData;
  1033.     }
  1034.     return hReqData;
  1035. }
  1036.  
  1037.  
  1038.  
  1039.  
  1040. /*
  1041.                 DDE Search List Functions
  1042. */
  1043.  
  1044. /* Is this channel number a valid DDE channel? 
  1045. */
  1046. int IsValidChannel(int Chnl)
  1047. {
  1048.     return CheckBit(ChannelMap, Chnl);
  1049. }
  1050.  
  1051. /* Is this PDDE a valid DDE channel? 
  1052. */
  1053. int IsValidPdde(PDDE pdde)
  1054. {
  1055.     PDDE tdde;
  1056.  
  1057.     for (tdde = DdeData.ChnlList; tdde != NULL; tdde = tdde->next)
  1058.         if (tdde == pdde)
  1059.             return TRUE;
  1060.     return FALSE;
  1061. }
  1062.  
  1063.  
  1064. /* Find Dde channel object by conversation handle
  1065. */
  1066. PDDE DdeFindConv(HCONV hConv)
  1067. {
  1068.     PDDE pdde;
  1069.  
  1070.     if (!DdeData.ChnlList)
  1071.         return NULL;
  1072.  
  1073.     for (pdde = DdeData.ChnlList; pdde != NULL; pdde = pdde->next) {
  1074.         if (pdde->hConv == hConv)
  1075.             return pdde;
  1076.     }
  1077.     return NULL;
  1078. }
  1079.  
  1080.  
  1081.  
  1082.  
  1083. /* Find dde channel object by channel number. 
  1084. */
  1085. PDDE DdeFindChnlNum(int channel)
  1086. {
  1087.     PDDE pdde;
  1088.     if (!DdeData.ChnlList)
  1089.         return NULL;
  1090.  
  1091.     for (pdde = DdeData.ChnlList; pdde != NULL; pdde = pdde->next) {
  1092.         /* match on channel num, check if still alive */
  1093.         if (pdde->channel == channel && IsActiveChnl(pdde))
  1094.             return pdde;
  1095.     }
  1096.     return NULL;
  1097. }
  1098.  
  1099.  
  1100.  
  1101. /* Find Dde channel object by application name, topic name. 
  1102. */
  1103. PDDE DdeFindChnl(char * app, char * topic)
  1104. {
  1105.     PDDE pdde;
  1106.     if (!DdeData.ChnlList)
  1107.         return NULL;
  1108.  
  1109.     for (pdde = DdeData.ChnlList; pdde != NULL; pdde = pdde->next) {
  1110.         if (!_stricmp(pdde->app, app) && !_stricmp(pdde->topic, topic) &&
  1111.                         IsActiveChnl(pdde))
  1112.             return pdde;
  1113.     }
  1114.     return NULL;
  1115. }
  1116.  
  1117.  
  1118.  
  1119. /* Is this DDE object a channel that hasn't been terminated
  1120.    by remote or locally 
  1121. */
  1122. int IsActiveChnl(PDDE pdde)
  1123. {
  1124.     if (pdde->hConv != 0 && !pdde->ChannelState.remoteterm && 
  1125.                                    !pdde->ChannelState.localterm) {
  1126. #if QCONV_WORKING
  1127.         if (DdeCheckConv(pdde->hConv))
  1128.             return TRUE;
  1129. #else
  1130.         return TRUE;
  1131. #endif
  1132.     }
  1133.     return FALSE;
  1134. }
  1135.  
  1136.  
  1137.  
  1138. /*
  1139.  
  1140.                 End of DDE Interface to Upper levels
  1141.  
  1142. */
  1143. /* This function is called by the DDE manager DLL and passes control onto
  1144.    the apropriate function pointed to by the global topic and item arrays.
  1145.    It handles all DDE interaction generated by external events.  */
  1146.  
  1147. HDDEDATA CALLBACK
  1148. /*FCN*/DdeCB(UINT uType, UINT uFmt, HCONV hconv, HSZ hsz1, HSZ hsz2,
  1149.     HDDEDATA hdata, DWORD dwData1, DWORD dwData2)
  1150. {
  1151.     PDDE pdde;
  1152.  
  1153.     pdde = DdeFindConv(hconv);
  1154.     if (pdde == NULL)
  1155.         return 0;
  1156.  
  1157.     switch (uType) {
  1158.     case XTYP_DISCONNECT:
  1159.         if (pdde->app == NULL)
  1160.             break;
  1161. #ifdef SHOWMSG
  1162.         sprintf(buf, "%s cancelled DDE conversation", pdde->app);
  1163.         MsgBox("AutoCAD DDE", buf);
  1164. #endif
  1165.         pdde->ChannelState.remoteterm = TRUE;
  1166.         DdeTerminate(pdde, FALSE);
  1167.         break;
  1168.  
  1169.     case XTYP_ADVDATA:
  1170.         if (pdde->AdviseFunc != NULL && !pdde->ChannelState.remoteterm)
  1171.             (*pdde->AdviseFunc)(pdde);
  1172.         break;
  1173.  
  1174.     default:
  1175.         break;
  1176.     }
  1177.  
  1178.     /*
  1179.       anything else fails - DDEML is designed so that a 0 return is ALWAYS ok.
  1180.      */
  1181.     return 0;
  1182. }
  1183.  
  1184.  
  1185. /* Create a DDE structure and a window for a channel
  1186.    (application and topic).  Channel number is in pdde->channel
  1187.    on return.  NULL returned on error. 
  1188. */
  1189. static PDDE DdeCreateChannel(char *app, char *topic)
  1190. {
  1191.     PDDE pdde;
  1192.     int channel;
  1193.     char MsgLine[80];
  1194.     char NumStr[30];
  1195.  
  1196.     channel = SetNextBit(ChannelMap, MAPLEN);
  1197.     if (channel == BITMAPFULL) {
  1198.         strcpy(MsgLine, "Only ");
  1199.         _itoa(MAXCHNL, NumStr, 10);
  1200.         strcat(MsgLine, NumStr);
  1201.         strcat(MsgLine, " DDE channels allowed.");
  1202.         MsgBox(DdeData.progname, MsgLine);
  1203.         return NULL;
  1204.     }
  1205.     pdde = DdeCreateLink(app, topic, 0, &DdeData.ChnlList);
  1206.     if (!pdde) {
  1207.         ClearBit(ChannelMap, channel);
  1208.         return NULL;
  1209.     }
  1210.  
  1211.     DdeData.ChnlNum = pdde->channel = channel;
  1212.     DdeData.CurChnl = pdde;
  1213.     return pdde;
  1214.  
  1215. }
  1216.  
  1217.  
  1218.  
  1219.  
  1220. /* Create a local Dde structure and add it to the chain
  1221.    of Dde structures.  If it's a new channel (app and topic),
  1222.    set hConv to NULL.  If it's data received on an item, 
  1223.    set AppName and TopicName to NULL, and hConv = the 
  1224.    HCONV. 
  1225. */
  1226. static PDDE DdeCreateLink(char *AppName, char *TopicName,
  1227.                           HCONV hConv, PDDE *List)
  1228. {
  1229.     PDDE pdde;
  1230.  
  1231.     pdde = (PDDE)AddLink((void **)List, sizeof(DDE));
  1232.     if (!pdde)
  1233.         return NULL;
  1234.     if (AppName)
  1235.         pdde->app = _strdup(AppName);
  1236.     if (TopicName)
  1237.         pdde->topic = _strdup(TopicName);
  1238.  
  1239.     if (hConv)                        /* Input data from msg */
  1240.         pdde->hConv = hConv;
  1241.  
  1242.     return pdde;
  1243. }
  1244.  
  1245.  
  1246.  
  1247.  
  1248.  
  1249. /* Free pointers and remove DDE link from list.  
  1250.    Waits for DdeSem.  Set SendTermFlag
  1251.    to free remote data.  Returns neighbor link. 
  1252. */
  1253. static PDDE DdeRemoveLink(PDDE pdde, int SendTermFlag, PDDE *List)
  1254. {
  1255.     PDDE Neighbor;
  1256.     int stat = 1;
  1257.  
  1258.     if (!pdde)
  1259.         return NULL;
  1260.  
  1261.     if (pdde->app)
  1262.         free(pdde->app);
  1263.     if (pdde->topic)
  1264.         free(pdde->topic);
  1265.     if (pdde->item)
  1266.         free(pdde->item);
  1267.     if (pdde->acadadvise)
  1268.         free(pdde->acadadvise);
  1269.     if (pdde->hAppName)
  1270.         DdeFreeStringHandle(idInst, pdde->hAppName);
  1271.     if (pdde->hTopicName)
  1272.         DdeFreeStringHandle(idInst, pdde->hTopicName);
  1273.  
  1274.     Neighbor = (PDDE)RemoveLink((void **)List, (PLINK)pdde);
  1275.     free(pdde);
  1276.  
  1277.     return Neighbor;
  1278. }
  1279.  
  1280. /* Free the shared data sent to us.  Update DDE structure data. */
  1281. void DdeFreeData(PDDE pdde)
  1282. {
  1283.     if (pdde == NULL || pdde->hReqData == (HDDEDATA)0)
  1284.         return;
  1285.     DdeUnaccessData(pdde->hReqData);
  1286.     pdde->hReqData = (HDDEDATA)0;
  1287.     pdde->pData = NULL;
  1288.     pdde->DataSize = 0;
  1289. }
  1290.  
  1291.  
  1292. void strhandle_msg(char *name)
  1293. {
  1294.     char buf[80];
  1295.  
  1296.     sprintf(buf, "Couldn't create a string handle for %s", name);
  1297.     MsgBox(MSG_TITLE, buf);
  1298. }
  1299.  
  1300.  
  1301.  
  1302. #if NEEDED
  1303. /* Switch channels using a 0 based list index, for 
  1304.    list boxes. 
  1305. */
  1306. int SwitchChnlIndex(int ChnlIdx)
  1307. {
  1308.     int idx = 0;
  1309.     int ChnlNum;
  1310.     PDDE pdde1;
  1311.  
  1312.     for (pdde1 = DdeData.ChnlList; pdde1 != NULL; pdde1 = pdde1->next) {
  1313.         if (ChnlIdx == idx) {
  1314.             ChnlNum = pdde1->channel;
  1315.             break;
  1316.         }
  1317.         ++idx;
  1318.     }
  1319.     return DdeSwitchChnl(ChnlNum);
  1320. }
  1321.  
  1322. #endif
  1323.  
  1324.  
  1325. /* Switch DDE channel, return 0 if not valid. */
  1326. int DdeSwitchChnl(int ChnlNum)
  1327. {
  1328.     PDDE pdde;
  1329.  
  1330.     if (!IsValidChannel(ChnlNum))
  1331.         return 0;
  1332.     pdde = DdeFindChnlNum(ChnlNum);
  1333.     if (!pdde)
  1334.         return 0;
  1335.     DdeData.ChnlNum = ChnlNum;
  1336.     DdeData.CurChnl = pdde;
  1337.     DdeData.apptype = pdde->apptype;
  1338.     return ChnlNum;
  1339. }
  1340.  
  1341.  
  1342. #if USE_DDEDLG
  1343.  
  1344. /*
  1345.  
  1346.  
  1347.                         DDE Dialog
  1348.  
  1349.  
  1350. */
  1351.  
  1352.  
  1353.  
  1354.  
  1355. /* QuickCase:W KNB Version 1.00 */
  1356.  
  1357. int DdeDialog(HWND hwnd)
  1358. {
  1359.     int ok;
  1360.     FARPROC lpfnDDEDLGMsgProc;
  1361.  
  1362.     lpfnDDEDLGMsgProc = MakeProcInstance((FARPROC)DDEDLGMsgProc, DdeData.hInst); 
  1363.     ok = DialogBox(DdeData.hInst, (LPSTR)"DDEINIT", hwnd, lpfnDDEDLGMsgProc);
  1364.  
  1365.     /* Set focus back to AutoCAD's graphics window */
  1366.     SetFocus(adsw_hwndAcad);
  1367.  
  1368.     FreeProcInstance(lpfnDDEDLGMsgProc);
  1369.     return ok;
  1370. }
  1371.  
  1372.  
  1373.  
  1374. /************************************************************************/
  1375. /*                                                                      */
  1376. /* Dialog Window Procedure                                              */
  1377. /*                                                                      */
  1378. /* This procedure is associated with the dialog box that is included in */
  1379. /* the function name of the procedure. It provides the service routines */
  1380. /* for the events (messages) that occur because the end user operates   */
  1381. /* one of the dialog box's buttons, entry fields, or controls.          */
  1382. /*                                                                      */
  1383. /************************************************************************/
  1384.  
  1385. BOOL FAR PASCAL DDEDLGMsgProc(HWND hWndDlg, UINT Message,
  1386.                               WPARAM wParam,  LPARAM lParam)
  1387. {
  1388.     static char     app[MAXNAME + 1];
  1389.     static char     topic[MAXPATH + 1];
  1390.     static char     path[MAXPATH + 1];
  1391.     static short    advisestate;
  1392.     int             chng;
  1393.     PDDE            pdde;
  1394.  
  1395.     switch (Message) {
  1396.  
  1397.     case WM_INITDIALOG:
  1398.         /* initialize working variables                 */
  1399.         strcpy(app, DdeData.app);
  1400.         strcpy(topic, DdeData.topic);
  1401.         strcpy(path, DdeData.path);
  1402.         SetDlgItemText(hWndDlg, IDD_DDE_APP, app);
  1403.         SetDlgItemText(hWndDlg, IDD_DDE_TOPIC, topic);
  1404.         SetDlgItemText(hWndDlg, IDD_DDE_PATH, path);
  1405.         SendDlgItemMessage(hWndDlg, IDD_DDE_APP, EM_SETSEL,
  1406.                    0, MAKELONG(0, 0x7fff));
  1407.         CheckDlgButton(hWndDlg, IDD_ADVISE, DdeData.flags.advise);
  1408.         advisestate = DdeData.flags.advise;
  1409.         break;        /* End of WM_INITDIALOG                  */
  1410.  
  1411.     case WM_CLOSE:
  1412.         /* Closing the Dialog behaves the same as Cancel         */
  1413.         PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
  1414.         break;        /* End of WM_CLOSE                     */
  1415.  
  1416.     case WM_COMMAND:
  1417.         switch (wParam) {
  1418.         case IDD_DDE_APP:    /* Edit Control                 */
  1419.             /* Indicates changes have occured */
  1420.             /* if(HIWORD(lParam) == EN_CHANGE) */
  1421.             break;
  1422.  
  1423.         case IDD_DDE_TOPIC:    /* Edit Control              */
  1424.             break;
  1425.  
  1426.         case IDD_DDE_PATH:    /* Edit Control                 */
  1427.             break;
  1428.  
  1429.         case IDD_ADVISE:    /* Checkbox text: "Automatic Update (warm link)" */
  1430.             break;
  1431.  
  1432.         case IDD_DDE_PARAMS:    /* Change Spreadsheet Parameters */
  1433.             if (DdeSpreadshDialog(hWndDlg)) {
  1434.                 SetDlgItemText(hWndDlg, IDD_DDE_TOPIC, DdeData.worksheet);
  1435.             }
  1436.             break;
  1437.  
  1438.         case IDOK:
  1439.             chng = FALSE;
  1440.             GetDlgItemText(hWndDlg, IDD_DDE_APP, app, MAXNAME);
  1441.             GetDlgItemText(hWndDlg, IDD_DDE_TOPIC, topic, MAXPATH);
  1442.             GetDlgItemText(hWndDlg, IDD_DDE_PATH, path, MAXPATH);
  1443.  
  1444.             if (_stricmp(DdeData.app, app)) {
  1445.                 chng = TRUE;
  1446.                 strcpy(DdeData.app, app);
  1447.             }
  1448.             if (_stricmp(DdeData.topic, topic)) {
  1449.                 chng = TRUE;
  1450.                 strcpy(DdeData.topic, topic);
  1451.             }
  1452.             if (_stricmp(DdeData.path, path)) {
  1453.                 chng = TRUE;
  1454.                 strcpy(DdeData.path, path);
  1455.             }
  1456.             advisestate = IsDlgButtonChecked(hWndDlg, IDD_ADVISE);
  1457.             if (advisestate != (short)DdeData.flags.advise) {
  1458.                 chng = TRUE;
  1459.                 DdeData.flags.advise = advisestate;
  1460.                 pdde = DdeFindChnl(DdeData.app, DdeData.topic);
  1461.                 if (pdde != NULL)
  1462.                     DdeAdviseCur(pdde, advisestate, WARMLINK);
  1463.             }
  1464.             if (chng && DdeData.ProfFile != NULL)
  1465.                 DdeWriteProfile(DdeData.ProfFile);
  1466.             EndDialog(hWndDlg, TRUE);
  1467.             break;
  1468.  
  1469.         case IDCANCEL:
  1470.             /* Ignore data values entered into the controls     */
  1471.             /* and dismiss the dialog window returning FALSE     */
  1472.             EndDialog(hWndDlg, FALSE);
  1473.             break;
  1474.         }
  1475.         break;        /* End of WM_COMMAND                  */
  1476.  
  1477.     default:
  1478.         return FALSE;
  1479.     }
  1480.     return TRUE;
  1481. }                /* End of DDEDLGMsgProc                       */
  1482.  
  1483. int 
  1484. DdeSpreadshDialog(HWND hwnd)
  1485. {
  1486.     int             ok;
  1487.     FARPROC         lpfnDDESPREADSHDLGMsgProc;
  1488.  
  1489.     lpfnDDESPREADSHDLGMsgProc = MakeProcInstance((FARPROC)
  1490.                                                  DDESPREADSHDLGMsgProc,
  1491.                                                  DdeData.hInst);
  1492.     ok = DialogBox(DdeData.hInst, (LPSTR) "DDESPREADSH", hwnd,
  1493.                    lpfnDDESPREADSHDLGMsgProc);
  1494.     FreeProcInstance(lpfnDDESPREADSHDLGMsgProc);
  1495.     return ok;
  1496. }
  1497.  
  1498. BOOL FAR PASCAL 
  1499. DDESPREADSHDLGMsgProc(HWND hWndDlg, UINT Message, WPARAM wParam,
  1500.               LPARAM lParam)
  1501. {
  1502.     static int      spreadsheet;
  1503.     static char     rowspec[MAXSPEC + 1];
  1504.     static char     colspec[MAXSPEC + 1];
  1505.     static char     rangespec[MAXSPEC + 1];
  1506.     static char     opencmd[MAXCMD + 1];
  1507.     static char     worksheet[MAXNAME + 1];
  1508.     int             chng;
  1509.  
  1510.     switch (Message) {
  1511.     case WM_INITDIALOG:
  1512.         strcpy(rowspec, DdeData.rowspec);
  1513.         strcpy(colspec, DdeData.colspec);
  1514.         strcpy(rangespec, DdeData.rangespec);
  1515.         strcpy(opencmd, DdeData.opencmd);
  1516.         strcpy(worksheet, DdeData.worksheet);
  1517.  
  1518.         SetDlgItemText(hWndDlg, IDD_SS_ROW, rowspec);
  1519.         SetDlgItemText(hWndDlg, IDD_SS_COL, colspec);
  1520.         SetDlgItemText(hWndDlg, IDD_SS_RANGE, rangespec);
  1521.         SetDlgItemText(hWndDlg, IDD_SS_OPEN, opencmd);
  1522.         SetDlgItemText(hWndDlg, IDD_SS_DOCUMENT, worksheet);
  1523.  
  1524.         spreadsheet = DdeData.spreadsheet;
  1525.  
  1526.         CheckRadioButton(hWndDlg, IDD_SS_EXCEL, IDD_SS_OTHER,
  1527.                  spreadsheet + IDD_SS_EXCEL);
  1528.         PostMessage(hWndDlg, WM_COMMAND, spreadsheet + IDD_SS_EXCEL, 0L);
  1529.  
  1530.         break;        /* End of WM_INITDIALOG */
  1531.  
  1532.     case WM_CLOSE:
  1533.         /* Closing the Dialog behaves the same as Cancel */
  1534.         PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
  1535.         break;        /* End of WM_CLOSE */
  1536.  
  1537.     case WM_COMMAND:
  1538.         switch (wParam) {
  1539.         case IDD_SS_EXCEL:
  1540.             spreadsheet = wParam - IDD_SS_EXCEL;
  1541.  
  1542.             SetDlgItemText(hWndDlg, IDD_SS_ROW, /*MSG0*/"R");
  1543.             SetDlgItemText(hWndDlg, IDD_SS_COL, /*MSG0*/"C");
  1544.             SetDlgItemText(hWndDlg, IDD_SS_RANGE, /*MSG0*/":");
  1545.             SetDlgItemText(hWndDlg, IDD_SS_OPEN, /*MSG0*/"[OPEN(\"%1\")]");
  1546.             SetDlgItemText(hWndDlg, IDD_SS_DOCUMENT, /*MSG0*/"Sheet1");
  1547.  
  1548.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_ROW), FALSE);
  1549.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_COL), FALSE);
  1550.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_RANGE), FALSE);
  1551.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_OPEN), FALSE);
  1552.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_DOCUMENT), FALSE);
  1553.             break;
  1554.  
  1555.         case IDD_SS_OTHER:
  1556.             spreadsheet = wParam - IDD_SS_EXCEL;
  1557.  
  1558.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_ROW), TRUE);
  1559.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_COL), TRUE);
  1560.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_RANGE), TRUE);
  1561.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_OPEN), TRUE);
  1562.             EnableWindow(GetDlgItem(hWndDlg, IDD_SS_DOCUMENT), TRUE);
  1563.             break;
  1564.  
  1565.         case IDOK:
  1566.             GetDlgItemText(hWndDlg, IDD_SS_ROW, rowspec, MAXSPEC);
  1567.             GetDlgItemText(hWndDlg, IDD_SS_COL, colspec, MAXSPEC);
  1568.             GetDlgItemText(hWndDlg, IDD_SS_RANGE, rangespec, MAXSPEC);
  1569.             GetDlgItemText(hWndDlg, IDD_SS_OPEN, opencmd, MAXCMD);
  1570.             GetDlgItemText(hWndDlg, IDD_SS_DOCUMENT, worksheet, MAXNAME);
  1571.  
  1572.             chng = FALSE;
  1573.  
  1574.             if (spreadsheet != DdeData.spreadsheet) {
  1575.                 chng = TRUE;
  1576.                 DdeData.spreadsheet = spreadsheet;
  1577.             }
  1578.             if (_stricmp(DdeData.rowspec, rowspec)) {
  1579.                 chng = TRUE;
  1580.                 strcpy(DdeData.rowspec, rowspec);
  1581.             }
  1582.             if (_stricmp(DdeData.colspec, colspec)) {
  1583.                 chng = TRUE;
  1584.                 strcpy(DdeData.colspec, colspec);
  1585.             }
  1586.             if (_stricmp(DdeData.rangespec, rangespec)) {
  1587.                 chng = TRUE;
  1588.                 strcpy(DdeData.rangespec, rangespec);
  1589.             }
  1590.             if (_stricmp(DdeData.opencmd, opencmd)) {
  1591.                 chng = TRUE;
  1592.                 strcpy(DdeData.opencmd, opencmd);
  1593.             }
  1594.             if (_stricmp(DdeData.worksheet, worksheet)) {
  1595.                 chng = TRUE;
  1596.                 strcpy(DdeData.worksheet, worksheet);
  1597.             }
  1598.             if (chng && DdeData.ProfFile != NULL) {
  1599.                 DdeWriteProfile(DdeData.ProfFile);
  1600.             }
  1601.             EndDialog(hWndDlg, TRUE);
  1602.             break;
  1603.  
  1604.         case IDCANCEL:
  1605.             /* Ignore data values entered into the controls  */
  1606.             /* and dismiss the dialog window returning FALSE */
  1607.             EndDialog(hWndDlg, FALSE);
  1608.             break;
  1609.         }
  1610.         break;        /* End of WM_COMMAND */
  1611.  
  1612.     default:
  1613.         return FALSE;
  1614.     }
  1615.     return TRUE;
  1616. }                /* End of DDESPREADSHDLGMsgProc */
  1617.  
  1618. #endif  /* USE_DDEDLG */
  1619.  
  1620.  
  1621.  
  1622. /* Save and restore user options from INI file */
  1623.  
  1624. void DdeWriteProfile(char *profFile)
  1625. {
  1626.     char           *str;
  1627.  
  1628.     if (profFile == NULL)
  1629.         profFile = DdeData.ProfFile;
  1630.         
  1631.     WritePrivateProfileString(profMain,
  1632.                   profDdeApp, DdeData.app, profFile);
  1633.     WritePrivateProfileString(profMain,
  1634.                   profDdeTopic, DdeData.topic, profFile);
  1635.     WritePrivateProfileString(profMain,
  1636.                   profDdePath, DdeData.path, profFile);
  1637.     str = (DdeData.flags.advise ? "1" : "0");
  1638.     WritePrivateProfileString(profMain,
  1639.                   profAdvise, str, profFile);
  1640.     str = (DdeData.spreadsheet == 0) ? "0" : "1";
  1641.     WritePrivateProfileString(profMain,
  1642.                   profSpreadsheet, str, profFile);
  1643.     WritePrivateProfileString(profMain,
  1644.                   profRowSpec, DdeData.rowspec, profFile);
  1645.     WritePrivateProfileString(profMain,
  1646.                   profColSpec, DdeData.colspec, profFile);
  1647.     WritePrivateProfileString(profMain,
  1648.                   profRangeSpec, DdeData.rangespec, profFile);
  1649.     WritePrivateProfileString(profMain,
  1650.                   profOpenCmd, DdeData.opencmd, profFile);
  1651.     WritePrivateProfileString(profMain,
  1652.                   profDefaultDoc, DdeData.worksheet, profFile);
  1653. }
  1654.  
  1655. /* Get user options from INI file */
  1656.  
  1657. int DdeGetProfile(char *profFile)
  1658. {
  1659.     BOOL            status;
  1660.  
  1661.     if (profFile == NULL)
  1662.         profFile = DdeData.ProfFile;
  1663.         
  1664.     status = GetPrivateProfileString(profMain, profDdeApp, DdeData.app,
  1665.                 DdeData.app, MAXNAME, profFile);
  1666.     GetPrivateProfileString(profMain, profDdeTopic, DdeData.topic,
  1667.                 DdeData.topic, MAXPATH, profFile);
  1668.     GetPrivateProfileString(profMain, profDdePath, DdeData.path,
  1669.                 DdeData.path, MAXPATH, profFile);
  1670.     DdeData.flags.advise = GetPrivateProfileInt(
  1671.                 profMain, profAdvise, 1, profFile);
  1672.     DdeData.spreadsheet = GetPrivateProfileInt(profMain, 
  1673.                 profSpreadsheet, DdeData.spreadsheet, profFile);
  1674.     GetPrivateProfileString(profMain, profRowSpec, DdeData.rowspec,
  1675.                 DdeData.rowspec, MAXSPEC, profFile);
  1676.     GetPrivateProfileString(profMain, profColSpec, DdeData.colspec,
  1677.                 DdeData.colspec, MAXSPEC, profFile);
  1678.     GetPrivateProfileString(profMain, profRangeSpec, DdeData.rangespec,
  1679.                 DdeData.rangespec, MAXSPEC, profFile);
  1680.     GetPrivateProfileString(profMain, profOpenCmd, DdeData.opencmd,
  1681.                 DdeData.opencmd, MAXCMD, profFile);
  1682.     GetPrivateProfileString(profMain, profDefaultDoc, DdeData.worksheet,
  1683.                 DdeData.worksheet, MAXNAME, profFile);
  1684.                 
  1685.     return status;
  1686. }
  1687.  
  1688.  
  1689. /* Search Registration Database for any of the known spreadsheet
  1690.    apps.  Set in ddeApps array: defPath, openCmd, and flags.regDbFound. 
  1691.    Return app index. */
  1692. int DdeFindApps(void)
  1693. {
  1694.     int idx, idxFound = -1;
  1695.  
  1696.     for (idx = 0; idx < NUM_APPS; ++idx) {
  1697.         if (DdeFindApp(ddeApps[idx].className, ddeApps[idx].defPath)) { 
  1698.             if (idx == APP_QPW) {
  1699.                 /* For Quattro Pro, change notebk1.wb1 to 
  1700.                    something like f:\qpw\notebk1.wb1. */
  1701.                 char *saveTopic;
  1702.                 char path[MAXPATH];
  1703.                 char fname[13];
  1704.  
  1705.                 if (!ddeApps[idx].flags.defTopic) {
  1706.                     saveTopic = _strdup(ddeApps[idx].defTopic);
  1707.                     getpath(ddeApps[idx].defPath, path, fname);
  1708.                     strcpy(ddeApps[idx].defTopic, path);
  1709.                     strcat(ddeApps[idx].defTopic, "\\");
  1710.                     strcat(ddeApps[idx].defTopic, saveTopic);
  1711.                     free(saveTopic);
  1712.                     ddeApps[idx].flags.defTopic = TRUE;
  1713.                 }
  1714.             }    
  1715.             DdeFindOpenCmd(ddeApps[idx].className, ddeApps[idx].openCmd);
  1716.             ddeApps[idx].flags.regDbFound = TRUE;
  1717.             idxFound = idx;
  1718.         }
  1719.     }
  1720.     return idxFound;
  1721. }
  1722.  
  1723.  
  1724.  
  1725. /* Find path of app (classname) from Windows Registration
  1726.    Database.   E.g., "ExcelWorksheet" might return (in exePath)
  1727.    "d:\excel\excel.exe".  
  1728. */
  1729. int DdeFindApp(char *classname, char *exePath)
  1730. {
  1731.     int stat;
  1732.     WORD estat = 0;
  1733.  
  1734.  
  1735.     stat = findRegValue(classname, "protocol", 
  1736.                         "StdFileEditing", "server", exePath);
  1737.     if (!stat) {
  1738.         /* RegDB "QuattroProNotebook-shell-open-command = g:\qpw\qpw.exe" */
  1739.         stat = findRegValue(classname, "shell", 
  1740.                         "open", "command", exePath);
  1741.         if (!stat)
  1742.             return FALSE;
  1743.     } 
  1744. #ifdef DEBUG
  1745.     debugPrintf("DdeFindApp: ok %s %s\r\n", exePath, openCmd);
  1746. #endif
  1747.     return TRUE;
  1748. }
  1749.  
  1750.  
  1751. /* Find the open command for a classname 
  1752. */
  1753. int DdeFindOpenCmd(char *classname, char *openCmd)
  1754. {
  1755.     /* RegDB "QuattroProNotebook-shell-open-ddeexec = [open("%1")]" */
  1756.     return findRegValue(classname, "shell", "open", "ddeexec", openCmd);
  1757. }
  1758.  
  1759.  
  1760. /* Expand open command such as [open("%1")] into [open("shaft.xls")] 
  1761. */
  1762. static int DdeExpandOpenCmd(char *openCmd, char *topic, char *expCmd)
  1763. {
  1764.     int idx, foundIt = FALSE;
  1765.  
  1766.     for (idx = 0; openCmd[idx] != 0; ++idx) {
  1767.         if (openCmd[idx] == '%' && openCmd[idx+1] == '1') {
  1768.             foundIt = TRUE;
  1769.             break;
  1770.         } else
  1771.             expCmd[idx] = openCmd[idx];
  1772.     }
  1773.     if (!foundIt)
  1774.         return FALSE;
  1775.     strcpy(&expCmd[idx], topic);
  1776.     strcat(expCmd, &openCmd[idx+2]);
  1777.     return TRUE;
  1778. }
  1779.  
  1780. /* Is a topic the default topic for this application 
  1781. */
  1782. int DdeIsDefTopic(char *topic)
  1783. {
  1784.     return _stricmp(topic, ddeApps[DdeData.appIdx].defTopic) == 0;
  1785. }
  1786.  
  1787. /* Find an exe path from several levels of keys in the Windows
  1788.    Registration Database. */
  1789.  
  1790. static int findRegValue(char *key1, char *key2, char *key3, char *key4, 
  1791.                 char *szValue) 
  1792. {
  1793.     HKEY hKey;
  1794.     char *lastkey;
  1795.     char szSubKey[120];
  1796.     LONG cb;
  1797.     int stat = FALSE;
  1798.  
  1799.     strcpy(szSubKey, key1);
  1800.     lastkey = key2;
  1801.     if (key2) {
  1802.         strcat(szSubKey, "\\");
  1803.         strcat(szSubKey, key2);
  1804.         lastkey = key3;
  1805.         if (key3) {
  1806.             strcat(szSubKey, "\\");
  1807.             strcat(szSubKey, key3);
  1808.             lastkey = key4;
  1809.         }
  1810.     }
  1811.         
  1812.     if (RegOpenKey(HKEY_CLASSES_ROOT, szSubKey, &hKey) == ERROR_SUCCESS) {
  1813.         cb = MAXPATH;
  1814.         if (RegQueryValue(hKey, lastkey, szValue, &cb) == ERROR_SUCCESS)
  1815.             stat = TRUE;
  1816.         RegCloseKey(hKey);
  1817.     }
  1818.     return stat;
  1819. }
  1820.  
  1821.  
  1822. #ifdef DEBUG
  1823.  
  1824. #if !defined(__TURBOC__) && !defined(HCWIN)
  1825. /* Windows version of printf for output debug terminal or window 
  1826.    (see Windows SDK, DBWIN.EXE). */
  1827. int debugPrintf(char *format, ...)
  1828. {
  1829.     va_list va;
  1830.     int stat;
  1831.     char buf[1024];
  1832.  
  1833.     va_start(va, format);
  1834.     vsprintf(buf, format, va);
  1835.     va_end(va);
  1836.  
  1837.     /* Output to DBWIN program (from Windows SDK). */
  1838.     OutputDebugString(buf);
  1839.     return 1;
  1840. }
  1841. #endif
  1842. #endif
  1843.  
  1844. /* end of file */
  1845.  
  1846.