Calling a Delphi DLL from a C# .NET application

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

Leave a Comment