Recent

Author Topic: Problem trapping in-process COM DLL IDispatch methods  (Read 813 times)

rjking58

  • New member
  • *
  • Posts: 7
Re: Problem trapping in-process COM DLL IDispatch methods
« Reply #15 on: July 26, 2026, 01:23:10 pm »
@Thaddy. I updated FPC and Laz to trunk, 3.3.1 and 4.99. that made no difference (command shown but choosing it did not trigger anything). I then changed uses windows to jwaWindows and had to keep ActiveX and the two Com units for it to compile. This change caused the AddCommandItem not to be displayed and in fact the connection from SW was not made (the SwConnect callback never fired). No errors but nothing logged in the Event Viewer by the OutputDebugString statements (which is how I know the COM connection from SW o the Addin was not made). So very broken.

I'm going to try starting with a very small COM Server program using TComObject with IDispatch support added on top and have a client program connect and make a late bound callby name and get that to work. That should verify the IDispatch methods GetIDsByName and Invoke can be added to a TComObject. No Solidworks, just something as simple as I can make it.

@jkorba812 please reread what I have already described and why we don't want to create a type library for the AddIn. We don't have a type lib in the Delphi code to handle the callback events as (somehow) Delphi handles late bound calls (calls by name) without trouble. Note we do use the SW type libs to make early bound (dispinterface) calls to SW and this works without problem.  The issue is handling late-bound calls from SolidWorks in response to clicking a command (which we have successfully added using the SolidWorks type library functions).

Note foreveryone. What is very puzzling is that the SwConnect methods are being triggered and handled by the Addin in our Addin class. The SW doco (mentioned in the starter post) says SW expects the Addin to handle both the SwConnect and Discnnnect events as late bound COM events.  Ad these are working, so why not the menu item events.  I would have thought we don't even need the IDispatch methods as the Command callback are the same type of late bound events.  But clearly I don't understand something.
« Last Edit: July 26, 2026, 01:51:42 pm by rjking58 »

korba812

  • Hero Member
  • *****
  • Posts: 501
Re: Problem trapping in-process COM DLL IDispatch methods
« Reply #16 on: July 26, 2026, 01:44:21 pm »
Ok. Try this instead:
Code: Pascal  [Select][+][-]
  1. FSWApp.SetAddinCallbackInfo2(HInstance, ThisSW, Cookie);
  2.  
this:
Code: Pascal  [Select][+][-]
  1. FSWApp.SetAddinCallbackInfo2(0, Self as IUnknown, Cookie);
  2.  

Thaddy

  • Hero Member
  • *****
  • Posts: 19611
  • Glad to be alive.
Re: Problem trapping in-process COM DLL IDispatch methods
« Reply #17 on: July 26, 2026, 02:46:07 pm »
He has the type library.
Any "programmer" that knows only one programming language is not a programmer

rjking58

  • New member
  • *
  • Posts: 7
Re: Problem trapping in-process COM DLL IDispatch methods
« Reply #18 on: July 27, 2026, 08:51:15 am »
@korba812.  That does not compile-- an IDispatch is expected..

BUT you are correct - the target object for the callbacks must be this addin object not SW !!!

So the line needs to be

Code: Pascal  [Select][+][-]
  1. FSWApp.SetAddinCallbackInfo2(0, Self as IDispatch, Cookie);

Thank you very much. Now GetIDsOfNames and Invoke is being called  :)  I have to sort out a few things and will update this thread when it is working 100%
« Last Edit: July 27, 2026, 09:59:35 am by rjking58 »

korba812

  • Hero Member
  • *****
  • Posts: 501
Re: Problem trapping in-process COM DLL IDispatch methods
« Reply #19 on: July 27, 2026, 06:03:53 pm »
@korba812.  That does not compile-- an IDispatch is expected..

BUT you are correct - the target object for the callbacks must be this addin object not SW !!!

So the line needs to be

Code: Pascal  [Select][+][-]
  1. FSWApp.SetAddinCallbackInfo2(0, Self as IDispatch, Cookie);

