Sanity check on call to imported DLL function

  • Thread starter Charles Calvert
  • Start date
C

Charles Calvert

All,

I think that I've been staring at this for too long. I need a sanity
check.

Problem: To get values from an unmanaged DLL written in C++, I set
m_data.TunerID = 1 and pass it to the imported function GetData().
When the function call returns, TunerID is zero. It shouldn't be
modified by the DLL and should still be 1.

Note that I don't have access to all of the souce for the DLL, only
the header that specifies the interface, so it may be that the DLL is
doing something incorrectly. The author says it is not.

Please take a look at the following code and let me know if I've done
something stupid.

Using VS 2008. The C++ is being built for Unicode, not ANSI.
Everything is built for 32-bit Windows.

C++ code from DLL (Win32 unmanaged code):
-----------------------------------------
enum TunerMode
{
DVB_S,
DVB_S2
};

enum Modulation
{
QPSK,
EightPSK
};

enum FEC
{
FEC_1_2,
FEC_3_5,
FEC_2_3,
FEC_3_4,
FEC_4_5,
FEC_5_6,
FEC_6_7,
FEC_7_8,
FEC_8_9,
FEC_9_10
};

#define MAXDATAPIDS 7
#define MAX_ERROR_LEN 255
typedef struct
{
int TunerID;
bool TunerLock;
DWORD TunerFrequency;
DWORD SymbolRate;
TunerMode TunerMode;
Modulation Modulation;
FEC ForwardErrorCode;
int CommandPID;
int DataPID[MAXDATAPIDS];
int SignalQuality;
int SignalLevel;
float SignalToNoiseRatio;
long BitErrorRate;
long TotalIPPackets;
long TotalDVBPackets;
TCHAR szError[MAX_ERROR_LEN + 1];
} TunerData;

// Uses .def file to avoid C++ decoration
__declspec( dllexport ) DWORD GetData(TunerData &data);


C# code:
--------
public enum FECRates
{
FEC_1_2,
FEC_3_5,
FEC_2_3,
FEC_3_4,
FEC_4_5,
FEC_5_6,
FEC_6_7,
FEC_7_8,
FEC_8_9,
FEC_9_10
}

public enum ModulationTypes
{
QPSK,
EightPSK
}

