如何模仿静态库但使用DLL的“应用程序中的全局变量的多个实例”行为?

时间:2023-01-18 18:22:33

We have an application written in C/C++ which is broken into a single EXE and multiple DLLs. Each of these DLLs makes use of the same static library (utilities.lib).

我们有一个用C / C ++编写的应用程序,它分为单个EXE和多个DLL。这些DLL中的每一个都使用相同的静态库(utilities.lib)。

Any global variable in the utility static library will actually have multiple instances at runtime within the application. There will be one copy of the global variable per module (ie DLL or EXE) that utilities.lib has been linked into.

实用程序静态库中的任何全局变量实际上都会在应用程序中的运行时具有多个实例。每个模块(即DLL或EXE)的一个全局变量副本将与utilities.lib链接。

(This is all known and good, but it's worth going over some background on how static libraries behave in the context of DLLs.)

(这一切都是众所周知的,但是值得讨论一下静态库在DLL上下文中的行为方式。)

Now my question.. We want to change utilities.lib so that it becomes a DLL. It is becoming very large and complex, and we wish to distribute it in DLL form instead of .lib form. The problem is that for this one application we wish to preserve the current behaviour that each application DLL has it's own copy of the global variables within the utilities library. How would you go about doing this? Actually we don't need this for all the global variables, only some; but it wouldn't matter if we got it for all.

现在我的问题..我们想要更改utilities.lib,使它成为一个DLL。它变得非常庞大和复杂,我们希望以DLL形式而不是.lib形式分发它。问题是,对于这个应用程序,我们希望保留当前行为,即每个应用程序DLL在实用程序库中拥有它自己的全局变量副本。你会怎么做呢?实际上我们并不需要这个全局变量,只有一些;但是如果我们得到所有这一点并不重要。


Our thoughts:

  1. There aren't many global variables within the library that we care about, we could wrap each of them with an accessor that does some funky trick of trying to figure out which DLL is calling it. Presumably we can walk up the call stack and fish out the HMODULE for each function until we find one that isn't utilities.dll. Then we could return a different version depending on the calling DLL.
  2. 我们关心的库中没有很多全局变量,我们可以使用一个访问器来封装每个变量,这些访问器会尝试找出哪个DLL正在调用它。据推测,我们可以向上走调用堆栈,并为每个函数找出HMODULE,直到找到一个不是utilities.dll的函数。然后我们可以根据调用DLL返回不同的版本。

  3. We could mandate that callers set a particular global variable (maybe also thread local) prior to calling any function in utilities.dll. The utilities DLL could then use this global variable value to determine the calling context.
  4. 在调用utilities.dll中的任何函数之前,我们可以强制调用者设置一个特定的全局变量(也可能是本地线程)。然后,实用程序DLL可以使用此全局变量值来确定调用上下文。

  5. We could find some way of loading utilities.dll multiple times at runtime. Perhaps we'd need to make multiple renamed copies at build time, so that each application DLL can have it's own copy of the utilities DLL. This negates some of the advantages of using a DLL in the first place, but there are other applications for which this "static library" style behaviour isn't needed and which would still benefit from utilities.lib becoming utilities.dll.
  6. 我们可以找到一些在运行时多次加载utilities.dll的方法。也许我们需要在构建时制作多个重命名的副本,以便每个应用程序DLL都可以拥有它自己的实用程序DLL副本。这首先否定了使用DLL的一些优点,但是还有其他应用程序不需要这种“静态库”样式行为,并且仍然可以从utilities.lib成为utilities.dll中受益。

2 个解决方案

#1


You are probably best off simply having utilities.dll export additional functions to allocate and deallocate a structure that contains the variables, and then have each of your other worker DLLs call those functions at runtime when needed, such as in the DLL_ATTACH_PROCESS and DLL_DETACH_PROCESS stages of DllEntryPoint(). That way, each DLL gets its own local copy of the variables, and can pass the structure back to utilities.dll functions as an additional parameter.

你可能最好只是让utilities.dll导出附加函数来分配和释放包含变量的结构,然后让你的每个其他工作者DLL在运行时在需要时调用这些函数,例如在DLL_ATTACH_PROCESS和DLL_DETACH_PROCESS阶段的DllEntryPoint()。这样,每个DLL都获得自己的变量本地副本,并可以将结构作为附加参数传递回utilities.dll函数。

The alternative is to simply declare the individual variables locally inside each worker DLL directly, and then pass them into utilities.dll as input/output parameters when needed.

另一种方法是直接在每个worker DLL中本地声明各个变量,然后在需要时将它们作为输入/输出参数传递给utilities.dll。

Either way, do not have utilities.dll try to figure out context information on its own. It won't work very well.

无论哪种方式,都没有utilities.dll尝试自己找出上下文信息。它不会很好。

#2


If I were doing this, I'd factor out all stateful global variables - I would export a COM object or a simple C++ class that contains all the necessary state, and each DLL export would become a method on your class.

如果我这样做,我会分解所有有状态的全局变量 - 我会导出一个COM对象或一个包含所有必要状态的简单C ++类,每个DLL导出将成为你的类的一个方法。

Answers to your specific questions:

您的具体问题的答案:

  1. You can't reliably do a stack trace like that - due to optimizations like tail call optimization or FPO you cannot determine who called you in all cases. You'll find that your program will work in debug, work mostly in release but crash occasionally.
  2. 您无法可靠地执行类似的堆栈跟踪 - 由于尾部调用优化或FPO等优化,您无法确定在所有情况下谁给您打电话。您会发现您的程序将在调试中工作,主要是在发布时工作但偶尔会崩溃。

  3. I think you'll find this difficult to manage, and it also puts a demand that your library can't be reentrant with other modules in your process - for instance, if you support callbacks or events into other modules.
  4. 我认为您会发现这很难管理,并且还要求您的库不能与您流程中的其他模块重新进行 - 例如,如果您支持回调或事件到其他模块。

  5. This is possible, but you've completely negated the point of using DLL's. Rather than renaming, you could copy into distinct directories and load via full path.
  6. 这是可能的,但你完全否定了使用DLL的意义。您可以复制到不同的目录并通过完整路径加载,而不是重命名。

#1


You are probably best off simply having utilities.dll export additional functions to allocate and deallocate a structure that contains the variables, and then have each of your other worker DLLs call those functions at runtime when needed, such as in the DLL_ATTACH_PROCESS and DLL_DETACH_PROCESS stages of DllEntryPoint(). That way, each DLL gets its own local copy of the variables, and can pass the structure back to utilities.dll functions as an additional parameter.

你可能最好只是让utilities.dll导出附加函数来分配和释放包含变量的结构,然后让你的每个其他工作者DLL在运行时在需要时调用这些函数,例如在DLL_ATTACH_PROCESS和DLL_DETACH_PROCESS阶段的DllEntryPoint()。这样,每个DLL都获得自己的变量本地副本,并可以将结构作为附加参数传递回utilities.dll函数。

The alternative is to simply declare the individual variables locally inside each worker DLL directly, and then pass them into utilities.dll as input/output parameters when needed.

另一种方法是直接在每个worker DLL中本地声明各个变量,然后在需要时将它们作为输入/输出参数传递给utilities.dll。

Either way, do not have utilities.dll try to figure out context information on its own. It won't work very well.

无论哪种方式,都没有utilities.dll尝试自己找出上下文信息。它不会很好。

#2


If I were doing this, I'd factor out all stateful global variables - I would export a COM object or a simple C++ class that contains all the necessary state, and each DLL export would become a method on your class.

如果我这样做,我会分解所有有状态的全局变量 - 我会导出一个COM对象或一个包含所有必要状态的简单C ++类,每个DLL导出将成为你的类的一个方法。

Answers to your specific questions:

您的具体问题的答案:

  1. You can't reliably do a stack trace like that - due to optimizations like tail call optimization or FPO you cannot determine who called you in all cases. You'll find that your program will work in debug, work mostly in release but crash occasionally.
  2. 您无法可靠地执行类似的堆栈跟踪 - 由于尾部调用优化或FPO等优化,您无法确定在所有情况下谁给您打电话。您会发现您的程序将在调试中工作,主要是在发布时工作但偶尔会崩溃。

  3. I think you'll find this difficult to manage, and it also puts a demand that your library can't be reentrant with other modules in your process - for instance, if you support callbacks or events into other modules.
  4. 我认为您会发现这很难管理,并且还要求您的库不能与您流程中的其他模块重新进行 - 例如,如果您支持回调或事件到其他模块。

  5. This is possible, but you've completely negated the point of using DLL's. Rather than renaming, you could copy into distinct directories and load via full path.
  6. 这是可能的,但你完全否定了使用DLL的意义。您可以复制到不同的目录并通过完整路径加载,而不是重命名。