Проблема с функцией обратного вызова C в C # - как передать значение указателю? - PullRequest
0 голосов
/ 07 августа 2009

У меня есть обратный вызов C, определенный следующим образом:

Int16 (CALLBACK *ProcessMessage)(Uint16 ServerId,
   const char PTR *RequestMsg, Uint32 RequestSize,
   char PTR **ResponseMsg, Uint32 PTR *ResponseSize,
   Int16 PTR *AppErrCode);

Пример использования этого обратного вызова в C:

Int16 CALLBACK ProcessMessage(Uint16 ServerId, const char PTR *RequestMsg, Uint32 RequestSize, char PTR **ResponseMsg, Uint32 PTR *ResponseSize, Int16 PTR *AppErrCode)
{
    printf("ProcessMessage() -> ServerId=%u\n", ServerId);

    //**** SET THE VALUE FOR RESPONSEMSG (POINTER), THAT'S WHAT I NEED TO DO IN C# ****       
    sprintf(resp,"(%05lu) REPLY TEST", ServerId);
    *ResponseMsg = resp;

    printf("ProcessMessage() -> atribuido %p(p) a *ResponseMsg\n", *ResponseMsg);
    *ResponseSize = strlen(*ResponseMsg);
    *AppErrCode = -1; 
    return SS_OK;
}

Тогда я реализовал этот обратный вызов в C #:

 [DllImport("Custom.dll", SetLastError = true)]
    static extern Int16 SS_Initialize(
        UInt16[] ServerIds,
        UInt16 ServerQty,
        [MarshalAs(UnmanagedType.LPStr)] string Binding,
        [MarshalAs(UnmanagedType.LPStr)] string LogPath,
        UInt16 LogDays,
        Int16 LogLevel,
        UInt16 MaxThreads,
        UInt16 MaxConThread,
        ProcessMessageCallback callback);  

Определение обратного вызова:

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

Метод, который устанавливает обратный вызов:

public void Call_SS_Initialize(
        UInt16[] serverIds,
        string binding,
        string logPath,
        UInt16 logDays,
        Int16 logLevel,
        UInt16 maxThreads,
        UInt16 maxConThread
        )
    {
        Int16 ret;
        try
        {
            pmc = new ProcessMessageCallback(ProcessMessage);

            ret = SS_Initialize(
                serverIds,
                Convert.ToUInt16(serverIds.ToList().Count),
                binding,
                logPath,
                logDays,
                logLevel,
                maxThreads,
                maxConThread,
                pmc);
        }
    }

И, наконец, метод обратного вызова, где ПРОБЛЕМА ЕСТЬ:

public Int16 ProcessMessage(
      UInt16 ServerId,
      string RequestMsg,
      UInt32 RequestSize,
      ref string ResponseMsg,
      ref UInt32 ResponseSize,
      ref Int16 AppErrCode)
    {
       //Implement return to ResponseMsg POINTER
    }

Проблема в том, что ResponseMsg на самом деле является POINTER в C. Поэтому в методе C # ProcesMessage я должен установить для ResponseMsg пробел в память (указатель), откуда DLL будет получать строку.

Я не могу просто установить ResponseMsg = "REPLY", потому что, когда метод заканчивает память, где строка уже уничтожена.

Как я могу это сделать ?? Пожалуйста, любые советы приветствуются !!

Спасибо!

Ответы [ 5 ]

1 голос
/ 07 августа 2009

Вот что я сделал, чтобы воссоздать это. Может быть, мой эксперимент поможет.

Код C #:

public delegate void ProcessMessageCallback(
        [MarshalAs(UnmanagedType.LPStr)] ref string ResponseMsg);

static class test
{
    [DllImport("test.dll")]
    static extern void TestCallback(ProcessMessageCallback callback);

    static public void Main(string[] args)
    {
        TestCallback(MyCallback);
    }

    static void MyCallback(ref string ResponseMsg)
    {
        ResponseMsg = "hi there";
    }
}

Код C (в DLL):

#include <windows.h>
#include "objbase.h"

__declspec(dllexport) void TestCallback(
    void (* CALLBACK managedCallback(char **)))
{
    char *test = NULL;
    managedCallback(&test);
    printf(test);
    CoTaskMemFree(test); // NB!
}

Это успешно распечатывает "привет там".

Примечание: CoTaskMemFree должен использоваться для освобождения памяти, выделенной слоем P / Invoke. Если вы хотите, чтобы возвращаемая строка оставалась дольше, чем метод, который вызывает ваш обратный вызов C #, попробуйте скопировать ее в другое место перед освобождением возвращенной памяти.

0 голосов
/ 05 декабря 2015

Код C #:

public delegate void ProcessMessageCallback(StringBuilder ResponseMsg);

static class test
{
    [DllImport("test.dll")]
    static extern void TestCallback(ProcessMessageCallback callback);

    static public void Main(string[] args)
    {
        TestCallback(MyCallback);
    }

    static void MyCallback(StringBuilder ResponseMsg)
    {
        ResponseMsg.Append("hi there");
    }
}

Код C (в DLL):

typedef int(__stdcall * Callback)(char *message);

__declspec(dllexport) void TestCallback(Callback callback) 
{
    char* test = (char*)malloc(10000);
    test[0] = '\0';
    callback(test);
    printf(test);
    free(test);
}
0 голосов
/ 24 октября 2009

Попробуйте изменить тип ResponseMsg на StringBuilder и убедитесь, что емкости достаточно для хранения ответа.

0 голосов
/ 07 августа 2009

Причиной, по которой это приводит в замешательство P / Invoke, является необычная семантика управления памятью, подразумеваемая этим. Вы фактически возвращаете строку, выделенную неизвестным способом, вызывающей стороне, которая затем не освобождает ее (по крайней мере, насколько я вижу). Это довольно проблематичный дизайн (не ориентированный на многопотоковое исполнение и, возможно, не реентерабельный), и определенно не типичный.

На самом деле вы ничего не можете здесь сделать со строкой, потому что код C на самом деле не получает указатель непосредственно на ваши строковые данные. Поскольку строки C # являются Unicode, и вы запросили, чтобы строка была маршалирована как ANSI, будет произведена и возвращена «копия» (с результатом преобразования Unicode в ANSI). Я понятия не имею ни о времени жизни, ни о том, как это контролировать, и я не вижу никаких гарантий в документации.

Так что, похоже, вам лучше всего управлять этим самостоятельно, используя Marshal.AllocHGlobal для выделения буфера (вероятно, только один раз для всех вызовов, так же, как ваш код C), Encoding.GetBytes для преобразования строк в байтовые массивы и Marshal.Copy для копирования результирующих байтов в выделенный буфер.

0 голосов
/ 07 августа 2009

Как насчет:

IntPtr p = Marshal.GetFunctionPointerForDelegate(pmc);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...