public enum TunerModes
{
DVB_S,
DVB_S2
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct TunerData
{
public int TunerID;
public bool TunerLock;
public uint TunerFrequency;
public uint SymbolRate;
public TunerModes TunerMode;
public ModulationTypes Modulation;
public FECRates ForwardErrorCode;
public int CommandPID;
//public IntPtr DataPID;
// NOTE: we cannot use a constant to define the length here,
// and C# doesn't support macros like C++, so we must be careful
// to ensure that this constant matches MAXDATAPIDS as defined
// in the C++ header.
[MarshalAs(UnmanagedType.ByValArray, SizeConst=7)]
public int[] DataPID;
public int SignalQuality;
public int SignalLevel;
public float SignalToNoiseRatio;
// The next three are declared as "long" in C++, but
// that's a 32-bit long, so we'll declare them as int here.
public int BitErrorRate;
public int TotalIPPackets;
public int TotalDVBPackets;
[MarshalAs(UnmanagedType.LPTStr)]
public string szError;
}

[DllImport("DLL.dll", CharSet = CharSet.Unicode, CallingConvention =
CallingConvention.Cdecl)]
private static extern uint GetData(ref TunerData data);
 
G

G Himangi

Seems your struct declaration is wrong.

The IntPtr DataPID should not be present and the last char array should be
defined similar to the way you defined the DataPID array.

However, I dont know if this is what is causing the changed member.

---------
- G Himangi, LogicNP Software http://www.ssware.com
Shell MegaPack: GUI Controls For Drop-In Windows Explorer like File/Folder
Browsing Functionality (.Net & ActiveX Editions).
EZNamespaceExtensions: Develop namespace extensions rapidly in .Net and
MFC/ATL/C++
EZShellExtensions: Develop all shell extensions,explorer bars and BHOs
rapidly in .Net & MFC/ATL/C++
---------

Charles Calvert said:
All,

I think that I've been staring at this for too long. I need a sanity
check.

Problem: To get values from an unmanaged DLL written in C++, I set
m_data.TunerID = 1 and pass it to the imported function GetData().
When the function call returns, TunerID is zero. It shouldn't be
modified by the DLL and should still be 1.

Note that I don't have access to all of the souce for the DLL, only
the header that specifies the interface, so it may be that the DLL is
doing something incorrectly. The author says it is not.

Please take a look at the following code and let me know if I've done
something stupid.

Using VS 2008. The C++ is being built for Unicode, not ANSI.
Everything is built for 32-bit Windows.

C++ code from DLL (Win32 unmanaged code):
-----------------------------------------
enum TunerMode
{
DVB_S,
DVB_S2
};

enum Modulation
{
QPSK,
EightPSK
};

enum FEC
{
FEC_1_2,
FEC_3_5,
FEC_2_3,
FEC_3_4,
FEC_4_5,
FEC_5_6,
FEC_6_7,
FEC_7_8,
FEC_8_9,
FEC_9_10
};

#define MAXDATAPIDS 7
#define MAX_ERROR_LEN 255
typedef struct
{
int TunerID;
bool TunerLock;
DWORD TunerFrequency;
DWORD SymbolRate;
TunerMode TunerMode;
Modulation Modulation;
FEC ForwardErrorCode;
int CommandPID;
int DataPID[MAXDATAPIDS];
int SignalQuality;
int SignalLevel;
float SignalToNoiseRatio;
long BitErrorRate;
long TotalIPPackets;
long TotalDVBPackets;
TCHAR szError[MAX_ERROR_LEN + 1];
} TunerData;

// Uses .def file to avoid C++ decoration
__declspec( dllexport ) DWORD GetData(TunerData &data);


C# code:
--------
public enum FECRates
{
FEC_1_2,
FEC_3_5,
FEC_2_3,
FEC_3_4,
FEC_4_5,
FEC_5_6,
FEC_6_7,
FEC_7_8,
FEC_8_9,
FEC_9_10
}

public enum ModulationTypes
{
QPSK,
EightPSK
}

public enum TunerModes
{
DVB_S,
DVB_S2
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct TunerData
{
public int TunerID;
public bool TunerLock;
public uint TunerFrequency;
public uint SymbolRate;
public TunerModes TunerMode;
public ModulationTypes Modulation;
public FECRates ForwardErrorCode;
public int CommandPID;
//public IntPtr DataPID;
// NOTE: we cannot use a constant to define the length here,
// and C# doesn't support macros like C++, so we must be careful
// to ensure that this constant matches MAXDATAPIDS as defined
// in the C++ header.
[MarshalAs(UnmanagedType.ByValArray, SizeConst=7)]
public int[] DataPID;
public int SignalQuality;
public int SignalLevel;
public float SignalToNoiseRatio;
// The next three are declared as "long" in C++, but
// that's a 32-bit long, so we'll declare them as int here.
public int BitErrorRate;
public int TotalIPPackets;
public int TotalDVBPackets;
[MarshalAs(UnmanagedType.LPTStr)]
public string szError;
}

[DllImport("DLL.dll", CharSet = CharSet.Unicode, CallingConvention =
CallingConvention.Cdecl)]
private static extern uint GetData(ref TunerData data);
--
Charles Calvert | Web-site Design/Development
Celtic Wolf, Inc. | Software Design/Development
http://www.celticwolf.com/ | Data Conversion
(703) 580-0210 | Project Management
 
C

Charles Calvert

Seems your struct declaration is wrong.

The IntPtr DataPID should not be present

That was commented out, so it is not being compiled. I should have
deleted it before posting. Sorry.
and the last char array should be defined similar to the way you
defined the DataPID array.

As a char array? I went back and rechecked the documentation and
found something that I had missed before. In the section entitled
"Default Marshaling for Strings", there is a subsection "Strings Used
in Structures" that indicates that it should be a System.String with
the attribute UnmanagedType.ByValTStr and the SizeConst attribute.
Here is the example given:

struct StringInfoT {
TCHAR * f1;
TCHAR f2[256];
};

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
struct StringInfoT {
[MarshalAs(UnmanagedType.LPTStr)] public String f1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public String
f2;
}
However, I dont know if this is what is causing the changed member.

It would seem a little odd, given the sequential layout of the struct,
that the last member would affect the first. I'm going to give it a
shot nonetheless. If that doesn't work, I'll try the char array
instead and see what happens.
 
C

Charles Calvert

[snip]
and the last char array should be defined similar to the way you
defined the DataPID array.

As a char array? I went back and rechecked the documentation and
found something that I had missed before. In the section entitled
"Default Marshaling for Strings", there is a subsection "Strings Used
in Structures" that indicates that it should be a System.String with
the attribute UnmanagedType.ByValTStr and the SizeConst attribute.
Here is the example given:

struct StringInfoT {
TCHAR * f1;
TCHAR f2[256];
};

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
struct StringInfoT {
[MarshalAs(UnmanagedType.LPTStr)] public String f1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)] public String
f2;
}
However, I dont know if this is what is causing the changed member.

It would seem a little odd, given the sequential layout of the struct,
that the last member would affect the first. I'm going to give it a
shot nonetheless. If that doesn't work, I'll try the char array
instead and see what happens.

That didn't help, and I tried this:

[MarshalAs(UnmanagedType.ByValArray, SizeConst=256)]
public char[] szError;

as well, but it didn't solve the problem. I'm not really surprised. I
may be getting the source for the DLL soon, so that should help.

In the meantime, if anyone any other suggestions, I'd appreciate them.

Thanks,
 
C

Charles Calvert

Problem: To get values from an unmanaged DLL written in C++, I set
m_data.TunerID = 1 and pass it to the imported function GetData().
When the function call returns, TunerID is zero. It shouldn't be
modified by the DLL and should still be 1.

Note that I don't have access to all of the souce for the DLL, only
the header that specifies the interface, so it may be that the DLL is
doing something incorrectly. The author says it is not.

The author was wrong. I finally got my hands on the source and it's a
logic error in the DLL, plain as day. I must have chased my tail for
8 hours on this one, and it's someone else's fault. I hate that.
<grumble>

Thanks to everyone who read my post.
 
Top