从 Delphi“访问冲突”正确调用 DLL 中的 C++ 函数

Posted

技术标签:

【中文标题】从 Delphi“访问冲突”正确调用 DLL 中的 C++ 函数【英文标题】:Properly calling C++ functions in DLL from Delphi "Access Violation" 【发布时间】:2016-11-21 17:42:14 【问题描述】:

我正在尝试从 Delphi 调用 C++ 代码。我是初学者。我不断收到访问冲突错误,尽管它是间歇性的(但很常见)。

这仅在执行ConfigccDLLPriorTran 时发生。据我所知,这可能是调用约定不匹配,但我的印象是我在两个代码库中都使用了stdcall。我已经遍历了我使用 Dependency Walker 创建的 dll,并将函数显示为 _functionName。我不确定是否应该使用前导下划线来调用它们。

我想尽可能少地更改 Delphi 代码,因为最终将使用我无法更改的 dll 的代码(我想说我根本无法更改它,但我已经不得不更改 PAnsiChar 并添加 AnsiStrings 以使 Delphi 编译)。

德尔福代码:

unit dllTest;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, AnsiStrings;

procedure CloseDLL; stdcall; external 'cccontrol.dll';
procedure ConfigccDLL(Variables: PAnsiChar); stdcall; external 'cccontrol.dll';
procedure PrepareDLL; stdcall; external 'cccontrol.dll';
procedure PriorTran(Variables: PAnsiChar); stdcall; external 'cccontrol.dll';


type
  TdllTestForm = class(TForm)
    PrepareBtn: TBitBtn;
    Label1: TLabel;
    ConfigccDLLbtn: TBitBtn;
    TranTypeEntry: TEdit;
    TranAmountEntry: TEdit;
    Label2: TLabel;
    PriorTranBtn: TBitBtn;
    TranIDEntry: TEdit;
    Label3: TLabel;
    CloseDLLBtn: TBitBtn;
    Label4: TLabel;
    Memo1: TMemo;
    BitBtn1: TBitBtn;
    procedure CloseDLLBtnClick(Sender: TObject);
    procedure PriorTranBtnClick(Sender: TObject);
    procedure ConfigccDLLbtnClick(Sender: TObject);
    procedure PrepareBtnClick(Sender: TObject);

  private
     Private declarations 
  public
     Public declarations 
  end;

var
  dllTestForm: TdllTestForm;

implementation

$R *.dfm

procedure TdllTestForm.PrepareBtnClick(Sender: TObject);
var
  AppHandle: HWND;
begin
  AppHandle := Application.Handle;
  PrepareDLL;
end;

procedure TdllTestForm.ConfigccDLLbtnClick(Sender: TObject);
var
  Variables: AnsiString;
  TransID, TransType, TranAmt: string;
begin
  TransType := TranTypeEntry.Text;
  TranAmt := TranAmountEntry.Text;
  Variables := TransType + '^' + TranAmt + '^';
  ConfigccDLL(PAnsiChar(Variables));
end;

procedure TdllTestForm.PriorTranBtnClick(Sender: TObject);
var
  Variables: AnsiString;
  TransID, TransType, TranAmt: string;
begin
  TransID := TranIDEntry.Text;
  Variables := TransID;
  PriorTran(PAnsiChar(Variables));
end;

procedure TdllTestForm.CloseDLLBtnClick(Sender: TObject);
begin
  CloseDLL;
end;

end.

C++代码如下:

头文件:

#pragma once

#ifndef ccControl
#define ccControl
#include <iostream>

#if defined DLL_EXPORT
#define DECLDIR __declspec(dllexport)
#else
#define DECLDIR __declspec(dllimport)
#endif

extern "C" 
    DECLDIR void __stdcall PrepareDLL();
    DECLDIR void __stdcall ConfigccDLL(char* pcharVar);
    DECLDIR void __stdcall PriorTran(char* pcharVar);
    DECLDIR void __stdcall CloseDLL();


#endif

Cpp 文件:

#include "stdafx.h"

#include <iostream>
#include <windows.h>
#include <Winuser.h>
#include <stdexcept>

#define DLL_EXPORT

#include "ccControl.h"

