Groups | Blog | Home
all groups > dotnet interop > january 2006 >

dotnet interop : Calling C written API from C# shows strange behaviour


jeanpaul.smit NO[at]SPAM gmail.com
1/31/2006 8:48:27 AM
I have some strange behaviour when calling a function on a C written
API.
The API is part of the XCOM server by Computer Associates.
It is very hard to find anything about this on the internet, so I hope
to find some help here.

The XCOM server documentation comes with a piece of sample code in C to
program against the API. I have converted that sample code into a
library and wrote a wrapper in C# to call it. (at the end the code
should be called from a webservice which in its turn is called from
BizTalk)

The following is going on:
1) if I build the sample code to a C written EXE, it runs fine
2) if I move the sample code to a library and build a C written EXE
calling it, it runs fine
3) If I build a C# wrapper calling the C written library, works only
for one function (??)

Concerning the third, the library contains 2 functions and one function
runs fine and the other fails with "Object reference not set to an
instance of an object".

I use the following declarations in my C written API (that used to be
the sample code):

__declspec(dllexport) int __cdecl XcomSendFile(char* param1, char*
param2, .... )
__declspec(dllexport) char* __cdecl XcomGetCodeDescription(int code)

In the C# wrapper I use the following statement to be able to access
the C written library:

[DllImport("XcomApi.dll")]
static extern char* XcomSendFile(string param1, string param2, ....);
[DllImport("XcomApi.dll")]
static extern string XcomGetCodeDescription(int code);

I'm pretty sure that the code that fails is identical to the code that
runs and the libraries used are the same.
At first I thought that there must be a difference between calling it
via C and calling it via C#, but I'm not so sure about that anymore
because one function works fine.

Does anyone know what is going on?

Thanks in advance!
Willy Denoyette [MVP]
1/31/2006 6:57:41 PM

[quoted text, click to view]
|I have some strange behaviour when calling a function on a C written
| API.
| The API is part of the XCOM server by Computer Associates.
| It is very hard to find anything about this on the internet, so I hope
| to find some help here.
|
| The XCOM server documentation comes with a piece of sample code in C to
| program against the API. I have converted that sample code into a
| library and wrote a wrapper in C# to call it. (at the end the code
| should be called from a webservice which in its turn is called from
| BizTalk)
|
| The following is going on:
| 1) if I build the sample code to a C written EXE, it runs fine
| 2) if I move the sample code to a library and build a C written EXE
| calling it, it runs fine
| 3) If I build a C# wrapper calling the C written library, works only
| for one function (??)
|
| Concerning the third, the library contains 2 functions and one function
| runs fine and the other fails with "Object reference not set to an
| instance of an object".
|
| I use the following declarations in my C written API (that used to be
| the sample code):
|
| __declspec(dllexport) int __cdecl XcomSendFile(char* param1, char*
| param2, .... )
| __declspec(dllexport) char* __cdecl XcomGetCodeDescription(int code)
|
| In the C# wrapper I use the following statement to be able to access
| the C written library:
|
| [DllImport("XcomApi.dll")]
| static extern char* XcomSendFile(string param1, string param2, ....);
| [DllImport("XcomApi.dll")]
| static extern string XcomGetCodeDescription(int code);
|
| I'm pretty sure that the code that fails is identical to the code that
| runs and the libraries used are the same.
| At first I thought that there must be a difference between calling it
| via C and calling it via C#, but I'm not so sure about that anymore
| because one function works fine.
|
| Does anyone know what is going on?
|
| Thanks in advance!
|

You didn't mention which call fails, so I suppose it is this one:
static extern string XcomGetCodeDescription(int code);
here you expect a string to be returned while the C function returns a char*
(I guess). Well this is't a supported scenario for PInvoke interop, you need
to change your declaration int:
static extern IntPtr XcomGetCodeDescription(int code);
and marshal the IntPtr to a string using Marshal.PtrToStringAuto or
Marshal.PtrToStringAnsi.

Willy.


