如何解决C#调用以优化C ++ dll函数的行为
经过为期一周的调试会议并搜索了问题之后,我问了我的朋友他的想法,幸运的是,他遇到了类似的问题,因此他指出了我要解决的问题,最终使我弄清楚了问题出在哪里,最后我设法找到了一个稳定的(希望)修复程序,但是我仍然不确定问题出在哪里(我猜不透,但稍后)。
首先,我有一个非托管的C ++ dll,可从C#中使用。基础结构(这是我重新创建的虚拟解决方案,仅用于重新创建和调试我在没有其他代码冲突的情况下遇到的问题),如下所示:
C ++代码:
===== device.h: =====
#pragma once
#include <cinttypes>
namespace abstraction
{
class device final
{
public: uint32_t activity;
public: uint32_t activities;
public: device(uint32_t limit);
public: bool execute();
};
}
===== device.cpp =====
#include "device.h"
namespace abstraction
{
device::device(uint32_t limit)
{
activity = 1u;
activities = limit;
}
bool device::execute()
{
if (activity >= activities) return false;
++activity;
return true;
}
}
C ++ dll API:
===== api.h: =====
#pragma once
#include <abstraction/device.h> // it's the device above
#ifdef __TEST_API__
#define __dll __declspec(dllexport)
#else
#define __dll __declspec(dllimport)
#endif
#define dynamic extern "C" __dll
struct __dll Hndl
{
uint64_t ptr;
};
// RC = ReinterpretCast
template <typename T> inline Hndl RC(T* obj) { return { reinterpret_cast<uint64_t>(obj) }; }
template <typename T> inline Hndl RC(T& obj) { return RC(&obj); }
template <typename T> inline T* RC(Hndl hndl) { return reinterpret_cast<T*>(hndl.ptr); }
// ===== Device API =====
dynamic Hndl CreateDevice(uint32_t limit);
dynamic void DisposeDevice(Hndl _device);
dynamic uint32_t DeviceActivity(Hndl _device);
dynamic uint32_t DeviceActivities(Hndl _device);
dynamic bool DeviceExecute(Hndl _device);
===== api.cpp: =====
#include "api.h"
using namespace abstraction;
// ===== Device API =====
dynamic Hndl CreateDevice(uint32_t limit)
{
return RC(new device(limit));
}
dynamic void DisposeDevice(Hndl _device)
{
delete RC<device>(_device);
}
dynamic uint32_t DeviceActivity(Hndl _device)
{
return RC<device>(_device)->activity;
}
dynamic uint32_t DeviceActivities(Hndl _device)
{
return RC<device>(_device)->activities;
}
dynamic bool DeviceExecute(Hndl _device)
{
return RC<device>(_device)->execute();
}
C#API:
using System.Runtime.InteropServices;
namespace CSDllProject
{
[StructLayout(LayoutKind.Sequential,CharSet = CharSet.Unicode)]
public struct Hndl
{
public static Hndl Invalid = new Hndl(0ul);
private ulong ptr;
public ulong Ptr { get => ptr; internal set => ptr = value; }
public bool ValidHandle => Ptr != 0ul;
internal Hndl(ulong ptr) { this.ptr = ptr; }
public override bool Equals(object obj) => base.Equals(obj);
public override int GetHashCode() => base.GetHashCode();
public static bool Equal(Hndl h1,Hndl h2) => h1.Ptr == h2.Ptr;
public static bool operator ==(Hndl h1,Hndl h2) => Equal(h1,h2);
public static bool operator !=(Hndl h1,Hndl h2) => !Equal(h1,h2);
}
public static class API
{
#if DEBUG
private const string TestApi = @"<path_to_debug_c++_dll>";
#else
private const string TestApi = @"<path_to_release_c++_dll>";
#endif
// ===== Device API =====
[DllImport(TestApi,CallingConvention = CallingConvention.Cdecl,CharSet = CharSet.Unicode)]
public static extern Hndl CreateDevice(uint limit);
[DllImport(TestApi,CharSet = CharSet.Unicode)]
public static extern void DisposeDevice(Hndl _device);
[DllImport(TestApi,CharSet = CharSet.Unicode)]
public static extern uint DeviceActivity(Hndl _device);
[DllImport(TestApi,CharSet = CharSet.Unicode)]
public static extern uint DeviceActivities(Hndl _device);
[DllImport(TestApi,CharSet = CharSet.Unicode)]
public static extern bool DeviceExecute(Hndl _device);
}
}
C#包装器:
using System;
namespace CSDllProject
{
public class Device : IDisposable
{
public Hndl Handle { get; private set; }
public uint Activity => API.DeviceActivity(Handle);
public uint Activities => API.DeviceActivities(Handle);
public Device(uint limit) => Handle = API.CreateDevice(limit);
~Device() => Dispose();
public bool Execute() => API.DeviceExecute(Handle);
public void Dispose()
{
API.DisposeDevice(Handle);
Handle = Hndl.Invalid;
}
}
}
C#单元测试:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using CSDllProject;
namespace UnitTest.NET
{
[TestClass] public class DeviceTests
{
[TestMethod] public void Test1()
{
using (var device = new Device(100u)) // [*]
{
do
{
Extension.LogLine($"Device Activity: {device.Activity:0000}"); // just outputs this string to a log file,nothing fancy
}
while (device.Execute());
}
}
}
}
接下来是这个小例子的目的。创建“设备”后(同样,这是一个暂存器,因此没有代码具有实际意义),您可以对其进行限制(可以用[*]标记)。在示例中,它是100u
。然后,当您调用.execute()
时,它将检查它是否已超过其可以执行的activities
的数目。如果有,它将返回false
。否则,它将增加activity
(然后在原始项目中它实际上会做某事)并返回true
,这意味着它“做了”一些有意义的事情。在调用方(C#)代码中,我引入了一个do-while
循环,并且在这种情况下,我仅调用了.execute()
方法。现在,一切正常,日志如下所示:
Device Activity: 0001
Device Activity: 0002
Device Activity: 0003
Device Activity: 0004
...
Device Activity: 0097
Device Activity: 0098
Device Activity: 0099
Device Activity: 0100
这是预期的。但是,如果我将活动限制([*]
行)更改为1000u
,则会发生一些奇怪的事情。我运行单元测试,它从不停止。因此,我强行将其停止并检查日志。日志现在看起来像这样:
Device Activity: 0001
Device Activity: 0002
Device Activity: 0003
Device Activity: 0004
...
Device Activity: 0997
Device Activity: 0998
Device Activity: 0999
Device Activity: 1000
Device Activity: 1000
Device Activity: 1000
Device Activity: 1000
Device Activity: 1000
Device Activity: 1000
Device Activity: 1000
Device Activity: 1000
起初我不知道该怎么想...直到1000年都做得不错,但是之后似乎并没有脱离循环(这就是单元测试从未完成的原因),但是,它也没有增加值(不,日志没有问题,因为它的实现效率很低,可以打开文件,写入文件,然后在每次调用时立即将其关闭,这样就可以100%工作)。
我已经在另一个C ++项目(device
类本身没有dll的情况下测试了C ++代码,因为它实际上位于共享项目中)并且没有错误(调用代码与C#单元测试中的一项,它在100u
和1000u
的限制下都表现出色)。 C#实际上有简单的调用,因此没有什么可以特别测试的。在我的原始项目中,在调试和释放模式下调用这段代码也有一些区别。这闻到了编译器可能会进行的某些优化可能会使代码混乱。
他对我的朋友说,在工作中他遇到了类似的问题,有一次他不得不将东西运送到经过调试编译的产品中。同样,有更多的理由认为优化可能是问题所在。我尝试了很多事情,最后,我在C ++ dll api中添加了一些日志记录:
dynamic bool DeviceExecute(Hndl _device)
{
device* dev = RC<device>(_device);
// log code that logs memory address of dev
bool result = dev->execute();
// log code that logs the result
return result;
}
现在此代码在1000u
的限制下可以正常工作。我尝试注释掉日志行,并保留其余代码,然后再次停止工作。这些都已经发布了。将其切换到调试模式会有不同的结果。有和没有日志行都可以工作。在这一点上,我绝对可以确定编译器会在发行时进行优化,以便具有:
dynamic bool DeviceExecute(Hndl _device)
{
device* dev = RC<device>(_device);
bool result = dev->execute();
return result;
}
最终将变成:
dynamic bool DeviceExecute(Hndl _device)
{
return RC<device>(_device)->execute();
}
无论如何,因为他可以得出结论,先前表达式的结果仅在一个表达式中使用,因此将其转换为第二个变量。最后,解决问题的方法是引入volatile
来阻止编译器进行这种优化,并最终得到:
dynamic bool DeviceExecute(Hndl _device)
{
volatile bool result = RC<device>(_device)->execute();
return result;
}
现在,对于活动中的100u
和1000u
限制,这在调试和发行时都可以正常工作。我的朋友告诉我,优化,堆栈,.NET调用非托管dll的方式等存在一些问题,但他甚至不知道它到底是什么,但是堆栈上有一些值(例如局部变量)可以帮助解决问题。这是我的问题,这到底是怎么回事?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。