Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a Delphi DLL from a C# .NET application

Tags:

c#

.net

dll

delphi

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 correct method for writing a DLL in Delphi, and being able to call it from C#, passing and returning strings. A lot of the information was incomplete or incorrect. After much trial and error, I found the solution.

This was compiled using Delphi 2007 and VS 2010. I suspect it will work fine in other versions as well.

Here's the Delphi code. Remember to include version information in the project.

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}  // Example function takes an input integer and input string, and returns // inputInt + 1, and inputString + ' ' + IntToStr(outputInt) as output // parameters. If successful, the return result is nil (null), otherwise it is // the exception message string.   // NOTE: I've posted a better version of this below. You should use that instead.  function DelphiFunction(inputInt : integer; inputString : PAnsiChar;                         out outputInt : integer; out outputString : PAnsiChar)                         : PAnsiChar; stdcall; export; var s : string; begin   outputInt := 0;   outputString := nil;   try     outputInt := inputInt + 1;     s := inputString + ' ' + IntToStr(outputInt);     outputString := PAnsiChar(s);     Result := nil;   except     on e : exception do Result := PAnsiChar(e.Message);   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. 

Here's the 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              string DelphiFunction(int inputInt, string inputString,                                   out int outputInt, out string outputString);          static void Main(string[] args)         {             int inputInt = 1;             string inputString = "This is a test";             int outputInt;             string outputString;   // NOTE: I've posted a better version of this below. You should use that instead.               Console.WriteLine("inputInt = {0}, intputString = \"{1}\"",                               inputInt, inputString);             var errorString = DelphiFunction(inputInt, inputString,                                              out outputInt, out outputString);             if (errorString != null)                 Console.WriteLine("Error = \"{0}\"", errorString);             else                 Console.WriteLine("outputInt = {0}, outputString = \"{1}\"",                                   outputInt, outputString);             Console.Write("Press Enter:");             Console.ReadLine();         }     } } 

I hope this information helps someone else to not have to pull their hair out as much as I did.

like image 298
Dan Thomas Avatar asked Nov 12 '10 09:11

Dan Thomas


People also ask

How do you call a DLL in C++?

To access a function in a dll, there's two main methods: Use dllimport, similarly to how you exported the functions with dllexport. Load the DLL using LoadLibrary, then get a pointer to your function with GetProcAddress.


1 Answers

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

like image 67
Dan Thomas Avatar answered Sep 21 '22 15:09

Dan Thomas