0
votes

I have a DLL, written in Delphi, which should determine some values and then pass the results back to the C++ app, which called the DLL.

The data to be passed back to the C++ app, is a set of four integer values (but in future there may be string values as well).

In order to do this, I need to declare those integer values as being shared between calling application and the DLL.

At the C++ application side, I can do this like this (according to http://msdn.microsoft.com/en-us/library/h90dkhs0%28v=vs.80%29.aspx ):

#pragma data_seg(".SHARED")

int value1;
int value2;
// ...
int valueN;
#pragma data_seg()

How can I do the same thing (declare that value1-N are to be stored in the shared memory) in Delphi 2009?

Update, 18.09.2012: I decided to use named pipes for implementing communication of the injected DLL and the outer world.

But before I can use named pipes, I need to solve following problem.

At the moment, the process looks like this:

1) The DLL is injected into the legacy application.

2) The DLL extracts some data from the legacy application.

3) The DLL is unloaded.

I need to modify it so that it works like this:

1) The DLL is injected into the legacy application. 2) The DLL starts a loop like

bool running = true;
while (running)
{
    command = receiveCommandFromInvokingApp();
    if ("GET_COORDINATES".equals(command))
    {
        // Read coordinates of some cell in the grid
        // Then communicate it via some channel to the outside world
    }
    else if ("READ_CELL_VALUE")
    {
        // Read value of some cell in the grid
        // Then communicate it via some channel to the outside world
    }
    else if ("EXIT")
    {
        // Close the communication channel
        // Perform cleanup work
        running = false;
    }

    // Sleep for, say, 500 milliseconds in order to avoid 100 % CPU usage
}

receiveCommandFromInvokingApp reads next command sent from the invoking application (from named pipe or any other suitable channel).

3) When the invoking application sends the EXIT command, the DLL stops the loop.

Let's say I have following DLL code:

procedure DllMain(reason: integer) ;
begin
  if reason = DLL_PROCESS_DETACH then
    OutputDebugString('DLL PROCESS DETACH')
  else if reason = DLL_THREAD_ATTACH then
    OutputDebugString('DLL THREAD ATTACH')
  else if reason = DLL_THREAD_DETACH then
    OutputDebugString('DLL THREAD DETACH')
  else if reason = DLL_PROCESS_ATTACH then
    OutputDebugString('DLL_PROCESS_ATTACH')
  end;
end; (*DllMain*)

Where (in what branch) should the loop be put?

Is it sensible to put it instead of OutputDebugString('DLL THREAD ATTACH') ?

Update 19.09.2012:

The design of my system changed and now I am sending data from Delphi DLL to a C# application via a named pipe.

Delphi code:

Opening a named pipe:

function OpenNamedPipe() : THandle;
var
  hPipe : THandle;
  name : string;
  connectResult : LongBool;
begin
  name := '\\.\pipe\delphi-to-cpp';
  hPipe := CreateNamedPipe(PChar(name),
    PIPE_ACCESS_DUPLEX,
    PIPE_TYPE_MESSAGE or PIPE_READMODE_MESSAGE  or PIPE_NOWAIT,
    PIPE_UNLIMITED_INSTANCES,
    4096 ,
    4096 ,
    4096 ,
    NIL);
  if (hPipe = INVALID_HANDLE_VALUE) then
  begin
    OutputDebugString(PChar('Invalid pipe handle: ' + IntToStr(GetLastError)));
    OutputDebugString(PChar(SysErrorMessage(GetLastError)));
  end;


  OutputDebugString(PChar('OpenNamedPipe, 1'));
  connectResult := ConnectNamedPipe(hPipe, NIL);
  OutputDebugString(PChar('connectResult: ' + BoolToStr(connectResult)));
  OutputDebugString(PChar(SysErrorMessage(GetLastError)));

  OpenNamedPipe := hPipe;
end;

Sending messages:

