Developing OPC Client 4 with Lazarus (DLL interworking with Dot Net)

Keywords: Delphi Windows iOS Android

The functions developed for historical reasons need to be provided for application in. NET environment. This should be a difficult point. C and dot NET are managed code. The release of resources is accomplished by GC. Simple data types can be declared as corresponding, and complex data is even more troublesome.

Here he talks about character sets again.

UNICODE is used as the default character set in windows NT and later, which means that many Windows API s provide functions in Ansi version and Wide Char version. Function names are usually distinguished by A or WC. For example: strcpy function and its wide character version wcscpy.

NET C # and Delphi\Lazarus are strongly typed languages. There are congenital occasions in character set processing, especially Delphi is more convenient than Lazarus. Delphi to the present version is closely following the core of Windows, although it can also generate IOS and Android programs, the effect is said to be good. Lazarus is more troublesome in implementation. Since version 1.4, Lazarus has been upgraded with FPC. The kernel is defaulted to UTF8 from Ansi. As a result, many Delphi programs can not be smoothly ported. Lazarus and. Net are more troublesome. In order to reduce barriers, of course, I plan to use Ansi as the default mode.

The previous blog post mentioned the use of interfaces, which is also an arrangement for software interoperability.

It is basically impossible to expose a class from one language to another and use it properly. Delphi used to interact with C/C++ programs through header file function declaration. Delphi also provided corresponding solutions (interfaces) for how to use classes, so Lazarus did the same.

Pascal declaration and partial implementation

unit OpcGeneralKitDll;
{$mode objfpc}{$H+}
 . . .
interface  
  procedure BuildOpcClient(out IClient: IOpcClient;const HostName: PAnsiChar = '';  const ProgID: PAnsiChar = ''); stdcall; export;

exports
  BuildOpcClient;   

implementation
 . . .
procedure BuildOpcClient(out IClient: IOpcClient;const HostName: PAnsiChar = '';  const ProgID: PAnsiChar = ''); stdcall; export;
    begin
        IClient := TOpcSimpleClient.Create(HostName, ProgID); 
    end; 
end.

Net C

