【问题标题】:How to initialize the C runtime when using a Windows executable as a DLL将 Windows 可执行文件用作 DLL 时如何初始化 C 运行时
【发布时间】:2023-03-31 07:23:01
【问题描述】:

我正在尝试对我的 Windows 可执行文件中的类和函数进行单元/组件测试。我想在不将测试用例构建到可执行文件中的情况下运行实际编译的代码*。 Microsoft 工具非常乐意从可执行文件中导出类和函数,我可以将其链接到它,就像它是一个 DLL 一样。问题 - 由于没有调用动态加载的可执行文件的入口点并且没有 DllMain(从技术上讲,它不是 DLL),因此 C 运行时没有在“DLL”中初始化,并且静态变量没有被初始化* *.

有没有办法在动态加载的 .EXE 的上下文中调用 CRT_INIT 并让一切正常运行,或者这很荒谬吗?

*如果我在这里遗漏了一些明显的东西,请随时指出我正确的方向。

**这似乎是我最大的问题。

【问题讨论】:

  • 代码中没有DLLMain,并不意味着没有DLLMain。从 VS2003 开始​​,如果您没有,则提供了一个默认的 DLLMain。检查使用依赖 - 您可能会发现有一个默认的 DLLMain。在这种情况下,您可以使用 LoadLibrary 和 GetAddress 进行测试。

标签: c++ windows unit-testing testing dll


【解决方案1】:

免责声明 - 我坚持我原来的答案。 这应该不起作用。最佳做法是将核心代码编译为 LIB 或 DLL,然后将此库链接到 UnitTest 代码和主程序代码。

但既然 OP 向我展示了他确实找到了一种使 EXE 作为 DLL 工作的方法,我觉得有必要让他完成剩下的工作......

诀窍是调用_CRT_INIT。但是由于没有调用 DllMain 来获取 HINSTANCE 句柄,因此您必须直接调用 GetModuleHandle 来获取它。然后我保留一个表来跟踪哪些线程已经为其调用了 CRT_INIT。

我拿了他提供的示例代码,并在 AppToTest.exe 代码中添加了这个数据结构:

bool g_isProcessInitialized = false;
std::map<DWORD, bool>* g_threadmap;

extern "C" BOOL __stdcall _CRT_INIT(HINSTANCE, DWORD, void*);

void ThreadInit()
{
    std::map<DWORD, bool>& themap = *g_threadmap;

    DWORD dwCurrentThreadID = ::GetThreadId(GetCurrentThread());
    if (themap[dwCurrentThreadID] == false)
    {
        _CRT_INIT(GetModuleHandle("AppToTest.exe"), DLL_THREAD_ATTACH, NULL);
        themap[dwCurrentThreadID] = true;
    }
}

void ProcessInit()
{
    if (g_isProcessInitialized == false)
    {
        _CRT_INIT(GetModuleHandle("AppToTest.exe"), DLL_PROCESS_ATTACH, NULL);
        _CRT_INIT(GetModuleHandle("AppToTest.exe"), DLL_THREAD_ATTACH, NULL);

        g_isProcessInitialized = true;

        g_threadmap = new std::map<DWORD, bool>();
        std::map<DWORD, bool>& themap = *g_threadmap;
        themap[GetThreadId(GetCurrentThread())] = true;
    }
}

void APPAPI InitializeCRT()
{
    ProcessInit();
    ThreadInit();
}

然后在TestApp.exe中,我修改了“main”,提前调用InitializeCRT:

int main(int argc, char* argv[])
{
    InitializeCRT();

这似乎有效 - 即使在我取消注释对标记为有问题的方法的调用之后。如果创建了后续线程,则这些线程可能需要调用 InitializeCRT。 (无论如何它可能会工作......)

我不敢相信这真的有效。可能不适用于旧版本的 Windows。不应该这样……

【讨论】:

  • 按线程 ID 索引地图可能不是世界上最好的主意 ;) 如果两个线程同时索引一个不存在的条目,它们都会修改地图的内部结构正在同时这样做。当然很少见,但 TLS 往往更适合这种事情,并且避免了使地图线程安全所需的任何锁定。
【解决方案2】:

这行不通。您不能像动态加载 DLL 一样动态加载 EXE。有一些有限的场景涉及从 EXE 加载资源,但没有导出函数。

最好将除 WinMain 之外的所有内容编译为 LIB 或 DLL。然后将您的 UnitTest.EXE 和 YourProgram.EXE(每个都提供 main 或 WinMain 的实现)链接到该库。

【讨论】:

  • 除了它确实工作(大部分)。我已经有了想要导出的类/函数,并且在构建时链接到它们并在运行时动态链接到它们。 依赖于静态初始化的类/方法/函数可以正常工作。只有那些这样做才有问题。
  • @user3422499 - 我怀疑您实际上是在两个项目中使用相同的代码进行静态链接。随意发布一个 Visual Studio 解决方案的最小工作示例,演示一个 EXE 动态链接到另一个 EXE 的代码。我总是愿意接受教育。但是,我相当肯定你不能做你所描述的。
  • 无论如何-我的原始答案仍然有效。这通常是大多数 Windows 开发人员将他们的单元测试代码从他们的产品代码中分离出来的方式。在我开发的一个产品上,我们确实在 EXE 中包含了单元测试,但它是一个单独的构建项目(例如 Debug、Release 和 DebugUnitTest)。只有后者实际上包含了 UT 代码。
  • A) 我忘记在解决方案文件中设置依赖项,因此您可能需要在构建 TestHarness 之前手动构建 AppToTest 和 B) 构建后,您可以重命名/删除 AppToTest.exe 和验证 TestHarness 确实将其作为 DLL 加载,并且没有它自己正在使用的类的副本。
  • @user3422499 - 哇。它确实有效。 (我还是不喜欢)。但无论如何,我确实想出了如何让它工作并调用 _CRT_INIT... 很快就会有答案。
猜你喜欢
  • 2013-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多