如何在C中使用函数的参数作为指针从DLL返回值?

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

我有一个简单的DLL:

dllmain.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define MYDLLDIR        
#include"pch.h"

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


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

pch.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#include"framework.h"

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

extern"C" {

    DLLDIR void callByPtr(int *i);
       
};

客户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 ----\
"
;

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

    }
}

结果:

----通话前----

i = 10

ptri = 0025FB40

----通话后-----

i = 11286192

ptri = 0025FB3C

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

如何正确实现此功能,以便可以使用上述方法从DLL获取值?

谢谢!


根据[MS.Docs]:__stdcall

Syntax

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

调用约定说明符位于函数返回类型之后。定义它的方式是以前的,所以(可能)编译器忽略了它????,最终出现在.dll中,将该函数导出为__cdecl(默认值),而当.exe将该函数称为__stdcall时,砰! ->堆栈损坏,并且您认为指针实际上是完全不同的,因此输出很奇怪。有趣的是,最后,当我尝试使用您的表单(#define DLL00_EXPORT_API __declspec(dllexport) __stdcall)构建.dll时,编译器(VS2017)吐出error C2062: type 'void' unexpected

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

dll00.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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:

1
2
3
4
5
6
7
8
9
#define DLL00_EXPORTS
#include"dll00.h"


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

main00.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#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 ----\
"
;

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

    CloseHandle(hDLL);

    return 0;
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
[cfati@CFATI-5510-0:e:\\Work\\Dev\\StackOverflow\\q063951075]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***

[prompt]>"c:\\Install\\pc032\\Microsoft\\VisualStudioCommunity\\2017\\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

注意事项:

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

    • 它不会编译:

      • 我在开始时提到的错误(尽管使用某些较旧的编译器(或(怀疑地是?)编译器标志可以避免),但是可以避免该错误)
      • .exe代码中缺少#includes和usings
  • "次要"代码问题:

    • 取消引用前的NULL指针测试
    • 关闭手柄
    • DLLDIR定义之间的区别(也由@Petr_Dokoupil提到):__declspec(dllexport) __stdcall__declspec(dllimport),但是由于您正在动态加载.dll而不是链接到它,因此它与错误无关
  • 为什么需要使用__stdcall? (注释中提供的答案:"与其他语言兼容"):

    • 仅在32位上有意义(在64位上它会被忽略)

    • 它引入了许多其他问题(其中一些您甚至没有遇到过),例如函数名称处理(检查dumpbin输出),只能使用.def文件来避免。

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

      • 使用此版本的代码,只需在构建.dll和.exe时不将/ DCALL_CONV_STDCALL参数传递给编译器
      • 您不太可能遇到(细微)问题(至少直到您在该领域获得更多经验之前)
      • 删除所有与调用约定相关的代码,将使其更短,更整洁
    • 所有列出的语言(Delphi,Python,C#等)都支持__cdecl(毕竟,我认为那里用完的大多数机器代码仍然是用C编写的)。

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

  • [SO]:Python Ctypes-加载dll引发OSError:[WinError 193]%1不是有效的Win32应用程序(@CristiFati的回答)

  • [SO]:在库中使用fstream时,我在可执行文件中收到链接器错误(@CristiFati的回答)


因为您在导出的函数DLLDIR中返回了void void callByPtr(int * i);您应该对C和C程序__cdecl使用默认的调用约定。
更改后:

  • 在您的pch.h文件中:
    #定义DLLDIR __declspec(dllexport)__stdcall

    #定义DLLDIR __declspec(dllexport)
  • 在您的客户档案中:
    typedef void(__ stdcall * callByPtr)(int *);

    typedef void(__ cdecl * callByPtr)(int *);
  • 重建没有错误和警告,并且输出是这样的:
    我10
    ptri 0113FCA4
    ----通话后----
    我11
    ptri 0113FCA4


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

    1
    2
    3
    4
    5
    #ifdef MYDLL_EXPORT
    #define MYDLLDIR  __declspec(dllexport)
    #else
    #define MYDLLDIR __declspec(dllimport)
    #endif

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

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

    然后在您的pch.h中:

    1
    MYDLLDIR void __stdcall callByPtr(int *i);