Konwersja z Delphi do C# - problem z pamięcią chronioną.

0

Witam!

Problem dotyczy obsługi biblioteki karty wejść/wyjść - program napisany w c# zgłasza przy próbie zapisu/odczytu (wejść i/lub wyjść karty) następujący błąd: Nastąpiła próba odczytu lub zapisu pamięci chronionej. Często wskazuje to, że inna pamięć jest uszkodzona.
Producent karty dostarczył przykładowe programy w VC, VB, i Delphi. Ponieważ "konwersja" kodu źródłowego do C# nastąpiła właśnie z Delphi przedstawię dla porównana działający kod Delphi oraz C#.
Środowisko programistyczne:
Borland Turbo Delphi
SharpDevelop 2.2
VS2008

Kod programu napisanego w Delphi (istotne fragmenty):

{MODUŁ Driver}
Interface
type
 PT_DEVLIST = Record
    dwDeviceNum      : Longint;
    szDeviceName     : array [0..49] of char;
    nNumOfSubdevices : Smallint;
 End;

 PT_WritePortWord  = Record
    port     : Smallint;
    WordData : Smallint;
 End;

Function DRV_DeviceGetNumOfList(Var NumOfDevices : Smallint) : Longint; stdcall;
Function DRV_DeviceGetList(Var DeviceList : PT_DEVLIST;
             MaxEntries : Smallint; Var OutEntries : Smallint) : Longint; stdcall;
Function DRV_DeviceOpen(DeviceNum : Longint;
             Var DriverHandle : Longint) : Longint; stdcall;
Function DRV_WritePortWord(DriverHandle : Longint;
             Var lpWritePortWord : PT_WritePortWord) : Longint; stdcall;

implementation

Function DRV_DeviceGetNumOfList;    external 'adsapi32.dll';
Function DRV_DeviceGetList;         external 'adsapi32.dll';
Function DRV_DeviceOpen;            external 'adsapi32.dll';
Function DRV_WritePortWord;         external 'adsapi32.dll';


{ZMIENNE GLOBALNE}
type
    lpDevList = ^PT_DEVLIST;

const MaxEntries = 255;

var
    DeviceHandle            : Longint;
    DeviceList              : array [0..MaxEntries] of PT_DEVLIST;
    ErrCde                  : Longint;
    szErrMsg                : string[100];
    pszErrMsg               : Pchar = @szErrMsg;
    bRun                    : Boolean;
    gwPort                  : Smallint;
    dwDeviceNum             : Longint;
    lpWritePortWord         : PT_WritePortWord;


{GŁÓWNE OKNO PROGRAMU}

{procedura buduje listę wszystkich kart I/O producenta zainstalowanych w systemie}
procedure Tfrmstart.FormCreate(Sender: TObject);
var
  MaxEntries, OutEntries : Smallint;
  NumOfDevice            : Smallint;
  i, ii                  : Integer;
  tempStr                : String;
  testRes                : boolean;
begin
  gwPort := $300;
  bRun := False;

  { Here MaxEntries = OutEntries }
  ErrCde := DRV_DeviceGetNumOfList(MaxEntries);
  If (ErrCde <> 0) Then
  begin
       DRV_GetErrorMessage(ErrCde, pszErrMsg);
       Response := Application.MessageBox(pszErrMsg, 'Error!!', MB_OK);
       Exit;
  end;

  { Add type of PC Laboratory Card }
  ErrCde := DRV_DeviceGetList(DeviceList[0], MaxEntries, OutEntries);
  If (ErrCde <> 0) Then
  begin
       DRV_GetErrorMessage(ErrCde, pszErrMsg);
       Response := Application.MessageBox(pszErrMsg, 'Error!!', MB_OK);
       Exit;
  end;

  For i := 0 To (MaxEntries - 1) do
  begin
    tempStr := '';
    For ii := 0 To MaxDevNameLen do
        tempStr := tempStr + DeviceList[i].szDeviceName[ii];
    lstDevice.Items.Add(tempStr);
  end;

  labIOAddr.Enabled := False;
  txtIOAddr.Enabled := False;
  cmdRun.Enabled := False;
end;


{pobranie identyfikatora urządzenia i przejście do formatki sterującej}
procedure Tfrmstart.cmdRunClick(Sender: TObject);
var
  I, Code: Integer;
begin
  ErrCde := DRV_DeviceOpen(dwDeviceNum, DeviceHandle);
  If (ErrCde <> 0) Then
  begin
    DRV_GetErrorMessage(ErrCde, pszErrMsg);
    Response := Application.MessageBox(pszErrMsg, 'Error!!', MB_OK);
    Exit;
  end;
  { Get text from TEdit control }
  Val('$' + txtIOAddr.Text, gwPort, Code);
  FormRun.frmRun.Show;
end;

{dwDeviceNum jest ustawiane wybraniu właściwej karty - poniżej fragment kodu}
dwDeviceNum := DeviceList[lstDevice.ItemIndex].dwDeviceNum;

{OKNO STEROWANIA - FormRun}

