AI muốn lập trình DLL cho Excel và các loại bằng Delphi thì xem video này nhé!

Liên hệ QC

Nguyễn Duy Tuân

Nghị Hách
Thành viên danh dự
Tham gia
13/6/06
Bài viết
4,649
Được thích
10,138
Giới tính
Nam
Nghề nghiệp
Giáo viên, CEO tại Bluesofts
Lần chỉnh sửa cuối:
2. Cái dòng em bôi đỏ là giải phóng PD: theo nguyên lý thì mình duyệt xong tất cả rồi mới giải phóng chứ, mà khi em viết như thế thì nó hiện cảnh báo là PD có thể ko được khởi tạo, còn nếu giải phóng bên trong vòng lặp, nghĩa là mỗi lần duyệt là mỗi lần giải phóng thì nó ko cảnh báo, vậy cái nào tốt hơn a.
Ở vòng FOR đầu các đối tượng Product được tạo ra liên tiếp và chúng được nhớ trong từ điển với tư cách là VALUE. Vòng FOR thứ 2 duyệt từng đối tượng được nhớ trong từ điển. Lẽ ra phải giải phóng các đối tượng này trong vòng FOR. Làm như hiện thời thì:
- chỉ đối tượng PD CUỐI CÙNG khi ra khỏi vòng FOR được giải phóng.
- do ngoài các vòng FOR không có chỗ nào đối tượng PD được tạo (vì chúng được tạo trong vòng FOR), nhưng Delphi không chắc lắm nên chỉ lưu ý là PD có thể ko được khởi tạo.

Nếu không hủy các đối tượng PD trong vòng FOR thì rất có thể chúng được hủy trước khi ra khỏi SUB. Nhưng để khỏi lăn tăn, hồ nghi thì nên hủy trong vòng FOR. Hủy sau FOR là chỉ hủy PD CUỐI CÙNG.
 
Upvote 0
Ở vòng FOR đầu các đối tượng Product được tạo ra liên tiếp và chúng được nhớ trong từ điển với tư cách là VALUE. Vòng FOR thứ 2 duyệt từng đối tượng được nhớ trong từ điển. Lẽ ra phải giải phóng các đối tượng này trong vòng FOR. Làm như hiện thời thì:
- chỉ đối tượng PD CUỐI CÙNG khi ra khỏi vòng FOR được giải phóng.
- do ngoài các vòng FOR không có chỗ nào đối tượng PD được tạo (vì chúng được tạo trong vòng FOR), nhưng Delphi không chắc lắm nên chỉ lưu ý là PD có thể ko được khởi tạo.

Nếu không hủy các đối tượng PD trong vòng FOR thì rất có thể chúng được hủy trước khi ra khỏi SUB. Nhưng để khỏi lăn tăn, hồ nghi thì nên hủy trong vòng FOR. Hủy sau FOR là chỉ hủy PD CUỐI CÙNG.
- cám ơn a, vậy là khi PD nó trỏ tới vùng nhớ khác thì vùng nhớ được tạo ra trước đó vẫn còn lưu trên ram, nó được dic(value) quản lý và vòng lặp sau thì vừa duyệt và vừa giải phóng luôn.
- Code bên trên em viết anh có biết vì sao khi dùng dữ liệu gốc là Pwidechar từ excel truyền qua thì khi duyệt qua các dic.values, ta lại nhận các giá trị Productcode giống nhau ( cụ thể ở đây là productcode cuối cùng được thêm vào dic) nhưng ngặt nỗi nó lại trả về đúng cột thứ 2 quantity.
 
Upvote 0
- cám ơn a, vậy là khi PD nó trỏ tới vùng nhớ khác thì vùng nhớ được tạo ra trước đó vẫn còn lưu trên ram, nó được dic(value) quản lý và vòng lặp sau thì vừa duyệt và vừa giải phóng luôn.
- Code bên trên em viết anh có biết vì sao khi dùng dữ liệu gốc là Pwidechar từ excel truyền qua thì khi duyệt qua các dic.values, ta lại nhận các giá trị Productcode giống nhau ( cụ thể ở đây là productcode cuối cùng được thêm vào dic) nhưng ngặt nỗi nó lại trả về đúng cột thứ 2 quantity.
Buồn buồn viết tí thôi. Tôi có Delphi đâu để mà test. Không test thì nhìn bằng mắt có nhiều chỗ sẽ sơ ý bỏ qua.
 
