首页 文章

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

提问于
浏览
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 * 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 回答

  • 5

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

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

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

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

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

    代码工作,但我仍然需要调查,如果调用非托管代码导致内存泄漏 .

相关问题