2

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.
tomi chen
  • 23
  • 4
  • I don't see where you're exporting the Delphi method. Check this link it gives some insight on what you might want to try. https://stackoverflow.com/a/38219260/3516555 – fourwhey Oct 24 '18 at 02:48
  • You have to show function description in DLL (especially calling convention) and specify Delphi version (is it Unicode?) – MBo Oct 24 '18 at 02:50
  • thanks for your reply, I can not tell you the function description in DLL right now ( I will post later) and the version of Delphi was 7. But the provider of DLL give me a brief function description: – tomi chen Oct 24 '18 at 02:56
  • (using C): Char *NIRSAPretreatInfor (char *ModelFileName); input parameters: char ModelFileName[600]; /* model file name,Extension name*.mdl or *.mdr */ return value:array char str [600],description content; – tomi chen Oct 24 '18 at 03:03
  • You can also show Delphi code to import function if possible. And include information about Delphi 7 in question body - it is important/ – MBo Oct 24 '18 at 03:10
  • Thanks for your advices and I have updated some necessary information in question body! – tomi chen Oct 24 '18 at 03:41
  • `stdcall` is defined in both cases - it's OK. But char size is 1 byte in Delphi 7 and perhaps 2 bytes in c# function with `CharSet = CharSet.Auto` specification – MBo Oct 24 '18 at 04:04
  • You are right, char size is 2 byte in C#, so what I need is just to change the size of array ? I will try later. – tomi chen Oct 24 '18 at 04:48
  • Probably you need `CharSet.Ansi`, but this simple replace won't solve all problems. – MBo Oct 24 '18 at 04:52
  • I have tried this method but failed again, is there something I can do to fix this problem ? – tomi chen Oct 24 '18 at 06:09
  • Now Error message: The PInvoke function “NIRSAPre!NIRSAPre.Form1::NIRSAPretreatInfor” causes the stack asymmetry. – tomi chen Oct 24 '18 at 06:22
  • There is a lot of questions concerning of C#-Delphi dll interaction in Delphi section of SO. Perhaps some of them deal with PAnsiChar. I have not enough experience with c# data types - for example, am not sure if using `ref string fileName` right. – MBo Oct 24 '18 at 06:22
  • `stack asymmetry` - calling convention and parameter list/type mismatch. `read or write protected memory` - erroneous interpretation of pointer to data or string body size mismatch – MBo Oct 24 '18 at 06:24
  • 1
    The character set isn't the main issue. I'll write an answer to hopefully explain. – David Heffernan Oct 24 '18 at 06:30
  • Using `ref string fileName` or `string fileName` will not change anything. So what I need to do ? This make me crazy ! – tomi chen Oct 24 '18 at 06:34
  • The function **returns** a 600 (Ansi)Char buffer. That is probably not what C# can swallow. See my article: [DLL dos and don'ts](http://rvelthuis.de/articles/articles-dlls.html#structs). That is about structs, but the same is true for static arrays. – Rudy Velthuis Oct 24 '18 at 07:42
  • I see that David comes to the same conclusion. If you have any influence on the author(s) of the DLL, then tell them that using a fixed size array, especially **returning one from a DLL function**, is never a good idea. They should rather follow the style of Windows APIs and perhaps read my article. – Rudy Velthuis Oct 24 '18 at 08:05

1 Answers1

4

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);
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • That is indeed a good solution to get around the inadequate design of the DLL. – Rudy Velthuis Oct 24 '18 at 08:06
  • Thanks for your reply. Currently, the program can run, but the coming data was empty, which means each size of `outputBytes` was (byte)0, I do not know what makes this condition. I just replaced the `foo` with the file path (`fileName `). – tomi chen Oct 24 '18 at 08:43
  • The code in my answer works. I have tested it. You can test it yourself by making a test DLL with a single function. Prove to yourself, as I did, that the data is passed correctly in both directions. – David Heffernan Oct 24 '18 at 09:19
  • Sorry to trouble you. I did not test it by using another DLL, however, I found a strange phenomenon, the original input parameters `byte[] ModelFileName` contained some coming back data correctly (not all), I don't know what reasons make this ? I think something about encoding may result it ? – tomi chen Oct 25 '18 at 02:01
  • Only the first line content came back and next line content should be Chinese description(unintelligible text didn't display). On the other hand, if the delphi dll accept array (like double[][]), can I just use C# double[,] as variable to call this function (and other types are the same, like Integer corresponding to int)? Thank you for your time and work ! – tomi chen Oct 25 '18 at 03:29
  • I think I answered what was asked in the question. – David Heffernan Oct 25 '18 at 03:39
  • I am sorry, but would you please tell me in details ? the variable `outputBytes` was empty, and the input parameters contained combing back data ? and when I use other function in DLL File, outputBytes came some data (not all) ? what's more, how should I do to fix unintelligible text (I have tried but failed) ! – tomi chen Oct 25 '18 at 04:02
  • As I said I tested this code. I know. It works. As would you if you tested with a simple test dll as previously recommended. You did not follow my advice. I can't help you debug your code and this dll, that's not how this site works, not least because you are asking about details that aren't present in the question. Also, it's not about you. This answer is of benefit to any future visitor facing the same issue. – David Heffernan Oct 25 '18 at 04:14