#pragma warning( disable : 4996 ) 

using namespace std;

extern "C" 

    DECLDIR void __stdcall PrepareDLL()
    

    

    DECLDIR void __stdcall ConfigccDLL(char* pcharVar)
    

    

    DECLDIR void __stdcall PriorTran(char* pcharVar)
    

    

    DECLDIR void __stdcall CloseDLL()
    

    

从依赖walker中看到的Dll

Dependency Walker 在打开 dll 时也会出现这些错误

【问题讨论】:

导出函数的名称是 _PriorTran@4,而不是您的 Delphi 代码所述的 PriorTran。如果您想在您的 DLL 中“干净”导出名称,您需要使用 Module Definition File 构建您的 DLL 请不要让这个问题成为一个移动的目标。 我没有尝试,但如果人们要提出更改建议,我将实施它们。 没有。那是不对的。你没有调用那个 dll,所以你对它做了什么并不重要。但不要破坏这个问题。 @DavidHeffernan 你是对的。不再违反访问权限。 【参考方案1】:

我认为很明显,您没有调用您认为正在调用的 DLL。如果您是,那么您的程序将无法启动,因为导出的函数名称不匹配。您在 Dependency Walker 中显示的 DLL 具有修饰名称。您不在 Delphi 代码中使用修饰名称。你的程序执行。因此,您正在链接到不同的 DLL。至于为什么会出现访问冲突,我们当然不能说,因为我们对那个 DLL 一无所知。

一旦您对其进行排序以调用正确的 DLL(将 DLL 与可执行文件放在同一目录中),我们就可以查看问题中的代码。那里的互操作很好,无论如何你的 DLL 函数什么都不做。但是Delphi代码不好。考虑这段代码:

Variables := AnsiStrAlloc(50);
AnsiStrings.StrPCopy(Variables, TransID);

在这里你分配一个长度为 50 的数组,然后将一个长度知道是什么的字符串复制到其中。如果你的源字符串太长,你会溢出缓冲区。

如果您必须使用动态分配,则需要采取措施确保缓冲区足够长。而且您还需要解除分配。您的代码目前像筛子一样泄漏。

但是手动动态分配容易出错,而且很乏味。不要满足于单调乏味的生活。让编译器为您完成工作。在AnsiString 类型的变量中构建文本。当您需要将其传递给您的 C++ 代码时,请使用 PAnsiChar(...) 演员表。

var
  Variables: AnsiString;
....
Variables := TransType + '^' + TranAmt + '^';
ConfigccDLL(PAnsiChar(Variables));

【讨论】:

我在使用 AnsiStrings 时仍然遇到访问冲突错误。同样,当我触发CloseDLL(虽然很少)时,即使它除了调用外部函数什么都不做。至于不使用这些名称的 Delphi 导入代码,我不确定您在说什么。 stdcall 就在那里,并且指定修饰名称会引发 ProcedureEntryPoint not found 错误。 查看我的更新答案。显然,您实际上并没有调用已编译的 DLL,并且导出的名称已被修饰。 使用进程资源管理器找出应用程序正在调用哪个 dll,结果发现它在网络驱动器上(以为我已经清除了 IDE 中的所有搜索路径,我猜我不知道它是怎么回事完全有效)。指定具有完整文件路径的 dll,并且不再违反访问权限。 将 DLL 与可执行文件放在同一目录下。总是先搜索。 Windows 如何定位 DLL:msdn.microsoft.com/en-us/library/7d83bc18.aspx.

以上是关于从 Delphi“访问冲突”正确调用 DLL 中的 C++ 函数的主要内容,如果未能解决你的问题,请参考以下文章

编写从 C++ 应用程序链接的 Delphi DLL:访问 C++ 接口成员函数会导致访问冲突

在 delphi 中使用 COM DLL - MSVCR80D.dll 错误中的访问冲突

MFC dll 中的访问冲突(用 C++/CLI 包装)从 C# 程序开始

如何在 Delphi 中调试从 Java 调用的 DLL?

Delphi ComboBox 对组合框的访问冲突

从 C++ 调用 C DLL 会导致访问冲突,但 C# 项目与 DllImport 工作