Thank you very much. Now GetIDsOfNames and Invoke is being called  :)  I have to sort out a few things and will update this thread when it is working 100%
I was just guessing the type since I didn't see the definition of this method.

He has the type library.
No, he doesn't use it. That's what Richard says. Besides, I don't see a directive anywhere in the code to include a type library file.

rjking58

  • New member
  • *
  • Posts: 7
Re: Problem trapping in-process COM DLL IDispatch methods
« Reply #20 on: July 28, 2026, 03:17:21 am »
For future reference, I had to rework the GetIDsOfNames (beware AI generated code!) as it did not work correctly.  Here is the working code to add one command with a callback for the menu item and a separate callback for Enbling/disabling the menu item. As I hint in the comments, even though we only need a few commands, it would be good to use RTTI to look up the late bound names in the GetIDsOfNames IDispatch routine.

Code: Pascal  [Select][+][-]
  1. library McadxSw2021;
  2.  
  3. {$mode delphi}
  4. {$H+}
  5. {$INTERFACES COM}
  6.  
  7. {
  8. This is a very basic Solidworks Addin DLL which connects with SW, makes a simple function call
  9. using early binding and the SW type library to retrieve the SW version
  10. and then sets up a command item and callback.
  11.  
  12. The special thing is that we do not have a type library of our own for the call backs
  13. so we roll our own IDispatch with the critical four functions (GetIDsOfNames, Invoke, etc).
  14. Note SW calls back with functions by name (late binding).
  15.  
  16. A further experiment will be to use RTTI to handle the GetIDsOfNames rather than the if else chain.
  17.  
  18. Configure the SW exe in Run Parameters so you can 'Run' the DLL which will start SW.
  19. Use the Lazarus event viewer to view the output.
  20. }
  21.  
  22. uses
  23.   Windows, Classes, SysUtils, ComObj, ActiveX, ComServ, Variants,
  24.   //SysUtils, ComObj, ComServ, ActiveX, jwaWindows,
  25.   SldWorks_29_0_TLB,
  26.   SWPublished_29_0_TLB
  27.   ;
  28.  
  29. const
  30.   cSwTargetRelease = '2021';
  31.   cProductTitleToRegister = 'MCADX Modeler';
  32.  
  33.   CLASS_McadxSw2021Addin: TGUID = '{000161BE-9C4D-41CF-AA8B-0FFED4698D15}';
  34.  
  35. type
  36.   // keep GUID same as Delphi addin to avoid having to register this dll
  37.   IMcadxSw2021Addin = interface(IDispatch)
  38.     ['{EE088A31-C162-416C-AC35-9F8F337C35F5}']
  39.     procedure ShowMessageCommand; safecall;
  40.     function EnableCommand:integer; safecall;
  41.   end;
  42.  
  43.   { TMcadxSw2021Addin }
  44.  
  45.   TMcadxSw2021Addin = class(TComObject, IMcadxSw2021Addin, ISwAddin, IDispatch, IUnknown)
  46.   private
  47.     FSWApp: ISldWorks;
  48.     FCmdManager: ICommandManager;
  49.     procedure CreateCommands;
  50.  
  51.     procedure ShowMessageCommand; safecall;    // can be private as our Dispatch mechanism will call
  52.     function EnableCommand:integer; safecall;  // ditto
  53.  
  54.   public
  55.     function ConnectToSW(ThisSW: IDispatch; Cookie: Integer): HResult; stdcall;
  56.     function DisconnectFromSW: HResult; stdcall;
  57.  
  58.     // roll our own IDispatch handling as we do not have a type library and only have a couple of commands to handle
  59.     // in this proof of concept
  60.     function GetIDsOfNames(const IID: TGUID; Names: Pointer;
  61.       NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
  62.     function Invoke(DispID: TDispID; const IID: TGUID; LocaleID: Integer;
  63.       Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
  64.     function GetTypeInfoCount(out Count: Longint): HResult; stdcall;
  65.     function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
  66.   end;
  67.  
  68.   CoMcadxSw2021Addin = class
  69.     class function Create: IMcadxSw2021Addin;
  70.     class function CreateRemote(const MachineName: string): IMcadxSw2021Addin;
  71.   end;
  72.  
  73.   TMcadxSw2021AddinFactory = class(TAutoObjectFactory)
  74.   public
  75.     procedure UpdateRegistry(Register: boolean); override;
  76.   end;
  77.  
  78. class function CoMcadxSw2021Addin.Create: IMcadxSw2021Addin;
  79. begin
  80.   Result := CreateComObject(CLASS_McadxSw2021Addin) as IMcadxSw2021Addin;
  81. end;
  82.  
  83. class function CoMcadxSw2021Addin.CreateRemote(const MachineName: string): IMcadxSw2021Addin;
  84. begin
  85.   Result := CreateRemoteComObject(MachineName, CLASS_McadxSw2021Addin) as IMcadxSw2021Addin;
  86. end;
  87.  
  88.  
  89. // adapted from Claude and Gemini generated code
  90.  
  91. function TMcadxSw2021Addin.GetIDsOfNames(const IID: TGUID; Names: Pointer;
  92.   NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
  93. type
  94.   PPOleStrArray = ^TPOleStrArray;
  95.   TPOleStrArray = array[0..0] of LPOleStr;
  96.   PDISPIDArray = ^TDISPIDArray;
  97.   TDISPIDArray = array[0..0] of TDispID;
  98. var
  99.   NamesArray: PPOleStrArray;
  100.   IDsArray: PDISPIDArray;
  101.   i: Integer;
  102. begin
  103.   OutputDebugStringW(PWideChar('GetIDsOfNames called!'));
  104.   NamesArray := PPOleStrArray(Names);
  105.   IDsArray := PDISPIDArray(DispIDs);
  106.   Result := S_OK;
  107.   // think about using RTTI to avoid this if else chain
  108.   for i := 0 to NameCount - 1 do
  109.   begin
  110.     // Convert PWideChar to string and match
  111.     if SameText(WideString(NamesArray^[i]), 'ShowMessageCommand') then
  112.       IDsArray^[i] := 1
  113.     else
  114.     if SameText(WideString(NamesArray^[i]), 'EnableCommand') then
  115.       IDsArray^[i] := 2
  116.     else
  117.       IDsArray^[i] := DISPID_UNKNOWN;
  118.   end;
  119. end;
  120.  
  121. function TMcadxSw2021Addin.Invoke(DispID: TDispID; const IID: TGUID; LocaleID: Integer;
  122.   Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
  123. begin
  124.   OutputDebugString(PChar('Invoke called!'));
  125.   case DispID of
  126.     1: begin
  127.       OutputDebugString(PChar('menu item called back'));
  128.       ShowMessageCommand;
  129.       Result := S_OK;
  130.     end;
  131.     2: begin
  132.       OutputDebugString(PChar('enable menu item called back'));
  133.       PVariant(VarResult)^ :=  EnableCommand;
  134.       Result := S_OK;
  135.     end
  136.   else
  137.     OutputDebugString(PChar('Disp ID not found in invoke'));
  138.     Result := DISP_E_MEMBERNOTFOUND;
  139.   end;
  140. end;
  141.  
  142. function TMcadxSw2021Addin.GetTypeInfoCount(out Count: Longint): HResult; stdcall;
  143. begin
  144.   Count := 0;   // no type lib/info
  145.   Result := S_OK;
  146. end;
  147.  
  148. function TMcadxSw2021Addin.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
  149. begin
  150.   Result := E_NOTIMPL;
  151. end;
  152.  
  153. { notes from Claude when describing problem
  154. (returning S_OK/0 from GetTypeInfoCount and E_NOTIMPL from GetTypeInfo is the standard "I don't support type-info
  155. introspection, only name-based dispatch" pattern — fine here since SolidWorks only needs GetIDsOfNames/Invoke.)
  156. Wire that in, add IDispatch to your class's declared interfaces, click the menu item, and see if OnMenuItem1Click
  157. actually fires. If it does, you've now validated the entire chain end-to-end: type library fidelity, COM DLL plumbing,
  158. calls into SolidWorks, and callbacks from SolidWorks back into your code. At that point the migration risk picture
  159. looks genuinely good — the remaining work is breadth (implementing more of the API surface you actually use),
  160. not a new category of unknown.
  161. Worth noting: this same IDispatch requirement applies to the modern ICommandManager/AddCommandItem2 UI path too, and to
  162. event sinks (document/part events) if you use those — so solving it once here pays off across the rest of the add-in.
  163. }
  164.  
  165. // end from Claude
  166.  
  167. function TMcadxSw2021Addin.ConnectToSW(ThisSW: IDispatch; Cookie: Integer): HResult; stdcall;
  168. var
  169.   base, curr: WideString;
  170. begin
  171.   try
  172.     FSWApp := ThisSW as ISldWorks;
  173.     OutputDebugString(PChar('Connected to Sw application interface'));
  174.   except
  175.     on e: exception do
  176.       OutputDebugString(PChar('Exception getting Sw application interface ' + e.Message));
  177.   end;
  178.  
  179.   // test that we can call a function on SW (using SW type lib and dispinterface)
  180.   FSWApp.GetBuildNumbers(base, curr);
  181.   OutputDebugStringW(PWideChar('SW Build numbers=' + base + ' ' + curr));
  182.  
  183.   try
  184.     // Set up callbacks linking back to this object instance
  185.     FSWApp.SetAddinCallbackInfo2(HInstance, Self as IDispatch, Cookie);  // HInstance or 0 also ok
  186.   except
  187.     on e: exception do
  188.       OutputDebugString(PChar('Failed to setup callback info ' + e.Message));
  189.   end;
  190.  
  191.   try
  192.     FCmdManager := FSwApp.GetCommandManager(Cookie);
  193.     if Assigned(FCmdManager) then
  194.     begin
  195.       OutputDebugString('Successfully retrieved ICommandManager.');
  196.       CreateCommands;  // add a single command
  197.     end
  198.     else
  199.       OutputDebugString('Failed to retrieve ICommandManager.');
  200.   except
  201.     on e: exception do
  202.       OutputDebugString(PChar('Failed getting command manager ' + e.Message));
  203.   end;
  204.  
  205.   Result := S_OK;
  206. end;
  207.  
  208. function TMcadxSw2021Addin.DisconnectFromSW: HResult; stdcall;
  209. begin
  210.   OutputDebugString(PChar('Disconnected from Sw application interface'));
  211.   Result := S_OK;
  212. end;
  213.  
  214. procedure TMcadxSw2021Addin.CreateCommands;
  215. const
  216.   swMenuItem = $00000001;
  217. //  swToolbarItem = $00000002;
  218. var
  219.   CommandGroup: ICommandGroup;
  220.   Title: widestring;
  221.   Tooltip: widestring;
  222.   Hint: widestring;
  223.   Actn: widestring;
  224.   CallbackFunction: widestring;
  225.   EnableMethodCallback: widestring;
  226.   res: Integer;
  227.   wTrue, wFalse: WordBool;
  228. begin
  229.   // ensure strings are COM compatible - probably unnecessary
  230.   Title := 'My Custom Commands';
  231.   Tooltip := 'My custom command group';
  232.   Hint := 'Performs custom actions in SolidWorks';
  233.   wTrue := True;
  234.   wFalse := False;
  235.  
  236.   // 2. Create the Command Group
  237.   CommandGroup := FCmdManager.CreateCommandGroup(
  238.     1, // User-defined ID for this group
  239.     Title, Tooltip, Hint, -1
  240.   );
  241.  
  242.   CommandGroup := FCmdManager.GetCommandGroup(1);
  243.   OutputDebugString('GetCommandGroup(1) succeeded');
  244.  
  245.   if CommandGroup <> nil then
  246.   begin
  247.     CallbackFunction := 'ShowMessageCommand';
  248.     EnableMethodCallback := 'EnableCommand';
  249.  
  250.     // 3. Add a command item
  251.     actn := 'My Action';
  252.     hint := 'Executes my custom action';
  253.     tooltip := 'My Action Tooltip';
  254.     res := CommandGroup.AddCommandItem2(
  255.       actn, // 'My Action',
  256.       0, // Menu Position
  257.       hint, // 'Executes my custom action',
  258.       tooltip, // 'My Action Tooltip',
  259.       0,
  260.       CallbackFunction,
  261.       EnableMethodCallback,
  262.       1, // User ID
  263.       swMenuItem // or swToolbarItem
  264.     );
  265.  
  266.     OutputDebugString(PChar('AddCommandItem succeeded ' + IntToStr(res)));
  267.  
  268.     // Activate the command group and make it visible
  269.     CommandGroup.HasToolbar := wFalse;
  270.     CommandGroup.HasMenu := wTrue;
  271.     CommandGroup.Activate;
  272.     OutputDebugString('state setting succeeded');  // menu appears
  273.   end;
  274. end;
  275.  
  276. procedure TMcadxSw2021Addin.ShowMessageCommand; safecall;
  277. begin
  278.   OutputDebugString(PChar('Hello from Test menu item'));
  279. end;
  280.  
  281. function TMcadxSw2021Addin.EnableCommand:integer; safecall;
  282. begin
  283.   OutputDebugStringW(PWideChar('enable called'));
  284.   result := 1;   // 0 to disable the command
  285. end;
  286.  
  287. procedure TMcadxSw2021AddinFactory.UpdateRegistry(Register: boolean);
  288. const
  289.   cSwAddinRegFolder = 'SOFTWARE\SolidWorks\SOLIDWORKS ' + cSwTargetRelease + '\Addins\';
  290. var
  291.   AppClassID: string;
  292. begin
  293.   AppClassID := GUIDToString(CLASS_McadxSw2021Addin);
  294.   if Register then
  295.   begin
  296.     inherited UpdateRegistry(Register);
  297.     CreateRegKey(cSwAddinRegFolder + AppClassID, '', '1', HKEY_LOCAL_MACHINE);
  298.     CreateRegKey(cSwAddinRegFolder + AppClassID, 'Description', cProductTitleToRegister + ' for ' + 'Sw' + cSwTargetRelease, HKEY_LOCAL_MACHINE);
  299.     CreateRegKey(cSwAddinRegFolder + AppClassID, 'Title', cProductTitleToRegister, HKEY_LOCAL_MACHINE);
  300.     OutputDebugString(PChar('created registry keys (called unexpectedly as should already be registered!)'));
  301.   end
  302.   else
  303.   begin
  304.     DeleteRegKey(cSwAddinRegFolder + AppClassID, HKEY_LOCAL_MACHINE);
  305.     inherited UpdateRegistry(Register);
  306.     OutputDebugString(PChar('deleted registry keys (unexpectedly!)'));
  307.   end;
  308. end;
  309.  
  310. exports
  311.   DllGetClassObject,
  312.   DllCanUnloadNow,
  313.   DllRegisterServer,
  314.   DllUnregisterServer;
  315.  
  316. initialization
  317.    OutputDebugString(PChar('about to create addin'));
  318.    TComObjectFactory.Create(ComServer, TMcadxSw2021Addin, Class_McadxSw2021Addin, 'SwAddin', 'FP Add in', ciMultiInstance, tmApartment);
  319.    OutputDebugString(PChar('created addin?'));
  320. end.

korba812

  • Hero Member
  • *****
  • Posts: 501
Re: Problem trapping in-process COM DLL IDispatch methods
« Reply #21 on: July 28, 2026, 12:09:07 pm »
If you only need to support a few methods, stick with "if then else" / "case of" and don't bother with RTTI. If you need to support a large number of methods and classes, use a type library.

 

TinyPortal © 2005-2018