2

I've written a Dll with C# with an exported function that save a file.

Here's the C# code

using RGiesecke.DllExport;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;    

namespace ClassLibrary1
{
    public class Class1
    {
        [DllExport("Funcion", CallingConvention = CallingConvention.StdCall)]
        public static void Funcion(IntPtr pDataIn, Int32 pSize, [Out, MarshalAs(UnmanagedType.I4)] int pArchivo)
        {
            byte[] documento = new byte[pSize];
            Marshal.Copy(pDataIn, documento, 0, pSize);
            File.WriteAllBytes("Document2.pdf", documento);            

            pArchivo = 25;
        }
    }    
}

In Delphi, I load the library and call the exported function and it works fine.

Here's the Delphi Code

procedure TForm1.Button1Click(Sender: TObject);
var
  lStream     : TMemoryStream;
  lArBytes      : array of Byte;
  lInDocSize : Integer;
  lHndle : THandle;

  Funcion : procedure(pDataIn : array of Byte;
                      pInSize : Integer;
                      var pDocumento : Integer
                      ); stdcall;
begin

  try
    lHndle := LoadLibrary('ClassLibrary1.dll');

    if (lHndle <> 0) then
    begin

      Funcion := GetProcAddress(lHndle, 'Funcion');

      if Assigned(Funcion) then
      begin
        try
          lStream := TMemoryStream.Create;

          lStream.LoadFromFile('Document1.PDF');
          lStream.Position := 0;

          SetLength(lArBytes, lStream.Size);

          lStream.Read(lArBytes[0], lStream.Size);

          lInDocSize := 0;
          Funcion(lArBytes, lStream.Size, lInDocSize);

          Label1.Caption := IntToStr(lInDocSize);

        except on E : Exception do
          begin
            RaiseLastOSError;
            ShowMessage(e.Message);
          end;
        end;
      end;
    end;

  finally
  end;

end;

I have an error with the output parameter, it is that always returns cero (0) value, not matter what value I assign to parameter, it always has cero value.

I've changing the parameter like this

out int pArchivo

and

ref int pArchivo

But when the function finish I get a memory exception.

With Marshal, function finish fine, without memory errors, but the output paramerter value is always cero (0).

[Out, MarshalAs(UnmanagedType.I4)] int pArchivo

I've read about this problem in this post in Stackoverflow

Passing array of struct from c# to Delphi

But in my case, it doesn't work

What I'm doing wrong?

I hope that you could help... thank you so much

1
  • out int pArchivo is correct. Use that. The other error is due to the open array not matching the c#. Commented Oct 26, 2017 at 6:01

1 Answer 1

2

On the Delphi side, a function parameter directly declared as array of ... is known as an "open array". An "open array" gets passed by the compiler using 2 parameters - a pointer to the first array element, and the high index (not the length!) of the array. This allows the calling code to pass either a static array or a dynamic array to the same parameter, and the compiler will pass the array data accordingly.

But, your .NET code is expecting only 1 parameter for the array - a raw pointer to the 1st array element. That is why you are not getting your output value correctly. Your lStream.Size parameter value gets passed where the .NET code expects the 3rd parameter to be, so the Size value gets misinterpreted as the memory address where the .NET code writes its output pArchivo value to, hence the memory error. Which won't matter anyway since you are corrupting the call stack! You end up pushing 4 parameter values onto the stack, and then stdcall on the .NET side pops off only 3 parameter values during stack cleanup when the function exits.

You need to change the declaration of your Funcion variable, either by:

  • keeping the pDataIn parameter declared as array of Byte, but removing the explicit pInSize parameter. Let the compiler pass it implicitly:

    Funcion : procedure(pDataIn : array of Byte;
                        //pInSize : Integer;
                        var pDocumento : Integer
                        ); stdcall;
    

    You will then have to change the call of Funcion() to allocate +1 more byte for the lArBytes array so the compiler passes the correct size value to the pSize parameter:

    SetLength(lArBytes, lStream.Size+1); // <-- +1 here!
    lStream.Read(PByte(lArBytes)^, High(lArBytes)); // <-- no +1 here!
    ...
    Funcion(lArBytes{, High(lArBytes)}, lInDocSize);
    

    Needless to say, this is not intuitive, though it should work since the behavior of open arrays is well-known, though it is a private implementation detail of the Delphi compiler.

  • using PByte (or just Pointer) instead of array of Byte:

    Funcion : procedure(pDataIn : PByte; // <-- here
                        pInSize : Integer;
                        var pDocumento : Integer
                        ); stdcall;
    

    You will then have to change the call of Funcion() to pass a pointer to the 1st array element, and pass the array length explicitly:

    SetLength(lArBytes, lStream.Size); // <-- no +1 here!
    lStream.Read(PByte(lArBytes)^, Length(lArBytes)); // <-- or here!
    ...
    Funcion(@lArBytes[0]{or: PByte(lArBytes)}, Length(lArBytes), lInDocSize);
    

    This is more intuitive, and closer to what the .NET code is expecting.

Alternatively, I suggest you simply get rid of your lArBytes variable altogether. You don't actually need it. Since the .NET code is expecting a raw pointer to the byte data, simply pass your TMemoryStream data directly:

procedure TForm1.Button1Click(Sender: TObject);
var
  lStream    : TMemoryStream;
  lInDocSize : Integer;
  lHndle : THandle;

  Funcion : procedure(pDataIn : Pointer;
                      pInSize : Integer;
                      var pDocumento : Integer
                      ); stdcall;
begin

  lHndle := LoadLibrary('ClassLibrary1.dll');
  if (lHndle <> 0) then
  try
    Funcion := GetProcAddress(lHndle, 'Funcion');
    if Assigned(Funcion) then
    begin
      lInDocSize := 0;

      lStream := TMemoryStream.Create;
      try
        lStream.LoadFromFile('Document1.PDF');
        Funcion(lStream.Memory, lStream.Size, lInDocSize);
      finally
        lStream.Free;
      end;

      Label1.Caption := IntToStr(lInDocSize);
    end;
  finally
    FreeLibrary(lHndle);
  end;
end;
Sign up to request clarification or add additional context in comments.

1 Comment

Hi Remy, I have tried with all your suggested changes and all of them works so fine. I've selected the last alternative, because it's the most easy way to do all that I need. Again, thanks for your help and your time!!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.