Upvote 0
Bạn sử dụng COM thì nên dùng Widestring

Mã:
type
  TProduct = class
  private
    ProductCode: WideString;
    Quantity: Double;
  public
    constructor Create(PC: WideString; Qu: Double); overload;
  end;

{$R *.res}


{$R *.res}
  { TProduct }

constructor TProduct.Create(PC: WideString; Qu: Double);
begin
  Inherited Create; // Phải thêm vào thừa kế
  ProductCode := PC;
  Quantity := Qu;
end;

function TotalProduct(var arr: Variant): Variant; stdcall;
var
  dic: TDictionary<string, TProduct>;
  i: Integer;
  Key:  WideString;
  Value: Double;
  PD: TProduct;
  Data: Variant;
begin
  dic := TDictionary<string, TProduct>.Create;
  try
    for i := VarArrayLowBound(arr, 1) to VarArrayHighBound(arr, 1) do
    begin
      //Kiểm tra tồn tại không đồng thời nhận luôn Product nếu đã có
      Key := TVarData(arr[i, 1]).VOleStr;
      if dic.TryGetValue(Key, PD) then
        PD.Quantity := PD.Quantity + TVarData(arr[i, 2]).VDouble
      else
      begin
        PD := TProduct.Create(Key, TVarData(arr[i, 2]).VDouble);
        dic.Add(Key, PD);
      end;
    end;
    Data := VarArrayCreate([1, dic.Count, 1, 2], varVariant);
    i := 0;
    for PD in dic.Values do
    begin
      i := i + 1;
      Data[i, 1] := (PD.ProductCode);
      Data[i, 2] := PD.Quantity;
      PD.Free; //Giải phóng trong vòng lặp
    end;
  finally
    dic.Free;
    Result := Data;
  end;
end;
 
Upvote 0
Bạn sử dụng COM thì nên dùng Widestring

Mã:
type
  TProduct = class
  private
    ProductCode: WideString;
    Quantity: Double;
  public
    constructor Create(PC: WideString; Qu: Double); overload;
  end;

{$R *.res}


{$R *.res}
  { TProduct }

constructor TProduct.Create(PC: WideString; Qu: Double);
begin
  Inherited Create; // Phải thêm vào thừa kế
  ProductCode := PC;
  Quantity := Qu;
end;

function TotalProduct(var arr: Variant): Variant; stdcall;
var
  dic: TDictionary<string, TProduct>;
  i: Integer;
  Key:  WideString;
  Value: Double;
  PD: TProduct;
  Data: Variant;
begin
  dic := TDictionary<string, TProduct>.Create;
  try
    for i := VarArrayLowBound(arr, 1) to VarArrayHighBound(arr, 1) do
    begin
      //Kiểm tra tồn tại không đồng thời nhận luôn Product nếu đã có
      Key := TVarData(arr[i, 1]).VOleStr;
      if dic.TryGetValue(Key, PD) then
        PD.Quantity := PD.Quantity + TVarData(arr[i, 2]).VDouble
      else
      begin
        PD := TProduct.Create(Key, TVarData(arr[i, 2]).VDouble);
        dic.Add(Key, PD);
      end;
    end;
    Data := VarArrayCreate([1, dic.Count, 1, 2], varVariant);
    i := 0;
    for PD in dic.Values do
    begin
      i := i + 1;
      Data[i, 1] := (PD.ProductCode);
      Data[i, 2] := PD.Quantity;
      PD.Free; //Giải phóng trong vòng lặp
    end;
  finally
    dic.Free;
    Result := Data;
  end;
end;
Trước mình dùng pwidechar nên nó lỗi bạn
 
