consuming unmanaged DLL from managed C#

T

Tremendo

[I posted this in ...dotnet.framework.interop last week, with no luck. The question seems long, but
I think that the answer should be easy and quick for someone who knows it.]

Hi,

I need to consume an unmanaged DLL from managed C#. The DLL is "ae766.dll". I have problems with one
function in it. Who developed the DLL, provided the following information, regarding that function:

-----------------------------
Declaration
-----------------------------

extern "C" long __stdcall GetAnalyzerTraceData(LPSAFEARRAY FAR *);

-----------------------------
Description of the function
-----------------------------

GetAnalyzerTraceData(LPSAFEARRAY FAR *);

A call to this routine will result in one trace of data being returned. A pointer to an array of
BYTE type (in Visual Basic) should be passed in. This array

will be allocated, initialized, and the data from the unit will be located in it as binary data.


The structure of the data is as follows:

Struct Data
{
char freq[7]; //center frequency (ascii characters) in units of kHz
char span; //Decimal index indicating the current span
char ref_level; //Decimal index indicating the current reference level
char RBW[7]; //ascii characters indicating the current RBW
unsigned char data[1000];
unsigned char checksum; //checksum of the bytes
unsigned char reserved; //unused
}

The total length of the data is 1018 bytes. The “data” portion of the structure is the actual trace
data.

A return value of 1 indicates success. A return value of 0 indicates a failure while attempting to
read from the GSA-810.

-----------------------------


What C# code do I need to write, to "translate" the struct and to import the function? You don't
need to write the whole code for me, because there are repeated data types (inside the struct, for
instance). A hint about how to do it should be enough.

Thank you very much.
 
W

Willy Denoyette [MVP]

Tremendo said:
[I posted this in ...dotnet.framework.interop last week, with no luck. The question seems
long, but
I think that the answer should be easy and quick for someone who knows it.]

Hi,

I need to consume an unmanaged DLL from managed C#. The DLL is "ae766.dll". I have
problems with one
function in it. Who developed the DLL, provided the following information, regarding that
function:

-----------------------------
Declaration
-----------------------------

extern "C" long __stdcall GetAnalyzerTraceData(LPSAFEARRAY FAR *);

-----------------------------
Description of the function
-----------------------------

GetAnalyzerTraceData(LPSAFEARRAY FAR *);

A call to this routine will result in one trace of data being returned. A pointer to an
array of
BYTE type (in Visual Basic) should be passed in. This array

will be allocated, initialized, and the data from the unit will be located in it as binary
data.


The structure of the data is as follows:

Struct Data
{
char freq[7]; //center frequency (ascii characters) in units of kHz
char span; //Decimal index indicating the current span
char ref_level; //Decimal index indicating the current reference level
char RBW[7]; //ascii characters indicating the current RBW
unsigned char data[1000];
unsigned char checksum; //checksum of the bytes
unsigned char reserved; //unused
}

The total length of the data is 1018 bytes. The "data" portion of the structure is the
actual trace
data.

A return value of 1 indicates success. A return value of 0 indicates a failure while
attempting to
read from the GSA-810.

-----------------------------


What C# code do I need to write, to "translate" the struct and to import the function? You
don't
need to write the whole code for me, because there are repeated data types (inside the
struct, for
instance). A hint about how to do it should be enough.

Thank you very much.


The function takes a pointer to a safearray pointer (LPSAFEARRAY FAR *), the safearray being
a BYTE array. But in reality the BYTE[] is a flat structure of type Data. It's weird that a
record (UDT) is flatten-out like this, if you are sure about this, then the following is
how you could handle the marshaling:


[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct Data
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
internal char[] freq; //center frequency (ascii characters) in units of kHz
internal char span; //Decimal index indicating the current span
internal char ref_level; //Decimal index indicating the current reference level
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
internal char[] RBW; //ascii characters indicating the current RBW
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1000)]
internal byte[] data;
internal byte checksum; //checksum of the bytes
internal byte reserved; //unused
}

[DllImport("yourDll"), SuppressUnmanagedCodeSecurity]
static extern int GetAnalyzerTraceData
([Out, MarshalAs( UnmanagedType.SafeArray )] out byte[] sa);

