PDA

View Full Version : ساخت dll استاندارد با c++



menosoft
دوشنبه 30 آذر 1394, 16:22 عصر
با سلام خدمت بروبچ برنامه نویس
دوستان من چنتا سوال در زمینه ساخت dll در c++ داشتم
1- چطور میشه یک dll استاندارد با زبان c++ نوشت که بشه در تمام زبان های برنامه نویسی مثل VB و Labview استفاده کرد.
2- من یک پروژه با کدبلاکس برای ساخت dll ساختم که در اون به صورت پیشفرض برای فایل header کد زیر:



#ifndef __MAIN_H__
#define __MAIN_H__

#include <windows.h>

/* To use this exported function of dll, include this header
* in your project.
*/

#ifdef BUILD_DLL
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif


#ifdef __cplusplus
extern "C"
{
#endif

void DLL_EXPORT SomeFunction(const LPCSTR sometext);

#ifdef __cplusplus
}
#endif

#endif // __MAIN_H__




و برای کد سورس، کد زیر نوشته شده بود:



#include "main.h"

// a sample exported function
void DLL_EXPORT SomeFunction(const LPCSTR sometext)
{
MessageBoxA(0, sometext, "DLL Message", MB_OK | MB_ICONINFORMATION);
}

extern "C" DLL_EXPORT BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
// attach to process
// return FALSE to fail DLL load
break;

case DLL_PROCESS_DETACH:
// detach from process
break;

case DLL_THREAD_ATTACH:
// attach to thread
break;

case DLL_THREAD_DETACH:
// detach from thread
break;
}
return TRUE; // succesful
}




همانطو که در این کدها مشاهده می شود. یک سری عبارات جدید وجود داره که من تابحال در برنامه های c++ ندیدم ازقبیل DLL_EXPORT ، extern "C" DLL_EXPORT BOOL APIENTRY DllMain ، extern "C"
#define DLL_EXPORT __declspec(dllexport)

#define DLL_EXPORT __declspec(dllimport)

می خواستم بدونم این عبارات مفهومش چی هست و از کجا آمده چون من این عبارات رو حذف کردم و فقط کد زیر رو در فایل هدر:



#ifndef __MAIN_H__
#define __MAIN_H__

#include <string>
/* To use this exported function of dll, include this header
* in your project.
*/

void SomeFunction(const std::string sometext);


#endif // __MAIN_H__


و کد زیر رو در فایل سورس قرار دادم و کامپایل کردم بازم کامپایل شد و dll ساخته شد. می خوام بدونم روش استانداردی برای ساخت dll در c++ است یا نه.



#include "main.h"
#include <iostream>
// a sample exported function
void SomeFunction(const std::string sometext)
{
std:: cout << "name is:" << sometext;
}

rahnema1
دوشنبه 30 آذر 1394, 18:19 عصر
سلام
همون روش اول درست بود
یعنی اولا باید تابعها به صورت تابعهای زبان C فرستاده بشه نه به صورت ++c یعنی extern "C" استفاده بشه در غیر اینصورت نام این تابعها به صورت عجق و وجق در میاد
همچنین از dllexport استفاده می شه چون نام اون تابع در dll توسط مثلا vb قابل استفاده بشه و مخفی نباشه و اگه dllexport نذارید dll ساخته می شه اما اون تابع مورد نظر در VB قابل رویت نخواهد بود
ضمن اینکه در مورد دوم که پارامتر تابع std::string هست را نمی شه در VB استفاده کرد. لازمه از آرایه char یا همون LPCSTR استفاده بشه
برای فرستادن داده های پیچیده تر می تونید از struct استفاده کنید البته لازمه این استراکچر در VB به صورت یک type تعریف بشه که معادل struct مربوط به ++C باشه

