65.9K
CodeProject 正在变化。 阅读更多。
Home

通过封闭源 C++ DLL 访问 API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (19投票s)

2013年1月17日

CPOL

6分钟阅读

viewsIcon

39039

downloadIcon

557

在没有源代码或头文件的情况下调用 C++ DLL,即使 DLL 依赖于 C 运行时库

引言

本文介绍了一种有用的技术,用于使用闭源 C++ DLL(在运行时加载)来访问流行的消费类外围设备的 API。假定开发人员无法访问导入库或源代码。

背景

这是我关于 Continuum 博客文章的后续技术文章,该文章介绍了我如何自动化一个简单的软件任务,以实现一个没有提供 API 的消费类网络摄像机的自动平移缩放功能。我们需要在不干扰用户体验的情况下完成此任务,因此 AutoIt 等标准程序自动化工具被排除在外。

在此过程中,我意识到在调用 C++ DLL 时存在一些陷阱,并确定了解决方法。本文的灵感来自 Recx Ltd 的一篇 博客文章《在没有源代码或头文件的情况下处理 C++ DLL 导出》。虽然提供了一个示例项目,但我们的具体案例依赖于一个专有的 DLL,由于许可限制无法包含。

API Monitor 是监视 API 调用活动的宝贵工具。它对 Windows API 调用具有更全面的功能,但也可以用于查看对外部 DLL 的调用。您可以在工具内部
启动进程,或附加到正在运行的进程。API 调用之前和之后的调用堆栈状态都可以看到,返回值也是可见的,甚至还提供了断点功能。

使用 API Monitor,我启动了网络摄像机控制器应用程序的一个实例,这使我能够看到哪些 DLL 正在被加载。从进程执行开始监视(而不是附加到正在运行的进程)对于查看初始化行为可能很重要。

模块依赖视图显示了大量的 DLL。其中大多数是 Windows 系统 DLL 或与 Qt 框架相关。有一个尤其引起了我们的注意:CameraControls_Core.dll。我设置了 API Monitor 来记录对该 DLL 的所有调用,这是相关的输出

LWS::CameraControls::CameraControls_Core::Init ( ... )
LWS::CameraControls::CameraControls_Core::GetCurrentVideoDevice ( ... )
LWS::CameraControls::CameraControls_Core::SetCurrentVideoDevice ( ... )
LWS::CameraControls::CameraControls_Core::GetFaceTracking ( ... ) 
在实时监视 API 活动时,我勾选和取消勾选了面部识别框。我注意到调用了 SetFaceTracking()。快速查看调用堆栈,发现布尔值作为参数传递给了该方法。我使用了 Microsoft 的 **dumpbin** 工具来检查 CameraControls_Core.dll 的导出方法列表。很快就发现我正在处理一个 C++ DLL(由于使用了名称修饰)。
方法名如下所示:
1 0 00001366 ??0CameraControls_Core@CameraControls@LWS@@QAE@XZ
2 1 000015C3 ??1CameraControls_Core@CameraControls@LWS@@UAE@XZ
3 2 00038740 ??_7CameraControls_Core@CameraControls@LWS@@6B@   

我将 **dumpbin** 工具的输出输入到 Microsoft 的 **undname** 工具中,以取消对名称的修饰。

这提供了更清晰的输出:

