Jump to content
kihor

Memory access problem when exchanging WideString in OLE object written in Delphi

Recommended Posts

Hello,

 

I am trying to implement COM object in Delphi. This is test COM object and its main module looks like this:

unit MyCOM;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  Windows, ActiveX, Classes, ComObj, TestCOM_TLB, StdVcl;

type
  TMyCOM = class(TAutoObject, IMyCOM)
  public
    function DoIt: WideString; stdcall;
  end;

  TMyComObjectFactory = class(TAutoObjectFactory)
  protected
    function GetProgID: string; override;
  end;

const
  SValue: WideString = 'It works!';

implementation

uses ComServ;

function TMyCOM.DoIt: WideString; stdcall;
begin
  Result := SValue;
end;

{ TMyComObjectFactory }

function TMyComObjectFactory.GetProgID: string;
begin
  Result := 'DeNovo.MyCOM';
end;

initialization
  TMyComObjectFactory.Create(ComServer, TMyCOM, Class_MyCOM,
    ciMultiInstance, tmApartment);
end.

 

I register this DLL with regsvr32 without problem. Then I try to invoke COM function via the following code in separate application:

 

procedure TForm1.btnInvokeMyCOMClick(Sender: TObject);
var
  MyCOM: Variant;
begin
  MyCOM := CreateOleObject('DeNovo.MyCOM');
  ShowMessage(MyCOM.DoIt);
end;

 

I contantly get the memorry access error.

 

Meanwhile when I am trying to exchange integer data - everything works fine.

 

Can you give me some clues how to cope with this WideString memory issue?

 

 

Share this post


Link to post
Guest

Just an idea. Shouldn't the calling convention for an automation object be safecall rather than stdcall?

Share this post


Link to post
5 minutes ago, Ondrej Kelle said:

Just an idea. Shouldn't the calling convention for an automation object be safecall rather than stdcall?

May be you're right, the return function as string should be a safearray.

 

17 minutes ago, kihor said:

Can you give me some clues how to cope with this WideString memory issue? 

Instead to use return value, use that as parameter:

 

procedure TMyCOM.DoIt(Value: WideString); stdcall;
begin
  Value := SValue;
end;

 

Share this post


Link to post
33 minutes ago, DelphiUdIT said:

How is your RIDL definition of COM class ... may be there is the issue ...

Thanks for your reply. RIDL definition is following:

[
  uuid(F59E7744-DBC5-4544-A3DA-FF71C41A7DEE),
  version(1.0)

]
library TestCOM
{

  importlib("stdole2.tlb");

  interface IMyCOM;
  coclass MyCOM;


  [
    uuid(6C0484FA-26EE-4945-913A-78146C8449C2),
    helpstring("Interface for MyCOM Object"),
    oleautomation
  ]
  interface IMyCOM: IDispatch
  {
    [id(0x00000065)]
    BSTR _stdcall DoIt(void);
  };

  [
    uuid(B64A68CB-EBAC-42C7-B753-9BCBD87EC57E),
    helpstring("MyCOM")
  ]
  coclass MyCOM
  {
    [default] interface IMyCOM;
  };

};

 

Share this post


Link to post
40 minutes ago, Ondrej Kelle said:

Just an idea. Shouldn't the calling convention for an automation object be safecall rather than stdcall?

T tried your approach. Unfortunately, it didn't help. But thanks for your reply.

Share this post


Link to post

May be with the use of property, (remeber something with my very old use of COM) I can exchange widestring. I remeber to never expose directly string as return function.

 

Let I try to modify your COM ...

Share this post


Link to post
1 hour ago, Ondrej Kelle said:

Shouldn't the calling convention for an automation object be safecall rather than stdcall?

safecall is a Delphi concept not a COM concept and safecall just "wraps" stdcall.

On the server side safecall traps exceptions and convert them to HRESULT error codes and on the client side it convert HRESULT back to exceptions (for error code only, of course).

In order for a method to be declared safecall the underlying COM method must return a HRESULT. Thus:

procedure HelloWorld(Value: WORD); safecall;

is the same as

function HelloWorld(Value: WORD): HResult; stdcall;

Here endeth the lesson.

Share this post


Link to post
59 minutes ago, kihor said:

RIDL definition is following:

I think your COM declarations are incorrect.

 