jeanpaul.smit NO[at]SPAM gmail.com
2/1/2006 12:25:19 AM
Willy, first of all thanks for your reply.

While reading back my post I noticed that you're right, I didn't
specify which function fails and I saw I also made a mistake.
The mistake is in the DllImport declaration, where the XcomSendFile
function actually doesn't return a char* but an int.
The correct declaration is:

[DllImport("XcomApi.dll")]
static extern int XcomSendFile(string param1, string param2, ....);

The method that fails is the XcomSendFile and not the
XcomGetCodeDescription.
While driving home last night I realized that I didn't supply enough
information about the call that fails so I hope to make it up now.

The function XcomSendFile contains the following code:

__declspec(dllexport) int XcomSendFile(char* param1, char* param2,
.......)
{
int rc;
XCOM_PARM xcomparm;
int starting_state;
memset(&xcomparm,0,sizeof(xcomparm));
xcomparm.file_type = SEND_FILE;
xcomparm.protocol = "TCPIP";
..................................................
starting_state = LOCAL_SEND;
--> rc = XcomAPI(starting_state, xcomparm);
return rc;
}

The code fails at the line marked with an arrow. That is a function
call to a library.
What I don't understand is why this function call works fine when I
call it from a C written Exe and it fails when I call it form the C#
wrapper.
The fact that the second function (XcomGetCodeDescription) does work
fine also bothers me, but that could be caused by the fact that the
function is located in a different library (?).

Thanks for your remark concerning the XcomGetCodeDescription method and
the way to return a string.
It solved a notification from Visual Studio while debugging. :-)

I hope you can help me again with the additional information.

Thanks!
jeanpaul.smit NO[at]SPAM gmail.com
2/1/2006 8:19:03 AM
Hi Willy,

Unfortunately I don't get something returned in rc.

I call the API method from a C# wrapper like this:
try
{
int retVal = XcomSendFile(val1, val2, ....);
}
catch(exception ex)
{
Console.WriteLine(ex.message);
}

If I step through this code it enters the catch without any value in
rc.

Regards,
Jean-Paul
Willy Denoyette [MVP]
2/1/2006 1:37:31 PM
I don't see what could go wrong in this function.
rc = XcomAPI(starting_state, xcomparm);
only uses local variables, I don't see any use of the arguments (param1...)
in the snip you posted, so I guess you pass some arguments in xcomparm .....
You said above function fails that means you have a value returned in rc
which indicates a failure right? This should give you some idea what's wrong
with the function and the args. passed isn't it?


Willy.


[quoted text, click to view]
| Willy, first of all thanks for your reply.
|
| While reading back my post I noticed that you're right, I didn't
| specify which function fails and I saw I also made a mistake.
| The mistake is in the DllImport declaration, where the XcomSendFile
| function actually doesn't return a char* but an int.
| The correct declaration is:
|
| [DllImport("XcomApi.dll")]
| static extern int XcomSendFile(string param1, string param2, ....);
|
| The method that fails is the XcomSendFile and not the
| XcomGetCodeDescription.
| While driving home last night I realized that I didn't supply enough
| information about the call that fails so I hope to make it up now.
|
| The function XcomSendFile contains the following code:
|
| __declspec(dllexport) int XcomSendFile(char* param1, char* param2,
| ......)
| {
| int rc;
| XCOM_PARM xcomparm;
| int starting_state;
| memset(&xcomparm,0,sizeof(xcomparm));
| xcomparm.file_type = SEND_FILE;
| xcomparm.protocol = "TCPIP";
| ..................................................
| starting_state = LOCAL_SEND;
| --> rc = XcomAPI(starting_state, xcomparm);
| return rc;
| }
|
| The code fails at the line marked with an arrow. That is a function
| call to a library.
| What I don't understand is why this function call works fine when I
| call it from a C written Exe and it fails when I call it form the C#
| wrapper.
| The fact that the second function (XcomGetCodeDescription) does work
| fine also bothers me, but that could be caused by the fact that the
| function is located in a different library (?).
|
| Thanks for your remark concerning the XcomGetCodeDescription method and
| the way to return a string.
| It solved a notification from Visual Studio while debugging. :-)
|
| I hope you can help me again with the additional information.
|
| Thanks!
|

