如何使用函数的参数作为 C++ 中的指针从 DLL 获取 return 值?

How to return value from DLL using parameter of function as a pointer in C++?

我有一个简单的 DLL:

dllmain.cpp:

#define MYDLLDIR        
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    ...
}


void callByPtr(int *i) {
    
    (*i)++;
}

pch.h

#include "framework.h"

#ifdef MYDLLDIR
#define DLLDIR __declspec(dllexport) __stdcall
#else
#define DLLDIR __declspec(dllimport)
#endif

extern "C" {

    DLLDIR void callByPtr(int *i);
        
};

客户:

typedef void(__stdcall* callByPtr)(int*);

int main()
{
    HINSTANCE hDLL;

    hDLL = LoadLibrary(_T("MyDll.dll"));

    if (NULL != hDLL)
    {

        callByPtr myCall = (callByPtr)GetProcAddress(hDLL, "callByPtr");

        if (!myCall) {
            return EXIT_FAILURE;
        }

        int i = 10;

        int* ptri = &i;

        std::cout << "i " << i << std::endl;
        std::cout << "ptri " << ptri << std::endl;

        myCall(ptri);

        std::cout << "---- After Call ----\n";

        std::cout << "i " << i << std::endl;
        std::cout << "ptri " << ptri << std::endl;    

    }
}

结果:

----调用前----

我=10

ptri = 0025FB40

---- 通话后----

我=11286192

ptri = 0025FB3C

ptri 的地址已更改,值不是 11。

如何正确实现这一点,以便我可以使用上述方法从 DLL 中获取值?

谢谢!

您的导出定义也不正确。应该是这样的:

#ifdef MYDLL_EXPORT
#define MYDLLDIR  __declspec(dllexport)
#else
#define MYDLLDIR __declspec(dllimport)
#endif