procedure SendMessageToNamedPipe(hPipe:THandle; msg:string);
var
  dwWrite : DWORD;
  lpNumberOfBytesWritten : LongBool;
  MsgLength: DWORD;
  MsgW : PWideChar;
begin
  MsgW := PWideChar(msg);
  MsgLength := lstrlenW(MsgW) * SizeOf(WideChar);
  lpNumberOfBytesWritten := WriteFile(hPipe, MsgW, MsgLength, dwWrite, NIL);

  if not lpNumberOfBytesWritten then
  begin
    OutputDebugString(PChar('Sending error: ' + SysErrorMessage(GetLastError)));
  end
  else
  begin
    OutputDebugString(PChar('Message sent, dwWrite: ' + IntToStr(dwWrite)));
  end;
end;

C# code, which should read the data sent by the Delphi application:

  NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "delphi-to-cpp",
      PipeDirection.InOut);

  new NamedPipeClientStream(".", "delphi-to-cpp",
      PipeDirection.InOut);

  Debug.WriteLine("Before pipeClient.Connect");

  this.IsRunning = true;


  pipeClient.Connect(5000);
  StreamReader reader = new StreamReader(pipeClient, Encoding.Unicode);

  while (this.IsRunning && pipeClient.IsConnected)
  {
      string message = reader.ReadLine();

      Thread.Sleep(250);
  }
  reader.Close();

This code doesn't work - reader.ReadLine(); doesn't return anything.

If I try to read the data bytewise into a char[] buffer, that buffer contains garbage at the end of reading.

Note that SOMETHING is actually being received by the C# application, I just don't know how to extract the text from the stream.

How should I modify my code (Delphi, C# or both) so that the text sent by the Delphi application correctly arrives at C# side?

2
Could you clarify whether you question concerns communication between a exe and DLL that are the same process (i.e. this exe started the DLL) or running as separate processes?Elemental
For what it is worth, I think you are not making the best use of Stack Overflow here. You have presented 4 or 5 proposed solutions for your problem, and then had each one knocked down. It would be better if you went back to the very beginning and present just your problem. The question would then be, "can you recommend a solution to that problem"? My recommendation would be AutoHotKey, but you may well get other better suggestions.David Heffernan
Actually, I intend to use something similar to AutoHotKey. What I want to do in order to write text into grid cell (X,Y) is a) get coordinates of the cell (this works already, I tried it), then b) pass these coordinates from Delphi DLL into C++ application (using named pipes) and c) double-click at that location and type in the text. Part a) is implemented. Part c) is easy (I've done this in the past). So at the moment, the hardest part is b) - communicating values from the Delphi DLL back to C++.Dmitrii Pisarenko
Window messages would be easier than named pipes.David Heffernan
Yes it is. You'll need to run a message pump though. Which might be easier in a different thread.David Heffernan

2 Answers

1
votes

The Delphi toolchain does not have that capability built in.

In order to do something similar in Delphi you need to use the memory mapped file API, or some other inter-process communication (IPC) mechanism, e.g. pipes, sockets, Windows messages etc.

Or, you could simply load the C++ DLL from your Delphi DLL. The C++ DLL can access the shared data, and your Delphi DLL can call functions in the C++ DLL that read and write to the shared data. However, shared data is really very unsuited to the task. Using IPC is more appropriate.

What you didn't make quite clear in the question, is that there are two processes here. Hence the need for IPC.

1
votes

There is a whole lot wrong with this solution in my opinion. The idea of global variables to be used as a return mechanism is already a pretty bad idea - not thread safe / complex if multiple processes are running. It's also worth noting that all instances of the DLL will share this space.

I would definitely either:

  1. Pass an empty structure from the C++ to delphi which the Delphi side fills in (or you strings you need to use buffers not just pointers) OR

  2. Pass pointers to the fields to the delphi process and allow the DLL to update the values at these pointers OR

  3. Store these results in globals in the DLL and create separate FUNCTION calls to retrieve there values from the C++. I.e. int getValueA()