使用CLR封装C++ dll

 2024-03-16 02:06:59  阅读 0

项目建议理由

我用C++写了一个动态链接库。 动态链接库包含三个文件:.h/.lib/.dll。 然而,许多用户使用 C# 进行编程。 这时候他们就需要提供一个C#可以调用的dll。 C# 调用 dll。 一般有两种方法,即

用来封装函数,但是根据网上提供的相关教程,这种方法只适用于纯C语言编写的dll。 我写的C++动态库中,功能都集中在一个类中,在VS中并没有找到相应的方法来添加它。 dll 参考。 使用此方法添加C++生成的dll时,会报“无法添加对xxx.dll的引用。请确保此文件可访问并且是有效的程序集或COM组件”。

因此,选择CLR()对C++ dll进行再次封装,生成可以在C#/VB等语言中引用的dll。

编写C++ dll

为C++生成的示例源代码头文件MYAPI.h如下

class MYAPI
{
    public:
	static const int kMaxDataLen = 1024;
public:
	/**
	* @brief 功能状态
	*/
	enum STATE {
		OFF,///<关闭
		ON,///<开启
	};
	struct IPAddr {
		uint8_t c1;///

该类自定义了一个枚举器和结构体,提供的函数接口使用对应的枚举器和结构体。

void MYAPI::setValue(double value)
{
}
void MYAPI::getValue(double* value)
{
}
void MYAPI::setState(STATE state)
{
}
void MYAPI::getState(STATE* state)
{
	
}
void MYAPI::setAddr(IPAddr addr)
{
}
void MYAPI::getAddr(IPAddr* addr)
{
	if (addr)
	{
		addr->c1 = 2;
		addr->c2 = 3;
		addr->c3 = 5;
		addr->c4 = 7;
	}
}
int MYAPI::getData(double *data, int max_length)
{
	if (data == nullptr)return 0;
	for (int i = 0; i < max_length; ++i)
	{
		data[i] = i;
	}
	return max_length;
}

对应的cpp实现文件如上所示。 由于重新生成dll时文件已经被封装,因此对CLR封装没有影响。 如果用户想直接使用C++ dll,提供的文件包括MYAPI.h、MYAPI.lib、MYAPI.dll

如何使用C++ dll创建新项目

在 =>C++=> 中添加 MYAPI.h 的路径

调用语句是什么_c语言调用dll文件_调用dll方法

在属性=>链接器=>常规=>附加库目录中添加MYAPI.lib所在目录

在属性=>链接器=>输入=>附加依赖项中添加MYAPI.lib

根据头文件编写代码

#include "MYAPI.h"
int main()
{
	MYAPI tmp;
	MYAPI::STATE state = MYAPI::OFF;
	tmp.getState(&state);
	printf("state: %d\n", state);
	MYAPI::IPAddr addr;
	tmp.getAddr(&addr);
	printf("addr: %d.%d.%d.%d\n", addr.c1, addr.c2, addr.c3, addr.c4);
	double value;
	tmp.getValue(&value);
	printf("value: %f\n", value);
	double data[10];
	memset(data, 0, sizeof(data));
	tmp.getData(data, 10);
	printf("data: ");
	for (int i = 0; i < 10; ++i)
	{
		printf("%f ", data[i]);
	}
	printf("\n");
	system("pause");
}

为CLR包源代码编写一个新项目。 在VS中创建一个新项目。 C++ => CLR => CLR 空项目。 在属性=>常规=>项目默认值=>配置类型中选择动态库dll。 在属性=>C++=>附加包含目录中选择动态库dll。 添加MYAPI.h所在路径。 在属性=>链接器=>常规=>附加库目录中添加MYAPI.lib所在目录。 在属性=>链接器=>输入=>附加依赖项中添加MYAPI.lib。 创建一个新的 CLR 类。

MYAPI.h 中的类无法通过添加引用来使用,因此需要在 myclr 中创建一个新类,并提供与 MYAPI 完全相同的功能。 其实现如下:

#pragma once
#include <stdint.h>
class MYAPI;
namespace myclr
{
	public enum class STATE {
		OFF,///<关闭
		ON,///<开启
	};
	public value struct IPAddr {
		uint8_t c1;///
		uint8_t c2;///IP地址第2位
		uint8_t c3;///IP地址第3位
		uint8_t c4;///IP地址第4位
	};
	public ref class MYAPINET
	{
	private:
		MYAPI*m_impl;
	public:
		MYAPINET();
		~MYAPINET();
		void setValue(double value);
		void getValue(double% value);
		void setState(STATE state);
		void getState(STATE% state);
		void setAddr(IPAddr addr);
		void getAddr(IPAddr% addr);
		int getData(System::Collections::Generic::List<double>^%data, int max_length);
		int setName(System::String^ name);
	};
}

上述文件中需要注意以下几点:

类的名称必须是ref class,否则在C#中添加引用时无法访问枚举名称。 将枚举的名称替换为枚举类,并将其替换为值。 将指针 * 替换为 %。 如果指针类型保持不变,会导致C#中的代码不安全。 替换为%后,C#中添加引用时,以ref形式传递参数原来的作用是传入数组首地址和数组长度,然后向传入的地址写入数据。 C#对应数组。 数据结构为List和enum,放在命名空间中,主要是为了避免外部访问时每次都添加类前缀。 也可以放在类中来传递CLR类中的参数。

#include "MYAPINET.h"
#include "MYAPI.h"
#include 
#include 
namespace
{
	std::string SysStrToStdStr(System::String ^ s)
	{
		using namespace System;
		using namespace Runtime::InteropServices;
		const char* chars =
			(const char*)(Marshal::StringToHGlobalAnsi(s)).ToPointer();
		std::string str = chars;
		Marshal::FreeHGlobal(IntPtr((void*)chars));
		return str;
	}
}
namespace myclr
{
	MYAPINET::MYAPINET()
	{
		m_impl = new MYAPI;
	}  
	MYAPINET::~MYAPINET()
	{
		delete m_impl;
	}
	void MYAPINET::setValue(double value)
	{
		m_impl->setValue(value);
	}
	void MYAPINET::getValue(double% value)
	{
		double value_tmp;
		m_impl->getValue(&value_tmp);
		value = value_tmp;
	}
	void MYAPINET::setState(STATE state)
	{
		MYAPI::STATE state_tmp;
		state_tmp = static_cast<MYAPI::STATE>(state);
		m_impl->setState(state_tmp);
	}
	void MYAPINET::getState(STATE% state)
	{
		MYAPI::STATE state_tmp;
		m_impl->getState(&state_tmp);
		state = static_cast<STATE>(state_tmp);
	}
	void MYAPINET::setAddr(IPAddr addr)
	{
		MYAPI::IPAddr addr_tmp;
		addr_tmp.c1 = addr.c1;
		addr_tmp.c2 = addr.c2;
		addr_tmp.c3 = addr.c3;
		addr_tmp.c4 = addr.c4;
		m_impl->setAddr(addr_tmp);
	}
	void MYAPINET::getAddr(IPAddr% addr)
	{
		MYAPI::IPAddr addr_tmp;
		m_impl->getAddr(&addr_tmp);
		addr.c1 = addr_tmp.c1;
		addr.c2 = addr_tmp.c2;
		addr.c3 = addr_tmp.c3;
		addr.c4 = addr_tmp.c4;
	}
	int MYAPINET::getData(System::Collections::Generic::List<double>^%data, int max_length)
	{
		std::vector<double> data_tmp(max_length);
		int nread = m_impl->getData(data_tmp.data(), data_tmp.size());
		data->Clear();
		for (int i = 0; i < nread; ++i)
		{
			data->Add(data_tmp[i]);
		}
		return nread;
	}
	int MYAPINET::setName(System::String^ name)
	{
		std::string str = SysStrToStdStr(name);
		return m_impl->setName(str.data());
	}
}

如果类中传递的参数是 MYAPI 定义的同名结构体或枚举,则实际上是 myclr 命名空间中定义的枚举类或值。 不能直接传递,所以需要在函数内部做相应的转换。 方法是:

如果是enum,则在函数内部的MYAPI中定义一个同名的enum临时变量tmp。 如果是传入数据的函数,则使用类型转换,然后将tmp传给MYAPI中的函数。 如果是获取数据的函数,则将MYAPI中的数据传给tmp,然后从tmp中读取数据

如果是,则将值赋值给

如果是读取数据的函数,内部定义一个,从MYAPI读取数据,然后在C#中转成列表

const char* 是 C++ 中的输入字符串类型,在 C#/VB 中被 ::^ 替代。 const char* 通常可以转换为 std::,::^ 到 std:: 的转换方法为:

std::string SysStrToStdStr(System::String ^ s)
	{
		using namespace System;
		using namespace Runtime::InteropServices;
		const char* chars =
			(const char*)(Marshal::StringToHGlobalAnsi(s)).ToPointer();
		std::string str = chars;
		Marshal::FreeHGlobal(IntPtr((void*)chars));
		return str;
	}

一些简单的代码提示

如果是enum赋值,可以使用下面的宏来赋值,其中x是要赋值的数量,y是要传输的值的数量,不需要手写数据类型。

#define CAST_ASSIGN(x,y) x = static_cast<std::remove_reference_t<decltype(x)>>(y);

对于带有%的变量,我们还没有找到合适的方法来去掉%,所以我们使用下面的模板

