3

I would like to create a dll library that will be loading in runtime. The library'll be using internally COM objects with MTA flag. The library will be created in main thread.

So I have question: Where there is best place where can I call 'CoInitializeEx' and 'CoUninitialize' functions. In the my dll(init/deinit functions) or client should call directly these functions?

I prefer first option. I would like avoid public dependig on COMs. Client shouldn't know I'm using COMs, but also I'd like avoid crashes when client unload my lib(then I call 'CoUninitialize' for my lib) and other libs(depend on COM) will be in undefined state.

EDIT:

ProviderApi.h

#ifndef API_H
#define API_H

class Provider;

define API __declspec(dllexport)

using ErrCode = int;

extern "C" {
  API ErrCode init(Provider** provider);
  API ErrCode deinit();
}

#endif

ProviderApi.cpp

#include <Objbase.h>

Provider* prov;

API ErrCode init(Provider** provider)
{
  const auto res = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  // create provider
  // then I can use any com object

  *provider = prov;
}

API ErrCode deinit()
{
  CoUninitialize();
}

client code

#include <Windows.h>

class Provider;

typedef ErrCode (*f_init)(Provider**);
typedef ErrCode (*f_deinit)();

int main(int /*argc*/, char** /*argv*/)
{
  auto lib = LoadLibrary("my_lib.dll");

  auto init = (f_init)GetProcAddress(lib, "init");
  auto deinit = (f_deinit)GetProcAddress(lib, "deinit");

  Provider* prov;
  init(&prov);

  // do something

  deinit();

  return 0;
}

UPDATE 28.02.2020:

OK, I finally found the solution of my problem. To hide the COM dependency, just create a separate thread and execute COM methods in it. Bellow there is sample of usage.

ComThread.h

#ifndef COM_THREAD_H
#define COM_THREAD_H

#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>

class ComThread
{
public:
  ComThread();
  ComThread(const ComThread&) = delete;
  ComThread(ComThread&&) = delete;
  ~ComThread() noexcept;

  ComThread& operator=(const ComThread&) = delete;
  ComThread& operator=(ComThread&&) = delete;

  void doTask(std::function<void(void)> comTask);

private:
  std::thread m_thread;
  std::mutex m_mutex;
  std::mutex m_taskMutex;
  std::condition_variable m_cv;
  bool m_stop;

  std::function<void(void)> m_currentTask;

  void worker();

};

#endif // COM_THREAD_H

ComThread.cpp

#include "ComThread.h"

#include <cassert>

#include <objbase.h>

ComThread::ComThread()
 : m_stop(false),
   m_thread(std::thread(&ComThread::worker, this))
{
}

ComThread::~ComThread() noexcept
{
  m_stop = true;
  m_cv.notify_one();

  if (m_thread.joinable())
    m_thread.join();
}

void ComThread::doTask(std::function<void()> comTask)
{
  std::lock_guard lock(m_taskMutex);

  m_currentTask = comTask;
  m_cv.notify_one();

  std::unique_lock taskLock(m_mutex);
  m_cv.wait(taskLock, [this] () { return !m_currentTask; });
}

void ComThread::worker()
{
  const auto res = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
  assert(res == S_OK);

  while (true)
  {
    {
      std::unique_lock lock(m_mutex);
      m_cv.wait(lock, [this] () { return m_stop || m_currentTask; });
    }

    if (m_stop)
      break;

    assert(m_currentTask);
    m_currentTask();
    m_currentTask = nullptr;

    m_cv.notify_one();
  }

  CoUninitialize();
}

main.cpp

#include "DeckLinkAPI_h.h

#include "ComThread.h"

int main(int /*argc*/, char** /*argv*/)
{
  ComThread comThread;

  IDeckLinkIterator* deckLinkIterator = nullptr;
  HRESULT hr = S_OK;

  comThread.doTash([&hr, &deckLinkIterator]() {
    hr = CoCreateInstance(
      CLSID_CDeckLinkIterator,
      nullptr,
      CLSCTX_ALL,
      IID_IDeckLinkIterator,
      reinterpret_cast<void**>(&deckLinkIterator));
  });

  if (hr != S_OK)
    return -1;

  // do other things
}

  • 1
    What does "publicly depending" mean? When you distribute this dll are you distributing these com objects? – candied_orange Jul 30 '19 at 23:57
  • @candied_orange In the 'public depending' I mean dependency about 'COM' world. If I want use these object I have to initialize 'COM' by the call 'CoInitalizeEx' function and when the library is no longer needed I call 'CoUninitialize', but if another client code use COM's the app may crash after release my lib. For the easiest understand, I've update main post and I added sample code which show what I'd like achieve. No don't distributing these com objects. – pbieguszewski Jul 31 '19 at 17:17
  • Do you actually have (need) a technical requirement that your DLL be unloadable? (That is, the requirement that certain cleanup code needs to be executed, and afterwards all resources need to be cleanly released, when the client calls `FreeLibrary`.) In my opinion, for all non-trivial COM DLL code I've seen so far, I've not encountered a case where a "clean" cleanup and release is ever achievable. Once a COM DLL is loaded into a process, you better assume that it will stay with the process, until the process terminates. If there's requirement (e.g. to delete the file), use multiple processes. – rwong Dec 28 '19 at 23:39

1 Answers1

1

Microsoft advises against calling these from the Dll load/unload functions:

Because there is no way to control the order in which in-process servers are loaded or unloaded, do not call CoInitialize, CoInitializeEx, or CoUninitialize from the DllMain function.

Reference

https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize

Jerry Coffin
  • 44,385
  • 5
  • 89
  • 162