IntPtr pnt = IntPtr.Zero;
try {
byte[] buffer = null;
if(GetAnalyzerTraceData(out buffer) == 0)
{
throw....
}
pnt = Marshal.AllocHGlobal(Marshal.SizeOf(data));
Marshal.Copy(buffer, 0, pnt, buffer.Length);
Data data = (Data)Marshal.PtrToStructure(pnt, typeof(Data));
// process data...
}
finally {
Marshal.FreeHGlobal(pnt);
}


Willy.
 
T

Tremendo

Willy,

Thank you very much for your time and effort. I haven't had time to try what you wrote until today.

Comments:
1) I have been unable to use the parameter "SuppressUnmanagedCodeSecurity" that you proposed. It
said something like that it is not a valid parameter for DllImport. I ended up not specifying that
parameter, and everything compiled ok.
2) All functions in "ae766.dll" except "GetAnalyzerTraceData" work ok in run time. I communicate
correctly with the spectrum analyzer. It looks like, therefore, that I can live without
"SuppressUnmanagedCodeSecurity", at least regarding those functions.
3) The only function that does not work ok is "GetAnalyzerTraceData". I get a run time error saying
something like (I have it in Spanish (against my will)) "Entry point 'GetAnalyzerTraceData' could
not be found in DLL file 'ae766.dll'". This error message points to line marked as "RUN TIME ERROR"
in the following code:

------------------
[DllImport("ae766.dll")] private static extern int GetSerialNum();
[DllImport("ae766.dll")] private static extern int SetFreq(int freq);
....
[DllImport("ae766.dll")] private static extern int
GetAnalyzerTraceData([Out,MarshalAs(UnmanagedType.SafeArray)] out byte[] buffer);

....

try
{
if (1!=GetAnalyzerTraceData(out buffer)) // <--- RUN TIME ERROR
{
// throw an exception.
return(false);
}
pnt =Marshal.AllocHGlobal(Marshal.SizeOf(data));
Marshal.Copy(buffer,0,pnt,buffer.Length);
data =(TraceData)Marshal.PtrToStructure(pnt,typeof(TraceData));
...
}
finally
{
Marshal.FreeHGlobal(pnt);
}
------------------

It is strange that the entry points of all the other functions in the DLL are found, but this one is
not. Unfortunately, this is the most important function, and I really need to call it. I've opened
the DLL with an hex viewer, and I do see a string named 'GETANALYZERTRACEDATA'.


Do you think that the error is in something that I'm coding, or in something in the DLL?

Is there any software tool that I can use to analyze what's inside a DLL, and what valid entry
points it "shows"?


Again, thank you very much.
Tremendo
 
W

Willy Denoyette [MVP]

Tremendo said:
Willy,

Thank you very much for your time and effort. I haven't had time to try what you wrote
until today.

Comments:
1) I have been unable to use the parameter "SuppressUnmanagedCodeSecurity" that you
proposed. It
said something like that it is not a valid parameter for DllImport. I ended up not
specifying that
parameter, and everything compiled ok.

You have to include the System.Security namespace for this, this attribute is a great help
when interop performance is important, it save some µsec per call.
2) All functions in "ae766.dll" except "GetAnalyzerTraceData" work ok in run time. I
communicate
correctly with the spectrum analyzer. It looks like, therefore, that I can live without
"SuppressUnmanagedCodeSecurity", at least regarding those functions.
3) The only function that does not work ok is "GetAnalyzerTraceData". I get a run time
error saying
something like (I have it in Spanish (against my will)) "Entry point
'GetAnalyzerTraceData' could
not be found in DLL file 'ae766.dll'". This error message points to line marked as "RUN
TIME ERROR"
in the following code:

------------------
[DllImport("ae766.dll")] private static extern int GetSerialNum();
[DllImport("ae766.dll")] private static extern int SetFreq(int freq);
...
[DllImport("ae766.dll")] private static extern int
GetAnalyzerTraceData([Out,MarshalAs(UnmanagedType.SafeArray)] out byte[] buffer);

...

try
{
if (1!=GetAnalyzerTraceData(out buffer)) // <--- RUN TIME ERROR
{
// throw an exception.
return(false);
}
pnt =Marshal.AllocHGlobal(Marshal.SizeOf(data));
Marshal.Copy(buffer,0,pnt,buffer.Length);
data =(TraceData)Marshal.PtrToStructure(pnt,typeof(TraceData));
...
}
finally
{
Marshal.FreeHGlobal(pnt);
}
------------------

