DelphiとAutoCAD

DelphiAutoCADのコマンドを作成する方法。といっても、コマンドの登録にはVCを利用しますし、Delphiで操作するのはCOM経由でAutoCADを操作する事になるのでObjectARX程自由度は有りません。外部プロセスでAutoCADを操作するよりは実行速度が速くなるかと思います。
今だと、.Net版のObjectARXを利用した方が色々出来て良いのかも知れません。

というわけで、まずはコマンドを登録するためのARXの方から。これはVCで作成します。ちなみに未だにAutoCAD2006を使ってるので最新版で試す場合は修正する必要があるかもしれません。

コマンド実行時に呼び出されるDLLの関数
typedef void (__stdcall *CMDFUNC)(AutoCAD::IAcadApplication *pAcadApp);
DLLロード後、初期化・終了処理用に呼び出す関数
typedef void (__stdcall *ARXINIT)(void);
コマンド登録の為に呼び出す関数
typedef void (__stdcall *ARXSETUP)(HWND AcadWnd);

処理のながれは、ARXからDelphiで作成されたDLLをロードした後にDLLからエクスポートされているARXInitを呼び出してDLLの初期化処理を行う。
その後、ARXSetupを呼び出してコマンドの登録処理を行う。ARXがアンロードされる時にARXFinalを呼び出してDLLの終了を処理を行う。
コマンド登録処理では、DLLからCommandRegister関数が呼び出される。

ARXのOn_kInitAppMsgでEnumPluginsを呼び出してコマンドの登録を行う。On_kUnloadAppMsgでReleasePluginsを呼び出して解放処理を行う。
CommandRegisterは、DLLから呼び出されるのでエクスポートする。

//コマンド名と関数ポインタのマップ
typedef std::map<std::string, CMDFUNC> COMMANDS_MAP;
//文字列リスト
typedef std::vector<std::string> STRINGARRAY;
//登録コマンドと関数ポインタのペアを保持する
COMMANDS_MAP cmds;
//登録グループのリスト
STRINGARRAY groups;

// AutoCADのCOMオブジェクトを取得する
bool GetAcadApp(AutoCAD::IAcadApplication **pAcadApp)
{
	HRESULT hr = NOERROR;
	LPDISPATCH pDisp = acedGetAcadWinApp()->GetIDispatch(TRUE);
	hr = pDisp->QueryInterface(AutoCAD::IID_IAcadApplication, (void**)pAcadApp);
	pDisp->Release();

	return SUCCEEDED(hr);
}

// 全てのコマンドが実行された時に実行される
void CommandTemplate()
{
  // 実行されたコマンド名が取得できるので取得する
  struct resbuf rb;
  acedGetVar("CMDNAMES", &rb);
  std::string cmd = rb.resval.rstring;
  free(rb.resval.rstring);

  AutoCAD::IAcadApplication *pApp;
  if(GetAcadApp(&pApp))
  {
    // 実行コマンドを UndoMark で挟む
    AcTransaction *pTrans = actrTransactionManager->startTransaction();
    // 取得したコマンド名を元に、記憶してあるDLLの関数ポインタを呼ぶ
    cmds[cmd](pApp);
    pApp->Release();

    if(pTrans != NULL)
      actrTransactionManager->endTransaction();
  }
}

DLLから呼び出されるコマンド登録用関数
bool __stdcall CommandRegister(const char* Group, const char* Cmd, const int CmdFlags, CMDFUNC Func)
{
  size_t length;
  LPTSTR pBuffer;
  bool result = false;

  // コマンド登録時 グループ・コマンド名を大文字化して登録する
  ::StringCbLength(Group, 50, &length);
  pBuffer = new char[length+1];
  ::StringCbCopy(pBuffer, 50, Group);
  ::CharUpper(pBuffer);
  // グループ名が登録済みで無い場合は登録する
  if(std::find(groups.begin(), groups.end(), pBuffer) == groups.end())
    groups.push_back(pBuffer);
  delete [] pBuffer;

  ::StringCbLength(Cmd, 50, &length);
  pBuffer = new char[length+1];
  ::StringCbCopy(pBuffer, 50, Cmd);
  ::CharUpper(pBuffer);

  if(cmds.find(pBuffer) == cmds.end())
  {
    // コマンドが未登録の場合、テンプレートコマンドを登録
    acedRegCmds->addCommand(Group, pBuffer, pBuffer, CmdFlags, CommandTemplate);
    // 登録に渡された関数ポインタをコマンド名とセットで記憶する
    cmds[pBuffer] = Func;
    result = true;
  }else{
    // コマンド登録済
    acutPrintf("コマンド登録失敗", pBuffer);
  }
  delete [] pBuffer;
  return result;
}

void EnumPlugins(void)
{
  std::string path = 検索用パス;
  WIN32_FIND_DATA fd;
  std::string searchpath = path + "\\*.dll";
  HANDLE hFind = ::FindFirstFile(searchpath.c_str(), &fd);
  HINSTANCE hDll = NULL;
  if(hFind != INVALID_HANDLE_VALUE)
  {
    do{
      searchpath = path + "\\" + fd.cFileName;
      hDll = ::LoadLibrary(searchpath.c_str());
      if(EnumCommands(hDll, fd.cFileName))
        hDlls.push_back(hDll);
      else
      {
        // EnumCommands に失敗した場合は、解放する
        ::FreeLibrary(hDll);
      }
    }while(0 != ::FindNextFile(hFind, &fd));
    ::FindClose(hFind);
  }
}

bool EnumCommands(HINSTANCE hPin, const char* pName)
{
  bool bContinue = true;
  ARXSETUP pARXSetup;
  ARXINIT pARXInit;

  // ARXInitがエクスポートされてれば呼び出す
  pARXInit = (ARXINIT)::GetProcAddress(hPin, "ARXInit");
  if (pARXInit)
    pARXInit();

  pARXSetup = (ARXSETUP)::GetProcAddress(hPin, "ARXSetup");
  // 関数ARXSetupがエクスポートされていない DLL は無視
  if(!pARXSetup )
    return false;

  pARXSetup(adsw_acadMainWnd());

  return true;
}

void ReleasePlugins(void)
{
  for(STRINGARRAY::iterator i=groups.begin(); i!=groups.end(); ++i)
    acedRegCmds->removeGroup((*i).c_str());

  groups.clear();
  cmds.clear();

  ARXINIT pARXFinal;
  // 呼び出したDLLを全て解放する
  for(DLLHANDLES::iterator i=hDlls.begin(); i!=hDlls.end(); ++i)
  {
    pARXFinal = (REGINIT)::GetProcAddress(*i, "ARXFinal");
    if (pARXFinal)
      pARXFinal();
      ::FreeLibrary(*i);
  }
  hDlls.clear();
}