Laden...

Problem mit P/Invoke: Pointer und eigene Struktur

Erstellt von HansHansen vor 18 Jahren Letzter Beitrag vor 18 Jahren 1.785 Views
H
HansHansen Themenstarter:in
5 Beiträge seit 2006
vor 18 Jahren
Problem mit P/Invoke: Pointer und eigene Struktur

Hallo!
Hab ein Problem und schon überall nach Lösungen geschaut, doch nichts gefunden, was mir weiterhelfen konnte.

Bevor ihr jetzt weiter lesen wollt:

Es handelt sich hier meiner Ansicht nach um ein sehr komplexes Problem!

Also bin gerade dabei eine Klasse zu schreiben, die Funktionen einer C-DLL aufruft, um mit einer SPS S5 zu kommunizieren.

Die C-Funktion in der DLL lautet:

C-Code


// <param name="rb">Zeiger auf einen anwenderdefinierten Requestblock.</param>
// <param name="hSRAccessPoint">Handle auf den übergebenen "Zugangspunkt der Applikation"</param>

int SRMD_Request(int hSRAccessPoint, RB *rb)

Eigentlich ein ganz normaler Aufruf, bis auf die Spizialität, dass ein Zeiger auf eine benutzerdefinierte Struktur als Übergabe Parameter gefordert wird.
Und von Zeigern hab ich relativ wenig Ahnung... ich hab zwar das Prinzip verstanden, doch da ich noch kein C/C++ vorher gemacht habe keine Erfahrung damit.

Ich habe den Aufruf in meinem Code folgendermaßen realisiert:

 
[DllImport("s7_sr.dll", SetLastError=true, CharSet = CharSet.Auto)]
private static extern int SRMD_Request(int hSRAccessPoint, ref RB rb);

Jetzt ist halt das Problem, dass die benutzerdefinierte Struktur passen muss, damit der Aufruf erfolgreich ist.

In einem C-Beispiel konnte ich sehen, wie das gemacht gehört:
C- CODE


/*****************************************************************************************
\*** Requestblock structures
\*****************************************************************************************/
#pragma pack(push, _SR_RB_alignment,1)
					
typedef struct	/* RB-Header (length=16) */
{	DWORD	rb_res;			
	BYTE	rb_length;		
	WORD	rb_user;        
	BYTE	rb_resp_port;	
	DWORD	rb_callback;    
	BYTE	rb_susys;       
	BYTE	rb_opcode;      
	WORD	rb_response;    
} SR_RBH;
#define	SR_RB SR_RBH
			
typedef struct	/* (length=16+2=18) */
{	SR_RBH	rbh;            /* Header */
	WORD   open_reference;	/* Identifikation */
}	SR_OPEN_REQ;

typedef struct	/* (length=31) */
{	BYTE	loc_nsap_id_len;
	BYTE	loc_tsap_id_len;
	BYTE	loc_tsap_id[8];
	BYTE	rem_net_addr_len;
	BYTE	afi;
	WORD	subnet;
	BYTE	host_id[6];
	BYTE	lsap_id;
	BYTE	rem_net_nsap;  
	BYTE	rem_tsap_id_len;
	BYTE	rem_tsap_id[8];
}	SR_TA;

typedef struct		/* Variablenfeld1 (length=16+26=42) */
{	SR_RBH	rbh;	/* Header */
	BYTE	conn_iso_reason_code;
	BYTE	conn_rfu[4];
	WORD	conn_ack_delay_estimate;
	SR_TA	*conn_ta;		
	WORD	conn_persistence_count;
	WORD	conn_abort_timeout;
	WORD	conn_reference;
	BYTE	conn_class;
	WORD	conn_negot_options;
	BYTE	*conn_buffer_ptr;
	WORD	conn_buffer_length;
} SR_VAR_ARRAY_1;

typedef struct		/* Variablenfeld2 (length=16+34=50) */
{	SR_RBH  rbh;	/* Header */
	BYTE	vc_iso_reason_code;
	BYTE	vc_rfu[15];
	WORD	vc_reference;
	BYTE	vc_conn_class;
	WORD	vc_buf_len;
	BYTE	vc_num_blks;
	BYTE	*vc_buf0_ptr;
	WORD	vc_buf0_length;
	BYTE	*vc_buf1_ptr;
	WORD	vc_buf1_length;
}	SR_VAR_ARRAY_2;
			