1    0 00001366 public: __thiscall LWS::CameraControls::CameraControls_Core::CameraControls_Core(void)
2    1 000015C3 public: virtual __thiscall LWS::CameraControls::CameraControls_Core::~CameraControls_Core(void)
3    2 00038740 const LWS::CameraControls::CameraControls_Core::`vftable'
...
40   27 000012E9 public: bool __thiscall LWS::CameraControls::CameraControls_Core::Init(class QString)  
...
86   55 000016D1 public: long __thiscall LWS::CameraControls::CameraControls_Core::SetFaceTracking(unsigned long,long) 

大约在这个时候,我偶然发现了上面提到的 Recx Ltd 文章。我意识到我们的特定 DLL 会带来一些挑战,使得这种技术的简单脚本应用变得不可能。我将 DLL 的一个副本移到我的应用程序的工作目录中,并尝试编写一些代码来动态加载它。

static HINSTANCE CameraControlDllHandle = NULL;
CameraControlDllHandle = LoadLibrary(L"CameraControls_Core.dll"); 

逐步执行代码,发现了第一个依赖库:Qt4Gui4。随着错误的出现,我将依赖项复制到工作目录中。结果发现需要十个额外的 DLL。我最后一次运行代码,遇到了关于 MSVCR90.DLL 丢失的错误。

将此 DLL 放在目录中会导致 C 运行时加载不正确的错误。

MSVCR90.DLL 是 Visual C++ 2008 的 C 运行时库。我尝试重新构建我的项目以使用此运行时运行,以便它使用与 DLL 相同的运行时,但这并没有缓解问题。事实证明,Microsoft 在 Windows 98 中引入了一种新的系统范围 DLL 管理形式,用于允许冲突的 DLL 同时存在于内存中(Wikipedia DLL Hell)。符合此标准的 DLL 包含一个清单文件,该文件被导入到 DLL 中,以告知调用进程需要加载哪些依赖项。

为了克服 C 运行时依赖关系,我需要创建一个指向运行时库的清单文件,并将其嵌入到我的 CameraControls_Core.dll 中。这篇 MSDN 文章概述了如何使用 Microsoft 的 MT.EXE 工具将清单文件嵌入到已构建的可执行文件或 DLL 中。

我需要用于 MSVCR90.DLL 的清单文件如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC90.DebugCRT" 
        version="9.0.21022.8" processorArchitecture="x86" 
        publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
    </dependentAssembly>
  </dependency>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" 
        version="9.0.21022.8" processorArchitecture="x86" 
        publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
    </dependentAssembly>
  </dependency>
</assembly>

完成此步骤后,库就可以无异常地加载到内存中了!现在,我只需要验证方法调用是否有效。早期尝试调用几乎任何方法时都会抛出异常。我意识到这是因为某个关键的初始化函数可能没有被调用。读者应该先阅读在没有源代码或头文件的情况下处理 C++ DLL 导出》

我将直接跳到有效的解决方案并进行解释:
 #include <QString>
//------------------------------------------------------------------------------
// Function Pointer Typedefs
//------------------------------------------------------------------------------
typedef long (__thiscall *_SetFaceTracking)(DWORD *pThis, unsigned long, long boolValue);
typedef long (__thiscall *_GetFaceTracking)(DWORD *pThis, unsigned long, long *boolValue);
typedef void (__thiscall *_CameraControls_Core)(DWORD *pThis);
typedef bool (__thiscall *_Init)(DWORD *pThis, class QString param1);
typedef long (__thiscall *_GetCurrentVideoDevice)(DWORD *pThis, unsigned long *param);
typedef long (__thiscall *_SetCurrentVideoDevice)(DWORD *pThis, unsigned long param);
//--------------------------------------------------------------------------------------------
// Static Variables
//--------------------------------------------------------------------------------------------
static BOOL            freeResult;
static _SetFaceTracking    CameraSetFaceTracking;
static _GetFaceTracking     CameraGetFaceTracking;
static DWORD            dwFakeObject[512];
static HINSTANCE        CameraControlDllHandle = NULL;
static unsigned long    currentVideoDevice = 0;
PRINCEDLL_API HRESULT APIENTRY InitLibrary()
{
    long retValue = 0;
    QString inputString = "";
 
    CameraControlDllHandle = LoadLibrary(L"CameraControls_Core.dll");
    
    if (CameraControlDllHandle == NULL)
        return E_FAIL;
    _CameraControls_Core CameraImportedConstructor = (_CameraControls_Core) GetProcAddress(
      CameraControlDllHandle,"??0CameraControls_Core@CameraControls@LWS@@QAE@XZ");
    if (CameraImportedConstructor == NULL)
        return E_FAIL;
    CameraSetFaceTracking = (_SetFaceTracking) GetProcAddress (CameraControlDllHandle,
      "?SetFaceTracking@CameraControls_Core@CameraControls@LWS@@QAEJKJ@Z");
    if (CameraSetFaceTracking == NULL)
        return E_FAIL;
    CameraGetFaceTracking = (_GetFaceTracking) GetProcAddress (CameraControlDllHandle, 
      "?GetFaceTracking@CameraControls_Core@CameraControls@LWS@@QAEJKPAJ@Z");
    if (CameraGetFaceTracking == NULL)
        return E_FAIL;
    _Init CameraInitializer = (_Init) GetProcAddress (CameraControlDllHandle, 
      "?Init@CameraControls_Core@CameraControls@LWS@@QAE_NVQString@@@Z");
    if (CameraInitializer == NULL)
        return E_FAIL;
    _SetCurrentVideoDevice CameraSetCurrentVideoDevice = (_SetCurrentVideoDevice) GetProcAddress (
      CameraControlDllHandle, "?SetCurrentVideoDevice@CameraControls_Core@CameraControls@LWS@@QAEJK@Z");
    if (CameraSetCurrentVideoDevice == NULL)
        return E_FAIL;
    _GetCurrentVideoDevice CameraGetCurrentVidDevice = (_GetCurrentVideoDevice) GetProcAddress (
      CameraControlDllHandle, "?GetCurrentVideoDevice@CameraControls_Core@CameraControls@LWS@@QAEJPAK@Z");
    if (CameraGetCurrentVidDevice == NULL)
        return E_FAIL;
    memset (dwFakeObject, 0x00, 512);
    CameraImportedConstructor(dwFakeObject);
    retValue = CameraInitializer(dwFakeObject, inputString);
    retValue = CameraGetCurrentVidDevice (dwFakeObject, &currentVideoDevice);
    retValue = CameraSetCurrentVideoDevice (dwFakeObject, currentVideoDevice);
    return S_OK;
} 
函数或方法的调用约定决定了信息如何从调用者传递和返回给被调用者。我们的 DLL 使用的调用约定与上面
文章中介绍的有所不同。他们的示例使用了 __cdecl,我刚了解到这现在是 Win x64 系统上所有函数的标准调用约定(这是为了消除许多复杂调用约定带来的问题)。

__cdecl 中,所有参数都通过调用堆栈传递。对象实例 this 指针最后传递。我们的 DLL 使用 __thiscall 约定 事实证明,__thiscall 通知被调用者 this 指针是通过 ECX 寄存器传递的(不在调用堆栈上)。__thiscall 关键字告诉编译器第一个参数应放在 ECX 寄存器中。技术上,第一个参数应该是指向对象实例的指针。

由于我们的 DLL 的 Init() 函数需要一个 QString 作为参数,因此我需要确定目标 DLL 使用的 Qt 版本,构建 Qt,并将其静态链接到我的 DLL。幸运的是,将一个空的 QString 传递给 Init 函数完成了初始化而没有错误,并允许调用其他方法。dwFakeObject 数组是必需的,因为 C++ 实例方法期望传递一个指向它们要操作的对象的引用。我们在内存中预留一个区域,并将其视为指向传递给 DLL 中方法的 것입니다
一个虚拟对象的引用。

关注点

希望这对遇到类似情况尝试使用第三方 DLL 的人会有所启发。在无法获取底层接口和数据定义
类型源代码的情况下,问题可能比本示例更复杂或更简单。但是,它说明了访问 DLL 函数并不总是有通用解决方案,在某些情况下需要临时方法。

涉及的主题

  • 嵌入式 DLL 清单文件
  • 查看导出函数以及从 C++ DLL 进行调用
  • APIMonitor 作为监视 API 调用的工具
  • 调用约定

历史

2013 年 1 月 17 日 - 首次发布。

© . All rights reserved.