menosoft
سه شنبه 01 دی 1394, 08:58 صبح
سلام
همون روش اول درست بود
یعنی اولا باید تابعها به صورت تابعهای زبان C فرستاده بشه نه به صورت ++c یعنی extern "C" استفاده بشه در غیر اینصورت نام این تابعها به صورت عجق و وجق در میاد
همچنین از dllexport استفاده می شه چون نام اون تابع در dll توسط مثلا vb قابل استفاده بشه و مخفی نباشه و اگه dllexport نذارید dll ساخته می شه اما اون تابع مورد نظر در VB قابل رویت نخواهد بود
ضمن اینکه در مورد دوم که پارامتر تابع std::string هست را نمی شه در VB استفاده کرد. لازمه از آرایه char یا همون LPCSTR استفاده بشه
برای فرستادن داده های پیچیده تر می تونید از struct استفاده کنید البته لازمه این استراکچر در VB به صورت یک type تعریف بشه که معادل struct مربوط به ++C باشه

مرسی از پاسختون
میشه یه مرجعی خوب برای ساخت dll استاندارد رو معرفی کنید که توش این عبارات رو توضیحی داده باشه

DLL_EXPORT ، extern "C" DLL_EXPORT BOOL APIENTRY DllMain ، extern "C"
#define DLL_EXPORT __declspec(dllexport)

#define DLL_EXPORT __declspec(dllimport)

