ПрограммированиеПодсказкиГрафика

3D Studio Max: Перезагружаемый плагин

Автор:

При разработке плагина для 3D Studio Max необходимо часто перезапускать студию. Перегружать студию, а тем более в режиме дебага, довольно долго. Было бы удобно перезагружать не студию, а плагин. К сожалению, 3D Studio Max не имеет такой возможности. Если она загрузила плагин, то это раз и навсегда. Описанный здесь способ был подсказан кем-то из участников данного ресурса. Так что авторство идеи не за мной, я лишь решил оформить это в виде подсказки.

Суть метода заключается в следующем. Мы создаём каркас плагина для 3D Studio Max, а весь функционал выносим в отдельную DLL, которую подгружаем непосредственно во время работы плагина, либо другими средствами (через интерфейс или команду).

В качестве примера буду использовать плагин экспорта. Саму разработку плагина с нуля рассматривать не буду. Итак имеем уже готовый плагин экспорта. Во время экспорта студия вызывает метод

int YourExport::DoExport(const TCHAR *name, ExpInterface *ei, Interface *i,
  BOOL suppressPromts, DWORD options)
{
  return IMPEXP_FAIL;
}

В этом методе мы и делаем все преобразования из студии в свои и сохраняем файл.

Как мы делаем в классическом случае (обход дерева я не юзаю, поэтому EnumTree вы не увидите):

int YourExport::DoExport(const TCHAR *name, ExpInterface *ei, Interface *i,
  BOOL suppressPromts, DWORD options)
{
  RetrieveMesh(i);
  RetrieveMaterial(i);
  SaveYourFile(name);
  return IMPEXP_FAIL;
}

Когда вы что-либо меняете, вы пересобираете плагин, перезапускаете студию. Меняем мы только наш функционал. То есть функции RetrieveMesh, RetrieveMaterial, SaveYourFile. Выносим эти функции в отдельную DLL. Создаем проект типа DLL, которая только экспортирует функции. В этом проекте реализуем эти функции RetrieveMesh, RetrieveMaterial, SaveYourFile.

Также реализуем одну функцию на экспорт:

extern "C"
{
  __declspec(dllexport)
  void __cdecl Process(ExpInterface*, Interface*, const TCHAR*, int*, std::wstring&);
}

// error = возвращаемый код ошибки
// errStr = сопроводительное сообщение об ошибке
__declspec(dllexport) 
void __cdecl Process(ExpInterface* peif, Interface* pmif, const TCHAR* file,
  int* error, std::wstring& errStr)
{
  RetrieveMesh(i);
  RetrieveMaterial(i);
  SaveYourFile(name);
  *error = IMPEXP_SUCCESS;
}

Обработка ошибок и прочие мелочи на ваше усмотрение. Компилируем DLL и кладем ее рядом с нашим плагином.

Теперь нам надо загружать эту библиотеку при запросе на экспорт. Я делаю это с помощью следующего индусского кода:

  // задача найти нашу библиотеку во всех возможных папках плагинов, 
  // вы можете тут захардкодить c:\\program files..... blah blah

  // получаем кол-во записей с путями к плагинам в ини файле студии
  int pluginsDirCount = i->GetPlugInEntryCount();

  HMODULE hLib = NULL;

  // для каждой записи пытаемся загрузить библиотеку
  for (int n=0; n<pluginsDirCount; n++)
  {
    std::wstring pluginDirectory = i->GetPlugInDir(n);
    std::wstring libraryPath = pluginDirectory + L"\\YourExportLibrary.dll";
    hLib = LoadLibrary(libraryPath.c_str());
    if( hLib ) // библиотека найдена, прерываем цикл
      break;
  } // у меня нет обработки случая, когда библиотека не найдена вообще

Затем получим адрес функции из библиотеки. Сначала объявим указатель на неё:

void (__cdecl * Process)(ExpInterface*, Interface*, const TCHAR*, int*, std::wstring&);

А затем получим этот указатель:

Process = 
  reinterpret_cast<void (__cdecl *)(ExpInterface*, Interface*, const TCHAR*, int*, std::wstring&)>
  (GetProcAddress(hLib, "Process"));

После просто вызываем её:

  int error = 0;
  std::wstring errStr;
  Process(ei, i, name, &error, errStr);

И в конце концов выгружаем DLL:

FreeLibrary(hLib);

Теперь при каждом экспорте будет загружаться ваша библиотека. Конечно, если она крашнется, то потянет и студию за собой.

Как дебажить, тут рецепта не знаю. Я лишь подбрасываю *.pdb file вижуал студии когда вхожу в код моей DLL.

Полный код DoExport:

int YourExport::DoExport(const TCHAR *name, ExpInterface *ei, Interface *i,
  BOOL suppressPromts, DWORD options)
{
  if ( !ei || !i )
    return IMPEXP_FAIL;

  int pluginsDirCount = i->GetPlugInEntryCount();

  HMODULE hLib = NULL;

  for (int n=0; n<pluginsDirCount; n++)
  {
    std::wstring pluginDirectory = i->GetPlugInDir(n);
    std::wstring libraryPath = pluginDirectory + L"\\YourExportLibrary.dll";
    hLib = LoadLibrary(libraryPath.c_str());
    if( hLib )
      break;
  }

  Exported::Process = 
    reinterpret_cast<void (__cdecl *)(ExpInterface*, Interface*, const TCHAR*, int*, std::wstring&)>
    (GetProcAddress(hLib, "Process"));

  int error = 0;
  std::wstring errStr;

  Exported::Process(ei, i, name, &error, errStr);

  if( error==IMPEXP_FAIL )
  {
    ShowError(errStr.c_str());
  }

  FreeLibrary(hLib);

  return IMPEXP_SUCCESS;
}

Успехов.

#3D Studio MAX, #plugin

12 февраля 2014 (Обновление: 17 фев 2014)

Комментарии [11]