2009-02-05 30 views
8

Tôi có một DLL Delphi mà tôi không viết, nhưng cần phải gọi từ một ứng dụng C# ASP.NET 3.5. Dưới đây là định nghĩa hàm tôi nhận được từ các nhà phát triển:Gọi một Delphi DLL từ C# tạo ra kết quả bất ngờ

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

Và đây là mã # C của tôi:

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

Và cuối cùng, cuộc gọi của tôi đến phương pháp này:

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

Mỗi lần Tôi biên dịch lại mã chính xác này và chạy nó, nó trả về một giá trị khác. Giá trị mong đợi là mã gồm 10 chữ số bao gồm các số. Giá trị trả về thực sự là 12 chữ số.

Thông tin quan trọng cuối cùng là tôi có một bài kiểm tra .EXE có GUI cho phép tôi kiểm tra DLL. Mỗi thử nghiệm sử dụng .EXE trả về cùng một số có 10 chữ số (kết quả mong đợi).

Vì vậy, tôi phải tin rằng tôi đã tuyên bố cuộc gọi của tôi với DLL không chính xác. Suy nghĩ?

Trả lời

22

Delphi sử dụng cái gọi là fastcall quy ước gọi điện theo mặc định. Điều này có nghĩa là trình biên dịch cố gắng chuyển các tham số đến một hàm trong thanh ghi CPU và chỉ sử dụng ngăn xếp nếu có nhiều tham số hơn các thanh ghi miễn phí. Ví dụ Delphi sử dụng (EAX, EDX, ECX) cho ba tham số đầu tiên cho một hàm.
Trong mã C#, bạn đang thực sự sử dụng quy ước gọi stdcall gọi cho trình biên dịch để chuyển thông số qua ngăn xếp (theo thứ tự ngược lại, ví dụ: param cuối cùng được đẩy đầu tiên) và để cho callee dọn dẹp ngăn xếp .
Ngược lại, số điện thoại cdecl được sử dụng bởi trình biên dịch C/C++ buộc người gọi dọn dẹp ngăn xếp.
Chỉ cần đảm bảo bạn đang sử dụng cùng một quy ước gọi điện thoại trên cả hai mặt. Stdcall chủ yếu được sử dụng bởi vì nó có thể được sử dụng gần như ở khắp mọi nơi và được hỗ trợ bởi mọi trình biên dịch (Win32 API cũng sử dụng quy ước này).
Lưu ý rằng fastcall không được .NET hỗ trợ.

+4

Lưu ý rằng "fastcall" có nghĩa là những thứ khác nhau trong các ngữ cảnh khác nhau. Phiên bản của Microsoft không giống với phiên bản của Embarcadero, và tôi nghi ngờ GCC khác với cả hai. Trong Delphi, quy ước gọi điện không được gọi là "fastcall"; đó là quy ước gọi điện thoại "đăng ký". –

1

Tôi chưa bao giờ làm điều này nhưng hãy thử thay đổi mã của bạn để:

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

Lưu ý stdcall thêm.

Chỉnh sửa2: Như bạn có thể thấy từ các câu trả lời khác, bạn phải thực hiện thay đổi ở trên hoặc viết một dll trình bao làm điều tương tự.

+0

Pascal như kiểu cuộc gọi sẽ không làm việc vì Delphi sử dụng các cuộc gọi đăng ký (còn gọi là __fastcall mà không được __msfastcall). –

+0

Theo http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.callingconvention.aspx không có loại FastCall nào không được hỗ trợ bởi .net. Vì vậy, điều này có nghĩa là anh ta phải yêu cầu họ thay đổi mã pascal? – PetriW

16

jn là đúng. Các nguyên mẫu chức năng, như được đưa ra, không thể dễ dàng được gọi trực tiếp từ C# miễn là nó trong Delphi của quy ước gọi là register. Bạn cần phải viết một hàm wrapper stdcall cho nó - có lẽ trong một DLL khác nếu bạn không có nguồn - hoặc bạn cần để có được những người duy trì chức năng thay đổi quy ước gọi của nó thành stdcall.

