Interaction between managed (C#) and unmanaged (Pascal) code

Question:

There was a task to pull C# methods from Pascal code.

The main decision condition is:
everything should be portable, without the need to register any COM objects and work with Delphi 7 + KOL.

Unfortunately, I am not familiar with Pascal/Delphi, a cursory reading of the manuals does not give any results, and learning the language is sorely lacking time.

The problem was partially solved after reading:

Export functions from .NET Framework dll to Metatrader
How to open the C# world from MQL5 by exporting unmanaged code
C# Project Template for Unmanaged Exports
Export Managed Code as Unmanaged
Unmanaged Exports ".NET DllExport"

Calling Pascal methods from C# code works, I have no complaints yet.

C# (dll)  
...
  static int test = 0;
  [System.Reflection.Obfuscation(Feature = "DllExport")]
  static int Get(){
      return test;
  }
  [System.Reflection.Obfuscation(Feature = "DllExport")]
  static void Set(int val){
      test = val;
  }
...

Pascal
...
  function Get(): integer; stdcall; external 'UnmanagedExport.dll'; 
  procedure Set(vl:integer ); stdcall; external 'UnmanagedExport.dll';    
...   

But with calls to Pascal functions from C# – so far it's empty. Tried a simple bike

C# (dll)
...
  delegate void del(int val);
  ...
  [System.Reflection.Obfuscation(Feature = "DllExport")]
  static void Test(del proc){
      proc(1234);
  }
...

Pascal
...
  type
    myProc=procedure(val: integer);
  ...
    function Test(fn: myProc): integer; stdcall; external 'UnmanagedExport.dll';
    procedure mycall(val: integer);
  ...
  procedure mycall(val: integer);
      ShowMessage(Int2Str(val));
  end;
  ...
    Test(mycall);
  ...
... 

Interaction from C# is quite feasible, since the message works, though with garbage.
But how to implement everything correctly is the question.
In addition to calls, I'm also interested in the possibility (and how to) subscribe to events in Pascal code in C #.


10/11/2016

Good day.

The problem has already been solved, I did not wait for an answer and decided to dig deeper on my own. And unsubscribe all hands did not reach.

Since tight integration was not part of the task, the results obtained were quite enough. Using the method described in Export functions from Net dll to Metatrader, we got a bridge in the form of an additional dll.

Of the delphi 4.7 + Kol 1.9, FPC 3.0 (Lazarus) and FPC 2.6 + Kol 3.2 available at hand, the above code section showed different results. Although in general, after minor edits, the template is applicable to all test subjects.

This is just an experiment to understand where to go. For the specified versions of delphi 4.7 + Kol 1.9 (which is the main condition) with its own (not known to me) options and edits in kol, this code is functional, although I have not used it in the given scale.

Briefly, in general terms, the experiment looks something like this

C#

public class Class1
{
    public delegate void del([MarshalAs(UnmanagedType.AnsiBStr)]string _string);


    [System.Reflection.Obfuscation(Feature = "DllExport")]
    public static unsafe int Test(int value1, string value2, [MarshalAs(UnmanagedType.AnsiBStr)] ref string value3, del value4, byte* value5)
    {
        MessageBox.Show("value1: " + value1.ToString()+ "\r\n" +
                        "value2: " + value2 + "\r\n" +
                        "value3: " + value3 + "\r\n" +
                        "value5: " + value5[1].ToString(), "C#");

        value3 = "C# йцукен";
        value4("bla-bla бла-бла");
        value5[1] = 123;
        return 54321;
    }
}

Pascal

...
type
  myProc=procedure(val: String); StdCall;

function Test(val1: integer; val2: string; var val3: string; val4: myProc; val5: pointer): integer; stdcall; external 'ClassLibrary1.dll';
procedure myCall(val: String); StdCall;

...

var vl1: string;
  vl2: array [0..5] of byte;
  vl3: integer;       
begin
 vl1:= 'ytrewq некуцй';
 vl2[1]:= 10;

 vl3:= Test(12345, 'qwerty йцукен', vl1, @myCall, @vl2);

 ShowMessage('vl1: ' + vl1 + #13#10 +
             'vl2: ' + int2str(vl2[1]) + #13#10 +
             'vl3: ' + int2str(vl3));
...

procedure myCall(val: String); StdCall;
begin
    ShowMessage('myCall: ' + val);
end;

PS Although the goal has been achieved, the question is still valid, especially the interaction from pascal without "bridges", a separate interest regarding events.


@Pavel Mayorov. Try learning COM anyway…
Unfortunately, my knowledge of Pascal is very limited, and it's pointless to learn something new without initial knowledge. This option is not excluded, but postponed until better times (until more free time appears)

Answer:

The second article gives an example from C, which can be translated into Pascal:

#include <stdio.h>
#include <string.h>
typedef void (__stdcall *callback)(wchar_t * str);
extern "C" __declspec(dllexport) void __stdcall caller(wchar_t * input, int count, callback call)
{
      for(int i = 0; i < count; i++)
      {
          call(input);
      }
}

And here's what it looks like in Pascal:

type
    TCallback = procedure(str: ^Char); stdcall;

procedure caller(input: ^Char, count: Integer; call: TCallback); stdcall;
var
    i: Integer;
begin
    for i := 1 to count do
         call(input);
end;

In your code, it is almost the same, but the calling function is implemented in C#, while according to the article it should be implemented in Pascal (here it is called caller and in Pascal terminology is a procedure).

PS I haven't written Pascal for 200 years, so syntactic flaws are possible in the code.

Scroll to Top