2

I have only DLL and text file of API description from customer (see below). I don't have any more details regarding the DLL. I don't know the Delphi version etc.

I tried to use both of API functions but was not successful. Generally first two parameters of string[255] (PatientID and AccessionNo) are important. Any my attempts to pass strings to that DLL don't provide proper result. I see random garbage values or part of strings in application GUI. I looked at all related question on this site and searched in the internet but didn't find anything which helped me.

  1. OpenStudy function - I tried different settings for CharSet = CharSet.Ansi and Auto, for MarshalAs all suitable values (see below). I see the garbage random value in the managed application GUI text fields.

    [DllImport("Lib\\RISInterface.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "Open_Study", ExactSpelling = false)]
    
    static extern internal int OpenStudy(
    
    // try to use here and for all string fields [MarshalAs(UnmanagedType.AnsiBStr)] , LPStr, LPTStr, LPWStr, BStr, TBStr, HString 
    string PatientID,
    string AccessionNo,
    bool CloseCurrentStudy,
    bool AddToWindow,
    int SeriesRows,
    int SeriesCols,
    int PresentationMode,
    bool AutoTile,
    bool AutoLoad,
    bool RemoteExam);
    
  2. OpenStudy1 function - I fill the structure and call function. I see PatientID and AccessionNo as normal string but PatientID misses the first letter and AccessionNo misses the first two letters. ( send: "qwerty", "12345" and see: "werty", "345")

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct TIQStudyAutomation
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
        public string PatientID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
        public string AccessionNo;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
        public string StudyUID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
        public string SeriesUID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
        public string InstanceUID;
        public bool CloseCurrentStudy;
        public bool AddToWindow;
        public int SeriesRows;
        public int SeriesCols;
        public int PresentationMode;
        public bool AutoTile;
        public bool AutoLoad;
        public bool RemoteExam;
        public bool LoadFromAllSources;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
        public string ArchiveName;
    }
    
    [DllImport("Lib\\RISInterface.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "Open_Study1", ExactSpelling = false)]
    
    static extern internal int OpenStudy1(TIQStudyAutomation automationInfo);
    

================= API description ==========================================

These function declarations are Delphi Code and all string references are Ansi strings.

function Open_Study(PatientID, AccessionNo: PAnsiChar; CloseCurrentStudy, AddToWindow: Boolean; 
    SeriesRows, SeriesCols, PresentationMode: Integer; 
    AutoTile, AutoLoad, RemoteExam: Boolean): Integer; stdcall;
Return
    Error Code.

Remarks
    The parameters will be packed into a TIQStudyAutomation record and passed to Open_Study1.

//--------------------------------------------------------------------------------------------------

function Open_Study1(AutomationInfo: TIQStudyAutomation): Integer; stdcall;
Return
    Error Code.

Parameters
    Takes a TIQStudyAutomation record.

//--------------------------------------------------------------------------------------------------


TIQStudyAutomation = record 

  PatientID, AccessionNo, StudyUID, SeriesUID, InstanceUID: STRING[255];

  CloseCurrentStudy, AddToWindow: BOOLEAN;

  SeriesRows, SeriesCols, PresentationMode: Integer;

  AutoTile, AutoLoad, RemoteExam, LoadFromAllSources : BOOLEAN; ArchiveName: STRING[255];

end;

Any help?

valger
  • 37
  • 5
  • 1
    Possible duplicate of [How to use Delphi Dll(with PChar type) in C#](http://stackoverflow.com/questions/5086645/how-to-use-delphi-dllwith-pchar-type-in-c-sharp) – Z.B. Jun 01 '16 at 09:13
  • @ZENsan There's way more here than that. `string[255]` takes some marshalling. – David Heffernan Jun 01 '16 at 09:26
  • David, Could you clarify what do you mean by "string[255] takes some marshalling"? – valger Jun 01 '16 at 10:04
  • Well, that type has size 256. The first byte is the length, 0 to 255. The remaining bytes are the ANSI characters that make up the string. I do not believe that the string is necessarily null terminated. So all your marshalling is wrong. I would do it as a `byte[]` marshalled as `ByValArray` of length 256. And I'd write helper methods to move between a C# string and the Delphi shortstring `string[255]`. – David Heffernan Jun 01 '16 at 10:09
  • Another problem is the Delphi `Boolean`. That is a 1 byte type. You need to marshal it as `UnmanagedType.U1` IIRC. I'm not really in a position to write an answer with all of this in detail since it would be a hugely long answer and I don't quite have time. It would feel like I was writing the entire code for you. Anyway, I hope that these two pointers help! – David Heffernan Jun 01 '16 at 10:11
  • Another point is that I don't see any reason for you translating `Open_Study1` and `TIQStudyAutomation`. You can simply call `Open_Study` can't you? Actually, I will make that an answer. Just wait. – David Heffernan Jun 01 '16 at 10:12

2 Answers2

1

There's no need to translate the record, which is quite tricky due to the use of Delphi shortstrings. Instead you can simply call Open_Study. The Delphi declaration is:

function Open_Study(
  PatientID: PAnsiChar; 
  AccessionNo: PAnsiChar; 
  CloseCurrentStudy: Boolean;
  AddToWindow: Boolean; 
  SeriesRows: Integer; 
  SeriesCols: Integer; 
  PresentationMode: Integer; 
  AutoTile: Boolean;
  AutoLoad: Boolean;
  RemoteExam: Boolean
): Integer; stdcall;

The only real wrinkle in translating this is that the Delphi Boolean type is a 1 byte type. But C# bool defaults to marshalling as a 4 byte type, to match the Win32 type BOOL.

So I would translate the function like so:

[DllImport("Lib\\RISInterface.dll", CallingConvention = CallingConvention.StdCall,
    CharSet = CharSet.Ansi, EntryPoint = "Open_Study", ExactSpelling = true)]
static extern internal int OpenStudy(
    string PatientID,
    string AccessionNo,
    [MarshalAs(UnmanagedType.U1)] bool CloseCurrentStudy,
    [MarshalAs(UnmanagedType.U1)] bool AddToWindow,
    int SeriesRows,
    int SeriesCols,
    int PresentationMode,
    [MarshalAs(UnmanagedType.U1)] bool AutoTile,
    [MarshalAs(UnmanagedType.U1)] bool AutoLoad,
    [MarshalAs(UnmanagedType.U1)] bool RemoteExam
);

As for string[255] that is somewhat tricky to deal with. That type has size 256. The first byte is the string length, and the remaining bytes are the payload. This payload is an array of ANSI encoded 8 bit characters. And this array need not be null-terminated.

You cannot marshal that automatically because the p/invoke framework does not support such a type. Personally I would declare translate this type to byte[]. I would marshal it with [MarshalAs(UnmanagedType.ByValArray, SizeConst=256)]. Then I would write helper functions to marshal to and from a C# string. These would involve calls to Encoding.GetString and Encoding.GetBytes, taking care to handle the string length appropriately.

It's certainly possible, but a little messy. Hence I suggest you avoid doing so if possible and call the helper function Open_Study.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • UnmanagedType.ByValArray is applicable only for structure field. BTW I followed the manual converting recommendation for string[255] structure fields and made it works. Thank you very much. – valger Jun 01 '16 at 15:32
  • *`UnmanagedType.ByValArray` is applicable only for structure field.* Correct. That's exactly where I am advocating its use. – David Heffernan Jun 01 '16 at 15:35
0

The final solution is following (structure declaration, function declaration and function to convert string to Delphi String[255] as a byte array):


public struct TIQStudyAutomation
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public byte[] PatientID;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public byte[] AccessionNo;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public byte[] StudyUID;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public byte[] SeriesUID;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public byte[] InstanceUID;
    [MarshalAs(UnmanagedType.U1)]
    public bool CloseCurrentStudy;
    [MarshalAs(UnmanagedType.U1)]
    public bool AddToWindow;
    public int SeriesRows;
    public int SeriesCols;
    public int PresentationMode;
    [MarshalAs(UnmanagedType.U1)]
    public bool AutoTile;
    [MarshalAs(UnmanagedType.U1)]
    public bool AutoLoad;
    [MarshalAs(UnmanagedType.U1)]
    public bool RemoteExam;
    [MarshalAs(UnmanagedType.U1)]
    public bool LoadFromAllSources;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public byte[] ArchiveName;
}
    [DllImport("Lib\\RISInterface.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, EntryPoint = "Open_Study1", ExactSpelling = false)]
    static extern internal int OpenStudy1(TIQStudyAutomation automationInfo);

    private byte[] ConvertToDelphiString255(string input)
    {
        var output = new byte[256];
        if(String.IsNullOrEmpty(input))
        {
            return output;
        }

        var ar = Encoding.ASCII.GetBytes(input);
        var length = ar.Length > 255 ? 255 : ar.Length;
        output[0] = (byte)length;
        for(int i=0; i<length; i++)
        {
            output[i+1] = ar[i];
        }
        return output;
    }
valger
  • 37
  • 5
  • You don't need to do this surely. Call the high level function. The encoding should be ANSI rather than ASCII. You can copy the byte array contents directly rather than looping. – David Heffernan Jun 01 '16 at 17:35
  • I replace the loop by copy function, thanks. But I don't understand how to use ANSI instead of ASCII. ("ANSI: There's no one fixed ANSI encoding - there are lots of them. Usually when people say "ANSI" they mean "the default locale/codepage for my system" which is obtained via Encoding.Default, and is often Windows-1252 but can be other locales." see - [link](http://stackoverflow.com/questions/700187/unicode-utf-ascii-ansi-format-differences)) – valger Jun 03 '16 at 06:17
  • Well that's just how it is. The Delphi type is ANSI encoded. Using the locale's default code page. – David Heffernan Jun 03 '16 at 06:25