Event notification from dll to main application

I am developing an application that intends to be cross-platform. I used to use Windows messaging, but now I refuse it. I replaced the messages with callbacks, but regardless of whether I can use different technologies, I am not aware of different possibilities when Windows messages are not used.

Well, I have a main exe application and some dll plugins. I have some objects and threads in a dll, and I would like to notify the main application of some changes that the DLL has created for the data structure.

As I said, I am currently working with some callbacks. To ensure compatibility with different languages ​​(C ++, VB, C #), I have a non-object callback type. I'm not sure if other languages ​​support object callbacks.

So my questions are:

  • What are the alternatives (cross-platform) for Windows messaging? Can callbacks replace messages?
  • Do other languages ​​support object callbacks?
  • I think other languages ​​have different technologies as an alternative to messages.
+4
source share
4 answers

So my questions are: What are the alternatives (cross-platform) for Windows messaging? Can callbacks replace messages?

Yes, you can replace messages with callbacks.

Do other languages ​​support object callbacks?

You should not use object methods as callbacks. Common practice in portable code is to use handles (call notification):

DLL source:

type THandle = LongWord; {$IF SizeOf(THandle) < SizeOf(Pointer))} {$MESSAGE Error 'Invallid handle type'} {$ENDIF} TCallback = procedure(const aHandle: THandle); cdecl; var gCallback: record Routine: TCallback; Obj: TObject; Info: string end; function Object2Handle(const aObj: TObject): THandle; begin Result:= THandle(Pointer(aObj)) end; function Handle2Object(const aHandle: THandle; out aObj: TObject): Boolean; begin if gCallback.Obj <> nil then if aHandle = Object2Handle(gCallback.Obj) then begin aObj:= gCallback.Obj; Result:= true; Exit // WARRNING: program flow disorder end; aObj:= nil; Result:= false end; procedure DoCallback(); begin if Assigned(gCallback.Routine) then gCallback.Routine(Object2Handle(gCallback.Obj)) end; procedure SetupCallback(const aCallback: TCallback); cdecl; begin gCallback.Routine:= aCallback; end; procedure DoSomething(const aHandle: THandle; out aInfo: string); cdecl; var O: TObject; begin if Handle2Object(aHandle, O) then aInfo:= Format('%s class object %s', [O.ClassName(), gCallback.Info]) end; procedure Test(); begin gCallback.Obj:= TStream.Create(); try gCallback.Info:= 'created'; DoCallback(); finally FreeAndNil(gCallback.Obj) end; gCallback.Obj:= TMemoryStream.Create(); try gCallback.Info:= 'will be freed'; DoCallback(); finally FreeAndNil(gCallback.Obj) end end; exports SetupCallback, DoSomething, Test; 

Executable source:

 procedure Cb(const aHandle: THandle); cdecl; const STUPID: THandle = 1; EQUALLY_STUPID = $DEAD; var S: string; begin DoSomething(STUPID, S); DoSomething(aHandle, S); DoSomething(EQUALLY_STUPID, S) end; begin SetupCallback(@Cb); Test() end. 

Edited: Now you can’t shoot in the foot.

I believe other languages ​​have different technologies as an alternative to messages?

The OS has several message alternatives. However, not many are really portable.

You can also use:

  • sockets
  • (IMO too big in this case?) Ready-made messaging system (my favorite 0MQ )
+2
source

You can use callback functions instead of messages. You cannot use callback methods because only Delphi and C ++ Builder understand how to call pointers to Delphi methods. However, you can use callback objects with any language that supports COM. Here is an example plug-in for notifying the application that the data structure has changed:

  • Define an interface.

     type IDataStructureChanged = interface ['{GUID}'] procedure Call; stdcall; end; 

    You can add some parameters to the method so that the plug-in can determine how the data structure has changed, or pass some value indicating which plug-in makes the notification.

  • Embed it in the app.

     type TDataStructureChangedListener = class(TInterfacedObject, IDataStructureChanged) private FForm: TForm; procedure Call; stdcall; public constructor Create(Form: TForm); end; 

    When you create an instance of this class, you can pass it a link to the main form of your program or any other information that your program will need to execute when the plug-in ultimately calls the Call method. Make a Call so that your application does what it needs when the data structure changes.

  • Pass a link to each of the plug-ins when they are initialized.

     ChangeListener := TDataStructureChangedListener.Create(Self); for i := 0 to Pred(PlugIns.Count) do PlugIns[i].Init(ChangeListener); 

    The plugin must store a reference to the listener object, and when the data structure is changed, it can call the Call method to notify your application.

What I described here is what is commonly known as an event receiver. There may be more than one in your program. If there are several events to handle, you can have a separate interface for each type of event, or you can group them all into one interface and have a different method for each event. You can have a different receiver object for each plug-in, or you can give each plug-in a link to the same receiver object, and then pass the plug-in identifier parameter.

+4
source

I would definitely use callbacks. The main application can provide a DLL callback function for calling if necessary, and then the callback function itself can send window messages to the application if necessary.

+3
source

I agree with Remy, (!). A direct callback allows the handler to implement any additional communication that he chooses - he can post a message, he can push the parameter to the queue, regardless of what he wants. If you want to be cross-platform, you will have to resort to passing and passing simple types. Usually, when calls are made, it goes to the "user context" pointer. The callback passes this pointer to the handler. This allows callers to pass a context object in the form of a / int pointer and restore it to the handler (by dropping the / int pointer back to the object). Then the handler can call the methods in the context, regardless of whether they are Delphi, C ++, etc.

+3
source

Source: https://habr.com/ru/post/1401056/


All Articles