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.
jn is right. The function prototype, as given, cannot be easily called directly from C# as long as it is in Delphi's register
calling convention. You either need to write a stdcall
wrapper function for it - perhaps in another DLL if you don't have source - or you need to get the people who maintain the function to change its calling convention to stdcall
.
Update: I also see that the first argument is a Delphi string. This isn't something that C# can supply either. It should be a PChar instead. Also, it's important to be clear about whether the function is Ansi or Unicode; if the DLL is written with Delphi 2009 (or later), then it is Unicode, otherwise it is Ansi.
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.
I've never done this but try changing your code to:
function CreateCode(SerialID : String;
StartDateOfYear, YearOfStartDate, YearOfEndDate, DatePeriod : Word;
CodeType,RecordNumber,StartHour,EndHour : Byte) : PChar; stdcall;
external 'CreateCodeDLL.dll';
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With