tags:

views:

2184

answers:

5

Hi all,

I'm trying to call a method from a DLL written in C, from my C# application. Here is how I've implemented the call:

--> C method signature: (from a third-part company)

Int16 SSEXPORT SS_Initialize(Uint16 ServerIds[], Uint16 ServerQty, const char PTR *Binding, const char PTR *LogPath, Uint16 LogDays, Int16 LogLevel,
Uint16 MaxThreads, Uint16 ThreadTTL, Int16 (CALLBACK *ProcessMessage)(Uint16, const char PTR *, Uint32, char PTR **, Uint32 PTR *, Int16 PTR *));
--> C types definition:
#ifndef _CSISERVERTYPES_
#define _CSISERVERTYPES_

#if defined(OS_NT)

#define CALLBACK __stdcall 
#define SSEXPORT __stdcall
#define PTR      

typedef char       Int8;
typedef unsigned char    Uint8;
typedef short      Int16;
typedef unsigned short   Uint16;
typedef long       Int32;
typedef unsigned long    Uint32;
typedef unsigned char    Byte;
typedef unsigned short   Word;
typedef unsigned long    Dword;
typedef short      Bool;

#endif

typedef Int16 (CALLBACK *PM_Function)
     (Uint16,const char PTR *,Uint32,char PTR **,Uint32 PTR *,Int16 PTR *);

#define SS_OK    0
#define SS_NOT_INIT   -1
#define SS_PROC_ERR   -2
#define SS_ERR_TCP_SRV  -3
#define SS_ERR_OUT_MEM  -4

#ifndef NULL
#define NULL       0
#endif

#endif

--> C# DLL Method declaration:

[DllImport("CSITCPServer.dll", SetLastError = true)]
    static extern Int16 SS_Initialize(
        UInt16[] ServerIds,
        UInt16 ServerQty,
        ref string Binding,
        ref string LogPath,
        UInt16 LogDays,
        Int16 LogLevel,
        UInt16 MaxThreads,
        UInt16 ThreadTTL,
        ProcessMessageCallback callback);

Method call:

public static void Call_SS_Initialize()
    {   
        Int16 ret;
        UInt16[] serverIds = new UInt16[] { 2020, 2021 };
        string binding = "";
        string logPath = "";

        try
        {
            ret = SS_Initialize(
                serverIds,
                Convert.ToUInt16(serverIds.ToList().Count),
                ref binding,
                ref logPath,
                10,
                0,
                256,
                300,
                ProcessMessage);

            Console.WriteLine("Call_SS_Initialize() -> Result of SS_Initialize:  {0}", ret.ToString());
        }
        catch (Exception ex)
        {
            Int32 err = Marshal.GetLastWin32Error();
            throw new Win32Exception(err);
            //throw;
        }
    }

And then I get the Win32Exception: 1008 - An attempt was made to reference a token that does not exist

I know that the problem must be in the CHAR to STRING conversion between unmanaged (C) and managed (C#) codes. If I modify the Binding or LogPath parameters to type SByte, it doesn't give any errors. But since the method expects for a text (string), I don't know how can I pass the text to the method since it expects a SByte variable...

I'm aware that I might have to use something like MarshalAs, but I've tried a few options and didn't had any success.

Can anybody tell me what am I doing wrong??

Thank you very much!!

A: 

For marshalling strings you don't pass ref string, just string. Also, you should decorate each string with [MarshalAs( UnmanagedType.LPStr)] to indicate, that you are passing string that contains ascii chars and it's null terminated.
Edit: Could you also give your definition of this callback procedure, as you could made similar mistakes.

Ravadre
A: 

Here is the callback definition:

 public delegate Int16 ProcessMessageCallback(
        UInt16 ServerId,
        [MarshalAs(UnmanagedType.LPStr)] ref string RequestMsg,
        UInt32 RequestSize,
        [MarshalAs(UnmanagedType.LPStr)] ref string ResponseMsg,
        ref UInt32 ResponseSize,
        ref Int16 AppErrCode);

The thing is that the C DLL method is expecting a "REF" parameter. The call to SS_Initialize attachs the execution to the ProcessMessage callback function. In this function I need to be able to get the modified parameters (ref) from SS_Initialize ...

Could you please suggest how do you think the code should be structured?

Thank you!!

Rodrigo
What do you mean by "dll method is expecting a "REF" parameter"? There's no such thing as "ref" parameter from C point of view. scratch ref for char*, and as for char**, you can refer to http://forums.devx.com/showthread.php?t=15511 , or check Ubben's post.
Ravadre
A: 

You do not need to use ref for Binding and LogPath; they are const char* and will not change.

That ResponseMessage in the callback will return an array of strings, (char **) not just a single string. The Response size probably will indicate the depth of the array. Try [MarshalAs(UnamangedType.LPArray, ArraySubType=UnmanagedType.LPStr)] string[] instead.

R Ubben
A: 

Thank you Ravadre and R Ubben, your help combined did the trick!!

In the end, it was all about controlling properly the REF clause in the parameters and using the MarshalAs tag. The parameters that are as "const char*" in the C part really doesn't need the REF tag. Also I used [MarshalAs(UnmanagedType.LPStr)] for the string parameters. That's it!

ps: Stackoverflow.com is amazing! I've already found a lot of answers to other problems here just by searching. This is my first post and I'm surprised with the quick replies I've got for this one. Congratulations to all staff team and members! ;)

Rodrigo
A: 

Hi guys,

I have a similar problem I can't fix with your solution.. I need to convert a char* to string instead of const char*... Do you know how to do it?

Thanks.

Best regards.