For a method returning a WideString I would have expected something like this:

HRESULT _stdcall GetStringValue([out, retval] BSTR* Result);

Note that the result is a HResult so I can use safecall; Do yourself a big favor and use that instead of stdcall.

 

So you need to specify that the string:

  • Is being returned from the method
  • Is the function return value (mostly for use by VB and such).

And the Delphi implementation will then look like this:

function TMyCOM.GetStringValue(out _Result: WideString): HRESULT; stdcall;
begin
  _Result := 'Hello world';
  Result := S_OK;
end;

or this:

function TMyCOM.GetStringValue: WideString; safecall;
begin
  Result := 'Hello world';
end;

 

Delphi is actually excellent for writing COM servers and clients. You just need to learn the basics - and if you will be doing a lot of COM (or just a lot of Windows development), learn the low level stuff too.

Share this post


Link to post
Guest
27 minutes ago, Anders Melander said:

safecall is a Delphi concept

Oh really. Right. Thanks for reminding me. This is a Delphi forum.

Returning WideString with safecall should work, IIRC. In Delphi code. Oh, and compiled with Delphi.

Thanks for the lesson!

Share this post


Link to post
1 minute ago, Ondrej Kelle said:

Oh really. Right. Thanks for reminding me. This is a Delphi forum.

Um... You asked "Shouldn't the calling convention for an automation object be safecall" which to me reads as "the calling convention for an automation object must be safecall".

Or was there another reason you mentioned safecall...?

Share this post


Link to post

If you want this is the global project (DLL_COM + APP) with WIDESTRING result use .... It's only a basic skeleton of course.

 

Bye

 

Demo COM.zip

Edited by DelphiUdIT
  • Thanks 1

Share this post


Link to post

Another thing about  COM objects ... look at thread mission (ciMultiIstance, tmApartment) and how they work.

 

Also be very carefull to use global variable for working COM. I suggest you don't use anyglobal variable (except const).

 

 

Edited by DelphiUdIT

Share this post


Link to post
9 minutes ago, DelphiUdIT said:

this is the global project (DLL_COM + APP) with WIDESTRING result use

Not that it matter much but it isn't necessary to use a dispatch property to return the value. Apart from that I agree with your implementation.

  • Thanks 1

Share this post


Link to post
41 minutes ago, DelphiUdIT said:

If you want this is the global project (DLL_COM + APP) with WIDESTRING result use .... It's only a basic skeleton of course.

 

Bye

 

Demo COM.zip

 

That is wonderful! It really works! Thanks a lot.

 

Share this post


Link to post
6 hours ago, kihor said:

That is wonderful! It really works! Thanks a lot. 

 

If you want, you can try this.

 

Is the same COM but with new application: you don't need to register the COM :classic_smile:

 

Try to unregsiter the old one.

 

After that build the projetc group (it is setting for WIn64 but you can change for WIn32) and run ... all is working without register the COM thanks to SxS technology (SideBySide).

The EXE and DLL will be put in Bin32 and Bin64 (and there are already the manifests).

 

There is a ReadMe and some Manifests (they are all equal excpet for the manifest that is included in the Project1 options).

 

Bye

Demo COM SxS.zip

Edited by DelphiUdIT
  • Like 1

Share this post


Link to post

If you are using the type library editor to create your RIDL - delphi should be mapping those methods as safecall. Check your options

 

SafecallOptions.thumb.png.d20a0d01dd59c05429e1200058a07ab7.png

 

Edit : the default is only dual interfaces - this is something I change when ever I install a new version of delphi. 

 

Edited by Vincent Parrett
additional info
  • Like 1

Share this post


Link to post
9 hours ago, DelphiUdIT said:

 

If you want, you can try this.

 

Is the same COM but with new application: you don't need to register the COM :classic_smile:

 

Try to unregsiter the old one.

 

After that build the projetc group (it is setting for WIn64 but you can change for WIn32) and run ... all is working without register the COM thanks to SxS technology (SideBySide).

The EXE and DLL will be put in Bin32 and Bin64 (and there are already the manifests).

 

There is a ReadMe and some Manifests (they are all equal excpet for the manifest that is included in the Project1 options).

 

Bye

Demo COM SxS.zip

 

Thanks a lot! I will try this.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×