I have a Delphi DLL that I did not write, but need to call from a C# ASP.NET 3.5 app. Here is the function definition I got from the developers:
function CreateCode(SerialID : String;
StartDateOfYear, YearOfStartDate, YearOfEndDate, DatePeriod : Word;
CodeType,RecordNumber,StartHour,EndHour : Byte) : PChar;
external 'CreateCodeDLL.dll';
And here is my C# code:
[DllImport( "CreateCodeDLL.dll",
CallingConvention = CallingConvention.StdCall,
CharSet=CharSet.Ansi)]
public static extern IntPtr CreateCode( string SerialID,
UInt16 StartDateOfYear,
UInt16 YearOfStartDate,
UInt16 YearOfEndDate,
UInt16 DatePeriod,
Byte CodeType,
Byte RecordNumber,
Byte StartHour,
Byte EndHour);
And finally, my call to this method:
//The Inputs
String serialID = "92F00000B4FBE";
UInt16 StartDateOfYear = 20;
UInt16 YearOfStartDate = 2009;
UInt16 YearOfEndDate = 2009;
UInt16 DatePeriod = 7;
Byte CodeType = 1;
Byte RecordNumber = 0;
Byte StartHour = 15;
Byte EndHour = 14;
// The DLL call
IntPtr codePtr = CodeGenerator.CreateCode(serialID, StartDateOfYear,
YearOfStartDate, YearOfEndDate, DatePeriod, CodeType,
RecordNumber, StartHour, EndHour);
// Take the pointer and extract the code in a string
String code = Marshal.PtrToStringAnsi(codePtr);
Every time I re-compile this exact code and run it, it returns a different value. The expected value is a 10-digit code comprised of numbers. The returned value is actually 12 digits.
The last important piece of information is that I have a test .EXE that has a GUI that allows me to test the DLL. Every test using the .EXE returns the same 10-digit number (the expected result).
So, I have to believe that I have declared my call to the DLL incorrectly. Thoughts?
Delphi uses the so called fastcall calling convention by default. This means that the compiler tries to pass parameters to a function in the CPU registers and only uses the stack if there are more parameters than free registers. For example Delphi uses (EAX, EDX, ECX) for the first three parameters to a function.
In your C# code you're actually using the stdcall calling convention, which instructs the compiler to pass parameters via the stack (in reverse order, i.e. last param is pushed first) and to let the callee cleanup the stack.
In contrast, the cdecl calling used by C/C++ compilers forces the caller to cleanup the stack.
Just make sure you're using the same calling convention on both sides. Stdcall is mostly used because it can be used nearly everywhere and is supported by every compiler (Win32 APIs also use this convention).
Note that fastcall isn't supported by .NET anyway.
Create a COM wrapper in Delphi and call that in your C# code via interop. Voila.. easy to use from C# or any other future platform.
I was messing around the other day trying to learn about calling conventions and I wrote some methods to convert between various ones. Here is one for StdCall->FastCall.
}
I've never done this but try changing your code to:
Note the extra stdcall.
Edit2: As you can see from the other replies you either have to do the change above or write a wrapper dll that does the same thing.
While you are asking them to change the calling convention, you should also ask them to change the first parameter so that it is not a "string". Get them to use a pointer to a (null-terminated) char or widechar array instead. Using Delphi strings as DLL parameters is a bad idea even without the added complexity of trying to achieve cross-language compatibility. In addition the string variable will either contain ASCII or Unicode content depending on which version of Delphi they are using.
The return value might be another problem. It is probably either a memory leak(They allocate a buffer on the heap and never free it) or an access to already free memory(They return a local string variable cast to PChar).
Returning strings(or variable sized data in general) from a function to another module is problematic in general.
One solution(used by winapi) is to require the caller to pass in a buffer and its size. The disadvantage of that is that if the buffer is too small the function fails, and the caller must call it again with a larger buffer.
Another possible solution is to allocate the buffer from the heap in the function and return it. Then you need to export another function which the caller must use to free the allocated memory again. This ensures that the memory is freed by the same runtime which allocated it.
Passing a (Delphi)string parameter between different(not borland) languages is probably impossible. And even between Delphi modules you to ensure both modules use the same instance of the memory manager. Usually this means adding "uses ShareMem" as the first uses to all modules. Another difference is the calling convention "register" which is a fastcall convention, but not identical with the fastcall MS compilers use.
A completely different solution could be recompiling the Delphi dll with one of the Delphi.net compilers. How much work that is depends on their code.