C#调用以优化C ++ dll函数的行为

如何解决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#单元测试中的一项,它在100u1000u的限制下都表现出色)。 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;
}

现在,对于活动中的100u1000u限制,这在调试和发行时都可以正常工作。我的朋友告诉我,优化,堆栈,.NET调用非托管dll的方式等存在一些问题,但他甚至不知道它到底是什么,但是堆栈上有一些值(例如局部变量)可以帮助解决问题。这是我的问题,这到底是怎么回事?

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-