Upvote 0
Lâu lâu quay lại tham gia góp vui.
Mã ASM của các dạng GetSumRange vẫn còn chậm. Bị vướng 2 cái sau, nên khó đè đầu ngoạn mục thằng VBA được. Viết đúng nó phải nhanh hơn gấp 10 lần
1. Trong vòng for i, biến j được get lại nhiều lần qua 2 hàm VarArrayLowBound và VarArrayHighBound
Nhìn kỹ thì VarArrayLowBound(arr, 2) và VarArrayHighBound(arr, 2) là hằng khi vào hàm.
Nên chúng ta đẩy (cache) giá trị nó ra ngoài vòng for i.
2. iSum := iSum + TVarData(arr[i, j]).VDouble => Delphi compiler chèn mã lệnh call tới hàm internal linkproc VarArrayGet, làm chậm đi ít nữa
Nên ép kiểu qua PSafeArray rồi truy xuất pointer tới trực tiếp từng giá trị double luôn.
Dùng trực tiếp các hàm API về SafeArray, seek pointer tới ngay vùng đầu data của PSafeArray.
Vd hàm API SafeArrayPtrOfIndex
Các bạn đọc source hàm _VarArrayGet trong file System.Variants.pas để tham khảo thêm
Giải quyết 1 và 2 đủ bóp cổ code thông dịch của VBA

PS: bổ sung, nếu biến truyền vào hàm các bạn muốn dùng truyền theo kiểu byref giống VBA và các bạn không thay đổi nội dung biến đó, các bạn thay vì viết var X: YYYY thì có thể viết const X: YYYY
Compiler sẽ sinh mã truyền by pointer cho các bạn, và kiểm tra luôn nếu code các bạn có tiềm tàng thay đổi nội dung biến hay không, và sẽ báo error hay warning ngay lúc compile.
VD: GetSumRange(const arr: Variant): Double; stdcall;
Khai báo là var arr: Variant thì compiler mặc định xem như biến arr sẽ bị thay đổi

1634206517138.png
Vài góp ý nhỏ, chân chọng, bét xì ga :D
 
Lần chỉnh sửa cuối:
Upvote 0
2 năm trước tôi thấy Atools.dll có 2 chức năng trong 1 File đó là COM và API viết = Delphi trong 1 Files rất hay
mấy ngày trước tùng có hỏi COM Delphi bất chợt tôi mới nhớ lại cái ngày ấy và vọc có 2 ngày thế là viết Xong

COM Delphi và API Delphi chung vào 1 File tạm keo MyLibrary.dll

1/ Nếu sử dụng API thì chỉ cần Copy vào System là sử dụng

2/ Nếu Sử dụng COM thì Đăng ký nó xong ... Từ VBE : Tools\References\Browse..\MyLibrary.dll\Open\OK
và sử dụng

Demo cho ai đó tò mò chút

Cảm ơn @minhtungph đã hỏi làm Anh tò mò ... phải chăng code két khi đạt đến độ chất thì lượng sẻ biến đổi theo :wiggle::weight_lift2:@#$


Lưu ý:
1/ Demo là DLL 64 bit thì chỉ chạy trên Office 64 bít
2/ thay đổi lại SQL ADODB và thưởng thức
 

File đính kèm

  • COM_API_Delphi.rar
    1 MB · Đọc: 17
Upvote 0
Bạn úp file Excel và code cả VBA, Delphi đi. Mình code, optimize lại cho.
Giờ làm data cho file Excel lười lắm.
Phải tránh được hàm internal mà Delphi compiler chèn vô, _VarArrayGet thì sẽ nhanh hơn nữa.
Vì _VarArrayGet tạo 1 bản copy Variant cho phần tử arr[i, j]
 
Upvote 0
Các bạn cẩn thận với string truyền vào và trả ra từ VBA qua Delphi nhé.
String trong VBA là kiểu BSTR của Windows, và kiểu WideString trong Delphi cũng là BSTR.
Không thể tùy ý dùng PWideChar/PChar (Unicode) được. Phải dùng các hàm API của BSTR của Windows.
Mình sẽ giải thích về cái này rõ hơn sau.
 
Upvote 0
Mạnh mới test lại các kiểu thì thấy hàm đó viết lộn một chút Mạnh điều chỉnh lại như sau mới chạy OK khi ta sử dụng ADO lấy dữ liệu từ 1 Sheet vào Mảng sử dung Phức thức GetRows ... xong dùng hàm chuyển mảng gán lên Range
Mã:
function TransArr(ssArr: OleVariant): OleVariant; stdcall;
var
    tmpArr    : OleVariant;
    x, y    : integer;
    lcol,lRows  : integer;
