2009-02-05 97 views
8

我有一个Delphi DLL,我没有写,但需要从C#ASP.NET 3.5应用程序调用。下面是函数定义我从开发商得到:从C#调用Delphi DLL会产生意想不到的结果

function CreateCode(SerialID : String; 
    StartDateOfYear, YearOfStartDate, YearOfEndDate, DatePeriod : Word; 
    CodeType,RecordNumber,StartHour,EndHour : Byte) : PChar; 
    external 'CreateCodeDLL.dll'; 

,这里是我的C#代码:

[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); 

最后,我调用此方法:

//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); 

每次我重新编译这个确切的代码并运行它,它返回一个不同的值。期望值是由数字组成的10位数字代码。返回的值实际上是12位数字。

最后一个重要的信息是我有一个测试.EXE,它有一个GUI,允许我测试DLL。每个使用.EXE的测试都会返回相同的10位数字(预期结果)。

所以,我必须相信我已经错误地声明了我对DLL的调用。思考?

回答

22

德尔福使用所谓的fastcall默认调用约定。这意味着编译器会尝试将参数传递给CPU寄存器中的一个函数,并且如果参数比自由寄存器多,则仅使用堆栈。例如,德尔福使用(EAX,EDX,ECX)作为函数的前三个参数。
在您的C#代码中,实际上使用的是调用约定,它指示编译器通过堆栈传递参数(以相反顺序,即最后一个参数被先推送),并让被调用者清除堆栈。
相比之下,C/C++编译器使用的调用强制调用者清除堆栈。
只要确保你在两边使用相同的调用约定。 Stdcall最常用,因为它几乎可以在任何地方使用,并且受到每个编译器的支持(Win32 API也使用这种约定)。
请注意,fastcall不受.NET支持。

1

我从来没有这样做,但尝试改变你的代码:

function CreateCode(SerialID : String; 
    StartDateOfYear, YearOfStartDate, YearOfEndDate, DatePeriod : Word; 
    CodeType,RecordNumber,StartHour,EndHour : Byte) : PChar; stdcall; 
    external 'CreateCodeDLL.dll'; 

注意额外STDCALL。

Edit2:正如你从其他答复你可以看到,你必须做上面的改变或写一个包装DLL,做同样的事情。

+0

帕斯卡作为呼叫类型是行不通的,因为Delphi使用寄存器呼叫(也称为__fastcall未__msfastcall)。 – 2009-02-05 21:28:25

+0

根据http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.callingconvention.aspx .net不支持任何类型的FastCall。所以这意味着他必须要求他们更改pascal代码? – PetriW 2009-02-05 21:34:36

16

jn是对的。只要在Delphi的register调用约定中,就不能直接从C#中直接调用函数原型。您需要为它编写一个stdcall包装函数 - 如果您没有源代码,可能在另一个DLL中 - 或者需要让维护该函数的人员将其调用约定更改为stdcall

更新:我还看到,第一个参数是一个德尔福字符串。这不是C#可以提供的东西。它应该是一个PChar。此外,清楚该函数是Ansi还是Unicode是很重要的。如果DLL是用Delphi 2009(或更高版本)编写的,那么它是Unicode的,否则它是Ansi。

0

当你要求他们改变调用约定,你也应该问他们改变的第一个参数,以便它不是一个“串”。让他们改用一个指向(空终止)char或widechar数组的指针。即使没有试图实现跨语言兼容性的复杂性,使用Delphi字符串作为DLL参数也是一个糟糕的主意。此外,字符串变量将包含ASCII或Unicode内容,具体取决于他们使用的是哪个版本的Delphi。

2

返回值可能是另一个问题。它可能是内存泄漏(它们在堆上分配一个缓冲区并且永远不释放它)或者访问已经释放的内存(它们返回一个本地的字符串变量cast到PChar)。从函数到另一个模块

返回字符串(或一般而言可变大小的数据)是在一般问题。 (由WINAPI使用)

一种解决方案是所需要的呼叫者在缓冲器中,其大小通过。这样做的缺点是,如果缓冲区太小,则函数失败,调用者必须使用更大的缓冲区再次调用它。

另一种可能的解决方案是从函数中的堆中分配缓冲区并返回它。然后您需要导出另一个函数,调用者必须使用该函数来再次释放分配的内存。这确保内存被分配它的相同运行时释放。