{Zapis do karty - wysterowanie prekaźników na karcie}
procedure Tfrmrun.cmdWriteWordClick(Sender: TObject);
begin
     lpWritePortWord.port := gwPort;
     if (DoValue > $FFFF) then
          lpWritePortWord.WordData := -1  {-1 in memory is 65535}
     else
          lpWritePortWord.WordData := DoValue;

     ErrCde := DRV_WritePortWord(DeviceHandle, lpWritePortWord);
     If (ErrCde <> 0) Then
     begin
          DRV_GetErrorMessage(ErrCde, pszErrMsg);
          Response := Application.MessageBox(pszErrMsg, 'Error!!', MB_OK);
          Exit;
     end;
end;

Kod niedziałający w C#

		 public long DeviceHandle;
		 public class PT_DEVLIST
		 {
    		       public long dwDeviceNum;
    		       public char[] szDeviceName = new char[50];
    		       public short nNumOfSubdevices;
		 }
		
		
		 public class PT_WritePortWord
		 {
    		       public short port;
    		       public short WordData;
		 }
		
		 public int OutEntries;
		 public static PT_WritePortWord lpWritePortWord;
		 public PT_DEVLIST[] DeviceList = new PT_DEVLIST[100];

 		[DllImport("adsapi32.dll")] 
 		private	 static extern int DRV_WritePortWord(long DriverHandle, ref  lpWritePortWord lpWritePortWord);

 		[DllImport("adsapi32.dll")]
 		private static extern int DRV_DeviceGetNumOfList(ref int NumeOfDevices);
 		
 		private static extern int DRV_DeviceGetList( ref  PT_DEVLIST DeviceList,
 		         int MaxEntries,  ref int OutEntries);
 		
 		[DllImport("adsapi32.dll")]
 		private static extern int DRV_DeviceOpen( int DeviceNum, ref long DriverHandle);
 		
{Procedura zapisu danych do karty}
void Button1Click(object sender, EventArgs e)
{
            string nazwa = "";
            int MaxEntries =100;
            int err  = DRV_DeviceGetNumOfList(ref MaxEntries);
             if (err == 0)
	{
		PT_DEVLIST DeviceList1 = new PT_DEVLIST();
	             DeviceList[0] = DeviceList1;

		if (err == 0)
		{
			for (int i= 1; i<49; i++)
			{
			           nazwa+=DeviceList[0].szDeviceName[i].ToString();
                                                 //nazwa i dwDeviceNum nie zwraca żadnych wartości  [???] 
						
			}
						
		             DRV_DeviceOpen(0, ref DeviceHandle).ToString();
			PT_WritePortWord lpWritePortWord = new PT_WritePortWord();
			lpWritePortWord.port = -16384;//0xc000 jest poprawnym adresem portu
			lpWritePortWord.WordData = 1;
			DRV_WritePortWord( DeviceHandle, ref lpWritePortWord);
			//Powyższe zwraca opisany błąd  [???] 
		
		}
		else
		            MessageBox.Show("Błąd DevicGetList: "+err.ToString());
	}
	else
		MessageBox.Show("Błąd DeviceGetNumeOfList: "+err.ToString());

Rozważałem jako możliwe przyczyny - niewłaściwe typy zmiennych, adres portów etc.
Może komuś coś się rzuci w oczy :-)

Pozdrawiam

0

Zupełnie źle. Jak to w ogóle ma działać jak nie dbasz nawet o poprawny typ int'a ?

To powinno być ok:

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
private struct PT_DEVLIST
{
    [FieldOffset(0)] public Int32 dwDeviceNum;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
    [FieldOffset(4)] public string szDeviceName;

    [FieldOffset(54)] public Int16 nNumOfSubdevices;
}

[StructLayout(LayoutKind.Sequential)]
private struct PT_WritePortWord
{
    public Int16 port;
    public Int16 WordData;
}

[DllImport("adsapi32.dll")]
private static extern Int32 DRV_WritePortWord(Int32 DriverHandle, ref PT_WritePortWord lpWritePortWord);

[DllImport("adsapi32.dll")]
private static extern Int32 DRV_DeviceGetNumOfList(out Int16 NumeOfDevices);

[DllImport("adsapi32.dll")]
private static extern Int32 DRV_DeviceGetList(
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] PT_DEVLIST[] DeviceList,
    Int16 MaxEntries, out Int16 OutEntries);

[DllImport("adsapi32.dll")]
private static extern Int32 DRV_DeviceOpen(Int32 DeviceNum, out Int32 DriverHandle);

void ShowError(string functionName, int error)
{
    MessageBox.Show(String.Format("Błąd {0}: {1}", functionName, error));
}