begin
  lcol := VarArrayHighBound(ssArr, 2); //Cot
  lRows := VarArrayHighBound(ssArr, 1); //dong
  tmpArr := VarArrayCreate([1, lcol + 1, 1, lRows + 1],varVariant);
    for x:= 0 to lcol do begin
        for y:= 0  to lRows do begin
            tmpArr[x + 1, y + 1] := ssArr[y, x];
        end; // y
    end; // x
    Result := tmpArr;
end;
Mô tả sơ bộ 1 chút
1/ Nếu ta ko + thêm 1 trong hàm thì khi lấy lên ta phải cộng thêm 1 khi Resize
Mã:
Range("A2").Resize(UBound(dArr, 1) + 1, UBound(dArr, 2) + 1) = dArr
2/ Còn nếu ta đã cộng thêm 1 trong Hàm TransArr thì khi ta lấy lên gán dữ liệu ko + thêm 1 nữa
Mã:
Range("A2").Resize(UBound(dArr, 1), UBound(dArr, 2)) = dArr
3/ 1 ở đây là 1 cột và 1 dòng

Cảm ơn thuyyeu99 Viết cho Mạnh cái Hàm hay và hay hết tất cả là Mạnh biết xài mảng 2dArray trong Delphi
@ThangCuAnh ... 2 năm trước mới nhập Môn Delphi ... Mạnh viết hàm này nó chạy rất chậm với 1 Array lớn ... vậy có cách nào viết lại nó cho nó chạy nhanh hơn không Mong trợ giúp
Xin cảm ơn
 
Upvote 0
Mã:
function TransArr(ssArr: OleVariant): OleVariant; stdcall;
var
    tmpArr    : Variant;
begin
    tmpArr := ssArr.Value2;
    Result := tmpArr;
end;
Anh mạnh thử code thế này xem thử xem.
 
Upvote 0
Upvote 0
Các bạn cẩn thận với string truyền vào và trả ra từ VBA qua Delphi nhé.
String trong VBA là kiểu BSTR của Windows, và kiểu WideString trong Delphi cũng là BSTR.
Không thể tùy ý dùng PWideChar/PChar (Unicode) được. Phải dùng các hàm API của BSTR của Windows.
Mình sẽ giải thích về cái này rõ hơn sau.
Em chào anh @ThangCuAnh theo em tìm hiểu thì sự khác biệt cơ bản giữa Excel và delphi theo bảng dưới, tuy nhiên em vẫn chưa áp dụng một cách chính xác khi sử dụng. Em thấy anh chia sẻ "Phải dùng các hàm API của BSTR của Windows." anh có thể gợi ý rõ hơn về cách dùng được không ạ
Chân thành cảm ơn anh!

OLE TYPEDelphi Type
BSTRWIDETRING
BYTEShortInt
CURRENCYCurrency
DateTDateTime
DECIMALTdecimal
DoubleDouble
floatsingle
GUIDGUID
INTSYSINT
LongInteger
LPSTRPchar
LPPWSTRPwidechar
shortsmallint
unsigned charbyte
unsigned intsysunint
unsigned longUint
VariantOlevariant
unsigned shortword
 
Upvote 0
Nói chung chung mình không nói được bạn Bảo Ninh. Phải có code cụ thể.
Code của Mạnh chậm do cũng bị trường hợp 2 hàm internal của Delphi compiler chèn vào là _VarArrayGet và _VarArrayPut
Delphi compiler nó làm hậu trường nhiều lắm, vô số kể. Để code mình nhanh thì mình phải biết nó làm gì và cách tránh.
 
Upvote 0
Nói chung chung mình không nói được bạn Bảo Ninh. Phải có code cụ thể.
Code của Mạnh chậm do cũng bị trường hợp 2 hàm internal của Delphi compiler chèn vào là _VarArrayGet và _VarArrayPut
Delphi compiler nó làm hậu trường nhiều lắm, vô số kể. Để code mình nhanh thì mình phải biết nó làm gì và cách tránh.
nói vậy = xin chịu ... nếu được code lại cho mạnh code đó ... may ra học được
Lý thuyết nói theo sách cũng xin = thua
 
Upvote 0
Web KT
Back
Top Bottom