Nils Guillermin
Nils Guillermin

Reputation: 1987

Properly calling C++ functions in DLL from Delphi "Access Violation"

I am trying to call C++ code from Delphi. I am a beginner. I keep getting an access violation error, although it is intermittent (but very common).

Access violation

This only occurs when executing ConfigccDLL or PriorTran From what I've read it might be a calling convention mismatch, but I'm under the impression I am using stdcall in both codebases. I have walked the dll I create with Dependency Walker and its showing the functions as _functionName. I'm not sure if I should be calling them with the leading underscore.

I'd like to change the Delphi code as little as possible because the code that will end up using the dll I cannot change (I was going to say I can't change it at all but I've already had to change the PAnsiChar and add AnsiStrings in order to get the Delphi to compile).

Delphi code:

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.

The C++ code is as follows:

Header file:

#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 file:

#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 as seen from dependency walker

enter image description here

Dependency Walker also gives these errors when opening the dll

enter image description here

Upvotes: 3

Views: 1174

Answers (1)

David Heffernan
David Heffernan

Reputation: 612794

I think it is clear that you aren't calling the DLL that you think you are calling. If you were then your program would not start because the exported function names don't match. The DLL that you show in Dependency Walker has decorated names. You don't use the decorated names in your Delphi code. Your program executes. Ergo, you are linking to a different DLL. As for why you get an access violation, well we certainly cannot say because we know nothing at all about that DLL.

Once you get it sorted such that you are calling the correct DLL (place the DLL in the same directory as your executable), we can look at the code in the question. The interop there is fine, and in any case your DLL's functions do nothing. But the Delphi code is no good. Consider this code:

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

Here you allocate an array of length 50, and copy into it a string of length who knows what. If your source string is too long, you will overrun the buffer.

If you must use dynamic allocation, then you'll need to take steps to ensure the buffers are long enough. And you'll also need to deallocate. Your code currently leaks like a sieve.

But manual dynamic allocation is error prone, and tedious. Don't settle for a life of tedium. Let the compiler do the work for you. Build the text in a variable of type AnsiString. When you need to pass that to your C++ code, use a PAnsiChar(...) cast.

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

Upvotes: 2

Related Questions