none
How to call Delphi DLL which transfer back a PChar as parameter RRS feed

  • Question

  • I need to load DLL write by Delphi, In Delphi DLL, the test DLL function like:


    function GetRecordByPointer(p : PChar) : integer; stdcall;
    var pRec : pMyRec;
    begin
       pRec := AllocMem(SizeOf(TMyRec));
       pRec.var1 := 1233;
       pRec.var2 := 'beta';
       move(pRec^, p^, SizeOf(TMyRec));
       result := 3210;
    end;

    The structure is define as:

    type
       pMyRec = ^TMyRec;
       TMyRec = record
          var1 : integer;
          var2 : array[0..6]of char;
       end;


    On Delphi host application, I can call this DLL function by :

    procedure TForm1.Button14Click(Sender: TObject);
    var
       rec : TMyRec;
    begin
       GetRecordByPointer(@rec);

    .............

    Basically, the DLL function accept PChar parameter, transfer back to its host application a pchar to the structure TMyRec.


    At C#, I want do same thing? My try like this:

        public unsafe struct TMyRec
        {
            public int var1;
            public fixed char var2[7];
        }

    and then

            [DllImport("dllTestD.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
            //     public static extern int GetRecordByPointer(String s);
            // or public static extern int GetRecordByPointer(char[] s);
            // or public static extern int GetRecordByPointer(char* s);

    Nothing work.

    How to call Delphi DLL which the function will transfer back a PChar as parameter?

     

     

    • Moved by Leo Liu - MSFT Tuesday, February 15, 2011 3:49 AM Off-topic, moved for better support. (From:Visual C# Language)
    Saturday, February 12, 2011 12:06 AM

Answers

  • My apologies...I didn't look at the dll code as closely as I should have.  The dll doesn't actually allocate the memory so you have to do that on the C# side and pass it the function.  This following should work better :)

    class Program
    {
      [DllImport("DLLTestD.dll")]
      static extern int GetRecordByPointer(byte[] pChar);
    
      static void Main(string[] args)
      {
        byte[] ptrMyData = new byte[Marshal.SizeOf(typeof(TMyRec))];
        int result = GetRecordByPointer(ptrMyData);
        TMyRec r = ByteArrayToStruct<TMyRec>(ptrMyData, 0);
      }
    
      // Here's a generic method in case you want to do this again with something else.
      // Note: You probably want to add some error checking here...
      static T ByteArrayToStruct<T>(byte[] bytes, int offset = 0)
      {
        int size = Marshal.SizeOf(typeof(T));
        IntPtr buffer = Marshal.AllocHGlobal(size);
        try
        {
          Marshal.Copy(bytes, offset, buffer, size);
          return (T)Marshal.PtrToStructure(buffer, typeof(T));
        }
        finally
        {
          Marshal.FreeHGlobal(buffer);
        }
      }
    }
    
    

    ShaneB

    • Marked as answer by jtian Wednesday, February 16, 2011 5:32 PM
    Wednesday, February 16, 2011 4:11 AM

All replies

  • This should work although I didn't develop a DLL to test it with:

    [DllImport("dllTestD.dll", CharSet = CharSet.Ansi)]
    static extern int GetRecordByPointer(out IntPtr pChar);
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct TMyRec
    {
      public int var1;
    
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)]
      public string var2;
    }
    
    void Test()
    {
      IntPtr ptrMyData;
      int result = GetRecordByPointer(out ptrMyData);
      TMyRec rec = (TMyRec)(Marshal.PtrToStructure(ptrMyData, typeof(TMyRec)));
    
      Console.WriteLine("Result: " + result + " var1:" + rec.var1 + " var2: " + rec.var2);
    }
    HTH,

    ShaneB

     

    Saturday, February 12, 2011 2:17 AM
  • Hi, Shane:

    Thanks for help, however, I got exception at

    TMyRec rec=(TMyRec..........

    FatalExecutionEngineError was detected
    Message: The runtime has encountered a fatal error. The address of the error was at 0x709a013e, on thread 0xb58. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

    The code is as follow (same as you give me)

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct TMyRec
        {
            public int var1;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)]
            public string var2;
        }

        class Program
        {
            [DllImport("dllTestD.dll", CharSet = CharSet.Ansi)]
            static extern int GetRecordByPointer(out IntPtr pChar);

            static void Main(string[] args)
            {
                IntPtr ptrMyData;
                int result = GetRecordByPointer(out ptrMyData);
                TMyRec rec = (TMyRec)(Marshal.PtrToStructure(ptrMyData, typeof(TMyRec)));
                Console.WriteLine("Result: " + result + " var1:" + rec.var1 + " var2: " + rec.var2);
            }
        }

    Monday, February 14, 2011 5:55 PM
  • Are you certain that the dll exports using StdCall?  By default, Delphi uses Cdecl.  If you're not 100% sure, try adding the Cdecl calling convention to the DllImport line.

    ShaneB

    Monday, February 14, 2011 6:04 PM
  • Yes, I am.

    And if I try

            [DllImport("dllTestD.dll", CharSet = CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]

    then, the GetRecordByPointer will get exception:

    A call to PInvoke function 'ConsoleApplication1!ConsoleApplication1.Program::GetRecordByPointer' has unbalanced the stack.

     

    I also try start from most simple record: delete var1, like:

       TMyRec = record
          var2 : array[0..6]of char;
       end;

    Still Delphi host application work, but C# got same exception.

     

     

     

    Monday, February 14, 2011 6:32 PM
  •  

    So, turn back to stdcall:

     

    [DllImport("dllTestD.dll", CharSet = CharSet.Ansi, CallingConvention=CallingConvention.stdcall)]

     

    From your previous descriptions, there is an AccessViolation exception at the "TMyRec rec = (TMyRec)(Marshal.PtrToStructure(ptrMyData, typeof(TMyRec)));" line, could you please check the ptrMyData, is it a null value?

     

            static void Main(string[] args)

            {

                IntPtr ptrMyData;

                int result = GetRecordByPointer(out ptrMyData);

                TMyRec rec = (TMyRec)(Marshal.PtrToStructure(ptrMyData, typeof(TMyRec)));

                Console.WriteLine("Result: " + result + " var1:" + rec.var1 + " var2: " + rec.var2);

           }

     

    By the way, you may try following code:

     

                IntPtr ptrMyData = Marshal.AllocHGlobal(sizeof(int) + 7);

                int result = GetRecordByPointer(out ptrMyData);
                TMyRec rec = (TMyRec)(Marshal.PtrToStructure(ptrMyData, typeof(TMyRec)));
                Console.WriteLine("Result: " + result + " var1:" + rec.var1 + " var2: " + rec.var2);

                Marshal.FreeHGlobal(ptrMyData);


    Eric Yang [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, February 15, 2011 4:04 AM
  • Hi, Eric:

    Re: could you please check the ptrMyData, is it a null value?

    After debug over "           int result = GetRecordByPointer(out ptrMyData );"

    ptrMyData is not NULL anymore.

    However, on Marshal.PtrToStructure, it raise except

    The runtime has encountered a fatal error. The address of the error was at 0x709a013e, on thread 0x1158. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

     

    By using the code you give me,

    after AllocHGlobal, ptrMyData is 71943152
    after GetRecordByPointer, ptrMyData is 1233 (same as above code)

    and then same exception at Marshal.PtrToStructure.

     

    In case I miss something due to my C# knowledge, I paste my code at here (it is just copy from you and Shane's code)

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices;

    namespace ConsoleApplication1
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct TMyRec
        {
            public int var1;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)]
            public string var2;
        }

        class Program
        {
           // err: [DllImport("dllTestD.dll", CharSet = CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
            [DllImport("dllTestD.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
            static extern int GetRecordByPointer(out IntPtr pChar);

            static void Main(string[] args)
            {
             
            /*    IntPtr ptrMyData;
                int result = GetRecordByPointer(out ptrMyData);
                TMyRec rec = (TMyRec)(Marshal.PtrToStructure(ptrMyData, typeof(TMyRec)));
                Console.WriteLine("Result: " + result + " var1:" + rec.var1 + " var2: " + rec.var2);
             */

                IntPtr ptrMyData = Marshal.AllocHGlobal(sizeof(int) + 7);

                int result = GetRecordByPointer(out ptrMyData);
                TMyRec rec = (TMyRec)(Marshal.PtrToStructure(ptrMyData, typeof(TMyRec)));
                Console.WriteLine("Result: " + result + " var1:" + rec.var1 + " var2: " + rec.var2);

                Marshal.FreeHGlobal(ptrMyData);
            }
        }
    }

     

     

    Tuesday, February 15, 2011 6:09 PM
  • Hi, I just notice, in debug

    after GetRecordByPointer, ptrMyData is 1233, the 1233 is the int var1's value. (I change the value in dll, it change too)

    as in my test DLL function, it is assign as:

    function GetRecordByPointer(p : PChar) : integer; stdcall;
    var pRec : pMyRec;
    begin
       pRec := AllocMem(SizeOf(TMyRec));
       pRec.var1 := 1233;
       pRec.var2 := 'beta';
       move(pRec^, p^, SizeOf(TMyRec));
       result := 3210;
    end;

     

    I think ptrMyData should point to the structure, not the var1 value inside the structure, do I make something wrong?

     

     

    Tuesday, February 15, 2011 6:53 PM
  • Can you send me your compiled test dll (with source)?  stormfire1 <(at)> yahoo.com.  It would make finding the problem a lot simpler...

    ShaneB

    Wednesday, February 16, 2011 12:26 AM
  • My apologies...I didn't look at the dll code as closely as I should have.  The dll doesn't actually allocate the memory so you have to do that on the C# side and pass it the function.  This following should work better :)

    class Program
    {
      [DllImport("DLLTestD.dll")]
      static extern int GetRecordByPointer(byte[] pChar);
    
      static void Main(string[] args)
      {
        byte[] ptrMyData = new byte[Marshal.SizeOf(typeof(TMyRec))];
        int result = GetRecordByPointer(ptrMyData);
        TMyRec r = ByteArrayToStruct<TMyRec>(ptrMyData, 0);
      }
    
      // Here's a generic method in case you want to do this again with something else.
      // Note: You probably want to add some error checking here...
      static T ByteArrayToStruct<T>(byte[] bytes, int offset = 0)
      {
        int size = Marshal.SizeOf(typeof(T));
        IntPtr buffer = Marshal.AllocHGlobal(size);
        try
        {
          Marshal.Copy(bytes, offset, buffer, size);
          return (T)Marshal.PtrToStructure(buffer, typeof(T));
        }
        finally
        {
          Marshal.FreeHGlobal(buffer);
        }
      }
    }
    
    

    ShaneB

    • Marked as answer by jtian Wednesday, February 16, 2011 5:32 PM
    Wednesday, February 16, 2011 4:11 AM
  • Hi, Thanks a lot!
    Wednesday, February 16, 2011 5:34 PM
  • No problem, glad I could help. 

    Also, you should note that the default structure packing may be different between C# and Delphi code.  From what I just read, Delphi (v7 and 2009) pack structures differently by default.  Anyway, you may want check the size of the struct in Delphi and compare it to what C# (Marshal.SizeOf) is telling you to ensure they match exactly...otherwise you'll eventually run into a problem.

    If you're not familiar with any of this the link below may help.  The bottom line is that you may need to set/adjust the C# StructLayout.Pack attribute with a value to match Delphi's packing.

    http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.structlayoutattribute.pack.aspx

    ShaneB

    Thursday, February 17, 2011 4:19 AM
  • Yes, you are right, I will be careful to the packing difference.

     

    Thursday, February 17, 2011 11:57 PM