在主机应用程序和 dll 中使用相同的互斥体

Using same mutex in host app and dll

我有一个动态加载 dll(插件)的多线程应用程序。我在 DLL 中有线程。在主机应用程序和 dll 之间是我自己的 SDK,其目的是启用与 dll 插件(库接口)的通信并定义主机应用程序和 dll(数据结构等)之间的共享资源。

宿主应用程序正在使用 WinAPI 函数 CreateMutex 创建一个互斥锁对象,并将创建的互斥锁的 THandle 传递给我加载的每个 dll。当 DLL 中的线程正在更改公共资源时,它使用互斥锁来保护它。正如我所说,我正在使用 WinAPI.Windows 单元来调用 CreateMutex 和所有其他互斥相关函数(Release 等)。

我的想法是让 SDK 跨平台,因此我即将修改 SDK,当然我想摆脱单元 WinAPI.Windows 和所有 Windows 相关的东西。

我包含 System.SyncObjs 以使用 TMutex class。现在我不太确定如何让我的 dll 知道 class。一种选择是将 TMutex 传递给我的 DLL,但如果我想在我的 SDK 中保留原始数据类型,我认为这不是一个选项,因为 SDK 必须可用于其他语言,如 C++、C#...等想法是使用命名的互斥锁,并将名称(字符串)传递给 DLL。

根据 MSDN:如果互斥量是一个命名的互斥量并且该对象在此函数调用之前就已经存在,则 return 值是现有对象的句柄,GetLastError returns ERROR_ALREADY_EXISTS,bInitialOwner 被忽略,调用线程未被授予所有权。但是,如果调用者的访问权限有限,函数将失败并返回 ERROR_ACCESS_DENIED,调用者应使用 OpenMutex 函数。

所以我假设 DLL 在调用 TMutex.Create(...,'MyMutex').

时将能够使用现有的命名互斥体 (MyMutex)

我说的对吗?

感谢您的帮助和建议。非常感谢!

不允许在运行时包以外的模块之间传递 class 实例。这不是一种有效的互操作形式。

所以我认为最干净的解决方案是用接口包装 TMutex。您可以在任何类型的模块之间传递接口,甚至是不同编译器编译的模块。

有多种不同的互斥量可用,但在我看来,显而易见的选择是包装 TMonitor。界面简单:

type
  IMutex = interface
    procedure Acquire; stdcall;
    procedure Release; stdcall;
  end;

在可执行文件和 DLL 之间传递此接口是绝对安全的。

实现可能如下:

type
  TXplatMutex = class(TInterfacedObject, IMutex)
  public
    procedure Acquire; stdcall;
    procedure Release; stdcall;
  end;

procedure TXplatMutex.Acquire;
begin
  TMonitor.Enter(Self);
end;

procedure TXplatMutex.Release;
begin
  TMonitor.Exit(Self);
end;

您可以用这种方式包装您喜欢的任何互斥量。我选择 TMonitor 因为它在所有平台上都受支持,并且是 Embarcadero 库代码用于同步的主要工具。

Windows 互斥体可以 "shared" 跨进程(在 exe 和 DLL 中更是如此)——你有两种方式:

  1. 使用命名互斥体。使用 OpenMutex() API 获取现有互斥体的句柄 - 您需要确保在 CreateMutex() 之后调用它,否则它不会找到要打开的互斥体。
  2. 对未命名的互斥锁使用 DuplicateHandle(),它 returns 一个在目标进程中有效的新句柄(在 DLL 的情况下可以是相同的)

通常,Delphi 中的 API 包装器对于 "complex" 场景来说太有限了。直接使用 Windows API 将使您能够完全访问底层功能,即使 Delphi 没有公开它们。反过来,您将了解有关 Windows 工作原理的更多信息,并且您可以随时在 Delphi 之外回收这些知识...并且您不需要使用丑陋的技巧来包装不需要的东西需要包装在接口或类似的东西中...

如果您需要跨平台功能,请为 OS 互斥实现编写您自己的包装器 - 不要依赖 Delphi 那些。