Reputation: 23
Recently, I need to use a Delphi DLL in my C# project and I have search some answers, however all else fails. The name of DLL was modelDLL.dll, which need another DLL file (I have already put this two files in the debug folder)
Delphi code
type
TCharStr=array[0..599] of char;
Using Delphi to calling DLL works fine (the code is following), however,I don't know the specific comments in DLL File. The relative code of Delphi is as follow:
procedure TMainDLLForm.PedBitBtnClick(Sender: TObject);
var
fileName:TCharStr;
begin
OpenDataFileDlg.InitialDir:= GetCurrentDir;
OpenDataFileDlg.Title:='load model file';
OpenDataFileDlg.Filter := 'model_A[*.mdl]|*.mdl|model_T[*.mdr]|*.mdr';
if OpenDataFileDlg.Execute then
begin
StrPCopy(FileName,OpenDataFileDlg.FileName);
tmpD:=NIRSAModelForPred(graphyData,dataLength,FileName,targetName);
end;
if compareText(fileExt,'.MDR')=0 then
begin
memo1.Lines.Add('model_T: '+ExtractFileName(FileName));
memo1.Lines.Add(Format('Result: %10s:%0.0f',[targetName,tmpD]));
end;
memo1.Lines.Add('--------------');
memo1.Lines.Add(trim(NIRSAPretreatInfor(FileName)));// calling this function
memo1.Lines.Add('--------------');
memo1.Lines.Add(trim(NIRSAModelInfor(FileName)));
end;
My C# code was following, which hinted "Attempted to read or write protected memory. This is often an indication that other memory is corrupt." error.
[MarshalAs(UnmanagedType.LPStr, SizeConst = 600)]
public string fileName;
[DllImport(@"modelDLL.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.LPStr, SizeConst = 600)]
public static extern string NIRSAPretreatInfor(ref string fileName);
private void preCalcButton_Click(object sender, EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Multiselect = false;
if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
fileName = dialog.FileName;
string result = NIRSAPretreatInfor(ref fileName);
modelInfoTextBox.Text = result;
}
}
So, can anyone give me some advices? your reply will be appreciated!
PS: Delphi version: 7.0
import DLL code:
implementation
function
NIRSAModelForPred(Data:TGraphyData;dataLength:integer;ModelFileName:TCharStr;var targetName:TCharStr):double;stdcall;external 'modelDLL.dll';
function NIRSAModelInfor(ModelFileName:TCharStr):TCharStr;stdCall;external 'modelDLL.dll';
function NIRSAPretreatInfor(ModelFileName:TCharStr):TCharStr;stdCall;external 'modelDLL.dll';
Now I have change the CharSet = CharSet.Auto
to CharSet = CharSet.Ansi
and the error message appeared again.
The call to the PInvoke "NIRSAPre!NIRSAPre.Form1::NIRSAPretreatInfor" function causes the stack to be asymmetric.
Upvotes: 2
Views: 647
Reputation: 613541
The most significant problem (there are multiple problems) is that the Delphi code works with fixed length character arrays, which are not easy to marshal. The C# marshaler has no type that exactly matches these things. The issue is that a Delphi fixed length character array is not null terminated if it is the full length of the array.
Another problem is the character set. Delphi 7 char
is an 8 bit ANSI type. You marshal as CharSet.Auto
which is 16 bit UTF-16 on platforms other than Windows 9x. I'm confident you don't run on Windows 9x.
A final problem relates to the ABI for large types used as return values. The Delphi ABI implements such things as an extra (hidden) var
parameter. So
function NIRSAPretreatInfor(ModelFileName: TCharStr): TCharStr;
stdCall; external 'modelDLL.dll';
is actually implemented as:
procedure NIRSAPretreatInfor(ModelFileName: TCharStr; var ReturnValue: TCharStr);
stdCall; external 'modelDLL.dll';
In order to marshal this correctly you will need to handle the strings manually. Start with some helper methods:
public const int DelphiTCharStrLength = 600;
public static byte[] NewDelphiTCharStr()
{
return new byte[DelphiTCharStrLength];
}
public static byte[] ToDelphiTCharStr(string value)
{
byte[] result = NewDelphiTCharStr();
byte[] bytes = Encoding.Default.GetBytes(value + '\0');
Buffer.BlockCopy(bytes, 0, result, 0, Math.Min(bytes.Length, DelphiTCharStrLength));
return result;
}
public static string FromDelphiTCharStr(byte[] value)
{
int len = Array.IndexOf(value, (byte)0);
if (len == -1)
len = DelphiTCharStrLength;
return Encoding.Default.GetString(value, 0, len);
}
These deal with the fact that the fixed length Delphi character arrays need not be null-terminated.
Once this is in place the p/invoke declaration is like so:
[DllImport(@"modelDLL.dll", CallingConvention = CallingConvention.StdCall)]
public extern static void NIRSAPretreatInfor(byte[] ModelFileName, byte[] ReturnValue);
Call it like this:
byte[] outputBytes = NewDelphiTCharStr();
NIRSAPretreatInfor(ToDelphiTCharStr("foo"), outputBytes);
string output = FromDelphiTCharStr(outputBytes);
Upvotes: 4