pistacer
asked on
DLL-s, problem
I hope, somebody has a experience with this...
When i create a dll, in wich i export the function, wich returns a string (later i changed it to Pchar), sometimes it returns a value, in wich the last char is missing. I found not the reason of this "phenomena".
Needs it any spetial declaration? Spetial calling convention?
When i create a dll, in wich i export the function, wich returns a string (later i changed it to Pchar), sometimes it returns a value, in wich the last char is missing. I found not the reason of this "phenomena".
Needs it any spetial declaration? Spetial calling convention?
You can try shortstring, insted of string inside of your DLL
ASKER
well, but i need to pass from dll the string, thaat is longer than 255 chars ...
Can you show us a sample of the code?
Cheers,
Raymond.
Cheers,
Raymond.
ASKER
Well, it is a dll for novell connecting, Novell ndk, but i asked my neighbour and he has observed that on his dll-s too.
========================== ========== ====
Here is the project, wich calls the dll:
program t_n_fcs;
uses
Forms,
test_n_fcs in 'test_n_fcs.pas' {Form1},
nower in 'nower.pas';
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TFo rm1, Form1);
Application.Run;
end.
-------------------------- ---------
unit nower;
interface
function R_GetGroupMembers(GroupNam e:Pchar):P char; stdcall;
stdcall; external 'novll.dll';
function R_GetGroupMembership(UserN ame:Pchar) :Pchar; stdcall;
stdcall; external 'novll.dll';
procedure R_AddUserToGroup(UserName, GroupName: Pchar); stdcall;
stdcall; external 'novll.dll';
function R_GetNameOfContext:Pchar; stdcall;
stdcall; external 'novll.dll';
procedure R_SetContext_s(conts:Pchar ); stdcall;
stdcall; external 'novll.dll';
function R_GetActualObjs(Objtype:Pc har):Pchar ; stdcall;
stdcall; external 'novll.dll';
function R_AbreviateName(Obj:Pchar) :Pchar; stdcall;
stdcall; external 'novll.dll';
function R_CanonizeName(Obj:Pchar): Pchar; stdcall;
stdcall; external 'novll.dll';
function R_FindcontextOf(objname,Ob jtype:Pcha r):Pchar; stdcall;
stdcall; external 'novll.dll';
implementation
end.
-------------------------- ---------
unit test_n_fcs;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
Edit2: TEdit;
Button2: TButton;
Label1: TLabel;
Button3: TButton;
Button4: TButton;
Edit3: TEdit;
Edit4: TEdit;
Button5: TButton;
Button6: TButton;
Edit5: TEdit;
Button7: TButton;
Button8: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
procedure Button7Click(Sender: TObject);
procedure Button8Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
uses
nower;
procedure TForm1.Button1Click(Sender : TObject);
begin
edit1.Text:= R_GetGroupMembers(pchar(up percase(ed it1.text)) );
end;
procedure TForm1.Button2Click(Sender : TObject);
begin
edit2.Text:= R_GetGroupMembership(pchar (uppercase (edit2.tex t)));
end;
procedure TForm1.Button3Click(Sender : TObject);
begin
label1.Caption:= R_GetNameOfContext;
end;
procedure TForm1.Button4Click(Sender : TObject);
begin
R_SetContext_s(pchar(upper case(edit3 .text)));
end;
procedure TForm1.Button5Click(Sender : TObject);
begin
edit4.text:= R_GetActualObjs(pchar('Gro up'));
end;
procedure TForm1.Button6Click(Sender : TObject);
begin
edit4.text:= R_GetActualObjs(pchar('Use r'));
end;
procedure TForm1.Button7Click(Sender : TObject);
begin
// edit5.text:= R_FindcontextOf(edit5.text ,'User');
edit5.text:= R_FindcontextOf(pchar(uppe rcase('cer na610')),p char('User '));
end;
procedure TForm1.Button8Click(Sender : TObject);
begin
// edit5.text:= R_FindcontextOf(edit5.text ,'Group');
edit5.text:= R_FindcontextOf(pchar(uppe rcase('ref pc')),pcha r('Group') );
end;
end.
-------------------------- ---------
object Form1: TForm1
Left = 266
Top = 107
Width = 696
Height = 480
Caption = 'Form1'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 96
Top = 144
Width = 36
Height = 13
Caption = 'Context'
end
object Edit1: TEdit
Left = 8
Top = 8
Width = 673
Height = 21
TabOrder = 0
Text = 'Edit1'
end
object Button1: TButton
Left = 8
Top = 32
Width = 161
Height = 25
Caption = 'Members of group'
TabOrder = 1
OnClick = Button1Click
end
object Edit2: TEdit
Left = 8
Top = 80
Width = 673
Height = 21
TabOrder = 2
Text = 'Edit2'
end
object Button2: TButton
Left = 8
Top = 104
Width = 169
Height = 25
Caption = 'Is member of'
TabOrder = 3
OnClick = Button2Click
end
object Button3: TButton
Left = 8
Top = 144
Width = 75
Height = 25
Caption = 'Context'
TabOrder = 4
OnClick = Button3Click
end
object Button4: TButton
Left = 8
Top = 208
Width = 75
Height = 25
Caption = 'Set context'
TabOrder = 5
OnClick = Button4Click
end
object Edit3: TEdit
Left = 8
Top = 176
Width = 553
Height = 21
TabOrder = 6
Text = 'Edit3'
end
object Edit4: TEdit
Left = 8
Top = 240
Width = 673
Height = 21
TabOrder = 7
Text = 'Edit4'
end
object Button5: TButton
Left = 40
Top = 272
Width = 75
Height = 25
Caption = 'Groups'
TabOrder = 8
OnClick = Button5Click
end
object Button6: TButton
Left = 152
Top = 272
Width = 75
Height = 25
Caption = 'Users'
TabOrder = 9
OnClick = Button6Click
end
object Edit5: TEdit
Left = 16
Top = 320
Width = 345
Height = 21
TabOrder = 10
Text = 'Edit5'
end
object Button7: TButton
Left = 32
Top = 352
Width = 75
Height = 25
Caption = 'Find user'
TabOrder = 11
OnClick = Button7Click
end
object Button8: TButton
Left = 144
Top = 352
Width = 75
Height = 25
Caption = 'Find group'
TabOrder = 12
OnClick = Button8Click
end
end
========================== ========== =
and here the main paart of dll:
library novll;
uses
redukcia in 'redukcia.pas',
cal_win3 in '\\PSS_AVS2\DATA\AVS\PROJE KTY\DELPHI \sc_units\ Cal_win3.p as',
net_win3 in '\\PSS_AVS2\DATA\AVS\PROJE KTY\DELPHI \sc_units\ Net_win3.p as',
NWHelper in 'D:\novellibdel\samples\de lphilib_sa mple\AddGr oup\NWHelp er.pas',
NWHelper2 in 'D:\novellibdel\samples\de lphilib_sa mple\AddGr oup\NWHelp er2.pas';
exports
R_GetGroupMembers index 1 name 'R_GetGroupMembers',
R_GetGroupMembership index 2 name 'R_GetGroupMembership',
R_AddUserToGroup index 3 name 'R_AddUserToGroup',
R_GetNameOfContext index 4 name 'R_GetNameOfContext',
R_SetContext_s index 5 name 'R_SetContext_s',
R_GetActualObjs index 6 name 'R_GetActualObjs',
R_AbreviateName index 7 name 'R_AbreviateName',
R_CanonizeName index 8 name 'R_CanonizeName',
R_FindcontextOf index 9 name 'R_FindcontextOf';
end.
-------------------------- ---------- -
unit redukcia;
interface
uses
SysUtils,
Windows,
Classes,
cal_win3,
net_win3;
function R_GetGroupMembers(GroupNam e:Pchar):P char; stdcall;
function R_GetGroupMembership(UserN ame:Pchar) :Pchar; stdcall;
procedure R_AddUserToGroup(UserName, GroupName: Pchar); stdcall;
function R_GetNameOfContext:Pchar; stdcall;
procedure R_SetContext_s(conts:Pchar ); stdcall;
function R_GetActualObjs(Objtype:Pc har):Pchar ; stdcall;
function R_AbreviateName(Obj:Pchar) :Pchar; stdcall;
function R_CanonizeName(Obj:Pchar): Pchar; stdcall;
function R_FindcontextOf(objname,Ob jtype:Pcha r):Pchar; stdcall;
implementation
uses
nwhelper,
nwhelper2;
var
hc:NWDSContextHandle;
function StrlistToPcahr(sin:tstring list):pcha r;
var
i:integer;
resq:string;
begin
resq:= '';
for i:= 0 to (sin.count - 1)
do resq:= resq + '@' + sin[i];
result:= pchar(resq);
end;
function AbreviateStrlist(sin:tstri nglist):ts tringlist;
var
i:integer;
begin
result:= tstringlist.Create;
result.Clear;
for i:= 0 to (sin.count - 1)
do result.Add(Abreviatename(h c,sin[i])) ;
end;
function CanonizeStrlist(sin:tstrin glist):tst ringlist;
var
i:integer;
begin
result:= tstringlist.Create;
result.Clear;
for i:= 0 to (sin.count - 1)
do result.Add(Canonizename(hc ,sin[i]));
end;
function R_AbreviateName(Obj:Pchar) :pchar;
begin
result:= pchar(AbreviateName(hc,obj ));
end;
function R_CanonizeName(Obj:Pchar): pchar;
begin
result:= pchar(CanonizeName(hc,obj) );
end;
procedure R_AddUserToGroup(UserName, GroupName: Pchar);
begin
AddUserToGroup(hc,UserName ,GroupName );
end;
procedure R_SetContext_s(conts:Pchar );
begin
SetContext_s(hc,conts);
end;
function R_GetNameOfContext:pchar;
begin
result:= pchar(GetNameOfContext(hc) );
end;
function R_GetGroupMembers(GroupNam e:Pchar):P char;
var
ress,resu:tstringlist;
begin
try
ress:= GetGroupMembers(hc,GroupNa me);
resu:= AbreviateStrlist(ress);
result:= StrlistToPcahr(resu);
except
result:= pchar('ERROR - bad groupname');
end;
ress.Free;
resu.Free;
end;
function R_GetGroupMembership(UserN ame:Pchar) :pchar;
var
ress,resu:tstringlist;
begin
try
ress:= GetGroupMembership(hc,User Name);
resu:= AbreviateStrlist(ress);
result:= StrlistToPcahr(resu);
except
result:= pchar('ERROR - bad groupname');
end;
ress.Free;
resu.Free;
end;
function R_GetActualObjs(Objtype:Pc har):Pchar ;
var
ress,resu:tstringlist;
begin
try
ress:= GetObjectList(hC, objtype);
resu:= AbreviateStrlist(ress);
result:= StrlistToPcahr(resu);
except
result:= pchar('ERROR - bad groupname');
end;
ress.Free;
resu.Free;
end;
function ScanContextFor(hcontex:NWD SContextHa ndle;searc hed,typeob ,contname: string):st ring;
var
strle,strlf,strlc:tstringl ist;
i:integer;
curcon:string;
begin
result:= '';
SetContext_s(hcontex,contn ame);
strle:= GetObjectList(hcontex, typeob);
strlf:= AbreviateStrlist(strle);
if strlf.IndexOf(searched) = -1
then begin
strlc:= GetContextsStrList(hContex ,false);
for i:= 0 to (strlc.Count - 1)
do begin
if result = ''
then begin
curcon:= GetNameOfContext(hcontex);
if curcon = '[Root]'
then curcon:= strlc[i]
else curcon:= strlc[i] + '.' + curcon;
result:= ScanContextFor(hcontex,sea rched,type Ob,curcon) ;
SetContext_s_upward(hconte x);
end;
end;
end
else result:= contname;
strle.Free;
strlf.Free;
end;
function R_FindcontextOf(objname,Ob jtype:Pcha r):Pchar;
var
resq,objn,objt:string;
begin
objn:= R_AbreviateName(objname);
objt:= objtype;
resq:= ScanContextFor(hc,objn,Obj t,'[Root]' );
if resq = ''
then resq:= 'ERROR - ' + objtype + ' not found'
else resq:= objname + '.' + resq;
result:= pchar(resq);
end;
initialization
hc:= GetContextHandle;
finalization
end.
-------------------------- ----------
..... the other units are tooo big on a spetial wish i can put it here, but the problem can we observe here.
In function R_FindcontextOf in dll, all is running ok. But when i call it with parameter Objtype = 'Group', as i debugged it, when i come to last line "result:= pchar(resq);" and the value of resq is for example 'GROUP.CONTEXT.ENDOFCONTEX T',
on the form i see 'GROUP.CONTEXT.ENDOFCONTEX '.
==========================
Here is the project, wich calls the dll:
program t_n_fcs;
uses
Forms,
test_n_fcs in 'test_n_fcs.pas' {Form1},
nower in 'nower.pas';
{$R *.RES}
begin
Application.Initialize;
Application.CreateForm(TFo
Application.Run;
end.
--------------------------
unit nower;
interface
function R_GetGroupMembers(GroupNam
stdcall; external 'novll.dll';
function R_GetGroupMembership(UserN
stdcall; external 'novll.dll';
procedure R_AddUserToGroup(UserName,
stdcall; external 'novll.dll';
function R_GetNameOfContext:Pchar; stdcall;
stdcall; external 'novll.dll';
procedure R_SetContext_s(conts:Pchar
stdcall; external 'novll.dll';
function R_GetActualObjs(Objtype:Pc
stdcall; external 'novll.dll';
function R_AbreviateName(Obj:Pchar)
stdcall; external 'novll.dll';
function R_CanonizeName(Obj:Pchar):
stdcall; external 'novll.dll';
function R_FindcontextOf(objname,Ob
stdcall; external 'novll.dll';
implementation
end.
--------------------------
unit test_n_fcs;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
Edit2: TEdit;
Button2: TButton;
Label1: TLabel;
Button3: TButton;
Button4: TButton;
Edit3: TEdit;
Edit4: TEdit;
Button5: TButton;
Button6: TButton;
Edit5: TEdit;
Button7: TButton;
Button8: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
procedure Button7Click(Sender: TObject);
procedure Button8Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
uses
nower;
procedure TForm1.Button1Click(Sender
begin
edit1.Text:= R_GetGroupMembers(pchar(up
end;
procedure TForm1.Button2Click(Sender
begin
edit2.Text:= R_GetGroupMembership(pchar
end;
procedure TForm1.Button3Click(Sender
begin
label1.Caption:= R_GetNameOfContext;
end;
procedure TForm1.Button4Click(Sender
begin
R_SetContext_s(pchar(upper
end;
procedure TForm1.Button5Click(Sender
begin
edit4.text:= R_GetActualObjs(pchar('Gro
end;
procedure TForm1.Button6Click(Sender
begin
edit4.text:= R_GetActualObjs(pchar('Use
end;
procedure TForm1.Button7Click(Sender
begin
// edit5.text:= R_FindcontextOf(edit5.text
edit5.text:= R_FindcontextOf(pchar(uppe
end;
procedure TForm1.Button8Click(Sender
begin
// edit5.text:= R_FindcontextOf(edit5.text
edit5.text:= R_FindcontextOf(pchar(uppe
end;
end.
--------------------------
object Form1: TForm1
Left = 266
Top = 107
Width = 696
Height = 480
Caption = 'Form1'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 96
Top = 144
Width = 36
Height = 13
Caption = 'Context'
end
object Edit1: TEdit
Left = 8
Top = 8
Width = 673
Height = 21
TabOrder = 0
Text = 'Edit1'
end
object Button1: TButton
Left = 8
Top = 32
Width = 161
Height = 25
Caption = 'Members of group'
TabOrder = 1
OnClick = Button1Click
end
object Edit2: TEdit
Left = 8
Top = 80
Width = 673
Height = 21
TabOrder = 2
Text = 'Edit2'
end
object Button2: TButton
Left = 8
Top = 104
Width = 169
Height = 25
Caption = 'Is member of'
TabOrder = 3
OnClick = Button2Click
end
object Button3: TButton
Left = 8
Top = 144
Width = 75
Height = 25
Caption = 'Context'
TabOrder = 4
OnClick = Button3Click
end
object Button4: TButton
Left = 8
Top = 208
Width = 75
Height = 25
Caption = 'Set context'
TabOrder = 5
OnClick = Button4Click
end
object Edit3: TEdit
Left = 8
Top = 176
Width = 553
Height = 21
TabOrder = 6
Text = 'Edit3'
end
object Edit4: TEdit
Left = 8
Top = 240
Width = 673
Height = 21
TabOrder = 7
Text = 'Edit4'
end
object Button5: TButton
Left = 40
Top = 272
Width = 75
Height = 25
Caption = 'Groups'
TabOrder = 8
OnClick = Button5Click
end
object Button6: TButton
Left = 152
Top = 272
Width = 75
Height = 25
Caption = 'Users'
TabOrder = 9
OnClick = Button6Click
end
object Edit5: TEdit
Left = 16
Top = 320
Width = 345
Height = 21
TabOrder = 10
Text = 'Edit5'
end
object Button7: TButton
Left = 32
Top = 352
Width = 75
Height = 25
Caption = 'Find user'
TabOrder = 11
OnClick = Button7Click
end
object Button8: TButton
Left = 144
Top = 352
Width = 75
Height = 25
Caption = 'Find group'
TabOrder = 12
OnClick = Button8Click
end
end
==========================
and here the main paart of dll:
library novll;
uses
redukcia in 'redukcia.pas',
cal_win3 in '\\PSS_AVS2\DATA\AVS\PROJE
net_win3 in '\\PSS_AVS2\DATA\AVS\PROJE
NWHelper in 'D:\novellibdel\samples\de
NWHelper2 in 'D:\novellibdel\samples\de
exports
R_GetGroupMembers index 1 name 'R_GetGroupMembers',
R_GetGroupMembership index 2 name 'R_GetGroupMembership',
R_AddUserToGroup index 3 name 'R_AddUserToGroup',
R_GetNameOfContext index 4 name 'R_GetNameOfContext',
R_SetContext_s index 5 name 'R_SetContext_s',
R_GetActualObjs index 6 name 'R_GetActualObjs',
R_AbreviateName index 7 name 'R_AbreviateName',
R_CanonizeName index 8 name 'R_CanonizeName',
R_FindcontextOf index 9 name 'R_FindcontextOf';
end.
--------------------------
unit redukcia;
interface
uses
SysUtils,
Windows,
Classes,
cal_win3,
net_win3;
function R_GetGroupMembers(GroupNam
function R_GetGroupMembership(UserN
procedure R_AddUserToGroup(UserName,
function R_GetNameOfContext:Pchar; stdcall;
procedure R_SetContext_s(conts:Pchar
function R_GetActualObjs(Objtype:Pc
function R_AbreviateName(Obj:Pchar)
function R_CanonizeName(Obj:Pchar):
function R_FindcontextOf(objname,Ob
implementation
uses
nwhelper,
nwhelper2;
var
hc:NWDSContextHandle;
function StrlistToPcahr(sin:tstring
var
i:integer;
resq:string;
begin
resq:= '';
for i:= 0 to (sin.count - 1)
do resq:= resq + '@' + sin[i];
result:= pchar(resq);
end;
function AbreviateStrlist(sin:tstri
var
i:integer;
begin
result:= tstringlist.Create;
result.Clear;
for i:= 0 to (sin.count - 1)
do result.Add(Abreviatename(h
end;
function CanonizeStrlist(sin:tstrin
var
i:integer;
begin
result:= tstringlist.Create;
result.Clear;
for i:= 0 to (sin.count - 1)
do result.Add(Canonizename(hc
end;
function R_AbreviateName(Obj:Pchar)
begin
result:= pchar(AbreviateName(hc,obj
end;
function R_CanonizeName(Obj:Pchar):
begin
result:= pchar(CanonizeName(hc,obj)
end;
procedure R_AddUserToGroup(UserName,
begin
AddUserToGroup(hc,UserName
end;
procedure R_SetContext_s(conts:Pchar
begin
SetContext_s(hc,conts);
end;
function R_GetNameOfContext:pchar;
begin
result:= pchar(GetNameOfContext(hc)
end;
function R_GetGroupMembers(GroupNam
var
ress,resu:tstringlist;
begin
try
ress:= GetGroupMembers(hc,GroupNa
resu:= AbreviateStrlist(ress);
result:= StrlistToPcahr(resu);
except
result:= pchar('ERROR - bad groupname');
end;
ress.Free;
resu.Free;
end;
function R_GetGroupMembership(UserN
var
ress,resu:tstringlist;
begin
try
ress:= GetGroupMembership(hc,User
resu:= AbreviateStrlist(ress);
result:= StrlistToPcahr(resu);
except
result:= pchar('ERROR - bad groupname');
end;
ress.Free;
resu.Free;
end;
function R_GetActualObjs(Objtype:Pc
var
ress,resu:tstringlist;
begin
try
ress:= GetObjectList(hC, objtype);
resu:= AbreviateStrlist(ress);
result:= StrlistToPcahr(resu);
except
result:= pchar('ERROR - bad groupname');
end;
ress.Free;
resu.Free;
end;
function ScanContextFor(hcontex:NWD
var
strle,strlf,strlc:tstringl
i:integer;
curcon:string;
begin
result:= '';
SetContext_s(hcontex,contn
strle:= GetObjectList(hcontex, typeob);
strlf:= AbreviateStrlist(strle);
if strlf.IndexOf(searched) = -1
then begin
strlc:= GetContextsStrList(hContex
for i:= 0 to (strlc.Count - 1)
do begin
if result = ''
then begin
curcon:= GetNameOfContext(hcontex);
if curcon = '[Root]'
then curcon:= strlc[i]
else curcon:= strlc[i] + '.' + curcon;
result:= ScanContextFor(hcontex,sea
SetContext_s_upward(hconte
end;
end;
end
else result:= contname;
strle.Free;
strlf.Free;
end;
function R_FindcontextOf(objname,Ob
var
resq,objn,objt:string;
begin
objn:= R_AbreviateName(objname);
objt:= objtype;
resq:= ScanContextFor(hc,objn,Obj
if resq = ''
then resq:= 'ERROR - ' + objtype + ' not found'
else resq:= objname + '.' + resq;
result:= pchar(resq);
end;
initialization
hc:= GetContextHandle;
finalization
end.
--------------------------
..... the other units are tooo big on a spetial wish i can put it here, but the problem can we observe here.
In function R_FindcontextOf in dll, all is running ok. But when i call it with parameter Objtype = 'Group', as i debugged it, when i come to last line "result:= pchar(resq);" and the value of resq is for example 'GROUP.CONTEXT.ENDOFCONTEX
on the form i see 'GROUP.CONTEXT.ENDOFCONTEX
ASKER
.. or better i should say, the last char is not ommitted, but changed to an unshowable char ($18,$14, or so ...) and the editbox shows a space insteed of it. Then, when i take the input of that edit box after that i changed it, the unshowed char is attached to my input and thereafter the result is ERROR.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.