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



我有一个简单的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;    
}
}

结果:

----通话前----

i=10

ptri=0025FB40

----呼叫后----

i=11286192

ptri=0025FB3C

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

如何正确地实现这一点,我可以使用上面的方法从DLL中获得一个值?

谢谢!

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

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

并将相同的宏(MYDLLDIR)用于导出(dll,定义了#MYDLL_export)和导入(客户端,未定义#MYDLL_export)

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

在你的电脑里:

MYDLLDIR void __stdcall callByPtr(int *i);

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

  1. 在pch.h文件中:
    #define DLLDIR__declspec(dllexport)__stdcall
  2. 在客户端文件中:
    typedef void(__stdcall*callByPtr)(int*)

    typedef void(__cdecl*callByPtr)(int*)

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

i 10
ptri 0113FCA4
----调用后----
i11
pti 0113FCA4

根据[MS.Docs]:__stdcall

语法

返回类型 __stdcall 函数名[(参数列表)]

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

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

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:WorkDevStackOverflowq063951075]> sopr.bat
*** Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ***
[prompt]> "c:Installpc032MicrosoftVisualStudioCommunity2017VCAuxiliaryBuildvcvarsall.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和.exe代码中使用s
  • "次要的";代码问题:

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

    • 只在32位上重要(在64位上被忽略)

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

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

      • 使用此版本的代码,在构建.dll.exe时,不要将/DCALL_CONV_STDCALL参数传递给编译器
      • 你不太可能遇到(微妙的)问题(至少在你获得更多这方面的经验之前)
      • 删除所有与呼叫约定相关的代码,会使其更短、更干净
    • 所有列出的语言(DelphiPythonC#…)都支持__cdecl(毕竟,我认为大多数运行的机器代码仍然是用C编写的)

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

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

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

相关内容

  • 没有找到相关文章

最新更新