3

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

Nils Guillermin
  • 1,867
  • 3
  • 21
  • 51
  • The name of the exported function is `_PriorTran@4`, not `PriorTran` as your Delphi code is stating. If you want "clean" exported names in your DLL, you need to build your DLL using a [Module Definition File](http://stackoverflow.com/questions/31282683/dll-call-with-stdcall-getprocaddress-in-vs2013/31283377#31283377) – PaulMcKenzie Nov 21 '16 at 17:55
  • Please don't make this question be a moving target. – David Heffernan Nov 21 '16 at 18:36
  • I'm not trying to, but if people are going to suggest changes I'm going to implement them. – Nils Guillermin Nov 21 '16 at 18:37
  • No. That's not right. You aren't calling that dll so it doesn't matter what you do to it. But don't despoiled the question. – David Heffernan Nov 21 '16 at 18:39
  • @DavidHeffernan You are correct. Access violations no longer. – Nils Guillermin Nov 21 '16 at 18:45

1 Answers1

2

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));
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I still get access violation errors when using AnsiStrings. As well, when I trigger `CloseDLL` (although rarely), even though it does nothing but call the external function. As for Delphi import codes not using those names, I'm not sure what you're saying. stdcall is right there, and specifying the decorated name throws a ProcedureEntryPoint not found error. – Nils Guillermin Nov 21 '16 at 18:27
  • See my updated answer. Clearly you aren't actually calling the DLL that you have compiled and who's exported names are decorated. – David Heffernan Nov 21 '16 at 18:33
  • Used Process Explorer to find out which dll the app was calling, turns out it was on a network drive (thought I'd cleaned out all search paths in IDE, guess I don't know how it works entirely yet). Specified the dll with full filepath and access violations no longer. – Nils Guillermin Nov 21 '16 at 18:46
  • Put the DLL in the same directory as the executable. That is always searched first. – David Heffernan Nov 21 '16 at 18:48
  • How Windows locates DLLs: https://msdn.microsoft.com/en-us/library/7d83bc18.aspx. – Rudy Velthuis Nov 22 '16 at 11:00