Как мне вызвать эту функцию c в c # (неупорядоченная структура возврата)?

Я хочу использовать взаимодействие c # для вызова функции из dll, написанной на c. У меня есть файлы заголовков. Взгляните на это:

enum CTMBeginTransactionError {
    CTM_BEGIN_TRX_SUCCESS = 0,
    CTM_BEGIN_TRX_ERROR_ALREADY_IN_PROGRESS,
    CTM_BEGIN_TRX_ERROR_NOT_CONNECTED
};

#pragma pack(push)
#pragma pack(1)
struct CTMBeginTransactionResult {
    char *                        szTransactionID;
    enum CTMBeginTransactionError error;
};

struct CTMBeginTransactionResult ctm_begin_customer_transaction(const char * szTransactionID);

Как мне вызвать ctm_begin_customer_transaction из c #. Const char * хорошо отображается на строку, но, несмотря на различные попытки (глядя на stackoverflow и другие сайты), мне не удается выполнить маршалинг возвращаемой структуры. Если я определю функцию для возврата IntPtr, она будет работать нормально ...

Изменить Я изменил тип возвращаемого значения на IntPtr и использовал: CTMBeginTransactionResult structure = (CTMBeginTransactionResult) Marshal.PtrToStructure (ptr, typeof (CTMBeginTransactionResult)); но он выбрасывает AccessViolationException

Я также пробовал:

IntPtr ptr = Transactions.ctm_begin_customer_transaction("");
int size = 50;
byte[] byteArray = new byte[size];
Marshal.Copy(ptr, byteArray, 0, size);
string stringData = Encoding.ASCII.GetString(byteArray);

stringData == "70e3589b-2de0-4d1e-978d-55e22225be95 \ 0 \" \ 0 \ 0 \ a \ 0 \ 0 \ b \ b? "на этом этапе." 70e3589b-2de0-4d1e-978d-55e22225be95 "- szTransactionID из структуры. Где находится Enum? Это следующий байт?


person Eiver    schedule 08.03.2013    source источник


Ответы (2)


В этой структуре скрыта проблема управления памятью. Кому принадлежит указатель на строку C? Маршаллер pinvoke всегда будет предполагать, что он принадлежит вызывающему, поэтому он попытается освободить строку. И передает указатель на CoTaskMemFree (), ту же функцию, что и вызываемая Marshal.FreeCoTaskMem (). Эти функции используют распределитель памяти COM, универсальный диспетчер памяти взаимодействия в Windows.

Это редко заканчивается хорошо, код C обычно не использует этот распределитель, если программист не проектировал свой код с учетом взаимодействия. В этом случае он никогда бы не использовал структуру в качестве возвращаемого значения, взаимодействие всегда работает гораздо менее беспроблемно, когда вызывающий объект предоставляет буферы.

Таким образом, вы не можете позволить маршаллеру выполнять свои обычные обязанности. Вы должны объявить тип возвращаемого значения как IntPtr, чтобы он не пытался освободить строку. И вы должны упорядочить его самостоятельно с помощью Marshal.PtrToStructure ().

Однако это все еще оставляет без ответа вопрос, кому принадлежит строка? Вы ничего не можете сделать, чтобы освободить строковый буфер, у вас нет доступа к распределителю, используемому в коде C. Единственная надежда, что у вас есть, это то, что строка на самом деле не была размещена в куче. Возможно, программа на C может использовать строковые литералы. Вам нужно проверить это предположение. Вызовите функцию миллиард раз в тестовой программе. Если это не взорвет программу, то все в порядке. Если нет, то только C ++ / CLI может решить вашу проблему. Учитывая характер строки, «идентификатор транзакции» должен сильно измениться, я бы сказал, что у вас действительно есть проблема.

person Hans Passant    schedule 08.03.2013
comment
Спасибо за ваш ответ. Комментарий - плохое место для размещения моего ответа, поэтому вместо этого отредактировал вопрос. - person Eiver; 08.03.2013

Я не хочу отвечать на свой вопрос, но я нашел решение для маршалинга полученной структуры. Длина структуры 8 байтов (4 байта для char * и 4 байта для enum). Маршалинг строки не работает автоматически, но работает следующее:

// Native (unmanaged)
public enum CTMBeginTransactionError
{
    CTM_BEGIN_TRX_SUCCESS = 0,
    CTM_BEGIN_TRX_ERROR_ALREADY_IN_PROGRESS,
    CTM_BEGIN_TRX_ERROR_NOT_CONNECTED
};

// Native (unmanaged)
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
internal struct CTMBeginTransactionResult
{
    public IntPtr szTransactionID;
    public CTMBeginTransactionError error;
};

// Managed wrapper around native struct
public class BeginTransactionResult
{
    public string TransactionID;
    public CTMBeginTransactionError Error;

    internal BeginTransactionResult(CTMBeginTransactionResult nativeStruct)
    {
        // Manually marshal the string
        if (nativeStruct.szTransactionID == IntPtr.Zero) this.TransactionID = "";
        else this.TransactionID = Marshal.PtrToStringAnsi(nativeStruct.szTransactionID);

        this.Error = nativeStruct.error;
    }
}

[DllImport("libctmclient-0.dll")]
internal static extern CTMBeginTransactionResult ctm_begin_customer_transaction(string ptr);

public static BeginTransactionResult BeginCustomerTransaction(string transactionId)
{
    CTMBeginTransactionResult nativeResult = Transactions.ctm_begin_customer_transaction(transactionId);
    return new BeginTransactionResult(nativeResult);
}

Код работает, но мне все еще нужно выяснить, приводит ли вызов неуправляемого кода к утечкам памяти.

person Eiver    schedule 11.03.2013