热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++封装DLL供C#调用详细介绍

这篇文章主要介绍了C++封装DLL供C#调用(以C#调用C++二次封装的VLC播放库为介质,支持回调函数的封装),需要的朋友可以参考下面我文章的具体内容

1、VLC代码封装

1.1 QT(C++)工程

首先需要配置可使用 VLC 正常播放的 QT(C++)工程,获取VLC每一帧并渲染到Qwidget

Libvlcapi

public static class LIBVLCAPI
    {

        #region[libvlc.dll 导出函数]

        // 创建一个libvlc实例,它是引用计数的
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        private static extern IntPtr libvlc_new(int argc, IntPtr argv);

        // 释放libvlc实例
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_release(IntPtr libvlc_instance);

        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern String libvlc_get_version();

        // 从视频来源(例如Url)构建一个libvlc_meida
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        private static extern IntPtr libvlc_media_new_location(IntPtr libvlc_instance, IntPtr path);

        // 从视频来源(例如Url)抓图
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        private static extern int libvlc_video_take_snapshot(IntPtr libvlc_mediaplayer, int num, IntPtr filepath, int i_width, int i_height);

        // 从本地文件路径构建一个libvlc_media
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        private static extern IntPtr libvlc_media_new_path(IntPtr libvlc_instance, IntPtr path);

        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_release(IntPtr libvlc_media_inst);

        // 创建libvlc_media_player(播放核心)
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern IntPtr libvlc_media_player_new(IntPtr libvlc_instance);

        // 将视频(libvlc_media)绑定到播放器上
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_player_set_media(IntPtr libvlc_media_player, IntPtr libvlc_media);

        // 设置图像输出的窗口
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_player_set_hwnd(IntPtr libvlc_mediaplayer, Int32 drawable);

        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_player_play(IntPtr libvlc_mediaplayer);

        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_player_pause(IntPtr libvlc_mediaplayer);

        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_player_stop(IntPtr libvlc_mediaplayer);

        // 解析视频资源的媒体信息(如时长等)
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_parse(IntPtr libvlc_media);

        // 返回视频的时长(必须先调用libvlc_media_parse之后,该函数才会生效)
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern Int64 libvlc_media_get_duration(IntPtr libvlc_media);

        // 当前播放的时间
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern Int64 libvlc_media_player_get_time(IntPtr libvlc_mediaplayer);

        // 设置播放位置(拖动)
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_player_set_time(IntPtr libvlc_mediaplayer, Int64 time);

        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_media_player_release(IntPtr libvlc_mediaplayer);

        // 获取和设置音量
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern int libvlc_audio_get_volume(IntPtr libvlc_media_player);

        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_audio_set_volume(IntPtr libvlc_media_player, int volume);

        // 设置全屏
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_set_fullscreen(IntPtr libvlc_media_player, int isFullScreen);

        // 设置屏幕因子
        [DllImport("libvlc", CallingCOnvention= CallingConvention.Cdecl, ExactSpelling = true)]
        [SuppressUnmanagedCodeSecurity]
        public static extern void libvlc_video_set_scale(IntPtr libvlc_media_player, float f_factor);

        #endregion

        #region[VLC方法]

        public struct PointerToArrayOfPointerHelper
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeCOnst= 11)]
            public IntPtr[] pointers;
        }

        public static IntPtr libvlc_new(string[] arguments)
        {
            PointerToArrayOfPointerHelper argv = new PointerToArrayOfPointerHelper();
            argv.pointers = new IntPtr[11];

            for (int i = 0; i 

VLCPlayer

 public class VLCPlayer
    {
        private IntPtr libvlc_instance_;
        private IntPtr libvlc_media_player_;

        private double duration_;

        public VLCPlayer(string pluginPath, bool is_record)
        {
            if (is_record == true)
            {
                string plugin_arg = "--plugin-path=" + pluginPath;
                string filename = "c:\\" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".mp4";
                string record_paramter = "--sout=#duplicate{dst=display,dst=std{accs=file,mux=ts,dst=" + filename;
                string[] arguments = { "-I", "--fullscreen", "dummy", "--ignore-config", "--no-video-title", "--, "--, plugin_arg, record_paramter };// "--sout=#duplicate{dst=display,dst=std{accs=file,mux=ts,dst=c:\\1.mp4" };
                libvlc_instance_ = LIBVLCAPI.libvlc_new(arguments);
                libvlc_media_player_ = LIBVLCAPI.libvlc_media_player_new(libvlc_instance_);
            }
            else
            {
                string plugin_arg = "--plugin-path=" + pluginPath;
                string[] arguments = { "-I", "--fullscreen", "dummy", "--ignore-config", "--no-video-title", plugin_arg };
                libvlc_instance_ = LIBVLCAPI.libvlc_new(arguments);
                libvlc_media_player_ = LIBVLCAPI.libvlc_media_player_new(libvlc_instance_);
                //float f1=0.1f;
                //LIBVLCAPI.libvlc_video_set_scale(libvlc_media_player_,f1);
            }
        }

        public void Vlc_release()
        {
            if (libvlc_instance_ != IntPtr.Zero)
                LIBVLCAPI.libvlc_release(libvlc_instance_);

            // if (libvlc_media_player_ != IntPtr.Zero)
            // LIBVLCAPI.libvlc_media_release(libvlc_media_player_);


        }
        public void SetRenderWindow(int wndHandle)
        {
            if (libvlc_instance_ != IntPtr.Zero && wndHandle != 0)
            {
                LIBVLCAPI.libvlc_media_player_set_hwnd(libvlc_media_player_, wndHandle);
            }
        }

        public void PlayFile(string filePath)
        {
            IntPtr libvlc_media = LIBVLCAPI.libvlc_media_new_path(libvlc_instance_, filePath);
            if (libvlc_media != IntPtr.Zero)
            {
                LIBVLCAPI.libvlc_media_parse(libvlc_media);
                duration_ = LIBVLCAPI.libvlc_media_get_duration(libvlc_media) / 1000.0;

                LIBVLCAPI.libvlc_media_player_set_media(libvlc_media_player_, libvlc_media);
                LIBVLCAPI.libvlc_media_release(libvlc_media);

                LIBVLCAPI.libvlc_media_player_play(libvlc_media_player_);
            }
        }

        public void PlayFile_rtsp(string filePath)//libvlc_media_new_location
        {
            IntPtr libvlc_media = LIBVLCAPI.libvlc_media_new_location(libvlc_instance_, filePath);
            if (libvlc_media != IntPtr.Zero)
            {
                // LIBVLCAPI.libvlc_media_parse(libvlc_media);
                // duration_ = LIBVLCAPI.libvlc_media_get_duration(libvlc_media) / 1000.0;

                LIBVLCAPI.libvlc_media_player_set_media(libvlc_media_player_, libvlc_media);
                LIBVLCAPI.libvlc_media_release(libvlc_media);

                LIBVLCAPI.libvlc_media_player_play(libvlc_media_player_);
            }
        }


        public void Pause()
        {
            if (libvlc_media_player_ != IntPtr.Zero)
            {
                LIBVLCAPI.libvlc_media_player_pause(libvlc_media_player_);
            }
        }

        public void take_snapshot()
        {
            if (libvlc_media_player_ != IntPtr.Zero)
            {
                string filepath = "c:\\";
                LIBVLCAPI.libvlc_video_take_snapshot(libvlc_media_player_, 0, filepath, 0, 0);
            }
        }

        public void full_screen()
        {
            if (libvlc_media_player_ != IntPtr.Zero)
            {

                LIBVLCAPI.libvlc_set_fullscreen(libvlc_media_player_, 0);
            }
        }


        public void Stop()
        {
            if (libvlc_media_player_ != IntPtr.Zero)
            {
                LIBVLCAPI.libvlc_media_player_stop(libvlc_media_player_);
            }
        }

        public double GetPlayTime()
        {
            return LIBVLCAPI.libvlc_media_player_get_time(libvlc_media_player_) / 1000.0;
        }

        public void SetPlayTime(double seekTime)
        {
            LIBVLCAPI.libvlc_media_player_set_time(libvlc_media_player_, (Int64)(seekTime * 1000));
        }

        public int GetVolume()
        {
            return LIBVLCAPI.libvlc_audio_get_volume(libvlc_media_player_);
        }

        public void SetVolume(int volume)
        {
            LIBVLCAPI.libvlc_audio_set_volume(libvlc_media_player_, volume);
        }

        public void SetFullScreen(bool istrue)
        {
            LIBVLCAPI.libvlc_set_fullscreen(libvlc_media_player_, istrue ? 1 : 0);
        }

        public double Duration()
        {
            return duration_;
        }

        public string Version()
        {
            return LIBVLCAPI.libvlc_get_version();
        }
    }

1.2static 声明 m_instance 优化效率

如下:

#pragma once
#include 
#include 
typedef SSIZE_T ssize_t;
#include "vlc/vlc.h"
#include 

struct libvlc_media_track_info_t;
struct libvlc_media_t;
struct libvlc_instance_t;
struct libvlc_media_player_t;
struct libvlc_event_t;

class context;

enum  MediaState {

    NothingSpecial = 0,
    Opening = 1,
    Buffering = 2,
    Playing = 3,
    Paused = 4,
    Stopped = 5,
    Ended = 6,
    Error = 7
};


class  TestVlcVideo 
{
public:
    TestVlcVideo();
     void init( std::function eventCallback);
     void setHwnd(const int64_t iHwnd) ;
     bool loadMedia(const char* &url) ;
     int  play() ;
     void pause() ;
     void stop() ;
     void setRatio(const char* &ratio) ;
     int getVolume() ;
     int setVolume(const int volume) ;
     int getMediaState() ;
    libvlc_instance_t * getVlcInstance();
    libvlc_media_player_t * getVlcMediaPlayer();
private:
    static void vlcEvents(const libvlc_event_t *ev, void *param);
    static libvlc_instance_t *m_instance;
    libvlc_media_player_t *m_mediaPlayer = nullptr;
    int64_t m_durationMS;
    std::function m_eventCallback;
    MediaState m_currentMediaState;
};

上面 static 声明的 m_instance 是为了优化效率,不必每次播放视频的时候都新建。

这是第二步工作。

1.3封装 DLL

需要封装真正的 DLL 了,向C#暴露的也是这个类里面的方法。

#pragma once

typedef  int(*CallBackMediaState)(int);

#ifdef DLLVLC_EXPORTS // 用来导出函数
#define DLLVLC_API __declspec(dllexport)
#else // 用来标识为导入函数,对于引用该头文件的外部模块来说dllimport这个标记对编译优化有作用
#define DLLVLC_API __declspec(dllimport)
#endif

#include "Testvlcvideo.h"

namespace TestVLCDLL {

extern "C" {
    /*
     *  @brief   VLC Instance和Player实例初始化
     *  @param   CallBackMediaState callback  回调函数为媒体播放状态
     *  @return  每次vlcInit会返回一个VLC的Player ID,此ID唯一,后面的接口都需要此ID找到对应的Player
     */
    DLLVLC_API int   vlcInit(CallBackMediaState callback);
    /*
     *  @brief  VLC 媒体加载接口
     *  @param  int index   PlayerID
     *  @param  const char *path   媒体路径
     */
    DLLVLC_API bool  vlcLoad(int index, const char *path);
    /*
     *  @brief  设置句柄,如不设置则为默认窗口播放
     *  @param  const int64_t iHwnd windows窗口句柄
     */
    DLLVLC_API bool  vlcSetHwnd(int index,const int64_t iHwnd);
    DLLVLC_API bool  play(int index);
    DLLVLC_API bool  pause(int index);
    DLLVLC_API bool  stop(int index);
    /*
     *  @brief  设置播放窗口比例
     *  @param  形如  16:9  4:3 等字符串
     */
    DLLVLC_API bool  setRatio(int index,const char* ratio);
    /*
     *  @brief  设置媒体播放音量
     */
    DLLVLC_API bool  setVolume(int index, int volume);
    /*
    *  @brief  获取媒体总时长
    */
    DLLVLC_API int64_t  getMediaLength(int index);
    /*
    *  @brief  获取当前播放状态
    */
    DLLVLC_API int   getMediaState(int index);
    /*
    *  @brief  销毁VLC Player
    */
    DLLVLC_API bool  vlcDisponse(int index);

    }
}

首先在最开始定义了 CallBackMediaState 回调函数,对应C++ 层使用函数指针和std::function 都可以。然后使用 DLLVLC_EXPORTS 指示本类为导出类,然后再使用 DLLVLC_API 宏定义导出函数,这些方法都是 dll 暴露给外部调用的方法。

1.4应用程序的导出函数

// DLLVLC.cpp : 定义 DLL 应用程序的导出函数。
#define DLLVLC_EXPORTS
#include "DLLVLC.h"
#include "Testvlcvideo.h"
#include 
#include 
#include 
#include 

std::map g_mapVLC;
std::atomic_int g_iIndex = 0;
std::mutex g_mt;

DLLVLC_API int TestVLCDLL::vlcInit(CallBackMediaState callback)
{
    //如果是初次调用,则初始化instance,否则复用instance
    std::lock_guard l(g_mt);
    ++g_iIndex;
    TestVlcVideo *vlcVideo = new TestVlcVideo;
    g_mapVLC.emplace(g_iIndex, vlcVideo);
    g_mapVLC.at(g_iIndex)->init(callback);
    return g_iIndex;
}

DLLVLC_API bool  TestVLCDLL::play(int index)
{
    std::lock_guard l(g_mt);
    TestVlcVideo *vlcVideo = g_mapVLC.at(index);
    if (nullptr == vlcVideo)
    {
        return false;
    }
    vlcVideo->play();
    return true;
}

.......


因为我们采用的是导出接口方法,而不是导出类(导出类比较麻烦,自己测试未能成功),因此在制作 dll 库时,使用静态 map 保存相关实例,使用对应的 init方法和 dispose 方法借助 id 参数创建和销毁对象。

1.5 vlc 简单封装的具体实现

下来再看下我们第一段代码的 cpp 文件,就是 vlc 简单封装的具体实现:

#include "Testvlcvideo.h"
#include 

libvlc_instance_t *TestVlcVideo::m_instance = nullptr;

TestVlcVideo::TestVlcVideo()
: m_mediaPlayer(nullptr)
, m_durationMS(0)
, m_eventCallback(nullptr)
{

}

void TestVlcVideo::init(std::function eventCallback)
{
    getVlcInstance();
    {
        getVlcMediaPlayer();
        libvlc_event_manager_t *em = libvlc_media_player_event_manager(m_mediaPlayer);
        {
            libvlc_event_attach(em, libvlc_MediaPlayerPlaying, vlcEvents, this);
            libvlc_event_attach(em, libvlc_MediaPlayerPaused, vlcEvents, this);
            libvlc_event_attach(em, libvlc_MediaPlayerStopped, vlcEvents, this);
            libvlc_event_attach(em, libvlc_MediaPlayerNothingSpecial, vlcEvents, this);
            libvlc_event_attach(em, libvlc_MediaPlayerOpening, vlcEvents, this);
            libvlc_event_attach(em, libvlc_MediaPlayerBuffering, vlcEvents, this);
            libvlc_event_attach(em, libvlc_MediaPlayerEndReached, vlcEvents, this);
            libvlc_event_attach(em, libvlc_MediaPlayerPositionChanged, vlcEvents, this);
        }
        m_eventCallback = std::move(eventCallback);
    }
}

void TestVlcVideo::setHwnd(const int64_t iHwnd)
{
    libvlc_media_player_set_hwnd(m_mediaPlayer, (void *)iHwnd);
}

bool TestVlcVideo::loadMedia(const char* &url)
{
    libvlc_media_t *m_media = nullptr;
    std::string url_ = url;
    if (url_.find("://") == std::string::npos)
    {
        m_media = libvlc_media_new_path(getVlcInstance (), url);
    }
    else
    {
        m_media = libvlc_media_new_location(getVlcInstance(), url);
    }
    if (nullptr == m_media)
    {
        m_currentMediaState = MediaState::Error;
        return false;
    }

    libvlc_media_player_set_media(getVlcMediaPlayer (), m_media);
    libvlc_media_parse(m_media);
    m_duratiOnMS= libvlc_media_get_duration(m_media);
    libvlc_media_release(m_media);
    return true;
}

libvlc_instance_t *  TestVlcVideo::getVlcInstance()
{
    if (nullptr == m_instance)
    {
        m_instance = libvlc_new(0, NULL);
    }
    return m_instance;
}

libvlc_media_player_t * TestVlcVideo::getVlcMediaPlayer()
{
    if (nullptr == m_mediaPlayer)
    {
        m_mediaPlayer = libvlc_media_player_new(m_instance);
    }
    return m_mediaPlayer;
}

int TestVlcVideo::play()
{
    return libvlc_media_player_play(m_mediaPlayer);
}

void TestVlcVideo::pause()
{
    if(libvlc_media_player_is_playing(m_mediaPlayer))
    {
        libvlc_media_player_set_pause(m_mediaPlayer, 1);
    }
    else
    {
        libvlc_media_player_set_pause(m_mediaPlayer, 0);
    }
}

到这儿,一般情况下我们还需要配置 def 文件,以避免导出的函数名被增加额外的信息,而不是简短的“play”等。但是可以看到我们在所有的导出函数前增加了“extern C ”标识。意思是这些函数按照 C 标准进行编译,由于C++ 的函数重载,再加上各个编译器的不同,导致编译而出的函数名被(mangled name),且各不相同,但是C不支持重载,因此采用统一的编译规定,同时也可以保证此函数被 C 正确调用,所以我们就无需写 def 文件也可以保证函数名不被破坏。

2、C# 调用

上面简要说完了 C++ 端关于 DLL 的封装,再总结一下大概就是这几点:

  • 至少需要两个文件,一个是自己对具体实现的封装类,一个是导出方法文件,本文中我们没有使用类,而是直接导出函数。
  • 回调函数像这样 typedef int(*CallBackMediaState)(int); 去定义。
  • 导出文件添加宏 dllexport
#ifdef DLLVLC_EXPORTS // 用来导出函数
#define DLLVLC_API __declspec(dllexport)
#else // 用来标识为导入函数,对于引用该头文件的外部模块来说dllimport这个标记对编译优化有作用
#define DLLVLC_API __declspec(dllimport)
#endif

导出函数添加 extern "C" DLLVLC_API 声明。

2.1C# 回调函数声明与定义

[DllImport(@"C:\Users\HiWin10\Desktop\DLLVLC\DLLVLC\DLLVLC\x64\Release\DLLVLC.dll", EntryPoint = "vlcInit",
        SetLastError = true,
        CharSet = CharSet.Ansi,
        ExactSpelling = false,
        CallingCOnvention= CallingConvention.Cdecl)]
        public extern static int vlcInit(DllcallBack pfun);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate int DllcallBack(int MediaState);
   

C# 的回调函数即为委托,需要提前定义委托 DllcallBack ,然后我们假定它是被 C++ 认可的,作为参数传入 vlcInit。在下面我们需要写此委托函数具体的实现:

public static int CsharpCall(int MediaState)
        {
            Console.WriteLine(MediaState);
            return MediaState;
        }

使用的时候:

static int index;
        static void Main(string[] args)
        {
            DllcallBack mycall;
            mycall = new DllcallBack(Program.CsharpCall);
            index = vlcInit(mycall);
            ......
        }

经过验证,此种方式的回调函数能被 C++ 承认,对应于C++的 std::function。

2.2C# 导出普通函数调用

[DllImport(@"C:\Users\HiWin10\Desktop\DLLVLC\DLLVLC\DLLVLC\x64\Release\DLLVLC.dll", EntryPoint = "vlcLoad",
        CallingCOnvention= CallingConvention.Cdecl)]
        public extern static bool vlcLoad(int index, string path);

        [DllImport(@"C:\Users\HiWin10\Desktop\DLLVLC\DLLVLC\DLLVLC\x64\Release\DLLVLC.dll", EntryPoint = "vlcSetHwnd",
        CallingCOnvention= CallingConvention.Cdecl)]
        public extern static bool vlcSetHwnd(int index, int iHwnd);

上面是 C# 关于普通导出函数的加载方法,在 main 函数中直接进行调用即可。

static int index;
        static void Main(string[] args)
        {
            DllcallBack mycall;
            mycall = new DllcallBack(Program.CsharpCall);
            index = vlcInit(mycall);
            Console.WriteLine(vlcLoad(index, @"D:\1.mp4"));
            Console.WriteLine(getMediaLength(index));
            play(index);
            setRatio(index,"16:9");

其实 C# 端的调用还是比较简单的,上面的方式是采用静态加载的方式,需要将C++ 的 dll 放到 C# 工程 bin 目录下,而动态加载的方式笔者未进行尝试。

整个过程就完成了,使用 C++ 封装的方式可以使用一些只支持 C++,只有 C++ API 的强大库,也可以防止反编译,还可以使代码更好的分层等。

下面附上demo链接,有需要的小伙伴可下载运行(VS2015)

DLLVLCfor_jb51.rar

到此这篇关于C++ 封装 DLL 供 C# 调用详细介绍的文章就介绍到这了,更多相关C++ 封装 DLL 供 C# 调用内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 先上图引入插件在pubspec.yaml中引入charts_flutter插件使用的时候版本到0.6.0,插件地址:https:github.comgooglecharts使用插件 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 如何使用Python从工程图图像中提取底部的方法?
    本文介绍了使用Python从工程图图像中提取底部的方法。首先将输入图片转换为灰度图像,并进行高斯模糊和阈值处理。然后通过填充潜在的轮廓以及使用轮廓逼近和矩形核进行过滤,去除非矩形轮廓。最后通过查找轮廓并使用轮廓近似、宽高比和轮廓区域进行过滤,隔离所需的底部轮廓,并使用Numpy切片提取底部模板部分。 ... [详细]
  • Android跨进程通信IPC之9——Binder通信机制
    移步系列Android跨进程通信IPC系列1Android整体架构Android系统架构及系统源码目录Android系统架构 ... [详细]
author-avatar
涂凌萱_TLX_9s7_140
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有