8

我想使用 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 * 很好地映射到字符串,但尽管进行了各种尝试(查看 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。枚举在哪里?是下一个字节吗?

4

2 回答 2

5

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

这很少有好的结果,C 代码通常不使用该分配器,除非程序员在设计他的代码时考虑到互操作。在这种情况下,他永远不会使用结构作为返回值,当调用者提供缓冲区时,互操作的工作总是少得多。

因此,您不能让编组器履行其正常职责。您必须将返回值类型声明为 IntPtr,这样它就不会尝试释放字符串。你必须自己用 Marshal.PtrToStructure() 来编组它。

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

于 2013-03-08T13:18:46.223 回答
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);
}

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

于 2013-03-11T13:37:29.870 回答