在不同的(非Borland)语言之间传递(Delphi)字符串参数可能不太可能。甚至在Delphi模块之间,你也要确保两个模块使用相同的内存管理器实例。通常这意味着将“使用ShareMem”作为所有模块的首次使用。 另一个区别是调用约定“register”,它是fastcall约定,但与fastcall MS编译器使用的不同。

一个完全不同的解决方案可以与Delphi.net编译器中的一个被重新编译Delphi的DLL。多少工作取决于他们的代码。

1

在Delphi中创建一个COM包装并通过interop在C#代码中调用该包装。 Voila ..易于从C#或任何其他未来平台使用。

1

我乱搞前些天想了解调用约定和我写了一些方法,以不同的人之间的转换。这里是StdCall-> FastCall。

typedef struct 
{ 
    USHORT ParameterOneOffset; // The offset of the first parameter in dwords starting at one 
    USHORT ParameterTwoOffset; // The offset of the second parmaeter in dwords starting at one 
} FastCallParameterInfo; 



    __declspec(naked,dllexport) void __stdcall InvokeFast() 
{ 
    FastCallParameterInfo paramInfo; 
    int functionAddress; 
    int retAddress; 
    int paramOne, paramTwo; 
    __asm 
    { 
     // Pop the return address and parameter info. Store in memory. 
     pop retAddress; 
     pop paramInfo; 
     pop functionAddress; 

     // Check if any parameters should be stored in edx       
     movzx ecx, paramInfo.ParameterOneOffset;  
     cmp ecx,0; 
     je NoRegister; 

     // Calculate the offset for parameter one. 
     movzx ecx, paramInfo.ParameterOneOffset; // Move the parameter one offset to ecx 
     dec ecx;         // Decrement by 1 
     mov eax, 4;         // Put 4 in eax 
     mul ecx;         // Multiple offset by 4 

     // Copy the value from the stack on to the register. 
     mov ecx, esp;        // Move the stack pointer to ecx 
     add ecx, eax;        // Subtract the offset. 
     mov eax, ecx;        // Store in eax for later. 
     mov ecx, [ecx];        // Derefernce the value 
     mov paramOne, ecx;       // Store the value in memory. 

     // Fix up stack 
     add esp,4;         // Decrement the stack pointer 
     movzx edx, paramInfo.ParameterOneOffset; // Move the parameter one offset to edx 
     dec edx;         // Decrement by 1 
     cmp edx,0;         // Compare offset with zero 
     je ParamOneNoShift;       // If first parameter then no shift. 

    ParamOneShiftLoop: 
     mov ecx, eax; 
     sub ecx, 4; 
     mov ecx, [ecx] 
     mov [eax], ecx;        // Copy value over 
     sub eax, 4;         // Go to next 
     dec edx;         // decrement edx 
     jnz ParamOneShiftLoop;      // Loop 
    ParamOneNoShift: 
     // Check if any parameters should be stored in edx       
     movzx ecx, paramInfo.ParameterTwoOffset;  
     cmp ecx,0; 
     je NoRegister; 

     movzx ecx, paramInfo.ParameterTwoOffset; // Move the parameter two offset to ecx 
     sub ecx, 2;         // Increment the offset by two. One extra for since we already shifted for ecx 
     mov eax, 4;         // Put 4 in eax 
     mul ecx;         // Multiple by 4 

     // Copy the value from the stack on to the register. 
     mov ecx, esp;        // Move the stack pointer to ecx 
     add ecx, eax;        // Subtract the offset. 
     mov eax, ecx;        // Store in eax for later. 
     mov ecx, [ecx];        // Derefernce the value 
     mov paramTwo, ecx;       // Store the value in memory.   

     // Fix up stack 
     add esp,4;         // Decrement the stack pointer 
     movzx edx, paramInfo.ParameterTwoOffset; // Move the parameter two offset to ecx 
     dec edx;         // Decrement by 1 
     cmp edx,0;         // Compare offset with zero 
     je NoRegister;        // If first parameter then no shift. 
    ParamTwoShiftLoop: 
     mov ecx, eax; 
     sub ecx, 4; 
     mov ecx, [ecx] 
     mov [eax], ecx;        // Copy value over 
     sub eax, 4;         // Go to next 
     dec edx;         // decrement edx 
     jnz ParamTwoShiftLoop;      // Loop 


    NoRegister: 
     mov ecx, paramOne;       // Copy value from memory to ecx register 
     mov edx, paramTwo;       // 
     push retAddress; 
     jmp functionAddress; 
    } 
} 

}

相关问题