	template <typename T1, typename T2>
	void simple_cast_assign(T1% dest, const T2&src)
	{
		dest = static_cast<T1>(src);
	}

如果是赋值的话,如果两个类型一模一样,而且都是枚举或者通用数据类型(int/char/short//float等),那么就可以使用内存复制

template <typename T1, typename T2>
void sameStructMemCopy(T1&dest, const T2&src)
{
void*p1 = (void*)&dest;
void*p2 = (void*)&src;
memcpy(p1, p2, sizeof(T1));
}

在C#中使用CLR封装的dll。 在VS中创建一个新项目。 新建项目C#=>控制台应用程序,选择项目中的引用=>添加引用,选择CLR生成的dll。 使用了.dll。

经过上述步骤后,就可以直接用C#编写程序了,使用C#编写如下代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using myclr;
namespace CSTest
{
    class Program
    {
        static void Main(string[] args)
        {
            MYAPINET tmp = new MYAPINET();
            STATE state = STATE.OFF;
            tmp.getState(ref state);
            Console.Write("state: {0}\n", state);
            IPAddr addr = new IPAddr();
            tmp.getAddr(ref addr);
            Console.Write("addr: {0}.{1}.{2}.{3}\n", addr.c1, addr.c2, addr.c3, addr.c4);
            double value = 0;
            tmp.getValue(ref value);
            Console.Write("value: {0}\n", value);
            List<double> data = new List<double>();
            tmp.getData(ref data, 10);
            Console.Write("data: ");
            for (int i = 0; i < 10; ++i)
            {
                Console.Write("{0} ", data[i]);
            }
            Console.Write("\n");
            Console.ReadKey();
        }
    }
}

在VB中使用CLR编写的dll

与C#类似,vb也可以通过添加引用来实现。

创建一个新项目。 在VS中创建一个新项目。 Basic=> ,在项目中选择=>Add ,选择CLR生成的dll使用。

经过上述步骤后,就可以直接用C#编写程序,用VB编写如下代码

Imports myclr
Module Module1
    Sub Main()
        Dim tmp As MYAPINET = New MYAPINET()
        Dim state As STATE = STATE.OFF
        tmp.getState(state)
        Console.WriteLine("state: {0}", state)
        Dim addr As IPAddr = New IPAddr()
        tmp.getAddr(addr)
        Console.WriteLine("addr: {0}.{1}.{2}.{3}", addr.c1, addr.c2, addr.c3, addr.c4)
        Dim value As Double = 0
        tmp.getValue(value)
        Console.WriteLine("value: {0}", value)
        Dim data As List(Of Double) = New List(Of Double)()
        tmp.getData(data, 10)
        Console.Write("data: ")
        Dim i As Integer = 0
        For i = 0 To 9
            Console.Write("{0} ", data(i))
        Next
        Console.WriteLine()
        Console.ReadKey()
    End Sub
End Module

添加引用dll时可能出现的问题

使用C#直接写dll时可以选择Any CPU,但使用CLR进行封装则依赖于C++生成的dll。 C++生成时有一个平台选择。 同时,CLR生成的dll依赖于C++生成的dll,因此可能会出现以下两种错误:

ion,这个错误是平台不对应造成的。 例如引用了x64平台的dll,但VB/C#项目中选择的平台是x86或者平台选择Any CPU,但属性中勾选了前32。 少量。 同理,如果引用了x86平台的dll,但是VB/C#工程中选择的平台是x64或者平台选择了Any CPU,但是属性中没有勾选32位优先,会报错也会发生。 名词这个错误是由于C++ dll没有复制到VB/C#生成的exe的路径引起的,因为CLR生成的dll中的函数最终实现是在C++ dll中,而VB/C#的路径是通常 bin/Debug 和 bin/ 区域需要优化,在 CLR 中封装 dll 时,需要重写 C++ 中的所有枚举和函数。 如果C++发生变化,手动同步代码的方法就太麻烦了。 您可以编写一个自动生成 CLR 代码的操作来减少手工劳动。 复制CLR生成的dll也依赖于C++生成的dll,所以容易出错。 如果只想生成一个dll,可以选择将C++生成的动态库转为静态库,这样就不用依赖C++的dll了。

标签: 引用 生成 编写

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码