You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何调用外部DLL中未在头文件声明的结构体函数?

问题描述

我在Visual Studio 2022的解决方案里实现一个独立组件,需要访问外部DLL的内容、修改变量并调用其他组件的函数。由于部分函数/类没在原头文件声明,我自己写了“伪头文件”。用这个伪头文件能成功编译,也能访问修改原实例的变量,但调用结构体内部函数时会触发LNK2019链接错误。


公共代码部分

CommonClass和CommonContainer是解决方案共享类,CommonContainer通过GetReference()返回内部引用。

Common.h(系统包装类)

#pragma once

class CommonClass {

};

template<class T>
class CommonContainer {
private:
    T* reference;

public:
    CommonContainer(T* newReference)
        : reference(newReference)
    {
    }

    T* GetReference() const {
        return reference;
    }
    
    CommonContainer& operator=(const CommonContainer& other)
    {
        reference = other.GetReference();
        return *this;
    }
    
    template<class OtherClass>
    CommonContainer& operator=(const CommonContainer<OtherClass>& other)
    {
        reference = other.GetReference();
        return *this;
    }
    
    CommonContainer& operator=(T* newReference)
    {
        reference = newReference;
        return *this;
    }
};

SharedComponent.h(跨组件共享存储类)

#pragma once
#include "Common.h"
#include <map>
#include <string>

template<class T>
class SharedComponent {
    private:
    std::map<std::string, T*> componentsMap = {};
        
    public:
    T* Get(std::string name) {
        return componentsMap[name];
    }
    
    T* Set(std::string name, T* component) {
        componentsMap[name] = component;
        return component;
    }
};

外部DLL部分

Handler.h(独立DLL中的Handler组件)

#pragma once

namespace outside
{
class HandlerComponent : public CommonClass
{
public:
    HandlerComponent();

private:
    void GetData(std::vector<std::string>* data);
    void Initialize(SharedComponent<CommonClass>* shared);
    CommonContainer<CommonClass> implementation;
};
}

Handler.cpp(实现代码)

#include "Common.h"
#include "SharedComponent.h"
#include <vector>
#include <string>
#include "Handler.h"

namespace outside
{

struct HandlerComponentImplementation : CommonClass
{
    void Initialize();
    std::vector<std::string> GetJsonData();

    struct InternalData
    {
        std::vector<std::string> requests;
        std::vector<std::string> responses;
        int counter;

        InternalData() : counter(0)
        {
            Update();
        }

        void Update()
        {
            // Update internal data
        }
    };

    InternalData internal;
};

void HandlerComponentImplementation::Initialize() {
    // Some init code
    internal = InternalData();
}

std::vector<std::string> HandlerComponentImplementation::GetJsonData() {
    internal.Update();
    return internal.responses;
}

void HandlerComponent::GetData(std::vector<std::string>* data) {
    auto componentImplementation = static_cast<HandlerComponentImplementation*>(implementation.GetReference());
    *data = componentImplementation->GetJsonData();
}

void HandlerComponent::Initialize(SharedComponent<CommonClass>* shared) {
    CommonContainer<HandlerComponentImplementation> componentImplementation = new HandlerComponentImplementation();
    implementation = componentImplementation;
    
    shared->Set("Handler", this);
}

}

我的组件部分

HandlerFake.h(伪头文件,将Handler类成员设为公有)

#pragma once
#include "Common.h"
#include "SharedComponent.h"
#include <vector>
#include <string>

namespace outside
{
struct HandlerComponentImplementation : CommonClass
{
public:
    std::vector<std::string> GetJsonData();

    struct InternalData
    {
    public:
        std::vector<std::string> requests;
        std::vector<std::string> responses;
        int counter;

        InternalData();

        void Update();
    };

    InternalData* internal;
};

class HandlerComponent : public CommonClass
{

public:
    HandlerComponent();
    void GetData(std::vector<std::string>* data);
    void Initialize();
    CommonContainer<CommonClass> implementation;
};

// Component to manipulate Handler instance
class ManipulatorComponent : public CommonClass
{
    void Initialize(SharedComponent<CommonClass>* shared);
};
}

HandlerFake.cpp(获取Handler实例并操作的代码)