Willy Denoyette [MVP]
2/1/2006 10:15:14 PM

[quoted text, click to view]
| Hi Willy,
|
| Unfortunately I don't get something returned in rc.
|
| I call the API method from a C# wrapper like this:
| try
| {
| int retVal = XcomSendFile(val1, val2, ....);
| }
| catch(exception ex)
| {
| Console.WriteLine(ex.message);
| }
|
| If I step through this code it enters the catch without any value in
| rc.
|
| Regards,
| Jean-Paul
|

Please post the exception. And the complete declaration of the structure
XCOM_PARM and the complete code that fills the structure.

Willy.

jeanpaul.smit NO[at]SPAM gmail.com
2/2/2006 12:03:13 AM
Willy,

This is the complete code of the function in the C library:
------------------------------------------------------------------------------------------------------------------------------------
__declspec(dllexport) int __cdecl XcomSendFile(char*
remoteSystemUserId,
char* remoteSystemUserPwd,
char* remoteSystemIpAddress,
char* remoteSystemPortNumber,
char* localSystemFileLocation,
char* remoteSystemFileLocation)
{
int rc;

XCOM_PARM xcomparm;
int starting_state;
memset(&xcomparm,0,sizeof(xcomparm));

// Mark the file as to send
xcomparm.file_type = SEND_FILE;

// Define the TCPIP protocol is used
xcomparm.protocol = "TCPIP";

// IP address and portnumber of remote server
xcomparm.remote_system = remoteSystemIpAddress;
xcomparm.port = remoteSystemPortNumber; // "8044" is a regular number;

xcomparm.xluname = "";
xcomparm.xnodespec = NULL;

xcomparm.xmode = "";

xcomparm.xidest = NULL;

// Source and destination location of files to send
xcomparm.local_file = localSystemFileLocation;
xcomparm.remote_file = remoteSystemFileLocation;

// Source and destination location of files to receive, not used
here!!
xcomparm.local_file_rf = "";
xcomparm.remote_file_rf = "";

xcomparm.file_option = "REPLACE";
xcomparm.remove_trail_blanks = NULL;
xcomparm.volume = NULL;
xcomparm.unit = NULL;
xcomparm.record_format = NULL;
xcomparm.lrecl = NULL;
xcomparm.blksize = NULL;

xcomparm.local_file_sr = NULL;
xcomparm.class = NULL;
xcomparm.destination = NULL;
xcomparm.form = NULL;
xcomparm.fcb = NULL;
xcomparm.copies = NULL;
xcomparm.report_title = NULL;
xcomparm.hold = NULL;
xcomparm.spool_flag = NULL;
xcomparm.disposition = NULL;
xcomparm.carriage_control_characters = NULL;

xcomparm.local_file_sj = NULL;

// UserId and password of account on remote server
xcomparm.userid = remoteSystemUserId;
xcomparm.password = remoteSystemUserPwd;

/* if queue = NO, must link with right xcom lib */
xcomparm.queue = "NO";

xcomparm.start_time = NULL;
xcomparm.start_date = NULL;

xcomparm.notifyr = NULL;
xcomparm.notify_name = NULL;
xcomparm.local_notify = NULL;
xcomparm.notifyl = NULL;
xcomparm.notify_term = NULL;

xcomparm.maxreclen = NULL;
xcomparm.code_flag = "ASCII";
xcomparm.carriage_flag = NULL;
xcomparm.truncation = "YES";
xcomparm.compress = NULL;

xcomparm.xtrace = "10";
xcomparm.stat_frequency = "1";
xcomparm.debug_flag = NULL;

xcomparm.xlogfile = NULL;
xcomparm.tempdir = NULL;

xcomparm.xlpcmd = NULL;
xcomparm.xnotifycmd = NULL;
xcomparm.eol_classes = NULL;
xcomparm.convert_classes = NULL;
xcomparm.metacode_classes = NULL;

/* These are version two parms */
xcomparm.version = NULL;
xcomparm.number_of_retries = NULL;
xcomparm.allocation_type = NULL;
xcomparm.checkpoint_count = NULL;
xcomparm.num_of_dir_blocks = NULL;
xcomparm.primary_alloc = NULL;
xcomparm.secondary_alloc = NULL;
xcomparm.restart_supported = NULL;

/* These parms are new for V3 XCOM */
xcomparm.xppcmd = NULL;
xcomparm.xprecmd = NULL;
xcomparm.xendcmd = NULL;
xcomparm.shell_cmd = NULL;

/* These parms are new for V3.1 XCOM */
xcomparm.trusted = NULL;
xcomparm.domain = NULL;

xcomparm.den = NULL;
xcomparm.expdt = NULL;
xcomparm.retpd = NULL;
xcomparm.label = NULL;
xcomparm.tape = NULL;
xcomparm.unitct = NULL;
xcomparm.volct = NULL;
xcomparm.volsq = NULL;
xcomparm.storcls = NULL;
xcomparm.datacls = NULL;
xcomparm.mgtclas = NULL;
xcomparm.dsntype = NULL;
xcomparm.hfs_flag = NULL;
xcomparm.labelnum = NULL;

/* These parms are user data for V3.1 XCOM */
xcomparm.user_data = NULL;
xcomparm.transfer_usr_data = NULL;
xcomparm.transfer_id = NULL;

/* These parms are new for r11 XCOM */
xcomparm.xcomfullssl = NULL;
xcomparm.configssl = NULL;

xcomparm.codetabl = NULL;
xcomparm.rmtntfyl = NULL;
xcomparm.lclntfyl = NULL;
xcomparm.storcls = NULL;
xcomparm.datacls = NULL;
xcomparm.mgtclas = NULL;
xcomparm.dsntype = NULL;

// Send the file
starting_state = LOCAL_SEND;

rc = XcomAPI(starting_state, xcomparm);

return rc;
}

