|
-
Dec 27th, 2010, 02:02 PM
#1
Thread Starter
Junior Member
Problem with calling an old dll
Hi,
I'm trying to call a dll from C#, but get System.AccessViolationException. Besides the three long arguments, the fourth is a structure, which should be passed as a reference, because the dll writes data into it. That's where the error occurs: "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
I managed to get an answer to similar question five years ago when struggling with the same problem, but with VB5 at that time. Now I'm trying to port the code in c#.
See the discussion from: http://www.vbforums.com/archive/index.php/t-375387.html
The code in VB which works is:
Code:
Declare Function OCIRead Lib "oci200.dll" (ByVal lOIObject As Long, ByVal lDevice As Long, ByVal lIndex As Long, pOIData As OIDATA) As Long
--------------------------
Public Type OIDATA
lData(0 To 31) As Long 'array of data, values depeds of index number
lCheckAlarm As Long 'when read request is done and this is > 0 user should check alarm
lCategory As Long 'when this is > 0 device configuration is changed
lReadError As Long 'read error is occured
szData(0 To 127) As Byte 'String * 128 'raw index read data as comma separated
End Type
-----------------------
retdata = OCIRead(lObject, lDevice, lIndex, pOIData)
The same in C# which causes an error above:
Code:
[DllImport(DllName)]
public static extern Int32 OCIRead(Int32 lOIObject, Int32 lDevice, Int32 lIndex, ref OIDATA pOIData);
--------------
public struct OIDATA
{
public Int32[] lData; //'array of data, values depeds of index number
public Int32 lCheckAlarm; //'when read request is done and this is > 0 user should check alarm
public Int32 lCategory; //'when this is > 0 device configuration is changed
public Int32 lReadError; //'read error is occured
public byte[] szData; // 'String * 128 'raw index read data as comma separated
}
----------------------
public OIDATA pOIDATA;
pOIDATA.lData = new Int32[32];
pOIDATA.lCategory = 1;
pOIDATA.lCheckAlarm = 0;
pOIDATA.lReadError = 0;
pOIDATA.szData = new byte[128];
Int32 k = oci.OCIRead(lObject, lDevice, lIndex, ref pOIDATA);
I have replaced Long with int32 in lObject, lDevice and lIndex.
Any ideas?
-
Dec 27th, 2010, 04:27 PM
#2
Re: Problem with calling an old dll
I think you need to do some marshaling.
Note I haven't read up on marshaling up till today, so anyone please correct me if I'm wrong.
My theory:
The function you are calling is working outside the application domain.
Your current code is giving the local struct address to the OCIRead method, but the external function is not allowed to touch your application's memory; hence the AccessViolation exception.
Try this:
Code:
public static extern Int32 OCIRead(Int32 lOIObject, Int32 lDevice, Int32 lIndex, [param: MarshalAs(UnmanagedType.Struct)] OIDATA pOIData);
If I'm correct, the struct is now copied to memory that is accessible by the external function, and copied back when the function returns.
Delete it. They just clutter threads anyway.
-
Dec 27th, 2010, 05:21 PM
#3
Thread Starter
Junior Member
Re: Problem with calling an old dll
Thanks.
You are certainly right with the reason. However, your proposal didn't work, unfortunately. The error was the same.
The parameter should be a ref to the struct. In your example there was no ref? Does that mean that the marshalling results as a ref? Or how is "ref" added into the example?
Do you think the declarations of the arrays in the struct are correct?
-
Dec 27th, 2010, 05:36 PM
#4
Re: Problem with calling an old dll
Try this for the struct:
Code:
public struct OIDATA
{
[MarshalAsAttribute(UnmanagedType.SafeArray, SafeArrayUserDefinedSubType = typeof(Int32))]
public Int32[] lData; //'array of data, values depeds of index number
public Int32 lCheckAlarm; //'when read request is done and this is > 0 user should check alarm
public Int32 lCategory; //'when this is > 0 device configuration is changed
public Int32 lReadError; //'read error is occured
[MarshalAsAttribute(UnmanagedType.SafeArray, SafeArrayUserDefinedSubType = typeof(byte))]
public byte[] szData; // 'String * 128 'raw index read data as comma separated
}
Hope this is it, I'm running out of ideas
Delete it. They just clutter threads anyway.
-
Dec 28th, 2010, 03:44 AM
#5
Thread Starter
Junior Member
Re: Problem with calling an old dll
Sorry, it wasn't Still the same error.
Maybe the reason is somewhere else... I have assumed that that c# Int32 is OK to pass argument to dll which expects to have c++ 32bit long (c# long is 64bit). Am I correct in this?
-
Dec 28th, 2010, 04:35 PM
#6
Thread Starter
Junior Member
Re: Problem with calling an old dll
Still no progress 
Int32 seems to be the right choice, because if I increment the handle: lObject, making it invalid, I'll get respective error code. This in turn means that when the handle is correct, the dll tries to write into the structure, and this is when the System.AccessViolationException occures.
More ideas would be appreciated!
I put the whole code here.
Code:
public partial class Form1 : Form
{
public struct OIDATA
{
[MarshalAsAttribute(UnmanagedType.SafeArray, SafeArrayUserDefinedSubType = typeof(Int32))]
public Int32[] lData; //'array of data, values depeds of index number
public Int32 lCheckAlarm; //'when read request is done and this is > 0 user should check alarm
public Int32 lCategory; //'when this is > 0 device configuration is changed
public Int32 lReadError; //'read error is occured
[MarshalAsAttribute(UnmanagedType.SafeArray, SafeArrayUserDefinedSubType = typeof(byte))]
public byte[] szData; // 'String * 128 'raw index read data as comma separated
}
public OIDATA pOIDATA=new OIDATA();
public Int32 lObject = 0;
public Int32 lComPort = 1;
public Int32 OCI_OUTDOORTEMP = 3;
public sealed class oci // api // DLL Wrapper
{
internal const string DllName = "oci200.dll"; // handy const
[DllImport(DllName)]
public static extern Int32 OCIOpen(ref Int32 lOIObject, Int32 lComPort);
[DllImport(DllName)]
public static extern Int32 OCIRead(Int32 lOIObject, Int32 lDevice, Int32 lIndex, [param: MarshalAs(UnmanagedType.Struct)] ref OIDATA pOIData);
}
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Int32 l = oci.OCIOpen(ref lObject, lComPort);
pOIDATA.lData = new Int32[32];
pOIDATA.lCategory = 1;
pOIDATA.lCheckAlarm = 0;
pOIDATA.lReadError = 0;
pOIDATA.szData = new byte[128];
Int32 lIndex = OCI_OUTDOORTEMP;
Int32 lDevice = 0;
pOIDATA.szData[127] = 0;
Int32 k = oci.OCIRead(lObject, lDevice, lIndex, ref pOIDATA); //This is where error occurs
l = pOIDATA.lData[0];
}
}
-
Dec 29th, 2010, 02:18 AM
#7
Re: Problem with calling an old dll
Could you try adding this if it helps?
Code:
[StructLayout(LayoutKind.Sequential)]
public struct OIDATA
{
-
Dec 29th, 2010, 05:07 AM
#8
Thread Starter
Junior Member
Re: Problem with calling an old dll
Thanks for the hint dee-u. It didn't help unfortunately.
This is a bit confusing, because the first parameter, lObject, is passed also as reference in oci.OCIOpen(ref lObject, lComPort); It gets the handle in dll without errors, and it is declared in the same space as the struct is.
This is maybe a stupid question, but if the dll expects to have a reference to a allocated memory, so can't I just pass a refernece to a allocated long string, for example? How does the dll "know" that allocated memory was used as a structure in the managed side of code? Doesn't it only use the allocated memory, pointed by reference, as it likes, and in this case as it uses the structure internally in dll?
-
Dec 29th, 2010, 06:12 AM
#9
Fanatic Member
Re: Problem with calling an old dll
Going off at a totally different tangent, but do you not have to declare the code as 'unsafe' e.g.
Code:
public static extern unsafe Int32 OCIOpen(ref Int32 lOIObject, Int32 lComPort);
-
Dec 29th, 2010, 07:26 AM
#10
Thread Starter
Junior Member
Re: Problem with calling an old dll
I seem to learn a lot here 
Thank you all for your advice. Unfortunately that did not help either.
Code:
public static extern unsafe Int32 OCIRead(Int32 lOIObject, Int32 lDevice, Int32 lIndex, [param: MarshalAs(UnmanagedType.Struct)] ref OIDATA pOIData);
In addition, I check-marked the Project build options: "Allow unsafe code". Still the same error...
Remainding, that the dll is actively in use with my old VB5 code, so it works ok. This must be a question of declaring not right, or similar??
-
Dec 30th, 2010, 04:50 AM
#11
Thread Starter
Junior Member
Re: Problem with calling an old dll
If I declare the structure, instead of struct, but class (see below), and do no other changes, then the dll returns 0 indicating that no errors and it could do it's task to write into the allocated memory. However, the data in pOIDATA.lData[0] remains the same as before the call.
I don't know what this actually means, other than that I still believe that I cannot declare the original struct so that it can be written in dll.
Can anyone elaborate this a bit?
Thanks.
Code:
public class OIDATA
{
[MarshalAsAttribute(UnmanagedType.SafeArray, SafeArrayUserDefinedSubType = typeof(Int32))]
public Int32[] lData; //'array of data, values depeds of index number
public Int32 lCheckAlarm; //'when read request is done and this is > 0 user should check alarm
public Int32 lCategory; //'when this is > 0 device configuration is changed
public Int32 lReadError; //'read error is occured
[MarshalAsAttribute(UnmanagedType.SafeArray, SafeArrayUserDefinedSubType = typeof(byte))]
public byte[] szData; // 'String * 128 'raw index read data as comma separated
//public IntPtr szData;
}
-
Dec 30th, 2010, 09:13 AM
#12
Re: Problem with calling an old dll
I can't really offer a good solution but I can explain why it's not working.
As far as the API is concerned it does not really care how the passed structure is defined all it requires is a pointer to a buffer which it will populate. In this case the buffer needs to be 268 bytes in size. The VB6 UDT you show is fine for this as it defines a contiguous block of 268 bytes. Unfortunately this is not so easy in Net because arrays are reference types and therefore sit in the structure as pointers. All the structures shown here are only 20 bytes in size.
Using UnmanagedType.SafeArray will not work because it is still a reference type, the only difference is that it points to a OLE SAFEARRAY structure rather than a Net array object. You would use it with API that explicitly expect a pointer to a SafeArray.
Using [StructLayout(LayoutKind.Explicit)] or [StructLayout(LayoutKind.Sequencial)] is important but it does not help in this case because of the arrays.
There is most likely a better way than this but off the top of my head I would suggest passing the first element of either a Int32 or Byte array of appropriate size (67 or 268) and then extract the data you need from that temporary buffer (BitConverter). You would need to adjust the API declare slightly to accept either Int32 or Byte and remember to pass the first element rather than the arrays themselves.
-
Jan 1st, 2011, 04:34 PM
#13
Thread Starter
Junior Member
Re: Problem with calling an old dll
Thanks for the explanation Milk!
At least I can now retrieve data from the dll, and with some extra coding I can fill the original structure arrays from the int-array buffer.
This solves the problem for me.
Thanks again.
-
Mar 9th, 2011, 05:01 PM
#14
Re: Problem with calling an old dll
Okay, this thread is getting on a bit now but...
c# Code:
using System.Runtime.InteropServices;
//Public Type OIDATA
// lData(0 To 31) As Long 'array of data, values depeds of index number
// lCheckAlarm As Long 'when read request is done and this is > 0 user should check alarm
// lCategory As Long 'when this is > 0 device configuration is changed
// lReadError As Long 'read error is occured
// szData(0 To 127) As Byte 'String * 128 'raw index read data as comma separated
//End Type
[StructLayout(LayoutKind.Explicit)]
public unsafe struct OIDATA
{
[FieldOffset(0)]public fixed Int32 Data[32];
[FieldOffset(128)]public Int32 CheckAlarm;
[FieldOffset(132)]public Int32 Category;
[FieldOffset(136)]public Int32 ReadError;
[FieldOffset(140)]public fixed byte SzData[128];
}
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|