簡體   English   中英

如何使用函數的參數作為 C++ 中的指針從 DLL 返回值?

[英]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);

因為您在導出的函數 DLLDIR void callByPtr(int *i); 中返回 void; 您應該使用 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

句法

返回類型__stdcall函數名[(參數列表)]

在調用約定符自帶函數的返回類型之后 你定義它的方式是之前,所以(可能)編譯器忽略了它??? ,最終在.dll中將函數導出為__cdecl (默認),當.exe將其稱為__stdcall 時砰! -> Stack Corruption ,你認為你的指針實際上是完全不同的東西,因此你的輸出很奇怪。
有趣的是,在我的最后,編譯器( VS2017 )吐出error C2062: type 'void' unexpected當我嘗試使用您的表單#define DLL00_EXPORT_API __declspec(dllexport) __stdcall構建.dll時, 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:\\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

注意事項

  • 顯然,提供的代碼不是生成輸出的代碼

    • 它不編譯:
      • 我在開頭提到的錯誤(盡管使用一些較舊的編譯器(或(懷疑?)編譯器標志)可能可以避免這種情況)
      • 缺少#include s 並在.exe代碼中使用s
  • “次要”代碼問題:

    • 取消引用前的指針測試
    • 關閉句柄
    • DLLDIR定義之間的差異@Petr_Dokoupil也提到): __declspec(dllexport) __stdcall__declspec(dllimport) ,但由於您是動態加載.dll而不是鏈接到它,它與錯誤無關
  • 為什么需要使用__stdcall (答案在評論中提供:“與其他語言兼容”):

    • 僅在32 位上很重要(在64 位上它被忽略)

    • 它引入了很多額外的問題(其中一些你甚至沒有經歷過),比如函數名稱修改(檢查轉儲箱輸出),只能使用.def文件來避免

    • 總而言之,這似乎是一個XY 問題 您應該使用默認值(完全擺脫__stdcall ):

      • 使用此版本的代碼,在構建.dll.exe時,不要將/DCALL_CONV_STDCALL參數傳遞給編譯器
      • 你不太可能遇到(微妙的)問題(至少在你在這方面獲得更多經驗之前)
      • 刪除所有與調用約定相關的代碼,將使它更短更干凈
    • 所有列出的語言( DelphiPythonC# ,...)都支持__cdecl (畢竟,我認為那里運行的大多數機器代碼仍然是用C編寫的)

有關整個區域的更多詳細信息,您可以檢查(包括(遞歸)引用的URL s):

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM