在Delphi中正确调用DLL中的C++函数出现“访问冲突”问题

3

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

Access violation

这只发生在执行ConfigccDLL或者PriorTran时。根据我所读的,可能是调用约定不匹配,但是我印象中两个代码库都使用了stdcall。我已经使用Dependency Walker检查了我创建的dll,并且显示函数名为_functionName。我不确定是否应该带有前导下划线来调用它们。
我希望尽可能少地更改Delphi代码,因为最终将使用dll的代码我不能改变(我原本想说我完全不能改变它,但为了使Delphi编译通过,我已经不得不改变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()
    {

    }
}

从依赖项查看器中看到的Dll

enter image description here

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

enter image description here


导出函数的名称是_PriorTran@4,而不是您的Delphi代码中所声明的PriorTran。如果您想要在DLL中使用“干净”的导出名称,则需要使用模块定义文件构建您的DLL。 - PaulMcKenzie
请不要让这个问题成为一个移动目标。 - David Heffernan
我并不是有意这样做,但如果有人建议更改,我会实施它们。 - Nils Guillermin
不,那样做是不对的。你没有调用那个动态链接库,所以你对它做什么并不重要。但是不要破坏问题。 - David Heffernan
@DavidHeffernan 您是正确的。访问冲突不再存在。 - Nils Guillermin
1个回答

2
我认为很明显你没有调用你认为的DLL。如果你真的调用了,那么你的程序将无法启动,因为导出函数名称不匹配。在Dependency Walker中显示的DLL具有装饰名称,但你在Delphi代码中没有使用这些装饰名称。你的程序执行了。因此,你正在链接到另一个DLL。至于为什么会出现访问冲突,我们肯定不能说,因为我们对该DLL一无所知。
一旦你解决了调用正确DLL的问题(将DLL放置在可执行文件相同的目录下),我们可以查看问题中的代码。Interop很好,而且无论如何你的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 错误。 - Nils Guillermin
请查看我的更新答案。显然你并没有调用你编译过的带有装饰的导出名称的 DLL。 - David Heffernan
使用Process Explorer查找应用程序调用的dll,结果发现它在网络驱动器上(我以为我已经清理了IDE中的所有搜索路径,看来我还不完全了解它的工作原理)。指定具有完整文件路径的dll后,访问冲突不再出现。 - Nils Guillermin
将DLL文件放置在可执行文件的同一目录下,这样可以优先搜索到它。 - David Heffernan
Windows如何定位DLL:https://msdn.microsoft.com/zh-cn/library/7d83bc18.aspx。 - Rudy Velthuis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接