rahnema1
سه شنبه 01 دی 1394, 14:52 عصر
من خودم معمولا از گوگل (http://www.google.com) استفاده می کنم
مثلا این کلمات APIENTRY یا extern C یا DllMain را جداگانه جستجو کنید
اما DLL_EXPORT یک ماکرو هست یعنی در واقع همون __declspec(dllexport)

هست که وقتی از define استفاده می کنیم به برنامه میگیم هر جا به DLL_EXPORT رسید کلمه DLL_EXPORT را با __declspec(dllexport)

جایگزین کنه در این مورد لازمه کلمه ماکرو یا macro در گوگل یا همین سایت جستجو کنید

amirtork
سه شنبه 01 دی 1394, 21:12 عصر
سلام،
ممنون از توضیحاتتون،
سوالی برام پیش اومد و اون این هست که برای چی باید توابع به زبان C نوشته بشن و نمیشه از امکانات c++ مثل string استفاده کرد؟

rahnema1
سه شنبه 01 دی 1394, 22:50 عصر
سلام
مثلا تابع زیر را در نظر بگیرید که می خواهیم در یک dll باشه

int func(int a, int b)
{
return a+b;
}

این تابع را می شه به صورت ++C فرستاد ولی نام اون تابع در فایل dll به صورت در هم بر هم در میاد به این شکل: _Z4funcii
و موقع استفاده هم باید از نام در هم برهم استفاده کرد اما اگه به صورت c فرستاده بشه همون func می تونیم استفاده کنیم
اینکه چرا امکانات ++C مثلا std::string در زبانی مثل VB قابل استفاده نیست علتش اینه که این کلاس string یک کلاس پیچیده با اعضای مختلف هست که در داخل ++C تعریف شده ولی در داخل VB تعریف نشده بنابراین VB نمیتونه با اون کار کنه
البته استراکچرهای ساده (standard layout) را می شه فرستاد به شرطی که دو باره همون استراکچر در vb هم معادل ++C تعریف بشه

amirtork
سه شنبه 01 دی 1394, 22:55 عصر
آها،
خیلی ممنون.
منبعی برای آشنایی بیشتر با محدودیت ها توی نوشتن DLL سراغ دارید؟ اگر مثال عملی ساده ای هم دم دستتون داشتید ممنون میشم قرار بدید.

rahnema1
سه شنبه 01 دی 1394, 23:26 عصر
مثلا تو ویکی پدیا می تونید مطالب زیر را ببنید
Name_mangling (https://en.wikipedia.org/wiki/Name_mangling)
Calling_convention (https://en.wikipedia.org/wiki/Calling_convention)

یا در مورد memory layout یا standard layout جستجو کنید که کلاسهایی را شامل می شه که می شه از اونها در زبانهای برنامه نویسی دیگه استفاده کرد و مثلا تو vb می شه یک استراکچر معادل اون را نوشت
یا مثلا نحوه فراخوانی تابعهای win32api در vb6 یا در مورد pinvoke و استفاده تابعهای winapi در #C جستجو کنید مطالب زیاد هست
حالا مثال در چه موردی می خواهید؟

amirtork
چهارشنبه 02 دی 1394, 18:11 عصر
خیلی ممنون،
من بیشتر از روی مثال های ساده و نمونه کد نحوه ی کاربرد رو یاد میگیرم و به خاطر این عادت(که جاهایی هم به دلیل فهم اشتباه با مشکل مواجه شدم)، عموما تا کدی رو در اون زمینه نبینم نمیتونم با موضوع ارتباط برقرار کنم،
مثلا من میخواهم تابعی رو بنویسم که یک اسم رو میگیره و طول اون رو باز میگردونه،
به این صورت تعریف میکنمش:

std::string retrunLength(std::string str)
{
return str.lengh();
}

که اگر بخوام ببرمش به سینتکس C، میشه:

int returnLength(const char * str)
{
int i = 0;
for(i=0; str[i]!='\0'; ++i);
return i;
}

حالا اگر بخوام این رو به صورت DLL پیاده کنم چطور میشه؟
استفاده از اشاره گر ها و ... در DLL به همون صورت معمول هست یا باید حواسمون به چیز های دیگه ای هم باشه در DLL ایجاد کردن؟

rahnema1
چهارشنبه 02 دی 1394, 20:29 عصر
با استفاده از Mingw مثال می زنم
همین تابع را در فایلی به نام mysource.cpp ذخیره کنید و دستور زیر را بزنید
g++ mysource.cpp -shared -o mydll.dll
اما در این حالت نام تابع به صورت mangled در اومده با برنامه ای مثل dependency walker فایل dll را باز کنید و مشاهده می کنید که نام تابع به این شکل در اومده:
_Z12returnLengthPKc
این برنامه را در vb6 بنویسید و امتحان کنید البته باید dll در مسیر vb باشه

Declare Function returnLength Lib "mydll" Alias "_Z12returnLengthPKc" (ByVal str As String) As Long
Sub Main
MsgBox(returnLength("salam"))
End Sub

اما اگه همین برنامه را در فایلی با پسوند c. قرار بدید ودستور زیر را بزنید
gcc mysource.c -shared -o mydll.dll
نام تابع به شکل اصلی در میاد
حالا در فایل cpp تابع را به این شکل تغییر بدید

extern "C"
{
int returnLength(const char * str)
{
int i = 0;
for(i=0; str[i]!='\0'; ++i);
return i;
}
}

و با دستور زیر کامپایل کنید
g++ mysource.cpp -shared -o mydll.dll
که باز هم می بینیم شکل تابع به صورت درست نمایش داده می شه

در حالت کلی ما معمولا دو تا فایل داریم یکی سورس و یکی هدر که در هدر اعلان تابع و در سورس هم تعریف تابع هست خودمون هنگام کامپایل هم سورس را داریم و هم هدر و مثلا به مشتری فقط dll و هدر را می دهیم
یک فایل به نام myheader.h را ایجاد کنید و کد زیر را داخلش بذارید

#pragma once

#ifdef BUILD_DLL
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif


#ifdef __cplusplus
extern "C"
{
#endif

DLL_EXPORT int returnLength(const char * str);

#ifdef __cplusplus
}
#endif

که pragma once همون include guard هست که باعث می شه dll فقط یکبار Include بشه و دقیقا مثل کدی که دوستمون به صورت #ifndef __MAIN_H__ گذاشته عمل می کنه
توی gcc نام تابعها به طور پیشفرض فرستاده می شن و مخفی نمی شن و اگه dllexport نذاریم هم مشکلی نداره declspec(dllexport یک attribute مخصوص کامپایلر مایکروسافت هست که gcc هم از اون پشتیبانی می کنه
dllimport هم واسه وقتی هست که مشتری بخواد مثلا در یک برنامه ++C از dll استفاده کنه
دقت کنید که extern c فقط توسط کامپایلر ++c تشخیص داده می شه و توسط کامپایلر c قابل تشخیص نیست و اگه با دیدن extern c دچار اشکال می شه بنابراین اگه یه وقت این برنامه با کامپایلر c کامپایل شد و برای جلوگیری از اشکال یه شرط میذاریم به نام ifdef __cplusplus
چونکه __declspec(dllexport و __declspec(dllimport یه کمی نوشتنش مشکله به راحتی با ماکرو DLL_EXPORT می تونیم از اونها استفاده کنیم
فایل mysource.cpp هم به شکل زیر در بیارید

#include "myheader.h"
int returnLength(const char * str)
{
int i = 0;
for(i=0; str[i]!='\0'; ++i);
return i;
}

موقع کامپایل دستور زیر را بزنید:
g++ mysource.cpp -shared -DBUILD_DLL -o mydll.dll
توی vb6 هم به یک از دو شکل زیر می شه استفاده کرد

Declare Function returnLength Lib "mydll" Alias "returnLength" (ByVal str As String) As Long
Sub Main
MsgBox(returnLength("salam"))
End Sub

Declare Function returnLength Lib "mydll" (ByVal str As String) As Long
Sub Main
MsgBox(returnLength("salam"))
End Sub

البته داخل تابع می شه از امکانات ++C استفاده کرد ولی پارامترها و مقادیر برگشتی لازمه از نوعهای ساده باشه که بشه در vb استفاده کرد
البته داده های پیچیده تر مثل کلاسها و استراکت های ساده را می شه فرستاد که لازمه در vb6 بیاییم Type متناظر با اون استراکت را تعریف کنیم اشاره گر ها هم مشکلی نیست می شه فرستاد یعنی همون نوع داده های موجود در c
این مثالها با کامپایلر gcc و mingw بود حالا در مورد کامپایلر ویژوال سی پلاس پلاس هم تست کنید و اگه خواستید برای ما توضیح بدید

amirtork
چهارشنبه 02 دی 1394, 21:07 عصر
خیلی ممنون، توضیحاتتون خیلی کامل بود و موضوع کاملا شفاف شد،
این که گفتید میشه در داخل تابع از امکانات cpp استفاده کرد، از اونجایی که اگر با g++ کامپایلش کنیم اسم توابع تغییر میکنه، و باید/بهتر هست که از gcc استفاده کرد، اگر ما از امکانات cpp استفاده کنیم نمیشه توسط gcc کامپایل شه، امکانش هست؟
مورد دیگه که سوال دارم و اگر لازم بدونید تاپیک جدیدی براش ایجاد خواهم کرد، استفاده از کلاس هایی هست که خودمون مینویسیم و میخواهیم امکانش باشه که در برنامه های مختلفی از اون ها استفاده کنیم، بدون اینکه لازم باشه کد داخل فایل های source رو در اختیار داشته باشیم.
مثلا من کلاسی رو با اسم Name مثل کد زیر ایجاد کردم:
فایل Name.cpp


#include "name.h"


/*
* /////////////
* /// ToDo ///
* ////////////
*
* _bool operator =(Name _name);
* _bool operator ==(Name _name);
* _bool operator !=(Name _name);
* _bool operator >(Name _name);
* _bool operator <(Name _name);
* _bool printToFile(std::ofstream _output);
* _bool loadFromFile(std::ifstream _input);
*
*/


Name::Name()
{
firstName = "";
middleName = "";
lastName = "";
firstNameLength = 0;
middleNameLength = 0;
lastNameLength = 0;
}
Name::Name(std::string _firstName, std::string _middleName, std::string _lastName)
{
setFirstName(_firstName);
setMiddleName(_middleName);
setLastName(_lastName);
}
int Name::getLength()
{
return length;
}
int Name::getFirstNameLength()
{
return firstNameLength;
}
int Name::getMiddleNameLength()
{
return middleNameLength;
}
int Name::getLastNameLength()
{
return lastNameLength;
}
int Name::getLength()
{
return (firstNameLength+middleNameLength+lastNameLength);
}
std::string Name::getFirstName()
{
return firstName;
}
std::string Name::getMiddleName()
{
return middleName;
}
std::string Name::getLastName()
{
return lastName;
}
bool Name::setFirstName(std::string _firstName)
{
if(_firstName)
{
firstName = _firstName;
firstNameLength = _firstName.length();
return true;
}
else
return false;
}
bool Name::setMiddleName(std::string _middleName)
{
if(_middleName)
{
middleName = _middleName;
middleNameLength = _middleName.length();
return true;
}
else
return false;
}
bool Name::setLastName(std::string _lastName)
{
if(_lastName)
{
lastName = _lastName;
lastNameLength = _lastName.length();
return true;
}
else
return false;
}


و این هم فایل Name.h:


//Name OOP Class Using for storing names.
//Written By S.AmirMohammad Hassanli(amir_hassanli@yahoo.com)
//www.Github.com/SAMH


#ifndef NAME_H
#define NAME_H


#include <string>
#include <fstream>
class Name
{
private:
std::string firstName;
std::string middleName;
std::string lastName;
int length;
int firstNameLength;
int lastNameLength;
int middleNameLength;
public:
Name();
Name(std::string _firstName, std::string _middleName, std::string _lastName);
int getLength();
int getFirstNameLength();
int getMiddleNameLength();
int getLastNameLength();
std::string getFirstName();
std::string getMiddleName();
std::string getLastName();
bool setFirstName(std::string _firstName);
bool setMiddleName(std::string _middleName);
bool setLastName(std::string _lastName);
bool printToFile(std::ofstream _output);
bool loadFromFile(std::ifstream _input);
bool operator =(Name _name);
bool operator ==(Name _name);
bool operator !=(Name _name);
bool operator >(Name _name);
bool operator <(Name _name);
};
#endif // NAME_H


برای اینکار به موضوعی با عنوان interface برخورد کردم، اما طریقه ایجادش رو درست نفهمیدم! اگر امکانش هست توضیحاتی رو این مورد بدید. یا اگر لازم میدونید تاپیک جدیدی رو ایجاد کنم.

rahnema1
چهارشنبه 02 دی 1394, 22:07 عصر
معمولا به فایل هدر میگن interface و به سورس میگن implementation
dll را به صورت زیر کامپایل کنید
g++ Name.cpp -shared -o Name.dll
بعد مثلا توی فایل main.cpp می خواهید از این کلاس استفاده کنید
ابتدا فایل هدر را include می کنید و برای اینکه برنامه main با dll مورد نظر لینک بشه این دستور را بزنید
g++ main.cpp -L./ -lName
البته من نتونستم فایلها را کامپایل کنم و چند تا ارور داشت

amirtork
چهارشنبه 02 دی 1394, 22:30 عصر
در مورد کامپایل نشدن فایل ها حق با شما هست، کد ها کامل نیستن، هنوز وقت نکردم کامل کنم.
خیلی ممنون از توضیحاتتون.
در مورد سوال اول هم امکانش هست توضیحاتی رو بدید؟
بابت طولانی بودن پاسخ سوالات عذر خواهی میکنم، خیلی لطف میکنید.

rahnema1
چهارشنبه 02 دی 1394, 22:50 عصر
برای کدهایی که به زبان ++C نوشته شده حتما باید از ++g استفاده کنید و تابعهای منطبق با زبان c اگر داخلش باشه با همون extern c بدون مشکل فرستاده می شه اما تابعهای عضو باز اسمش بهم ریخته می شه