2015-05-04 4 views
2

У меня есть исходный код ниже.Call Delphi Function From C#

library Project1; 

uses 
    System.SysUtils, 
    System.Classes; 

type 

    IStringFunctions = interface 
    ['{240B567B-E619-48E4-8CDA-F6A722F44A71}'] 
    function GetMethodValueAsString():PAnsiChar; stdcall; 
    end; 

    TStringFunctions = class(TInterfacedObject, IStringFunctions) 
    public 
    function GetMethodValueAsString():PAnsiChar; stdcall; 
    end; 

{$R *.res} 

function TStringFunctions.GetMethodValueAsString():PAnsiChar; stdcall; 
begin 
    Result := 'test'; 
end; 


procedure GetImplementation(out instance:IStringFunctions); stdcall; export; 
begin 
    instance := TStringFunctions.Create; 
end; 

exports GetImplementation; 

begin 
end. 

Я хочу, используя в C#, как этот

using System; 
using System.Runtime.CompilerServices; 
using System.Runtime.InteropServices; 

namespace ConsoleApplication1 
{ 
    [ComVisible(true)] 
    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("240B567B-E619-48E4-8CDA-F6A722F44A71")] 
    public interface IStringFunctions 
    { 
     [MethodImplAttribute(MethodImplOptions.PreserveSig)] 
     [return: MarshalAs(UnmanagedType.AnsiBStr)] 
     string GetMethodValueAsString(); 
    } 

    class Program 
    { 
     [DllImport("kernel32.dll", EntryPoint = "LoadLibrary", CallingConvention = CallingConvention.StdCall)] 
     static extern int LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName); 

     [DllImport("kernel32.dll", EntryPoint = "GetProcAddress", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] 
     static extern IntPtr GetProcAddress(int hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName); 

     [DllImport("kernel32.dll", EntryPoint = "FreeLibrary", CallingConvention = CallingConvention.StdCall)] 
     static extern bool FreeLibrary(int hModule); 

     [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)] 
     delegate void GetImplementation([MarshalAs(UnmanagedType.Interface)] out IStringFunctions instance); 

     static void Main(string[] args) 
     { 
      const string dllName = "Project1.dll"; 
      const string functionName = "GetImplementation"; 

      int libHandle = LoadLibrary(dllName); 
      if (libHandle == 0) throw new Exception(string.Format("Could not load library \"{0}\"", dllName)); 

      var delphiFunctionAddress = GetProcAddress(libHandle, functionName); 
      if (delphiFunctionAddress == IntPtr.Zero) throw new Exception(string.Format("Can't find function \"{0}\" in library \"{1}\"", functionName, dllName)); 

      GetImplementation getImplementation = (GetImplementation)Marshal.GetDelegateForFunctionPointer(delphiFunctionAddress, typeof(GetImplementation)); 

      if (getImplementation != null) 
      { 
       IStringFunctions instance = null; 
       getImplementation(out instance); 

       if (instance != null) 
       { 
        //!!! don't return value !!!! 
        String result = instance.GetMethodValueAsString(); 
        Console.WriteLine(result); 
       } 
      } 
      Console.ReadLine(); 
     } 
    } 
} 

Но метод instance.GetMethodValueAsString не работает. И код выхода.

Я хочу использовать возвращаемое значение из функции dll (GetMethodValueAsString) в C#.

Я не понимаю.

Где моя ошибка?

Спасибо большое

ответ

6
[return: MarshalAs(UnmanagedType.AnsiBStr)] 

Это неправильно. Вы не возвращаете строку с кодировкой ANSI, выделенную в куче COM. Вы возвращаете обычную строку C, указатель на массив символов ANSI, завершающий нуль.

Ваше объявление интерфейса должно быть:

[MethodImplAttribute(MethodImplOptions.PreserveSig)] 
IntPtr GetMethodValueAsString(); 

Вызов метода должно быть сделано так:

IntPtr ptr = instance.GetMethodValueAsString(); 
string result = Marshal.PtrToStringAnsi(ptr); 

Конечно, ваш дизайн интерфейса становится весьма непрактичным, если вам нужно возвращать динамически распределяемой строка. Вам также нужно будет экспортировать deallocator. Чистый способ справиться с этим - использовать BSTR. Как это:

Delphi

IStringFunctions = interface 
    ['{240B567B-E619-48E4-8CDA-F6A722F44A71}'] 
    procedure GetMethodValueAsString(out value: WideString); stdcall; 
end; 

C#

[MethodImplAttribute(MethodImplOptions.PreserveSig)] 
void GetMethodValueAsString([MarshalAs(UnmanagedType.BStr)] out string result); 
+0

Большое спасибо за ответ и интерес. @David Heffernan Теперь я понял логику. Я постараюсь. –

+0

Привет @David Heffernan Как реализовать этот тип TByteArray = массив байтов; в C#? –

+0

Это массив байтов. 'byte []' в C#.Массив Delphi не может использоваться для взаимодействия. –

1

Это Delphi DLL видимым для C# код для COM Interop? В противном случае самым простым способом было бы подключить dll к проекту библиотеки классов C#, используя пункт меню «Добавить существующий элемент». Затем в окне свойств этой DLL установите «BuildAction» на «Нет» и «Скопировать в каталог вывода» на «Копировать всегда»

Тогда вы можете сделать что-то подобное в коде C#.

[DllImport("Project1.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] 
public static extern string GetMethodValueAsString(); 

Тогда везде, где вам нужно вызвать эту функцию можно сделать

var outputMessage = GetMethodValueAsString(); 
Console.WriteLine(outputMessage); 
+2

Там нет COM здесь. Ну, есть неуправляемая функция, которая возвращает COM-интерфейс. Но зачем что-то нужно регистрировать? –

+0

@DavidHeffernan Мой плохой. Я пытался выяснить, была ли DLL из Delphi видна, как DLL .NET должна была бы быть видимой через атрибут ComVisible. Но я стою исправлен и отредактировал ответ, чтобы отразить то, что я действительно имел в виду. –