并对导出(dll,#MYDLL_EXPORT 已定义)和导入(客户端,#MYDLL_EXPORT 未定义)使用相同的宏 (MYDLLDIR)

在你的情况下,你必须在所有地方对 callByPtr 使用相同的调用约定 __stdcall(默认为__cstdcall)。

在你的 pch.h 然后:

MYDLLDIR void __stdcall callByPtr(int *i);

因为你 return 在你的导出函数中无效 DLLDIR void callByPtr(int *i);你应该使用 C 和 C++ 程序的默认调用约定 __cdecl.
更改后:

  1. 在您的 pch.h 文件中:
    #define DLLDIR __declspec(dllexport) __stdcall

    #define DLLDIR __declspec(dllexport)

  2. 在您的客户端文件中:
    typedef void(__stdcall* callByPtr)(int*);

    typedef void(__cdecl* callByPtr)(int*);

重建没有错误和警告,输出如下:

我 10
ptri 0113FCA4
---- 通话后----
我 11
ptri 0113FCA4

根据[MS.Docs]: __stdcall

Syntax

return-type __stdcall function-name[( argument-list )]

调用约定说明符函数return类型之后。你定义它的方式是before,所以(可能)编译器忽略了它???,最终在 .dll 中将函数导出为 __cdecl(默认),当 .exe 称之为 __stdcall, Bang! -> Stack Corruption,你认为你的指针实际上是完全不同的东西,因此你的输出很奇怪。
有趣的是,在我这边,编译器 (VS2017) 在我尝试构建 .dll 使用你的表单 (#define DLL00_EXPORT_API __declspec(dllexport) __stdcall).

下面是一个有效的示例(我修改了文件名和内容)。

dll00.h:

#pragma once

#if defined(_WIN32)
#  if defined(DLL00_EXPORTS)
#    define DLL00_EXPORT_API __declspec(dllexport)
#  else
#    define DLL00_EXPORT_API __declspec(dllimport)
#  endif
#else
#  define DLL00_EXPORT_API
#endif

#if defined(CALL_CONV_STDCALL)
#  define CALL_CONV __stdcall
#else
#  define CALL_CONV
#endif

#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API void CALL_CONV callByPtr(int *pI);

#if defined(__cplusplus)
}
#endif

dll00.cpp:

#define DLL00_EXPORTS
#include "dll00.h"


void CALL_CONV callByPtr(int *pI) {
    if (pI) {
        (*pI)++;
    }
}

main00.cpp:

#include <iostream>
#include <Windows.h>
#include "dll00.h"

#if defined(CALL_CONV_STDCALL)
#  define FUNC_NAME "_callByPtr@4"
#else
#  define FUNC_NAME "callByPtr"
#endif

using std::cout;
using std::endl;


typedef void(CALL_CONV *CallByPtrFunc)(int*);


int main() {
    HMODULE hDLL;

    hDLL = LoadLibrary("dll00.dll");

    if (!hDLL) {
        std::cout << "LoadLibrary failed" << std::endl;
        return -1;
    }

    CallByPtrFunc callByPtr = (CallByPtrFunc)GetProcAddress(hDLL, FUNC_NAME);

    if (!callByPtr) {
        std::cout << "GetProcAddress failed" << std::endl;
        CloseHandle(hDLL);
        return EXIT_FAILURE;
    }

    int i = 10;

    int *ptri = &i;

    std::cout << "i " << i << std::endl;
    std::cout << "ptri " << ptri << std::endl;

    callByPtr(ptri);

    std::cout << "---- After Call ----\n";

    std::cout << "i " << i << std::endl;
    std::cout << "ptri " << ptri << std::endl;

    CloseHandle(hDLL);

    return 0;
}

输出:

[cfati@CFATI-5510-0:e:\Work\Dev\Whosebug\q063951075]> sopr.bat
*** Set shorter prompt to better fit when pasted in Whosebug (or other) pages ***

[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity17\VC\Auxiliary\Build\vcvarsall.bat" x86
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.27
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x86'

[prompt]>
[prompt]> dir /b
dll00.cpp
dll00.h
main00.cpp

[prompt]> :: Build the .dll (passing /DCALL_CONV_STDCALL)
[prompt]> cl /nologo /MD /DDLL /DCALL_CONV_STDCALL dll00.cpp  /link /NOLOGO /DLL /OUT:dll00.dll
dll00.cpp
   Creating library dll00.lib and object dll00.exp

[prompt]>
[prompt]> :: Build the .exe (also passing /DCALL_CONV_STDCALL)
[prompt]> cl /nologo /MD /W0 /EHsc /DCALL_CONV_STDCALL main00.cpp  /link /NOLOGO /OUT:main00.exe
main00.cpp

[prompt]>
[prompt]> dir /b
dll00.cpp
dll00.dll
dll00.exp
dll00.h
dll00.lib
dll00.obj
main00.cpp
main00.exe
main00.obj

[prompt]>
[prompt]> main00.exe
i 10
ptri 00F5FCC8
---- After Call ----
i 11
ptri 00F5FCC8

[prompt]> :: It worked !!!
[prompt]>
[prompt]> dumpbin /EXPORTS dll00.dll
Microsoft (R) COFF/PE Dumper Version 14.16.27043.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file dll00.dll

File Type: DLL

  Section contains the following exports for dll00.dll

    00000000 characteristics
    FFFFFFFF time date stamp
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0 00001000 _callByPtr@4

  Summary

        1000 .data
        1000 .rdata
        1000 .reloc
        1000 .text

备注:

  • 显然,提供的代码不是生成输出的代码:

    • 它不编译:
      • 我在开头提到的错误(尽管使用一些较旧的编译器(或(怀疑?)编译器标志)可能可以避免)
      • 缺少 #includes 和 usings 在你的 .exe 代码
  • “次要”代码问题:

    • NULL 取消引用前的指针测试
    • 关闭句柄
    • DLLDIR 定义之间的差异(@Petr_Dokoupil 也提到):__declspec(dllexport) __stdcall vs. __declspec(dllimport),但由于您是动态加载 .dll 而不是链接到它, 它与错误无关
  • 为什么要用__stdcall? (评论中提供的答案:“与其他语言兼容”):

    • 只对32位有影响(在64位它被忽略)

    • 它引入了很多额外的问题(其中一些你甚至没有经历过),比如函数名重整(检查 dumpbin 输出),只能使用 .def 文件

      来避免
    • 总而言之,这似乎是一个 XY 问题您应该使用默认值(完全摆脱 __stdcall):

      • 使用此版本的代码,在构建.dll.exe
      • 你不太可能 运行 陷入(微妙的)问题(至少在你获得更多这方面的经验之前)
      • 删除所有与调用约定相关的代码,会使它更短更清晰
    • 所有列出的语言 (Delphi, Python, C#, ...) 支持 __cdecl(毕竟,我认为大多数机器代码 运行 都在那里,还是写成C)

有关整个区域的更多详细信息,您可以查看(包括(递归)引用的 URLs):

  • [SO]: Python Ctypes - loading dll throws OSError: [WinError 193] %1 is not a valid Win32 application (@CristiFati's answer)

  • [SO]: When using fstream in a library I get linker errors in the executable (@CristiFati's answer)