Calling a Delphi DLL from a C# .NET application

前端 未结 4 809
南笙
南笙 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: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

提交回复
热议问题