从 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++ 代码。我是初学者。我不断收到访问冲突错误,尽管它是间歇性的(但很常见)。
这仅在执行ConfigccDLL
或PriorTran
时发生。据我所知,这可能是调用约定不匹配,但我的印象是我在两个代码库中都使用了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 错误中的访问冲突