如何在c#(解组返回结构)中调用此c函数?

时间:2013-03-08 12:03:18

标签: c# interop pinvoke unmarshalling

我想使用c#interop从用c编写的dll调用函数。我有头文件。 看看这个:

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);

如何从c#调用ctm_begin_customer_transaction。 const char * mapps很好地用于字符串,但是尽管有各种尝试(查看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。枚举在哪里?它是下一个字节吗?

2 个答案:

答案 0 :(得分:5)

此结构中隐藏了内存管理问题。谁拥有C字符串指针? pinvoke marshaller将始终假定调用者拥有它,因此它将尝试释放字符串。并将指针传递给CoTaskMemFree(),与Marshal.FreeCoTaskMem()调用的函数相同。这些函数使用COM内存分配器,Windows中的通用互操作内存管理器。

这很少有好结果,C代码通常不会使用该分配器,除非程序员设计了他的代码并考虑到了interop。在这种情况下,他从来没有使用过结构作为返回值,当调用者提供缓冲区时,互操作总是更加无故障。

所以你不能让编组人员履行其正常职责。您必须将返回值类型声明为IntPtr,以便它不会尝试释放字符串。你必须自己用Marshal.PtrToStructure()编组。

然而,这仍然没有答案,谁拥有字符串?您无法释放字符串缓冲区,也无法访问C代码中使用的分配器。你唯一的希望是字符串实际上没有在堆上分配。这是可能的,C程序可能正在使用字符串文字。你需要验证猜测。在测试程序中调用该函数十亿次。如果这没有爆炸程序,那么你很好。如果没有,那么只有C ++ / CLI可以解决您的问题。鉴于字符串的性质,“交易ID”应该改变很多,我会说你确实有问题。

答案 1 :(得分:1)

我讨厌回答我自己的问题,但是我找到了解决方案来编组生成的结构。结构长度为8个字节(char *为4个字节,枚举为4个字节)。编组字符串不会自动运行,但以下工作:

// 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);
}

代码有效,但我仍需要调查,如果调用非托管代码导致内存泄漏。