Calling a Delphi DLL from a C# .NET application

前端 未结 4 786
南笙
南笙 2020-12-02 07:46

EDIT: I\'ve posted a better implementation of this, below. I left this here so the responses would make sense.

I\'ve done numerous searches for the

相关标签:
4条回答
  • 2020-12-02 08:30

    It is easier to retireve a string using PString:

    function DelphiFunction(inputString : PAnsiChar;
                        var outputStringBuffer : PString;
                        var errorMsgBuffer : PString)
                        : WordBool; stdcall; export;
    var 
      s : string;
    begin
      try
        s := inputString;
        outputStringBuffer:=PString(AnsiString(s));
        Result := true;
      except
        on e : exception do
        begin
          s:= 'error';
          errorMsgBuffer:=PString(AnsiString(e.Message));
          Result := false;
        end;
      end;
    end;
    

    In c# then:

        const int stringBufferSize = 1024;
    
        var  str = new    IntPtr(stringBufferSize);
    
        string loginResult = Marshal.PtrToStringAnsi(str);
    
    0 讨论(0)
  • 2020-12-02 08:33

    As Jeroen Pluimers said in his comment, you should take note that Delphi strings are reference-counted.

    IMO, in such circumstances which you are supposed to return a string in heterogeneous environments, you should ask the caller to provide a buffer for the result, and the function should fill that buffer. This way, the caller is responsible for creating the buffer and disposing it when it is done with it. If you take a look at Win32 API functions, you'll see they do the same when they need to return a string to a caller.

    To do so, you can use PChar (either PAnsiChar or PWideChar) as the type of function parameter, but you should also ask caller to provide size of the buffer too. Take a look at my answer in the link below, for a sample source code:

    Exchanging strings (PChar) between a Freepascal compiled DLL and a Delphi compiled EXE

    The question is specifically about exchanging string between FreePascal and Delphi, but the idea and the answer is applicable to your case too.

    0 讨论(0)
  • 2020-12-02 08:46

    Based on responses to my post, I have created a new example that uses string buffers for the returned strings, instead of just returning PAnsiChars.

    Delphi DLL source:

    library DelphiLibrary;
    
    uses SysUtils;
    
    // Compiled using Delphi 2007.
    
    // NOTE: If your project doesn't have version information included, you may
    // receive the error "The "ResolveManifestFiles" task failed unexpectedly"
    // when compiling the C# application.
    
    {$R *.res}
    
    // A note on returing strings. I had originally written this so that the
    // output string was just a PAnsiChar. But several people pointed out that
    // since Delphi strings are reference-counted, this was a bad idea since the
    // memory for the string could get overwritten before it was used.
    //
    // Because of this, I re-wrote the example so that you have to pass a buffer for
    // the result strings. I saw some examples of how to do this, where they
    // returned the actual string length also. This isn't necessary, because the
    // string is null-terminated, and in fact the examples themselves never used the
    // returned string length.
    
    
    // Example function takes an input integer and input string, and returns
    // inputInt + 1, and inputString + ' ' + IntToStr(outputInt). If successful,
    // the return result is true, otherwise errorMsgBuffer contains the the
    // exception message string.
    function DelphiFunction(inputInt : integer;
                            inputString : PAnsiChar;
                            out outputInt : integer;
                            outputStringBufferSize : integer;
                            var outputStringBuffer : PAnsiChar;
                            errorMsgBufferSize : integer;
                            var errorMsgBuffer : PAnsiChar)
                            : WordBool; stdcall; export;
    var s : string;
    begin
      outputInt := 0;
      try
        outputInt := inputInt + 1;
        s := inputString + ' ' + IntToStr(outputInt);
        StrLCopy(outputStringBuffer, PAnsiChar(s), outputStringBufferSize-1);
        errorMsgBuffer[0] := #0;
        Result := true;
      except
        on e : exception do
        begin
          StrLCopy(errorMsgBuffer, PAnsiChar(e.Message), errorMsgBufferSize-1);
          Result := false;
        end;
      end;
    end;
    
    // I would have thought having "export" at the end of the function declartion
    // (above) would have been enough to export the function, but I couldn't get it
    // to work without this line also.
    exports DelphiFunction;
    
    begin
    end.
    

    C# Code:

    using System;
    using System.Runtime.InteropServices;
    
    namespace CsharpApp
    {
        class Program
        {
            // I added DelphiLibrary.dll to my project (NOT in References, but 
            // "Add existing file"). In Properties for the dll, I set "BuildAction" 
            // to None, and "Copy to Output Directory" to "Copy always".
            // Make sure your Delphi dll has version information included.
    
            [DllImport("DelphiLibrary.dll", 
                       CallingConvention = CallingConvention.StdCall, 
                       CharSet = CharSet.Ansi)]
            public static extern bool 
                DelphiFunction(int inputInt, string inputString,
                               out int outputInt,
                               int outputStringBufferSize, ref string outputStringBuffer,
                               int errorMsgBufferSize, ref string errorMsgBuffer);
    
            static void Main(string[] args)
            {
                int inputInt = 1;
                string inputString = "This is a test";
                int outputInt;
                const int stringBufferSize = 1024;
                var outputStringBuffer = new String('\x00', stringBufferSize);
                var errorMsgBuffer = new String('\x00', stringBufferSize);
    
                if (!DelphiFunction(inputInt, inputString, 
                                    out outputInt,
                                    stringBufferSize, ref outputStringBuffer,
                                    stringBufferSize, ref errorMsgBuffer))
                    Console.WriteLine("Error = \"{0}\"", errorMsgBuffer);
                else
                    Console.WriteLine("outputInt = {0}, outputString = \"{1}\"",
                                      outputInt, outputStringBuffer);
    
                Console.Write("Press Enter:");
                Console.ReadLine();
            }
        }
    }
    

    And here's an additional class that shows how to load the DLL dynamically (sorry for the long lines):

    using System;
    using System.Runtime.InteropServices;
    
    namespace CsharpApp
    {
        static class DynamicLinking
        {
            [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
            static extern int LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);
    
            [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
            static extern IntPtr GetProcAddress(int hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);
    
            [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
            static extern bool FreeLibrary(int hModule);
    
            [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]
            delegate bool DelphiFunction(int inputInt, string inputString,
                                         out int outputInt,
                                         int outputStringBufferSize, ref string outputStringBuffer,
                                         int errorMsgBufferSize, ref string errorMsgBuffer);
    
            public static void CallDelphiFunction(int inputInt, string inputString,
                                                  out int outputInt, out string outputString)
            {
                const string dllName = "DelphiLib.dll";
                const string functionName = "DelphiFunction";
    
                int libHandle = LoadLibrary(dllName);
                if (libHandle == 0)
                    throw new Exception(string.Format("Could not load library \"{0}\"", dllName));
                try
                {
                    var delphiFunctionAddress = GetProcAddress(libHandle, functionName);
                    if (delphiFunctionAddress == IntPtr.Zero)
                        throw new Exception(string.Format("Can't find function \"{0}\" in library \"{1}\"", functionName, dllName));
    
                    var delphiFunction = (DelphiFunction)Marshal.GetDelegateForFunctionPointer(delphiFunctionAddress, typeof(DelphiFunction));
    
                    const int stringBufferSize = 1024;
                    var outputStringBuffer = new String('\x00', stringBufferSize);
                    var errorMsgBuffer = new String('\x00', stringBufferSize);
    
                    if (!delphiFunction(inputInt, inputString, out outputInt,
                                        stringBufferSize, ref outputStringBuffer,
                                        stringBufferSize, ref errorMsgBuffer))
                        throw new Exception(errorMsgBuffer);
    
                    outputString = outputStringBuffer;
                }
                finally
                {
                    FreeLibrary(libHandle);
                }
            }
        }
    }
    

    -Dan

    0 讨论(0)
  • 2020-12-02 08:48

    In Delphi 2009 the code works better if you explicitly type variable s as an AnsiString viz:

    var s : Ansistring;
    

    giving the expected result from C# following the call:

    outputInt = 2, outputString = "This is a test 2"
    

    instead of

    outputInt = 2, outputString = "T"
    
    0 讨论(0)
提交回复
热议问题