[DllImport("OpcGeneralKitDLL.dll", EntryPoint = "BuildOpcClient", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern void BuildOpcClient([MarshalAs(UnmanagedType.Interface)] out IOpcClient IClient, 
                                         [MarshalAs(UnmanagedType.LPStr)] string aHostName = "",
                                         [MarshalAs(UnmanagedType.LPStr)] string aProgID = "");

This is part of the key, returning the main operations of the OPC class library through the IOpcClient interface. A part of IOpcClient interface C # is attached to compare with Pascal interface.

The UnmanagedType in the System.Runtime.InteropServices space is used in. Net to specify how to marshal parameters or fields to unmanaged code.

The key here is the use of String, strings are complex data, and there are many implementations, including counting, length indication, and NULL\nil indication, but fortunately. Net C\ supports all of these (see below). unmanagedtype).

Tested [MarshalAs(UnmanagedType.LPStr)], [MarshalAs(UnmanagedType.BStr)], [MarshalAs(UnmanagedType.BStr)] as input parameters can be used normally, but as a string output is more troublesome, can only use [MarshalAs(UnmanagedType.BStr)] as a Wide String, or if you do not want AnsiString, you can only transfer pointers, and then pass through. Passed Marshal. PtrToString Ansi (ptr);

I don't know why the return of function result is different from that of output, even if the result is the same, but because the GC may change later, it may be released by GC, so it is adjusted to output string of function, including method and other, rather than return string, although. Net's official proposal is to use pointer and then use transform function to declare as follows. :

 

//C#statement
[MethodImplAttribute(MethodImplOptions.PreserveSig)]
IntPtr GetMethodValueAsString();
//C#When calling:
IntPtr ptr = instance.GetMethodValueAsString();
string result = Marshal.PtrToStringAnsi(ptr);

It's more troublesome. After that, the output mode is changed to be BSTR/WideString.

Pascal statement

 procedure GetMethodValueAsString(out value: WideString); stdcall;

C

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

It's much simpler.

 

Pascal ->. Net C65507

IOpcClient = interface
    ['{93CFA635-E7EC-49B8-87E6-4BACF701BF7B}']
    procedure Connect();stdcall;
    procedure Disconnect;stdcall;
    function ServerState: LongInt;stdcall;
    function GetCount: LongInt;stdcall;
    procedure GetGroup(const Index: LongInt;out Result:IOpcSimpleGroup);stdcall;
    procedure SetProgID(const aValue: PAnsiChar);stdcall;
    procedure GetOnConnect(out Result: TNotifyEvent);stdcall;
    procedure SetOnConnect(const aValue: TNotifyEvent);stdcall;
    function Add(const aGroupName: PAnsiChar; const aUpdateRate: LongInt=100; const aEnbled :LongBool =true): LongInt; overload;stdcall;
  end;

Net C# -> Pascal Statement

[ComVisible(true)]
    [ComImport, Guid("93CFA635-E7EC-49B8-87E6-4BACF701BF7B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IOpcClient
    {
        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        void Connect();

        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        void Disconnect();

        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        int GetCount();

        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        void GetGroup(int Index, [MarshalAs(UnmanagedType.Interface)] out IOpcGroup refG);

        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        void GetProgID([MarshalAs(UnmanagedType.BStr)]out string Result);

        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        void SetProgID([MarshalAs(UnmanagedType.LPStr)] string aValue);

        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        void GetOnConnect([MarshalAs(UnmanagedType.FunctionPtr)] out TInfoEvent refFuc);

        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        void SetOnConnect([MarshalAs(UnmanagedType.FunctionPtr)] TInfoEvent aValue);

        [MethodImplAttribute(MethodImplOptions.PreserveSig)]
        int Add([MarshalAs(UnmanagedType.LPStr)] string aGroupPathName, int aUpdateRate = 100, bool aEnbled = true);

    }

Again, if C# cannot be typed explicitly,. NET must specify unmanaged types with "features", so it's better to use explicit data types.

Pascal Bytes .net Characteristic modification in use
Boolean 1 bool [MarshalAs(UnmanagedType.U1)] bool
LongBool 4 bool No decoration or [MarshalAs(UnmanagedType.Bool)] bool
integer 2,4,8 int No modification or [MarshalAs (Unmanaged Type. SysInt)] int
longInt 4 Int32,long No modification or [MarshalAs (Unmanaged Type. I4)] int
LongWord 4 Uint32,ulong No modification or [MarshalAs (Unmanaged Type. U4)] uint
TInfoEvent callback function 4 Define delegation TInfoEvent [MarshalAs(UnmanagedType.FunctionPtr)]  TInfoEvent
By analogy...      

Finally, I want to talk about Native DLL and. NET (native DLL and Dot Net), for callback function attention.

There are two main points, one is normal call, and the other is about normal use.

One is a normal call

There are too many ways to support the original ecology of dot NET, which can be transformed into pointer application and then used in dot NET. In fact, after using UnmanagedType.FunctionPtr modification, pointer function can be directly used, that is, the delegation in dot NET. But it won't work for a while.

It's all caused by GC. It's released!

Two are about normal use.

Normal use is to avoid the release of function references. At first, it's the same as many friends think - static, but unsuccessful (probably not on a very large scale). Then it's the way to keep the delegation in the GC class, through GC.KeepAlive, and then everything works.

Simplified Chinese Edition Error Information:

The xxxx::Invoke type of garbage collection delegation was called back. This can lead to application crashes, corruption, and data loss. When passing delegates to unmanaged code, managed applications must keep these delegates active until they are convinced that they will not be called again.

if (OnConnectDelegate == null)
  {
     OnConnectDelegate = new TInfoEvent(OnConnect);
     GC.KeepAlive(OnConnectDelegate);
  }

 

 

References:

http://stackoverflow.com/questions/14103973/interface-result-delphi-to-c-sharp-class-define-dll-use-in-c-sharp

http://stackoverflow.com/questions/30041480/call-delphi-function-from-c-sharp

http://blog.csdn.net/cmd9x/article/details/51507193

http://blog.csdn.net/catshitone/article/details/53641498

https://msdn.microsoft.com/zh-cn/library/system.runtime.interopservices.unmanagedtype(v=vs.110).aspx

https://msdn.microsoft.com/zh-cn/library/zah6xy75

https://msdn.microsoft.com/en-us/library/zah6xy75.aspx?cs-save-lang=1&cs-lang=csharp

Posted by yelvington on Tue, 01 Jan 2019 03:48:08 -0800