#include "Common.h"
#include "SharedComponent.h"
#include <vector>
#include <string>
#include "HandlerFake.h"

namespace outside {
    void ManipulatorComponent::Initialize(SharedComponent<CommonClass>* shared) {
        auto handler = shared->Get("Handler");
        auto handlerComponent = (HandlerComponent*) handler;
        
        // Can access whatever from handlerComponent
        auto implementation = handlerComponent->implementation;
        auto handlerComponentImplementation = static_cast<HandlerComponentImplementation*>(implementation.GetReference());
        auto handlerComponentImplementationInternal = handlerComponentImplementation->internal;
        
        // Get error if use this (LNK2019)
        handlerComponentImplementationInternal->Update();
    }
}

问题原因与解决方法

核心原因

LNK2019错误是因为链接器找不到InternalData::Update()的符号定义。外部DLL里的InternalData是嵌套在HandlerComponentImplementation内部的私有结构,它的成员函数Update()只在DLL内部实现,没有被导出到外部可见的符号表中。你写的伪头文件虽然声明了这个函数,但链接器无法在DLL的导出符号里找到对应的实现。

另外,伪头文件里把HandlerComponentImplementation::internal声明成了指针InternalData*,但实际DLL里它是值类型InternalData internal——这会导致内存布局不匹配,即使编译过了,运行时也会出问题。

解决步骤

  1. 修正内存布局不匹配问题:
    在伪头文件HandlerFake.h里,把HandlerComponentImplementation的internal成员改成和DLL里一致的类型:

    // 原伪头文件里的错误声明
    // InternalData* internal;
    // 改成和DLL一致的:
    InternalData internal;
    
  2. 通过函数指针调用私有成员函数:
    由于InternalData::Update()没有被导出,直接调用会触发链接错误,需要用函数指针的方式绕开链接器检查。具体步骤:

    • 找到InternalData::Update()的内存地址(可以通过逆向工程工具,比如x64dbg,或者根据类的内存布局计算偏移)。
    • 在代码里把地址转换成函数指针,然后调用。

    示例代码修改:

    void ManipulatorComponent::Initialize(SharedComponent<CommonClass>* shared) {
        auto handler = shared->Get("Handler");
        auto handlerComponent = (HandlerComponent*) handler;
        
        auto implementation = handlerComponent->implementation;
        auto handlerComponentImplementation = static_cast<HandlerComponentImplementation*>(implementation.GetReference());
        
        // 修正后的internal访问(值类型而非指针)
        auto& internalData = handlerComponentImplementation->internal;
        
        // 假设通过逆向得到Update()的函数地址是0x12345678(实际要根据DLL的导出表或内存计算)
        using UpdateFunc = void(__thiscall*)(InternalData*);
        UpdateFunc update = reinterpret_cast<UpdateFunc>(0x12345678);
        
        // 调用函数,传入this指针
        update(&internalData);
    }
    
  3. 替代方案:利用现有导出函数间接调用:
    如果能找到DLL里已经导出的函数间接触发Update()(比如HandlerComponent::GetData()内部会调用internal.Update()),可以通过调用这些公开函数来达到目的,避免直接调用私有函数。


访问外部DLL私有成员/函数的实用技巧
  • 内存布局匹配优先:伪头文件的类/结构必须和DLL内部的内存布局完全一致(成员顺序、类型、继承关系都不能错),否则会导致内存访问错误。
  • 函数指针跳转:对于未导出的成员函数,通过逆向工程获取函数地址,转换成对应签名的函数指针调用。注意__thiscall调用约定(成员函数默认调用约定)需要传入this指针作为第一个参数。
  • 利用RTTI或虚函数表:如果类有虚函数,可以通过虚函数表的偏移找到对应的函数地址。比如CommonClass如果有虚函数,HandlerComponentImplementation的虚表会包含它的虚函数入口。
  • 内存直接修改:如果只是需要修改变量,直接通过内存偏移访问即可,不需要函数调用,这也是你之前能成功修改变量的原因。
  • DLL注入与挂钩:如果需要长期调用私有函数,可以把自己的组件注入到目标进程空间,通过挂钩或直接内存访问的方式操作,但这种方式复杂度较高。

内容的提问来源于stack exchange,提问作者YYY

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:59:54