It is strange that the entry points of all the other functions in the DLL are found, but
this one is
not. Unfortunately, this is the most important function, and I really need to call it.
I've opened
the DLL with an hex viewer, and I do see a string named 'GETANALYZERTRACEDATA'.
Do you think that the error is in something that I'm coding, or in something in the DLL?

Is there any software tool that I can use to analyze what's inside a DLL, and what valid
entry
points it "shows"?


Try dumpbin.exe with the option /exports, another tool you can use is depends.exe.
Note that none of the COM interface methods are exported functions, so you have to make sure
the DLL is not a COM server DLL.

Willy.
 
T

Tremendo

You have to include the System.Security namespace for this, this attribute is a great help
when interop performance is important, it save some µsec per call.

Ok, now it accepts that parameter.

I'll try with the DLL inspecting tools you mentioned.

Thanks.
 
T

Tremendo

Try dumpbin.exe with the option /exports, another tool you can use is depends.exe.
Note that none of the COM interface methods are exported functions, so you have to make sure
the DLL is not a COM server DLL.

Sorry about so many questions, but how do I know that the DLL is not a COM server DLL?

"dumpbin.exe /exports ae766.dll" returns the text below. It looks like GETANALYZERTRACEDATA is as
valid an entry point as other functions that are working for me.

Thank you.

--------------------
Microsoft (R) COFF/PE Dumper Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.


Dump of file \tmp\ae766.dll

File Type: DLL

Section contains the following exports for ate_dll.dll

00000000 characteristics
38075C19 time date stamp Fri Oct 15 18:53:45 1999
0.00 version
1 ordinal base
29 number of functions
29 number of names

ordinal hint RVA name

1 0 00002560 CHANGECOMPORT
24 1 00002560 ChangeComPort
2 2 00002220 ECHOOFF
3 3 000022D0 ECHOON
4 4 00001870 EXITTG
5 5 00001D70 EXTREF
6 6 00002040 FPGAVERSION
7 7 00002380 GETANALYZERTRACEDATA
8 8 00001450 GETMARKERLEVEL
9 9 00002940 GETSERIALNUM
25 A 00002940 GetSerialNum
10 B 00001700 MARKERTOCENTER
11 C 00001640 MARKERTOPEAK
12 D 00001930 NORMALIZETG
13 E 000027A0 SETFREQ
14 F 000026E0 SETRBW
15 10 00002670 SETREFLVL
16 11 000025D0 SETSPAN
17 12 000029F0 STARTSTREAMING
18 13 000017B0 STARTTG
19 14 00002B00 STOPSTREAMING
20 15 00001E60 SWVERSION
26 16 000027A0 SetFreq
27 17 000026E0 SetRBW
28 18 00002670 SetReflvl
29 19 000025D0 SetSpan
21 1A 00001B00 TGLEVEL
22 1B 00001C30 TGOFFSET
23 1C 000019E0 TGONOFF

Summary

2000 .data
1000 .idata
1000 .rdata
1000 .reloc
1000 .rsrc
3000 .text
 
T

Tremendo

Solved! :)

All other functions have two entry points, one with text all in uppercase, and another one with text
in upper and lowercase. However, this specific function has only one entry point, with text all in
uppercase. My call was in upper and lower case because the documentation shows it that way (and only
that way). I changed it to all upper case and it works.

Thanks !!
 
W

Willy Denoyette [MVP]

Tremendo said:
Solved! :)

All other functions have two entry points, one with text all in uppercase, and another one
with text
in upper and lowercase. However, this specific function has only one entry point, with
text all in
uppercase. My call was in upper and lower case because the documentation shows it that way
(and only
that way). I changed it to all upper case and it works.

Thanks !!

Yep, function names are case sensitive. If in doubt you can always use the EntryPoint
attribute and fill it with the ordinal number.

// GETANALYZERTRACEDATA has ordinal 7 (see dumpbin).
[DllImport("xxxx.dll", EntryPoint = "#7")]
...... GetAnaylizerTraceData(...)

Willy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top