void Button1Click(object sender, EventArgs e)
{
    short MaxEntries;
    int err = DRV_DeviceGetNumOfList(out MaxEntries);
    if (err != 0) { ShowError("DeviceGetNumeOfList", err); return; }

    PT_DEVLIST[] DeviceList = new PT_DEVLIST[MaxEntries];
    short NumEntries;
    err = DRV_DeviceGetList(DeviceList, MaxEntries, out NumEntries);
    if (err != 0) { ShowError("DeviceGetList", err); return; }

    string devNames;
    for(int i = 0; i < NumEntries; i++) devNames += "\n   " + DeviceList[i].szDeviceName;
    MessageBox.Show(String.Format("Liczba urządzeń:{0}\nLista urządzeń:{1}", NumEntries, devNames));

    if (NumEntries > 0)
    {
        int DeviceHandle;
        err = DRV_DeviceOpen(0, out DeviceHandle);
        if (err != 0) { ShowError("DeviceOpen", err); return; }

        PT_WritePortWord WritePortWord = new PT_WritePortWord();

        unchecked
        {
            WritePortWord.port = (short)0xC000;
        }
        //lub
        //WritePortWord.port = 0xC000 - 0x10000;
        
        WritePortWord.WordData = 1;
        DRV_WritePortWord(DeviceHandle, ref WritePortWord);
    }
}
0

Wielkie dzięki za pomoc. Już przetestowane na fizycznie podpiętych urządzeniach - śmiga aż miło. ;]

0

Jeszcze jedno pytako :-) . Jak powinna wygladać deklaracja następującej struktury w C#:

Kod Delphi:

    PT_WritePortWord  = Record
          port     : Smallint;
          WordData : ^Longint;
    End;

Kod C#

        [StructLayout(LayoutKind.Sequential)]
        private struct PT_ReadPortWord
        {
        	public Int16 port;
        	public Int32 * WordData; ??
        }

Pozdrawiam

0

podejrzewam ze chodzi Ci o to jak przelumaczyc delphiowy ? czy to znaczy ze w kodzie ktory adf zamiescil, oryginalnie w PT_WritePortWord tez bylo Longint?

0

Nie nie było. Zamieszczony przez adf kod w C# jest jak najbardziej poprawny. To tylko takie moje pytanie pomocnicze :-)

0

To zależy, jak ten wskaźnik ma być użyty.

0

Użycie całkiem podobne jak dla procedury WritePortWord

procedure Tfrmrun.cmdReadWordClick(Sender: TObject);
var
     DiStr : string[5];
begin
     DiValue := 0;
     lpReadPortWord.port := gwPort;
     lpReadPortWord.WordData := @DiValue;
     ErrCde := DRV_ReadPortWord(DeviceHandle, lpReadPortWord);
     If (ErrCde <> 0) Then
     begin
          DRV_GetErrorMessage(ErrCde, pszErrMsg);
          Response := Application.MessageBox(pszErrMsg, 'Error!!', MB_OK);
          Exit;
     end;

    txtWord.Text := Format('%x',[DiValue]);
end;
0

Chodzi o to, w jaki sposób wskaźnik zostanie użyty przez funkcje, która go przyjmuje w swoich parametrach.

0

Jedyne info jakie mam dot. tej funckji to kod źródłowy Delphi (przedstawiony w temacie) ew. VC. Jakieś ewentualne sugestie co zrobić z tym fantem [???]

0
     DiValue := 0;
     lpReadPortWord.port := gwPort;
     lpReadPortWord.WordData := @DiValue;

sugeruje, ze DiValue to pojedynczy zwykly int, nie tablica..
proponuje po stronie C# uznac go za IntPtr (jesli tylko chcesz go przekazywac, a nie czytac-podmieniac) albo int lub int[] przyczym, opartrzyc go wtedy atrybutem MarshalAs-unmanagedttype-intptr

0

No właśnie DRV_ReadPortWord zapisuje dane pod wskazanym wskaźnikiem.

Spróbuj tak:

        [StructLayout(LayoutKind.Sequential)]
        private struct PT_ReadPortWord
        {
                public Int16 port;
                [MarshalAs(UnmanagedType.LPStruct)]
                public Int16 WordData;
        }
0

Przy podanej strukturze, w momencie wywołania funckji odczytującej, wyrzuca błąd:
Nie można zorganizować pola 'WordData' typu 'PT_ReadPortWord': Nieprawidłowa kombinacja typów zarządzanych/niezarządzanych (wartości Int16/UInt16 muszą łączyć się z elementem I2 lub U2).
Identycznie się mają sprawy przy zadeklarowaniu WordData jako Int32. :-/

0

Nie wiem czy dobrze kombinuję, ale zmontowałem cosik takiego:

unchecked 
{
        lpReadPortWord.port = (short)0xc000;
}
unsafe
{
        lpReadPortWord.WordData = *DiValue;
}
DRV_ReadPortWord( DeviceHandle, ref lpReadPortWord);

Otrzymuję komunikat: Odwołanie do obiektu nie zostało ustawione na wystąpienie obiektu.
Chyba źle wykombinowałem [glowa]

0
[StructLayout(LayoutKind.Sequential)]
private struct PT_ReadPortWord
{
        public Int16 port;
        public Int16* WordData;
}

PT_ReadPortWord ReadPortWord = new PT_ReadPortWord;
short DiValue = 0;
unchecked { ReadPortWord.port = (short)0xC000; }
unsafe { ReadPortWord.WordData = &DiValue; }
DRV_ReadPortWord( DeviceHandle, ref ReadPortWord);
0

Wielkie dzięki. Działa. ;]

1 użytkowników online, w tym zalogowanych: 0, gości: 1