------------------------------------------------------------------------------------------------------------------------------------
This is the calling code from C#
------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Runtime.InteropServices;

namespace TestXcomAppWrapper
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{

/// <summary>
/// Define the win32 API signature to be able to call LoadLibrary
/// </summary>
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
static extern IntPtr LoadLibrary(string lpFileName);

/// <summary>
/// Define the XCOM API signature to be able to call XcomSendFile
/// </summary>
[DllImport("Stater.Isvcs.Iface.Tandem.XcomApi.dll",
SetLastError=true)]
static extern int XcomSendFile( string remoteSystemUserId,
string remoteSystemUserPwd,
string remoteSystemIpAddress,
string remoteSystemPortNumber,
string localSystemFileLocation,
string remoteSystemFileLocation);

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
int retVal = 1;
try
{
string dllPath =
System.Configuration.ConfigurationSettings.AppSettings["XcomApiDllFileLocation"].ToString();
System.IntPtr libraryPtr = LoadLibrary(dllPath);
Console.WriteLine("LoadLibrary handle: " + libraryPtr);

retVal = XcomSendFile("UserID", "Pwd", "1.0.0.127", "8044",
"c:\\_TandemXcomTest\\test.txt",
"c:\\_TandemXcomTest\\test_after.txt");
Console.WriteLine("retval:" + retVal);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

------------------------------------------------------------------------------------------------------------------------------------
The exact exception is "Object reference not set to an instance of an
object".
After specifying SetLastError = true in the DllImport I can catch the
exception in the C# exception handler and it says then that a module
could not be found.
That would be strange in my view because the following C written EXE
calling the same library is running fine.

------------------------------------------------------------------------------------------------------------------------------------
#include <stdio.h>
#include "xcomapi.h"

main(int argc,char **argv)
{
int rc;
AddThis Social Bookmark Button