Cập nhật: Tôi cũng thấy đối số đầu tiên là chuỗi Delphi. Đây không phải là thứ mà C# có thể cung cấp. Nó phải là một PChar thay vào đó.Ngoài ra, điều quan trọng là phải rõ ràng về việc liệu chức năng là Ansi hay Unicode; nếu DLL được viết bằng Delphi 2009 (hoặc mới hơn), thì đó là Unicode, nếu không nó là Ansi.

0

Trong khi bạn yêu cầu họ thay đổi quy ước gọi điện, bạn cũng nên yêu cầu họ thay đổi tham số đầu tiên sao cho nó không phải là "chuỗi". Yêu cầu họ sử dụng một con trỏ tới một mảng char hoặc widechar (null-terminated) để thay thế. Sử dụng các chuỗi Delphi như các tham số DLL là một ý tưởng tồi ngay cả khi không có sự phức tạp thêm về cố gắng để đạt được khả năng tương thích giữa các ngôn ngữ. Ngoài ra, biến chuỗi sẽ chứa nội dung ASCII hoặc Unicode tùy thuộc vào phiên bản nào của Delphi mà chúng đang sử dụng.

2

Giá trị trả lại có thể là một vấn đề khác. Nó có lẽ hoặc là một rò rỉ bộ nhớ (Họ phân bổ một bộ đệm trên heap và không bao giờ miễn phí nó) hoặc truy cập vào bộ nhớ đã có miễn phí (Họ trả về một biến chuỗi địa phương đúc để PChar).

Chuỗi trả về (hoặc dữ liệu có kích thước biến đổi nói chung) từ một hàm đến mô-đun khác có vấn đề nói chung.

Một giải pháp (được sử dụng bởi winapi) là yêu cầu người gọi phải vượt qua trong bộ đệm và kích thước của bộ đệm. Điểm bất lợi của điều đó là nếu bộ đệm quá nhỏ thì chức năng không thành công, và người gọi phải gọi lại với bộ đệm lớn hơn.

Một giải pháp khác có thể là phân bổ bộ đệm từ vùng heap trong hàm và trả về nó. Sau đó, bạn cần phải xuất khẩu một chức năng khác mà người gọi phải sử dụng để giải phóng bộ nhớ được phân bổ một lần nữa. Điều này đảm bảo rằng bộ nhớ được giải phóng bởi cùng một thời gian chạy đã phân bổ nó.

Việc truyền tham số chuỗi (Delphi) giữa các ngôn ngữ khác nhau (không phải borland) có thể là không thể. Và thậm chí giữa các mô-đun Delphi bạn phải đảm bảo cả hai mô-đun sử dụng cùng một thể hiện của trình quản lý bộ nhớ. Thông thường, điều này có nghĩa là thêm "sử dụng ShareMem" làm ứng dụng đầu tiên cho tất cả các mô-đun. Một sự khác biệt là quy ước gọi là "đăng ký" là một quy ước fastcall, nhưng không giống với việc sử dụng trình biên dịch MS fastcall.

Một giải pháp hoàn toàn khác có thể biên dịch lại dll Delphi bằng một trong các trình biên dịch Delphi.net. Bao nhiêu công việc phụ thuộc vào mã của họ.

1

Tạo trình bao bọc COM trong Delphi và gọi mã đó trong mã C# của bạn qua interop. Voila .. dễ sử dụng từ C# hoặc bất kỳ nền tảng tương lai nào khác.

1

Tôi đã rối tung xung quanh một ngày khác cố gắng tìm hiểu về các quy ước gọi điện và tôi đã viết một số phương pháp để chuyển đổi giữa các quy tắc khác nhau. Đây là một cho 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; 
    } 
} 

}

Các vấn đề liên quan