typedef struct       /* Datagram functions of iNA 960 (length=16+18=34)*/
{   SR_RBH	rbh;				/* Header */
	BYTE	dg_rfu[4];
	SR_TA	*dg_ta;		
    BYTE    dg_tsap_class;
    WORD    dg_buf_len;
    BYTE    dg_num_blks;
    BYTE	*dg_buf0_ptr;
    WORD    dg_buf0_length;
}	SR_DG;


typedef struct	/* Setup (length=16+32=48)*/
{	SR_RBH	rbh;				/* Header */
	WORD	count;
	BYTE	address[6];
	BYTE	usertext[24];
}	SR_IA;


typedef struct	/* RB-struct*/
{   
	union	
	{	SR_OPEN_REQ		open;
		SR_VAR_ARRAY_1	va1;
		SR_VAR_ARRAY_2	va2;
		SR_DG			dg;
		SR_IA			setup;
	}	rb;

}	RB1;

#pragma pack(pop, _SR_RB_alignment)


Jetzt habe ich diese Struktur folgendermaßen in C# übernommen:


#region Requestblock structures

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct				SR_RBH				/* RB-Header (length=16) */
{
	public uint				rb_res;			
	public byte				rb_length;		
	public ushort			rb_user;        
	public byte				rb_resp_port;	
	public uint				rb_callback;    
	public byte				rb_susys;       
	public byte				rb_opcode;      
	public ushort			rb_response;    
}
// #define	SR_RB SR_RBH

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]	
public struct				SR_OPEN_REQ			/* (length=16+2=18) */
{
	public SR_RBH			rbh;				/* Header */
	public ushort			open_reference;		/* Identifikation */
}	

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct				SR_TA				/* (length=31) */
{
	public byte				loc_nsap_id_len;
	public byte				loc_tsap_id_len;
	[MarshalAs(UnmanagedType.ByValArray, SizeConst=8)]
	public byte[]			loc_tsap_id;		// = new byte[8];
	public byte				rem_net_addr_len;
	public byte				afi;
	public ushort			subnet;
	[MarshalAs(UnmanagedType.ByValArray, SizeConst=6)]
	public byte[]			host_id;			// = new byte[6];
	public byte				lsap_id;
	public byte				rem_net_nsap;  
	public byte				rem_tsap_id_len;
	[MarshalAs(UnmanagedType.ByValArray, SizeConst=8)]
	public byte[]			rem_tsap_id;		// = new byte[8];
}	

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct				SR_VAR_ARRAY_1		/* Variablenfeld1 (length=16+26=42) */
{		
	public SR_RBH			rbh;				/* Header */		
	public byte				conn_iso_reason_code;
	[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
	public byte[]			conn_rfu;			// = new byte[4];
	public ushort			conn_ack_delay_estimate;
	unsafe public			SR_TA *conn_ta;		
	public ushort			conn_persistence_count;
	public ushort			conn_abort_timeout;
	public ushort			conn_reference;
	public byte				conn_class;
	public ushort			conn_negot_options;
	unsafe public byte		*conn_buffer_ptr;
	public ushort			conn_buffer_length;
} 

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct				SR_VAR_ARRAY_2		/* Variablenfeld2 (length=16+34=50) */
{
	public SR_RBH			rbh;				/* Header */
	public byte				vc_iso_reason_code;
	[MarshalAs(UnmanagedType.ByValArray, SizeConst=15)]
	public byte[]			vc_rfu;				// = new byte[15];
	public ushort			vc_reference;
	public byte				vc_conn_class;
	public byte				vc_buf_len;
	public byte				vc_num_blks;
	unsafe public byte		*vc_buf0_ptr;
	public ushort			vc_buf0_length;
	unsafe public byte		*vc_buf1_ptr;
	public ushort			vc_buf1_length;
}	
		
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct				SR_DG				/* Datagram functions of iNA 960 (length=16+18=34)*/
{
	public SR_RBH			rbh;				/* Header */
	[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
	public byte[]			dg_rfu;				// = new byte[4];
	unsafe public			SR_TA	*dg_ta;		
	public byte				dg_tsap_class;
	public ushort			dg_buf_len;
	public byte				dg_num_blks;
	unsafe public byte		*dg_buf0_ptr;
	public ushort			dg_buf0_length;
}	

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct 				SR_IA				/* Setup (length=16+32=48)*/
{	
	public SR_RBH			rbh;				/* Header */
	public ushort			count;
	[MarshalAs(UnmanagedType.ByValArray, SizeConst=6)]
	public byte[]			address;			// = new byte[6];
	[MarshalAs(UnmanagedType.ByValArray, SizeConst=24)]
	public byte[]			usertext;			// = new byte[24];
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct				RB					/* RB-struct*/
{   		
	public SR_OPEN_REQ		open;
	public SR_VAR_ARRAY_1	va1;
	public SR_VAR_ARRAY_2	va2;
	public SR_DG			dg;
	public SR_IA			setup;
}
#endregion Requestblock Structures


Der Aufruf der SRMD_Request Funktion passiert in dem C-Beispiel in folgender Methode:
C-Code:


WORD SRMD_OpenReq(int hLogDev,WORD userid, RB1 *rb)
{
	short ret=0;
	
    /* RB1-Header */
	rb->rb.open.rbh.rb_res		= 0xFFAAFFAA;
	rb->rb.open.rbh.rb_length	= 18; 
    rb->rb.open.rbh.rb_user		= userid;
	rb->rb.open.rbh.rb_resp_port= 0xFF;
	rb->rb.open.rbh.rb_callback	= 0;
    rb->rb.open.rbh.rb_susys	= SUBSYS_TCL;
    rb->rb.open.rbh.rb_opcode	= OPEN_REQ;
	rb->rb.open.rbh.rb_response = REQ_WAITING;
	
	/* SRMD_Request */
	if(gLoadDLLDynamic == TRUE)
	{	ret = pSRMD_Request(hLogDev,rb);	}
	else
	{	ret = SRMD_Request(hLogDev,rb);	}

	if((ret != 0) || (SR_async) )
    {   return (ret);	}
		
	ret = SRMD_WaitOK(&rb->rb.open.rbh.rb_response,MAX_POLL_TIME_AKTIV);
	if(ret >= 0)
	{	ret = rb->rb.open.open_reference;	}

    return(ret);
}

Diese Funktion habe ich in C# abgebildet, wobei ich die jeweiligen Werte durch debuggen des C-Code herausgefunden und dann hart codiert habe:


private int Open_Req()
{
	int ret=-99;
	RB rb = new RB();

	/* RB1-Header */
	rb.open.rbh.rb_res		= 0xFFAAFFAA;
	rb.open.rbh.rb_length	= 18; 
	rb.open.rbh.rb_user		= 0x0000;//userid;
	rb.open.rbh.rb_resp_port= 0xFF;
	rb.open.rbh.rb_callback	= 0;
	rb.open.rbh.rb_susys	= Kernel.ISO_LAYER_4;
	rb.open.rbh.rb_opcode	= Kernel.OPEN_REQ;
	rb.open.rbh.rb_response = Kernel.REQ_WAITING;
	
        try
	{
		int i = Init();                
		
		ret = SRMD_Request(m_hSRAccessPoint,ref rb);

		int error = Marshal.GetLastWin32Error();		
		Console.WriteLine(message.ToString());
	}
	catch (Exception ex)
	{
		int error = Marshal.GetLastWin32Error();

		Console.WriteLine("-- EXCEPTION -- " + ex.Message + "-- /EXCEPTION -- ");
		Console.WriteLine("-- ERROR -- " + error.ToString() + "-- /ERROR --");

	}

	return ret;
}


Bei dem Aufruf der SRMD_Request Funktion erhalte ich immer einen Fehlercode und zwar Falscher rb-Parameter.

Jetzt habe ich keine Möglichkeit, herauszufinden, wo mein Fehler liegt, da ich die DLL ja nicht durchdebuggen kann.
Es gibt mehrer Möglichkeiten:

  • Der DLLImport Aufruf der Funktion ist falsch
  • Ich habe die Struktur falsch zusammengebaut
  • Das ganze Zeiger Zeug macht mir einen Strich durch die Rechnung
  • Laut Doku muss ich für diesen Funktionsaufruf nur den Header des Requestblock füllen. Ich habe aber auch schon probiert die anderen Felder zu füllen doch immer die gleiche Meldung erhalten.

Wenn ich den DllImport aufruf beispielsweise bewusst falsch mache, quasi so:

private static extern int SRMD_Request(int hSRAccessPoint, string rb);

erhalte ich auch die gleiche Fehler Meldung, also Falscher rb-Parameter.

Ich weiß, alles sehr speziell und komplex, doch vielleicht hat jemand hier eine Idee, wie ich das mit Zeigern, -> Operatoren und eigenen Strukturen am besten in C# umsetzen kann.

Vielen Dank schon mal 👍

Beauty is in the eye of the BEER Holder!

S
8.746 Beiträge seit 2005
vor 18 Jahren

Auf den ersten Blick würde ich sagen, dass du das Union-Konstrukt nicht richtig umgesetzt hast. RB ist ein Struct, welches verschiedene Ausprägungen annehmen kann. Bei deiner C#-Implementierung von RB hast du aber alles Varianten zugleich innerhalb des RB-Structs.

Versuch das mal so:

 
[StructLayout(LayoutKind.Explicit , CharSet=CharSet.Ansi)]
public struct                RB                    /* RB-struct*/
{           
    [ FieldOffset( 0 )]
    public SR_OPEN_REQ        open;
    [ FieldOffset( 0 )]
    public SR_VAR_ARRAY_1    va1;
    [ FieldOffset( 0 )]
    public SR_VAR_ARRAY_2    va2;
    [ FieldOffset( 0 )]
    public SR_DG            dg;
    [ FieldOffset( 0 )]
    public SR_IA            setup;
}

Die unsafe-Pointer sehen auch gefährlich aus (hier würde ich einfach IntPtr verwenden), aber erstmal den ersten Schritt.

H
HansHansen Themenstarter:in
5 Beiträge seit 2006
vor 18 Jahren

Wenn ich StructLayout(LayoutKind.Explicit und _ [ FieldOffset( 0 )]_ habe kommt folgende Exception.Message

ex.Message
"Der Typ 'Connect_SPS_Test.RB' der Assembly 'Connect_SPS_Test, Version=1.0.2285.19282, Culture=neutral, PublicKeyToken=null' konnte nicht geladen werden, da sie bei Offset 0 ein Objektfeld enthält, das falsch ausgerichtet ist oder von einem nicht-Objektfeld überlappt wird"

Hört sich irgendwie nach einem Problem im Speicherbereich an...

Diese Exception kommt noch bevor mein private int Open_Req() aufgerufen wird... ich kann nicht mal reindebuggen, er wirft sofort die Exception


private void bt_open_Click(object sender, System.EventArgs e)
{
	try
	{				
		Open_Req();
	}
	catch (Exception ex)
	{
		Console.WriteLine(ex.Message);
	}
}


Beauty is in the eye of the BEER Holder!

S
8.746 Beiträge seit 2005
vor 18 Jahren

Ok, der von mir vorgeschlagene Weg funktioniert nur mit ValueTypes. Vermutlich ist das "ref" der Killer.

Die Alternative ist folgende: Lege für jeden Union-Member eine eigene Struktur an und erzeuge eine entsprechende DLLImport-Überladung:

 
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct                RB_SR_OPEN_REQ                   /* RB-struct*/
{           
    public SR_OPEN_REQ        open;
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct                RB_SR_VAR_ARRAY_1                    /* RB-struct*/
{           
    public SR_VAR_ARRAY_1    va1;
}

und

 
[DllImport("s7_sr.dll", SetLastError=true, CharSet = CharSet.Auto)]
private static extern int SRMD_Request(int hSRAccessPoint, ref RB_SR_OPEN_REQ rb);

[DllImport("s7_sr.dll", SetLastError=true, CharSet = CharSet.Auto)]
private static extern int SRMD_Request(int hSRAccessPoint, ref RB_SR_VAR_ARRAY_1 rb);

usw.

Kannst nur beten, dass du keine Union innerhalb der Union hast, dann wird es sehr unangenehm. Zum Glück bei dir nicht der Fall.

P.s.: Du verdienst den mycsharp-Preis für das bisher komplexeste PInvoke-Problem! 😉