我有一个简单的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
更改后:
- 在pch.h文件中:
#define DLLDIR__declspec(dllexport)__stdcall - 在客户端文件中:
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参数传递给编译器
- 你不太可能遇到(微妙的)问题(至少在你获得更多这方面的经验之前)
- 删除所有与呼叫约定相关的代码,会使其更短、更干净
所有列出的语言(Delphi,Python、C#…)都支持__cdecl(毕竟,我认为大多数运行的机器代码仍然是用C编写的)
有关整个区域的更多详细信息,您可以检查(包括(递归)引用的URLs):
[SO]:Python Ctypes-加载dll引发OSError:[WinError 193]%1不是有效的Win32应用程序(@CristiFati的答案)
[SO]:当在库中使用fstream时,我在可执行文件中得到链接器错误(@CristiFati的答案)